001package co.codewizards.cloudstore.core.auth;
002
003import java.security.KeyFactory;
004import java.security.PublicKey;
005import java.security.SecureRandom;
006import java.security.spec.EncodedKeySpec;
007import java.security.spec.X509EncodedKeySpec;
008
009import javax.crypto.Cipher;
010import javax.crypto.spec.SecretKeySpec;
011
012import org.slf4j.Logger;
013import org.slf4j.LoggerFactory;
014
015import co.codewizards.cloudstore.core.config.ConfigImpl;
016import co.codewizards.cloudstore.core.util.AssertUtil;
017
018public class SignedAuthTokenEncrypter {
019        private static final Logger logger = LoggerFactory.getLogger(SignedAuthTokenEncrypter.class);
020
021        public static final int DEFAULT_KEY_SIZE = 128;
022        public static final String CONFIG_KEY_KEY_SIZE = "authTokenEncryption.keySize";
023
024        private static SecureRandom random = new SecureRandom();
025
026        private PublicKey publicKey;
027
028        public SignedAuthTokenEncrypter(final byte[] publicKeyData) {
029                AssertUtil.assertNotNull(publicKeyData, "publicKeyData");
030                BouncyCastleRegistrationUtil.registerBouncyCastleIfNeeded();
031                try {
032                        final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
033                        final EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyData);
034                        this.publicKey = keyFactory.generatePublic(publicKeySpec);
035                } catch (final RuntimeException e) {
036                        throw e;
037                } catch (final Exception e) {
038                        throw new RuntimeException(e);
039                }
040        }
041
042        public EncryptedSignedAuthToken encrypt(final byte[] signedAuthTokenData) {
043                try {
044                        final byte[] symKey = new byte[getKeySize() / 8];
045                        random.nextBytes(symKey);
046
047                        final Cipher asymCipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING");
048                        asymCipher.init(Cipher.ENCRYPT_MODE, publicKey);
049                        final byte[] symKeyEncrypted = asymCipher.doFinal(symKey);
050
051                        final Cipher symCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
052//                      symCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(symKey, "AES"), new IvParameterSpec(new byte[symKey.length]));
053                        symCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(symKey, "AES"));
054                        // We do not really need an IV, because we use a random key ONCE.
055                        // An IV is essentially important for security, if the key is used multiple times.
056                        // However, it doesn't cause us much trouble to transmit the IV and it may add
057                        // additional security due to the added complexity if it is not 0. Maybe the NSA
058                        // can attack easier, if the IV is 0. Very unlikely, but still. Hence we do not
059                        // enforce it to be 0 (which we could to save a few bytes in the transfer).
060                        // Marco :-)
061                        final byte[] symIV = symCipher.getIV();
062                        final byte[] signedAuthTokenDataEncrypted = symCipher.doFinal(signedAuthTokenData);
063
064                        final EncryptedSignedAuthToken result = new EncryptedSignedAuthToken();
065                        result.setEncryptedSignedAuthTokenData(signedAuthTokenDataEncrypted);
066                        result.setEncryptedSignedAuthTokenDataIV(symIV);
067                        result.setEncryptedSymmetricKey(symKeyEncrypted);
068                        return result;
069                } catch (final RuntimeException e) {
070                        throw e;
071                } catch (final Exception e) {
072                        throw new RuntimeException(e);
073                }
074        }
075
076        protected int getKeySize() {
077                final int keySize = ConfigImpl.getInstance().getPropertyAsInt(CONFIG_KEY_KEY_SIZE, DEFAULT_KEY_SIZE);
078                if (keySize < 64) {
079                        logger.warn("Config key '{}': keySize {} is out of range! Using default {} instead!", CONFIG_KEY_KEY_SIZE, keySize, DEFAULT_KEY_SIZE);
080                        return DEFAULT_KEY_SIZE;
081                }
082                return keySize;
083        }
084}