001/*******************************************************************************
002 * Copyright 2014 The MITRE Corporation
003 *   and the MIT Kerberos and Internet Trust Consortium
004 * 
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 * 
009 *   http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 ******************************************************************************/
017package org.mitre.jwt.encryption.service.impl;
018
019import java.security.NoSuchAlgorithmException;
020import java.security.spec.InvalidKeySpecException;
021import java.util.Collection;
022import java.util.HashMap;
023import java.util.HashSet;
024import java.util.Map;
025import java.util.Set;
026
027import javax.annotation.PostConstruct;
028
029import org.mitre.jose.keystore.JWKSetKeyStore;
030import org.mitre.jwt.encryption.service.JwtEncryptionAndDecryptionService;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034import com.google.common.base.Strings;
035import com.nimbusds.jose.EncryptionMethod;
036import com.nimbusds.jose.JOSEException;
037import com.nimbusds.jose.JWEAlgorithm;
038import com.nimbusds.jose.JWEDecrypter;
039import com.nimbusds.jose.JWEEncrypter;
040import com.nimbusds.jose.JWEObject;
041import com.nimbusds.jose.crypto.DirectDecrypter;
042import com.nimbusds.jose.crypto.DirectEncrypter;
043import com.nimbusds.jose.crypto.RSADecrypter;
044import com.nimbusds.jose.crypto.RSAEncrypter;
045import com.nimbusds.jose.jwk.JWK;
046import com.nimbusds.jose.jwk.OctetSequenceKey;
047import com.nimbusds.jose.jwk.RSAKey;
048
049/**
050 * @author wkim
051 *
052 */
053public class DefaultJwtEncryptionAndDecryptionService implements JwtEncryptionAndDecryptionService {
054
055        private static Logger logger = LoggerFactory.getLogger(DefaultJwtEncryptionAndDecryptionService.class);
056
057        // map of identifier to encrypter
058        private Map<String, JWEEncrypter> encrypters = new HashMap<String, JWEEncrypter>();
059
060        // map of identifier to decrypter
061        private Map<String, JWEDecrypter> decrypters = new HashMap<String, JWEDecrypter>();
062
063        private String defaultEncryptionKeyId;
064
065        private String defaultDecryptionKeyId;
066
067        private JWEAlgorithm defaultAlgorithm;
068
069        // map of identifier to key
070        private Map<String, JWK> keys = new HashMap<String, JWK>();
071
072        /**
073         * Build this service based on the keys given. All public keys will be used to make encrypters,
074         * all private keys will be used to make decrypters.
075         * 
076         * @param keys
077         * @throws NoSuchAlgorithmException
078         * @throws InvalidKeySpecException
079         * @throws JOSEException
080         */
081        public DefaultJwtEncryptionAndDecryptionService(Map<String, JWK> keys) throws NoSuchAlgorithmException, InvalidKeySpecException, JOSEException {
082                this.keys = keys;
083                buildEncryptersAndDecrypters();
084        }
085
086        /**
087         * Build this service based on the given keystore. All keys must have a key
088         * id ({@code kid}) field in order to be used.
089         * 
090         * @param keyStore
091         * @throws NoSuchAlgorithmException
092         * @throws InvalidKeySpecException
093         * @throws JOSEException
094         */
095        public DefaultJwtEncryptionAndDecryptionService(JWKSetKeyStore keyStore) throws NoSuchAlgorithmException, InvalidKeySpecException, JOSEException {
096
097                // convert all keys in the keystore to a map based on key id
098                for (JWK key : keyStore.getKeys()) {
099                        if (!Strings.isNullOrEmpty(key.getKeyID())) {
100                                this.keys.put(key.getKeyID(), key);
101                        } else {
102                                throw new IllegalArgumentException("Tried to load a key from a keystore without a 'kid' field: " + key);
103                        }
104                }
105
106                buildEncryptersAndDecrypters();
107
108        }
109
110
111        @PostConstruct
112        public void afterPropertiesSet() {
113
114                if (keys == null) {
115                        throw new IllegalArgumentException("Encryption and decryption service must have at least one key configured.");
116                }
117                try {
118                        buildEncryptersAndDecrypters();
119                } catch (NoSuchAlgorithmException e) {
120                        throw new IllegalArgumentException("Encryption and decryption service could not find given algorithm.");
121                } catch (InvalidKeySpecException e) {
122                        throw new IllegalArgumentException("Encryption and decryption service saw an invalid key specification.");
123                } catch (JOSEException e) {
124                        throw new IllegalArgumentException("Encryption and decryption service was unable to process JOSE object.");
125                }
126        }
127
128        public String getDefaultEncryptionKeyId() {
129                if (defaultEncryptionKeyId != null) {
130                        return defaultEncryptionKeyId;
131                } else if (keys.size() == 1) {
132                        // if there's only one key in the map, it's the default
133                        return keys.keySet().iterator().next();
134                } else {
135                        return null;
136                }
137        }
138
139        public void setDefaultEncryptionKeyId(String defaultEncryptionKeyId) {
140                this.defaultEncryptionKeyId = defaultEncryptionKeyId;
141        }
142
143        public String getDefaultDecryptionKeyId() {
144                if (defaultDecryptionKeyId != null) {
145                        return defaultDecryptionKeyId;
146                } else if (keys.size() == 1) {
147                        // if there's only one key in the map, it's the default
148                        return keys.keySet().iterator().next();
149                } else {
150                        return null;
151                }
152        }
153
154        public void setDefaultDecryptionKeyId(String defaultDecryptionKeyId) {
155                this.defaultDecryptionKeyId = defaultDecryptionKeyId;
156        }
157
158        public JWEAlgorithm getDefaultAlgorithm() {
159                return defaultAlgorithm;
160        }
161
162        public void setDefaultAlgorithm(JWEAlgorithm defaultAlgorithm) {
163                this.defaultAlgorithm = defaultAlgorithm;
164        }
165
166        /* (non-Javadoc)
167         * @see org.mitre.jwt.encryption.service.JwtEncryptionAndDecryptionService#encryptJwt(com.nimbusds.jwt.EncryptedJWT)
168         */
169        @Override
170        public void encryptJwt(JWEObject jwt) {
171                if (getDefaultEncryptionKeyId() == null) {
172                        throw new IllegalStateException("Tried to call default encryption with no default encrypter ID set");
173                }
174
175                JWEEncrypter encrypter = encrypters.get(getDefaultEncryptionKeyId());
176
177                try {
178                        jwt.encrypt(encrypter);
179                } catch (JOSEException e) {
180
181                        logger.error("Failed to encrypt JWT, error was: ", e);
182                }
183
184        }
185
186        /* (non-Javadoc)
187         * @see org.mitre.jwt.encryption.service.JwtEncryptionAndDecryptionService#decryptJwt(com.nimbusds.jwt.EncryptedJWT)
188         */
189        @Override
190        public void decryptJwt(JWEObject jwt) {
191                if (getDefaultDecryptionKeyId() == null) {
192                        throw new IllegalStateException("Tried to call default decryption with no default decrypter ID set");
193                }
194
195                JWEDecrypter decrypter = decrypters.get(getDefaultDecryptionKeyId());
196
197                try {
198                        jwt.decrypt(decrypter);
199                } catch (JOSEException e) {
200
201                        logger.error("Failed to decrypt JWT, error was: ", e);
202                }
203
204        }
205
206        /**
207         * Builds all the encrypters and decrypters for this service based on the key map.
208         * @throws
209         * @throws InvalidKeySpecException
210         * @throws NoSuchAlgorithmException
211         * @throws JOSEException
212         */
213        private void buildEncryptersAndDecrypters() throws NoSuchAlgorithmException, InvalidKeySpecException, JOSEException {
214
215                for (Map.Entry<String, JWK> jwkEntry : keys.entrySet()) {
216
217                        String id = jwkEntry.getKey();
218                        JWK jwk = jwkEntry.getValue();
219
220                        if (jwk instanceof RSAKey) {
221                                // build RSA encrypters and decrypters
222
223                                RSAEncrypter encrypter = new RSAEncrypter(((RSAKey) jwk).toRSAPublicKey()); // there should always at least be the public key
224                                encrypters.put(id, encrypter);
225
226                                if (jwk.isPrivate()) { // we can decrypt!
227                                        RSADecrypter decrypter = new RSADecrypter(((RSAKey) jwk).toRSAPrivateKey());
228                                        decrypters.put(id, decrypter);
229                                } else {
230                                        logger.warn("No private key for key #" + jwk.getKeyID());
231                                }
232
233                                // TODO: add support for EC keys
234
235                        } else if (jwk instanceof OctetSequenceKey) {
236                                // build symmetric encrypters and decrypters
237
238                                DirectEncrypter encrypter = new DirectEncrypter(((OctetSequenceKey) jwk).toByteArray());
239                                DirectDecrypter decrypter = new DirectDecrypter(((OctetSequenceKey) jwk).toByteArray());
240
241                                encrypters.put(id, encrypter);
242                                decrypters.put(id, decrypter);
243
244                        } else {
245                                logger.warn("Unknown key type: " + jwk);
246                        }
247
248                }
249        }
250
251        @Override
252        public Map<String, JWK> getAllPublicKeys() {
253                Map<String, JWK> pubKeys = new HashMap<String, JWK>();
254
255                // pull out all public keys
256                for (String keyId : keys.keySet()) {
257                        JWK key = keys.get(keyId);
258                        JWK pub = key.toPublicJWK();
259                        if (pub != null) {
260                                pubKeys.put(keyId, pub);
261                        }
262                }
263
264                return pubKeys;
265        }
266
267        @Override
268        public Collection<JWEAlgorithm> getAllEncryptionAlgsSupported() {
269                Set<JWEAlgorithm> algs = new HashSet<JWEAlgorithm>();
270
271                for (JWEEncrypter encrypter : encrypters.values()) {
272                        algs.addAll(encrypter.supportedAlgorithms());
273                }
274
275                for (JWEDecrypter decrypter : decrypters.values()) {
276                        algs.addAll(decrypter.supportedAlgorithms());
277                }
278
279                return algs;
280        }
281
282        /* (non-Javadoc)
283         * @see org.mitre.jwt.encryption.service.JwtEncryptionAndDecryptionService#getAllEncryptionEncsSupported()
284         */
285        @Override
286        public Collection<EncryptionMethod> getAllEncryptionEncsSupported() {
287                Set<EncryptionMethod> encs = new HashSet<EncryptionMethod>();
288
289                for (JWEEncrypter encrypter : encrypters.values()) {
290                        encs.addAll(encrypter.supportedEncryptionMethods());
291                }
292
293                for (JWEDecrypter decrypter : decrypters.values()) {
294                        encs.addAll(decrypter.supportedEncryptionMethods());
295                }
296
297                return encs;
298        }
299
300
301}