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 encryption key from the specified client secret.
027         *
028         * @param clientSecret The client secret. Must not be {@code null}.
029         * @param alg          The JWE algorithm. Must not be {@code null}.
030         * @param enc          The JWE method. Must not be {@code null}.
031         *
032         * @return The matching secret key (with algorithm set to "AES").
033         *
034         * @throws JOSEException If the JWE algorithm or method is not
035         *                       supported.
036         */
037        public static SecretKey deriveSecretKey(final Secret clientSecret,
038                                                final JWEAlgorithm alg,
039                                                final EncryptionMethod enc)
040                throws JOSEException {
041                
042                if (JWEAlgorithm.DIR.equals(alg)) {
043                        
044                        int cekBitLength = enc.cekBitLength();
045                        
046                        if (cekBitLength == 0) {
047                                throw new JOSEException("Unsupported JWE method: enc=" + enc);
048                        }
049                        
050                        return deriveSecretKey(clientSecret, enc.cekBitLength());
051                        
052                } else if (JWEAlgorithm.Family.AES_KW.contains(alg)) {
053                        
054                        if (JWEAlgorithm.A128KW.equals(alg)) {
055                                return deriveSecretKey(clientSecret, 128);
056                        } else if (JWEAlgorithm.A192KW.equals(alg)) {
057                                return deriveSecretKey(clientSecret, 192);
058                        } else if (JWEAlgorithm.A256KW.equals(alg)) {
059                                return deriveSecretKey(clientSecret, 256);
060                        }
061                        
062                } else if (JWEAlgorithm.Family.AES_GCM_KW.contains(alg)) {
063                        
064                        if (JWEAlgorithm.A128GCMKW.equals(alg)) {
065                                return deriveSecretKey(clientSecret, 128);
066                        } else if (JWEAlgorithm.A192GCMKW.equals(alg)) {
067                                return deriveSecretKey(clientSecret, 192);
068                        } else if (JWEAlgorithm.A256GCMKW.equals(alg)) {
069                                return deriveSecretKey(clientSecret, 256);
070                        }
071                }
072                
073                throw new JOSEException("Unsupported JWE algorithm / method: alg=" + alg + " enc=" + enc);
074        }
075        
076        
077        /**
078         * Derives a secret encryption key from the specified client secret.
079         *
080         * @param clientSecret The client secret. Must not be {@code null}.
081         * @param bits         The secret key bits (128, 192, 256, 384 or 512).
082         *
083         * @return The matching secret key (with algorithm set to "AES").
084         *
085         * @throws JOSEException If the secret key bit size it not supported.
086         */
087        public static SecretKey deriveSecretKey(final Secret clientSecret, final int bits)
088                throws JOSEException {
089                
090                final int hashBitLength;
091                
092                switch (bits) {
093                        case 128:
094                        case 192:
095                        case 256:
096                                hashBitLength = 256;
097                                break;
098                        case 384:
099                                hashBitLength = 384;
100                                break;
101                        case 512:
102                                hashBitLength = 512;
103                                break;
104                        default:
105                                throw new JOSEException("Unsupported secret key length: " + bits + " bits");
106                }
107                
108                final byte[] hash;
109                
110                try {
111                        hash = MessageDigest.getInstance("SHA-" + hashBitLength).digest(clientSecret.getValueBytes());
112                        
113                } catch (NoSuchAlgorithmException e) {
114                        throw new JOSEException(e.getMessage(), e);
115                }
116                
117                final byte[] keyBytes;
118                
119                // Left-truncate if necessary
120                switch (bits) {
121                        case 128:
122                                keyBytes = ByteUtils.subArray(hash, ByteUtils.byteLength(256 - 128), ByteUtils.byteLength(128));
123                                break;
124                        case 192:
125                                keyBytes = ByteUtils.subArray(hash, ByteUtils.byteLength(256 - 192), ByteUtils.byteLength(192));
126                                break;
127                        case 256:
128                                keyBytes = hash;
129                                break;
130                        case 384:
131                                keyBytes = hash;
132                                break;
133                        case 512:
134                                keyBytes = hash;
135                                break;
136                        default:
137                                throw new JOSEException("Unsupported secret key length: " + bits + " bits");
138                }
139                
140                return new SecretKeySpec(keyBytes, "AES");
141        }
142        
143        
144        /**
145         * Prevents public instantiation.
146         */
147        private SecretKeyDerivation() {
148        }
149}