001package com.nimbusds.oauth2.sdk.jose;
002
003
004import java.security.MessageDigest;
005import java.security.NoSuchAlgorithmException;
006import javax.crypto.SecretKey;
007import javax.crypto.spec.SecretKeySpec;
008
009import com.nimbusds.jose.EncryptionMethod;
010import com.nimbusds.jose.JOSEException;
011import com.nimbusds.jose.JWEAlgorithm;
012import com.nimbusds.jose.util.ByteUtils;
013import com.nimbusds.oauth2.sdk.auth.Secret;
014
015
016/**
017 * Derives an AES secret key from a client secret. Intended for performing
018 * symmetric encryption and decryption with a client secret, as specified in
019 * <a href="http://openid.net/specs/openid-connect-core-1_0.html#Encryption">OpenID
020 * Connect Core 1.0, section 10.2</a>.
021 */
022public class SecretKeyDerivation {
023        
024        
025        /**
026         * Derives a secret key for the specified JWE algorithm and method
027         * (implying symmetric encryption).
028         *
029         * @param clientSecret The client secret. Must not be {@code null}.
030         * @param alg          The JWE algorithm. Must not be {@code null}.
031         * @param enc          The JWE method. Must not be {@code null}.
032         *
033         * @return The matching secret key (with algorithm set to "AES").
034         *
035         * @throws JOSEException If the JWE algorithm or method is not
036         *                       supported.
037         */
038        public static SecretKey deriveSecretKey(final Secret clientSecret,
039                                                final JWEAlgorithm alg,
040                                                final EncryptionMethod enc)
041                throws JOSEException {
042                
043                if (JWEAlgorithm.DIR.equals(alg)) {
044                        
045                        int cekBitLength = enc.cekBitLength();
046                        
047                        if (cekBitLength == 0) {
048                                throw new JOSEException("Unsupported JWE method: enc=" + enc);
049                        }
050                        
051                        return deriveSecretKey(clientSecret, enc.cekBitLength());
052                        
053                } else if (JWEAlgorithm.Family.AES_KW.contains(alg)) {
054                        
055                        if (JWEAlgorithm.A128KW.equals(alg)) {
056                                return deriveSecretKey(clientSecret, 128);
057                        } else if (JWEAlgorithm.A192KW.equals(alg)) {
058                                return deriveSecretKey(clientSecret, 192);
059                        } else if (JWEAlgorithm.A256KW.equals(alg)) {
060                                return deriveSecretKey(clientSecret, 256);
061                        }
062                        
063                } else if (JWEAlgorithm.Family.AES_GCM_KW.contains(alg)) {
064                        
065                        if (JWEAlgorithm.A128GCMKW.equals(alg)) {
066                                return deriveSecretKey(clientSecret, 128);
067                        } else if (JWEAlgorithm.A192GCMKW.equals(alg)) {
068                                return deriveSecretKey(clientSecret, 192);
069                        } else if (JWEAlgorithm.A256GCMKW.equals(alg)) {
070                                return deriveSecretKey(clientSecret, 256);
071                        }
072                }
073                
074                throw new JOSEException("Unsupported JWE algorithm / method: alg=" + alg + " enc=" + enc);
075        }
076        
077        
078        /**
079         * Derives a secret key for the specified JWE algorithm and method
080         * (implying symmetric encryption).
081         *
082         * @param clientSecret The client secret. Must not be {@code null}.
083         * @param bits         The secret key bits (128, 192, 256, 384 or 512).
084         *
085         * @return The matching secret key (with algorithm set to "AES").
086         *
087         * @throws JOSEException If the secret key bit size it not supported.
088         */
089        public static SecretKey deriveSecretKey(final Secret clientSecret, final int bits)
090                throws JOSEException {
091                
092                final int hashBitLength;
093                
094                switch (bits) {
095                        case 128:
096                        case 192:
097                        case 256:
098                                hashBitLength = 256;
099                                break;
100                        case 384:
101                                hashBitLength = 384;
102                                break;
103                        case 512:
104                                hashBitLength = 512;
105                                break;
106                        default:
107                                throw new JOSEException("Unsupported secret key length: " + bits + " bits");
108                }
109                
110                final byte[] hash;
111                
112                try {
113                        hash = MessageDigest.getInstance("SHA-" + hashBitLength).digest(clientSecret.getValueBytes());
114                        
115                } catch (NoSuchAlgorithmException e) {
116                        throw new JOSEException(e.getMessage(), e);
117                }
118                
119                final byte[] keyBytes;
120                
121                // Left-truncate if necessary
122                switch (bits) {
123                        case 128:
124                                keyBytes = ByteUtils.subArray(hash, ByteUtils.byteLength(256 - 128), ByteUtils.byteLength(128));
125                                break;
126                        case 192:
127                                keyBytes = ByteUtils.subArray(hash, ByteUtils.byteLength(256 - 192), ByteUtils.byteLength(192));
128                                break;
129                        case 256:
130                                keyBytes = hash;
131                                break;
132                        case 384:
133                                keyBytes = hash;
134                                break;
135                        case 512:
136                                keyBytes = hash;
137                                break;
138                        default:
139                                throw new JOSEException("Unsupported secret key length: " + bits + " bits");
140                }
141                
142                return new SecretKeySpec(keyBytes, "AES");
143        }
144        
145        
146        /**
147         * Prevents public instantiation.
148         */
149        private SecretKeyDerivation() {
150        }
151}