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.signer.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; 026import java.util.UUID; 027 028import org.mitre.jose.keystore.JWKSetKeyStore; 029import org.mitre.jwt.signer.service.JwtSigningAndValidationService; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033import com.google.common.base.Strings; 034import com.nimbusds.jose.JOSEException; 035import com.nimbusds.jose.JWSAlgorithm; 036import com.nimbusds.jose.JWSSigner; 037import com.nimbusds.jose.JWSVerifier; 038import com.nimbusds.jose.crypto.MACSigner; 039import com.nimbusds.jose.crypto.MACVerifier; 040import com.nimbusds.jose.crypto.RSASSASigner; 041import com.nimbusds.jose.crypto.RSASSAVerifier; 042import com.nimbusds.jose.jwk.ECKey; 043import com.nimbusds.jose.jwk.JWK; 044import com.nimbusds.jose.jwk.OctetSequenceKey; 045import com.nimbusds.jose.jwk.RSAKey; 046import com.nimbusds.jwt.SignedJWT; 047 048public class DefaultJwtSigningAndValidationService implements JwtSigningAndValidationService { 049 050 // map of identifier to signer 051 private Map<String, JWSSigner> signers = new HashMap<String, JWSSigner>(); 052 053 // map of identifier to verifier 054 private Map<String, JWSVerifier> verifiers = new HashMap<String, JWSVerifier>(); 055 056 private static Logger logger = LoggerFactory.getLogger(DefaultJwtSigningAndValidationService.class); 057 058 private String defaultSignerKeyId; 059 060 private JWSAlgorithm defaultAlgorithm; 061 062 // map of identifier to key 063 private Map<String, JWK> keys = new HashMap<String, JWK>(); 064 065 /** 066 * Build this service based on the keys given. All public keys will be used 067 * to make verifiers, all private keys will be used to make signers. 068 * 069 * @param keys 070 * A map of key identifier to key 071 * 072 * @throws InvalidKeySpecException 073 * If the keys in the JWKs are not valid 074 * @throws NoSuchAlgorithmException 075 * If there is no appropriate algorithm to tie the keys to. 076 */ 077 public DefaultJwtSigningAndValidationService(Map<String, JWK> keys) throws NoSuchAlgorithmException, InvalidKeySpecException { 078 this.keys = keys; 079 buildSignersAndVerifiers(); 080 } 081 082 /** 083 * Build this service based on the given keystore. All keys must have a key 084 * id ({@code kid}) field in order to be used. 085 * 086 * @param keyStore 087 * the keystore to load all keys from 088 * 089 * @throws InvalidKeySpecException 090 * If the keys in the JWKs are not valid 091 * @throws NoSuchAlgorithmException 092 * If there is no appropriate algorithm to tie the keys to. 093 */ 094 public DefaultJwtSigningAndValidationService(JWKSetKeyStore keyStore) throws NoSuchAlgorithmException, InvalidKeySpecException { 095 // convert all keys in the keystore to a map based on key id 096 if (keyStore!= null && keyStore.getJwkSet() != null) { 097 for (JWK key : keyStore.getKeys()) { 098 if (!Strings.isNullOrEmpty(key.getKeyID())) { 099 // use the key ID that's built into the key itself 100 // TODO (#641): deal with JWK thumbprints 101 this.keys.put(key.getKeyID(), key); 102 } else { 103 // create a random key id 104 String fakeKid = UUID.randomUUID().toString(); 105 this.keys.put(fakeKid, key); 106 } 107 } 108 } 109 buildSignersAndVerifiers(); 110 } 111 112 113 /** 114 * @return the defaultSignerKeyId 115 */ 116 @Override 117 public String getDefaultSignerKeyId() { 118 return defaultSignerKeyId; 119 } 120 121 /** 122 * @param defaultSignerKeyId the defaultSignerKeyId to set 123 */ 124 public void setDefaultSignerKeyId(String defaultSignerId) { 125 this.defaultSignerKeyId = defaultSignerId; 126 } 127 128 /** 129 * @return 130 */ 131 @Override 132 public JWSAlgorithm getDefaultSigningAlgorithm() { 133 return defaultAlgorithm; 134 } 135 136 public void setDefaultSigningAlgorithmName(String algName) { 137 defaultAlgorithm = JWSAlgorithm.parse(algName); 138 } 139 140 public String getDefaultSigningAlgorithmName() { 141 if (defaultAlgorithm != null) { 142 return defaultAlgorithm.getName(); 143 } else { 144 return null; 145 } 146 } 147 148 /** 149 * Build all of the signers and verifiers for this based on the key map. 150 * @throws InvalidKeySpecException If the keys in the JWKs are not valid 151 * @throws NoSuchAlgorithmException If there is no appropriate algorithm to tie the keys to. 152 */ 153 private void buildSignersAndVerifiers() throws NoSuchAlgorithmException, InvalidKeySpecException { 154 for (Map.Entry<String, JWK> jwkEntry : keys.entrySet()) { 155 156 String id = jwkEntry.getKey(); 157 JWK jwk = jwkEntry.getValue(); 158 159 if (jwk instanceof RSAKey) { 160 // build RSA signers & verifiers 161 162 if (jwk.isPrivate()) { // only add the signer if there's a private key 163 RSASSASigner signer = new RSASSASigner(((RSAKey) jwk).toRSAPrivateKey()); 164 signers.put(id, signer); 165 } 166 167 RSASSAVerifier verifier = new RSASSAVerifier(((RSAKey) jwk).toRSAPublicKey()); 168 verifiers.put(id, verifier); 169 170 } else if (jwk instanceof ECKey) { 171 // build EC signers & verifiers 172 173 // TODO: add support for EC keys 174 logger.warn("EC Keys are not yet supported."); 175 176 } else if (jwk instanceof OctetSequenceKey) { 177 // build HMAC signers & verifiers 178 179 if (jwk.isPrivate()) { // technically redundant check because all HMAC keys are private 180 MACSigner signer = new MACSigner(((OctetSequenceKey) jwk).toByteArray()); 181 signers.put(id, signer); 182 } 183 184 MACVerifier verifier = new MACVerifier(((OctetSequenceKey) jwk).toByteArray()); 185 verifiers.put(id, verifier); 186 187 } else { 188 logger.warn("Unknown key type: " + jwk); 189 } 190 } 191 192 if (defaultSignerKeyId == null && keys.size() == 1) { 193 // if there's only one key, it's the default 194 setDefaultSignerKeyId(keys.keySet().iterator().next()); 195 } 196 } 197 198 /** 199 * Sign a jwt in place using the configured default signer. 200 */ 201 @Override 202 public void signJwt(SignedJWT jwt) { 203 if (getDefaultSignerKeyId() == null) { 204 throw new IllegalStateException("Tried to call default signing with no default signer ID set"); 205 } 206 207 JWSSigner signer = signers.get(getDefaultSignerKeyId()); 208 209 try { 210 jwt.sign(signer); 211 } catch (JOSEException e) { 212 213 logger.error("Failed to sign JWT, error was: ", e); 214 } 215 216 } 217 218 @Override 219 public void signJwt(SignedJWT jwt, JWSAlgorithm alg) { 220 221 JWSSigner signer = null; 222 223 for (JWSSigner s : signers.values()) { 224 if (s.supportedAlgorithms().contains(alg)) { 225 signer = s; 226 break; 227 } 228 } 229 230 if (signer == null) { 231 //If we can't find an algorithm that matches, we can't sign 232 logger.error("No matching algirthm found for alg=" + alg); 233 234 } 235 236 try { 237 jwt.sign(signer); 238 } catch (JOSEException e) { 239 240 logger.error("Failed to sign JWT, error was: ", e); 241 } 242 243 } 244 245 @Override 246 public boolean validateSignature(SignedJWT jwt) { 247 248 for (JWSVerifier verifier : verifiers.values()) { 249 try { 250 if (jwt.verify(verifier)) { 251 return true; 252 } 253 } catch (JOSEException e) { 254 255 logger.error("Failed to validate signature, error was: ", e); 256 } 257 } 258 return false; 259 } 260 261 @Override 262 public Map<String, JWK> getAllPublicKeys() { 263 Map<String, JWK> pubKeys = new HashMap<String, JWK>(); 264 265 // pull all keys out of the verifiers if we know how 266 for (String keyId : keys.keySet()) { 267 JWK key = keys.get(keyId); 268 JWK pub = key.toPublicJWK(); 269 if (pub != null) { 270 pubKeys.put(keyId, pub); 271 } 272 } 273 274 return pubKeys; 275 } 276 277 /* (non-Javadoc) 278 * @see org.mitre.jwt.signer.service.JwtSigningAndValidationService#getAllSigningAlgsSupported() 279 */ 280 @Override 281 public Collection<JWSAlgorithm> getAllSigningAlgsSupported() { 282 283 Set<JWSAlgorithm> algs = new HashSet<JWSAlgorithm>(); 284 285 for (JWSSigner signer : signers.values()) { 286 algs.addAll(signer.supportedAlgorithms()); 287 } 288 289 for (JWSVerifier verifier : verifiers.values()) { 290 algs.addAll(verifier.supportedAlgorithms()); 291 } 292 293 return algs; 294 295 } 296 297}