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