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}