001package com.nimbusds.jose.crypto;
002
003
004import javax.crypto.SecretKey;
005import javax.crypto.spec.SecretKeySpec;
006
007import net.jcip.annotations.ThreadSafe;
008
009import com.nimbusds.jose.*;
010import com.nimbusds.jose.jwk.OctetSequenceKey;
011import com.nimbusds.jose.util.Base64URL;
012import com.nimbusds.jose.util.ByteUtils;
013
014
015/**
016 * AES and AES GCM key wrap encrypter of {@link com.nimbusds.jose.JWEObject JWE
017 * objects}. Expects an AES key.
018 *
019 * <p>Encrypts the plain text with a generated AES key (the Content Encryption
020 * Key) according to the specified JOSE encryption method, then wraps the CEK
021 * with the specified AES key and returns it alongside the IV, cipher text and
022 * authentication tag. See RFC 7518, sections
023 * <a href="https://tools.ietf.org/html/rfc7518#section-4.4">4.4</a> and
024 * <a href="https://tools.ietf.org/html/rfc7518#section-4.7">4.7</a> for more
025 * information.
026 *
027 * <p>This class is thread-safe.
028 *
029 * <p>Supports the following key management algorithms:
030 *
031 * <ul>
032 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#A128KW}
033 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#A192KW}
034 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#A256KW}
035 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#A128GCMKW}
036 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#A192GCMKW}
037 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#A256GCMKW}
038 * </ul>
039 *
040 * <p>Supports the following content encryption algorithms:
041 *
042 * <ul>
043 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
044 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
045 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
046 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
047 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM}
048 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
049 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED}
050 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED}
051 * </ul>
052 *
053 * @author Melisa Halsband
054 * @author Vladimir Dzhuvinov
055 * @version 2015-06-29
056 */
057@ThreadSafe
058public class AESEncrypter extends AESCryptoProvider implements JWEEncrypter {
059
060
061        /**
062         * Algorithm family constants.
063         */
064        private enum AlgFamily {
065
066                AESKW, AESGCMKW
067        }
068
069
070        /**
071         * Creates a new AES encrypter.
072         *
073         * @param kek The Key Encryption Key. Must be 128 bits (16 bytes), 192
074         *            bits (24 bytes) or 256 bits (32 bytes). Must not be
075         *            {@code null}.
076         *
077         * @throws KeyLengthException If the KEK length is invalid.
078         */
079        public AESEncrypter(final SecretKey kek)
080                throws KeyLengthException {
081
082                super(kek);
083        }
084
085        /**
086         * Creates a new AES encrypter.
087         *
088         * @param keyBytes The Key Encryption Key, as a byte array. Must be 128
089         *                 bits (16 bytes), 192 bits (24 bytes) or 256 bits (32
090         *                 bytes). Must not be {@code null}.
091         *
092         * @throws KeyLengthException If the KEK length is invalid.
093         */
094        public AESEncrypter(final byte[] keyBytes)
095                throws KeyLengthException {
096
097                this(new SecretKeySpec(keyBytes, "AES"));
098        }
099
100
101        /**
102         * Creates a new AES encrypter.
103         *
104         * @param octJWK The Key Encryption Key, as a JWK. Must be 128 bits (16
105         *               bytes), 192 bits (24 bytes), 256 bits (32 bytes), 384
106         *               bits (48 bytes) or 512 bits (64 bytes) long. Must not
107         *               be {@code null}.
108         *
109         * @throws KeyLengthException If the KEK length is invalid.
110         */
111        public AESEncrypter(final OctetSequenceKey octJWK)
112                throws KeyLengthException {
113
114                this(octJWK.toSecretKey("AES"));
115        }
116
117
118        @Override
119        public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText)
120                throws JOSEException {
121
122                final JWEAlgorithm alg = header.getAlgorithm();
123
124                // Check the AES key size and determine the algorithm family
125                final AlgFamily algFamily;
126
127                if (alg.equals(JWEAlgorithm.A128KW)) {
128
129                        if(ByteUtils.bitLength(getKey().getEncoded()) != 128){
130                                throw new KeyLengthException("The Key Encryption Key (KEK) length must be 128 bits for A128KW encryption");
131                        }
132                        algFamily = AlgFamily.AESKW;
133
134                } else if (alg.equals(JWEAlgorithm.A192KW)) {
135
136                        if(ByteUtils.bitLength(getKey().getEncoded()) != 192){
137                                throw new KeyLengthException("The Key Encryption Key (KEK) length must be 192 bits for A192KW encryption");
138                        }
139                        algFamily = AlgFamily.AESKW;
140
141                } else if (alg.equals(JWEAlgorithm.A256KW)) {
142
143                        if (ByteUtils.bitLength(getKey().getEncoded()) != 256) {
144                                throw new KeyLengthException("The Key Encryption Key (KEK) length must be 256 bits for A256KW encryption");
145                        }
146                        algFamily = AlgFamily.AESKW;
147
148                } else if (alg.equals(JWEAlgorithm.A128GCMKW)) {
149
150                        if(ByteUtils.bitLength(getKey().getEncoded()) != 128){
151                                throw new KeyLengthException("The Key Encryption Key (KEK) length must be 128 bits for A128GCMKW encryption");
152                        }
153                        algFamily = AlgFamily.AESGCMKW;
154
155                } else if (alg.equals(JWEAlgorithm.A192GCMKW)) {
156
157                        if(ByteUtils.bitLength(getKey().getEncoded()) != 192){
158                                throw new KeyLengthException("The Key Encryption Key (KEK) length must be 192 bits for A192GCMKW encryption");
159                        }
160                        algFamily = AlgFamily.AESGCMKW;
161
162                } else if (alg.equals(JWEAlgorithm.A256GCMKW)) {
163
164                        if(ByteUtils.bitLength(getKey().getEncoded()) != 256){
165                                throw new KeyLengthException("The Key Encryption Key (KEK) length must be 256 bits for A256GCMKW encryption");
166                        }
167                        algFamily = AlgFamily.AESGCMKW;
168
169                } else {
170
171                        throw new JOSEException(AlgorithmSupportMessage.unsupportedJWEAlgorithm(alg, SUPPORTED_ALGORITHMS));
172                }
173
174
175                final JWEHeader updatedHeader; // We need to work on the header
176                final Base64URL encryptedKey; // The second JWE part
177
178                // Generate and encrypt the CEK according to the enc method
179                final EncryptionMethod enc = header.getEncryptionMethod();
180                final SecretKey cek = ContentCryptoProvider.generateCEK(enc, getJCAContext().getSecureRandom());
181
182                if(AlgFamily.AESKW.equals(algFamily)) {
183
184                        encryptedKey = Base64URL.encode(AESKW.wrapCEK(cek, getKey(), getJCAContext().getKeyEncryptionProvider()));
185                        updatedHeader = header; // simply copy ref
186
187                } else if(AlgFamily.AESGCMKW.equals(algFamily)) {
188
189                        final byte[] keyIV = AESGCM.generateIV(getJCAContext().getSecureRandom());
190                        final AuthenticatedCipherText authCiphCEK = AESGCMKW.encryptCEK(cek, keyIV, getKey(), getJCAContext().getKeyEncryptionProvider());
191                        encryptedKey = Base64URL.encode(authCiphCEK.getCipherText());
192
193                        // Add iv and tag to the header
194                        updatedHeader = new JWEHeader.Builder(header).
195                                iv(Base64URL.encode(keyIV)).
196                                authTag(Base64URL.encode(authCiphCEK.getAuthenticationTag())).
197                                build();
198                } else {
199                        // This should never happen
200                        throw new JOSEException("Unexpected JWE algorithm: " + alg);
201                }
202
203                return ContentCryptoProvider.encrypt(updatedHeader, clearText, cek, encryptedKey, getJCAContext());
204        }
205}