001    package com.nimbusds.jose.crypto;
002    
003    
004    import java.io.UnsupportedEncodingException;
005    
006    import javax.crypto.SecretKey;
007    
008    import org.bouncycastle.util.Arrays;
009    
010    import com.nimbusds.jose.DefaultJWEHeaderFilter;
011    import com.nimbusds.jose.EncryptionMethod;
012    import com.nimbusds.jose.JOSEException;
013    import com.nimbusds.jose.JWEAlgorithm;
014    import com.nimbusds.jose.JWEDecrypter;
015    import com.nimbusds.jose.JWEHeaderFilter;
016    import com.nimbusds.jose.ReadOnlyJWEHeader;
017    import com.nimbusds.jose.util.Base64URL;
018    
019    
020    /**
021     * Direct decrypter of {@link com.nimbusds.jose.JWEObject JWE objects} with a
022     * shared symmetric key. This class is thread-safe.
023     *
024     * <p>Supports the following JWE algorithms:
025     *
026     * <ul>
027     *     <li>{@link com.nimbusds.jose.JWEAlgorithm#DIR}
028     * </ul>
029     *
030     * <p>Supports the following encryption methods:
031     *
032     * <ul>
033     *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
034     *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
035     *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
036     *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
037     * </ul>
038     *
039     * <p>Accepts all {@link com.nimbusds.jose.JWEHeader#getReservedParameterNames
040     * reserved JWE header parameters}. Modify the {@link #getJWEHeaderFilter
041     * header filter} properties to restrict the acceptable JWE algorithms, 
042     * encryption methods and header parameters, or to allow custom JWE header 
043     * parameters.
044     * 
045     * @author Vladimir Dzhuvinov
046     * @version $version$ (2013-04-16)
047     *
048     */
049    public class DirectDecrypter extends DirectCryptoProvider implements JWEDecrypter {
050    
051    
052            /**
053             * The JWE header filter.
054             */
055            private final DefaultJWEHeaderFilter headerFilter;
056    
057    
058            /**
059             * Creates a new direct decrypter.
060             *
061             * @param key The shared symmetric key. Must be 128 bits (16 bytes),
062             *            256 bits (32 bytes) or 512 bits (64 bytes) long. Must not 
063             *            be {@code null}.
064             *
065             * @throws JOSEException If the key length is unexpected.
066             */
067            public DirectDecrypter(final byte[] key)
068                    throws JOSEException {
069    
070                    super(key);
071    
072                    headerFilter = new DefaultJWEHeaderFilter(supportedAlgorithms(), supportedEncryptionMethods());
073            }
074    
075    
076            @Override
077            public JWEHeaderFilter getJWEHeaderFilter() {
078    
079                    return headerFilter;
080            }
081    
082    
083            @Override
084            public byte[] decrypt(final ReadOnlyJWEHeader readOnlyJWEHeader,
085                                  final Base64URL encryptedKey,
086                                  final Base64URL iv,
087                                  final Base64URL cipherText,
088                                  final Base64URL integrityValue) 
089                    throws JOSEException {
090    
091                    // Validate required JWE parts
092                    if (encryptedKey != null) {
093    
094                            throw new JOSEException("Unexpected encrypted key, must be omitted");
095                    }       
096    
097                    if (iv == null) {
098    
099                            throw new JOSEException("The initialization vector (IV) must not be null");
100                    }
101    
102                    if (integrityValue == null) {
103    
104                            throw new JOSEException("The integrity value must not be null");
105                    }
106                    
107    
108                    JWEAlgorithm alg = readOnlyJWEHeader.getAlgorithm();
109    
110                    if (! alg.equals(JWEAlgorithm.DIR)) {
111    
112                            throw new JOSEException("Unsupported algorithm, must be \"dir\"");
113                    }
114    
115                    EncryptionMethod enc = readOnlyJWEHeader.getEncryptionMethod();
116    
117                    byte[] plainText;
118    
119                    if (enc.equals(EncryptionMethod.A128CBC_HS256) || enc.equals(EncryptionMethod.A256CBC_HS512)    ) {
120    
121                            byte[] epu = null;
122    
123                            if (readOnlyJWEHeader.getEncryptionPartyUInfo() != null) {
124    
125                                    epu = readOnlyJWEHeader.getEncryptionPartyUInfo().decode();
126                            }
127    
128                            byte[] epv = null;
129                            
130                            if (readOnlyJWEHeader.getEncryptionPartyVInfo() != null) {
131    
132                                    epv = readOnlyJWEHeader.getEncryptionPartyVInfo().decode();
133                            }
134    
135                            SecretKey cek = ConcatKDF.generateCEK(cmk, enc, epu, epv);
136    
137                            plainText = AESCBC.decrypt(cek, iv.decode(), cipherText.decode());
138    
139                            SecretKey cik = ConcatKDF.generateCIK(cmk, enc, epu, epv);
140    
141                            String macInput = readOnlyJWEHeader.toBase64URL().toString() + "." +
142                                              /* encryptedKey omitted */ "." +
143                                              iv.toString() + "." +
144                                              cipherText.toString();
145    
146                            byte[] mac = HMAC.compute(cik, macInput.getBytes());
147    
148                            if (! Arrays.constantTimeAreEqual(integrityValue.decode(), mac)) {
149    
150                                    throw new JOSEException("HMAC integrity check failed");
151                            }
152    
153                    } else if (enc.equals(EncryptionMethod.A128GCM) || enc.equals(EncryptionMethod.A256GCM)    ) {
154    
155                            // Compose the additional authenticated data
156                            String authDataString = readOnlyJWEHeader.toBase64URL().toString() + "." +
157                                                    /* encryptedKey omitted */ "." +
158                                                    iv.toString();
159    
160                            byte[] authData = null;
161    
162                            try {
163                                    authData = authDataString.getBytes("UTF-8");
164    
165                            } catch (UnsupportedEncodingException e) {
166    
167                                    throw new JOSEException(e.getMessage(), e);
168                            }
169    
170                            plainText = AESGCM.decrypt(cmk, iv.decode(), cipherText.decode(), authData, integrityValue.decode());
171    
172                    } else {
173    
174                            throw new JOSEException("Unsupported encryption method, must be A128CBC_HS256, A256CBC_HS512, A128GCM or A128GCM");
175                    }
176    
177    
178                    // Apply decompression if requested
179                    return DeflateHelper.applyDecompression(readOnlyJWEHeader, plainText);
180            }
181    }
182