switched signing & validation service to use JWK natively for keys
parent
f54dddd8c0
commit
6cc50e7cd5
|
@ -3,7 +3,6 @@
|
|||
*/
|
||||
package org.mitre.openid.connect.client.keypublisher;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -17,6 +16,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProce
|
|||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.nimbusds.jose.jwk.JWK;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
|
@ -32,9 +32,6 @@ public class ClientKeyPublisher implements BeanDefinitionRegistryPostProcessor {
|
|||
|
||||
private String jwkViewName = "jwkKeyList";
|
||||
|
||||
private String x509ViewName;
|
||||
|
||||
|
||||
/**
|
||||
* If either the jwkPublishUrl or x509PublishUrl fields are set on this bean, set up a listener on that URL to publish keys.
|
||||
*/
|
||||
|
@ -82,26 +79,13 @@ public class ClientKeyPublisher implements BeanDefinitionRegistryPostProcessor {
|
|||
public ModelAndView publishClientJwk() {
|
||||
|
||||
// map from key id to key
|
||||
Map<String, PublicKey> keys = signingAndValidationService.getAllPublicKeys();
|
||||
Map<String, JWK> keys = signingAndValidationService.getAllPublicKeys();
|
||||
|
||||
// TODO: check if keys are empty, return a 404 here or just an empty list?
|
||||
|
||||
return new ModelAndView(jwkViewName, "keys", keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a view to publish all keys in x509 format. Only used if x509publishUrl is set.
|
||||
* @return
|
||||
*/
|
||||
public ModelAndView publishClientx509() {
|
||||
// map from key id to key
|
||||
Map<String, PublicKey> keys = signingAndValidationService.getAllPublicKeys();
|
||||
|
||||
// TODO: check if keys are empty, return a 404 here or just an empty list?
|
||||
|
||||
return new ModelAndView(x509ViewName, "keys", keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the jwkPublishUrl
|
||||
*/
|
||||
|
|
|
@ -24,6 +24,14 @@ public class JWKSetKeyStore implements InitializingBean {
|
|||
|
||||
private Resource location;
|
||||
|
||||
public JWKSetKeyStore() {
|
||||
|
||||
}
|
||||
|
||||
public JWKSetKeyStore(JWKSet jwkSet) {
|
||||
this.jwkSet = jwkSet;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
|
@ -80,8 +88,7 @@ public class JWKSetKeyStore implements InitializingBean {
|
|||
}
|
||||
|
||||
/**
|
||||
* Pass through to underlying JwK set and return its keys.
|
||||
* @return
|
||||
* Get the list of keys in this keystore. This is a passthrough to the underlying JWK Set
|
||||
*/
|
||||
public List<JWK> getKeys() {
|
||||
return jwkSet.getKeys();
|
||||
|
|
|
@ -28,7 +28,7 @@ public interface JwtSigningAndValidationService {
|
|||
/**
|
||||
* Get all public keys for this service, mapped by their Key ID
|
||||
*/
|
||||
public Map<String, PublicKey> getAllPublicKeys();
|
||||
public Map<String, JWK> getAllPublicKeys();
|
||||
|
||||
/**
|
||||
* Checks the signature of the given JWT against all configured signers,
|
||||
|
|
|
@ -16,10 +16,8 @@
|
|||
package org.mitre.jwt.signer.service.impl;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mitre.jose.keystore.JWKSetKeyStore;
|
||||
|
@ -47,6 +45,7 @@ public class DefaultJwtSigningAndValidationService implements JwtSigningAndValid
|
|||
|
||||
// map of identifier to signer
|
||||
private Map<String, JWSSigner> signers = new HashMap<String, JWSSigner>();
|
||||
|
||||
// map of identifier to verifier
|
||||
private Map<String, JWSVerifier> verifiers = new HashMap<String, JWSVerifier>();
|
||||
|
||||
|
@ -56,22 +55,50 @@ public class DefaultJwtSigningAndValidationService implements JwtSigningAndValid
|
|||
|
||||
private JWSAlgorithm defaultAlgorithm;
|
||||
|
||||
private JWKSetKeyStore keyStore;
|
||||
// map of identifier to key
|
||||
private Map<String, JWK> keys = new HashMap<String, JWK>();
|
||||
|
||||
/**
|
||||
* default constructor
|
||||
* Build this service based on the keys given. All public keys will be used
|
||||
* to make verifiers, all private keys will be used to make signers.
|
||||
*
|
||||
* @param keys
|
||||
* A map of key identifier to key
|
||||
*
|
||||
* @throws InvalidKeySpecException
|
||||
* If the keys in the JWKs are not valid
|
||||
* @throws NoSuchAlgorithmException
|
||||
* If there is no appropriate algorithm to tie the keys to.
|
||||
*/
|
||||
public DefaultJwtSigningAndValidationService() {
|
||||
|
||||
}
|
||||
public DefaultJwtSigningAndValidationService(Map<String, JWK> keys) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
this.keys = keys;
|
||||
buildSignersAndVerifiers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new validation service from the given set of verifiers (no signing)
|
||||
* Build this service based on the given keystore. All keys must have a key
|
||||
* id ({@code kid}) field in order to be used.
|
||||
*
|
||||
* @param keyStore
|
||||
* the keystore to load all keys from
|
||||
*
|
||||
* @throws InvalidKeySpecException
|
||||
* If the keys in the JWKs are not valid
|
||||
* @throws NoSuchAlgorithmException
|
||||
* If there is no appropriate algorithm to tie the keys to.
|
||||
*/
|
||||
public DefaultJwtSigningAndValidationService(Map<String, JWSVerifier> verifiers) {
|
||||
this.verifiers = verifiers;
|
||||
}
|
||||
|
||||
public DefaultJwtSigningAndValidationService(JWKSetKeyStore keyStore) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
// convert all keys in the keystore to a map based on key id
|
||||
for (JWK key : keyStore.getKeys()) {
|
||||
if (!Strings.isNullOrEmpty(key.getKeyID())) {
|
||||
this.keys.put(key.getKeyID(), key);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Tried to load a key from a keystore without a 'kid' field: " + key);
|
||||
}
|
||||
}
|
||||
buildSignersAndVerifiers();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the defaultSignerKeyId
|
||||
*/
|
||||
|
@ -105,20 +132,7 @@ public class DefaultJwtSigningAndValidationService implements JwtSigningAndValid
|
|||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @return the keyStore
|
||||
*/
|
||||
public JWKSetKeyStore getKeyStore() {
|
||||
return keyStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param keyStore the keyStore to set
|
||||
*/
|
||||
public void setKeyStore(JWKSetKeyStore keyStore) {
|
||||
this.keyStore = keyStore;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
@ -127,49 +141,54 @@ public class DefaultJwtSigningAndValidationService implements JwtSigningAndValid
|
|||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() throws NoSuchAlgorithmException, InvalidKeySpecException{
|
||||
|
||||
if (keyStore != null) {
|
||||
// if we have a keystore, load it up into our signers
|
||||
|
||||
List<JWK> keys = keyStore.getKeys();
|
||||
for (JWK jwk : keys) {
|
||||
|
||||
if (!Strings.isNullOrEmpty(jwk.getKeyID())) {
|
||||
|
||||
if (jwk instanceof RSAKey) {
|
||||
// build RSA signers & verifiers
|
||||
RSASSASigner signer = new RSASSASigner(((RSAKey) jwk).toRSAPrivateKey());
|
||||
RSASSAVerifier verifier = new RSASSAVerifier(((RSAKey) jwk).toRSAPublicKey());
|
||||
|
||||
signers.put(jwk.getKeyID(), signer);
|
||||
verifiers.put(jwk.getKeyID(), verifier);
|
||||
|
||||
} else if (jwk instanceof ECKey) {
|
||||
// build EC signers & verifiers
|
||||
|
||||
// TODO: add support for EC keys
|
||||
logger.warn("EC Keys are not yet supported.");
|
||||
|
||||
} else if (jwk instanceof OctetSequenceKey) {
|
||||
// build HMAC signers & verifiers
|
||||
MACSigner signer = new MACSigner(((OctetSequenceKey) jwk).toByteArray());
|
||||
MACVerifier verifier = new MACVerifier(((OctetSequenceKey) jwk).toByteArray());
|
||||
|
||||
signers.put(jwk.getKeyID(), signer);
|
||||
verifiers.put(jwk.getKeyID(), verifier);
|
||||
} else {
|
||||
logger.warn("Unknown key type: " + jwk);
|
||||
}
|
||||
} else {
|
||||
logger.warn("Found a key with no KeyId: " + jwk);
|
||||
}
|
||||
}
|
||||
|
||||
if (keys == null) {
|
||||
throw new IllegalArgumentException("Signing and validation service must have at least one key configured.");
|
||||
}
|
||||
|
||||
buildSignersAndVerifiers();
|
||||
|
||||
logger.info("DefaultJwtSigningAndValidationService is ready: " + this.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Build all of the signers and verifiers for this based on the key map.
|
||||
* @throws InvalidKeySpecException If the keys in the JWKs are not valid
|
||||
* @throws NoSuchAlgorithmException If there is no appropriate algorithm to tie the keys to.
|
||||
*/
|
||||
private void buildSignersAndVerifiers() throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
for (Map.Entry<String, JWK> jwkEntry : keys.entrySet()) {
|
||||
|
||||
String id = jwkEntry.getKey();
|
||||
JWK jwk = jwkEntry.getValue();
|
||||
|
||||
if (jwk instanceof RSAKey) {
|
||||
// build RSA signers & verifiers
|
||||
RSASSASigner signer = new RSASSASigner(((RSAKey) jwk).toRSAPrivateKey());
|
||||
RSASSAVerifier verifier = new RSASSAVerifier(((RSAKey) jwk).toRSAPublicKey());
|
||||
|
||||
signers.put(id, signer);
|
||||
verifiers.put(id, verifier);
|
||||
|
||||
} else if (jwk instanceof ECKey) {
|
||||
// build EC signers & verifiers
|
||||
|
||||
// TODO: add support for EC keys
|
||||
logger.warn("EC Keys are not yet supported.");
|
||||
|
||||
} else if (jwk instanceof OctetSequenceKey) {
|
||||
// build HMAC signers & verifiers
|
||||
MACSigner signer = new MACSigner(((OctetSequenceKey) jwk).toByteArray());
|
||||
MACVerifier verifier = new MACVerifier(((OctetSequenceKey) jwk).toByteArray());
|
||||
|
||||
signers.put(id, signer);
|
||||
verifiers.put(id, verifier);
|
||||
} else {
|
||||
logger.warn("Unknown key type: " + jwk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a jwt in place using the configured default signer.
|
||||
*/
|
||||
|
@ -207,19 +226,19 @@ public class DefaultJwtSigningAndValidationService implements JwtSigningAndValid
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<String, PublicKey> getAllPublicKeys() {
|
||||
Map<String, PublicKey> keys = new HashMap<String, PublicKey>();
|
||||
public Map<String, JWK> getAllPublicKeys() {
|
||||
Map<String, JWK> pubKeys = new HashMap<String, JWK>();
|
||||
|
||||
// pull all keys out of the verifiers if we know how
|
||||
for (String keyId : verifiers.keySet()) {
|
||||
JWSVerifier verifier = verifiers.get(keyId);
|
||||
if (verifier instanceof RSASSAVerifier) {
|
||||
// we know how to do RSA public keys
|
||||
keys.put(keyId, ((RSASSAVerifier) verifier).getPublicKey());
|
||||
for (String keyId : keys.keySet()) {
|
||||
JWK key = keys.get(keyId);
|
||||
JWK pub = key.toPublicJWK();
|
||||
if (pub != null) {
|
||||
pubKeys.put(keyId, pub);
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
return pubKeys;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.concurrent.ExecutionException;
|
|||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.mitre.jose.keystore.JWKSetKeyStore;
|
||||
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -80,32 +81,9 @@ public class JWKSetSigningAndValidationServiceCacheService {
|
|||
String jsonString = restTemplate.getForObject(key, String.class);
|
||||
JWKSet jwkSet = JWKSet.parse(jsonString);
|
||||
|
||||
Map<String, JWSVerifier> verifiers = new HashMap<String, JWSVerifier>();
|
||||
JWKSetKeyStore keyStore = new JWKSetKeyStore(jwkSet);
|
||||
|
||||
for (JWK jwk : jwkSet.getKeys()) {
|
||||
|
||||
if (jwk.getKeyType().equals(KeyType.RSA)) {
|
||||
// we can handle RSA
|
||||
RSAKey rsa = (RSAKey)jwk;
|
||||
|
||||
byte[] modulusByte = rsa.getModulus().decode();
|
||||
BigInteger modulus = new BigInteger(1, modulusByte);
|
||||
byte[] exponentByte = rsa.getPublicExponent().decode();
|
||||
BigInteger exponent = new BigInteger(1, exponentByte);
|
||||
|
||||
RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent);
|
||||
KeyFactory factory = KeyFactory.getInstance("RSA");
|
||||
|
||||
RSAPublicKey pub = (RSAPublicKey) factory.generatePublic(spec);
|
||||
|
||||
|
||||
JWSVerifier verifier = new RSASSAVerifier(pub);
|
||||
|
||||
verifiers.put(jwk.getKeyID(), verifier);
|
||||
}
|
||||
}
|
||||
|
||||
JwtSigningAndValidationService service = new DefaultJwtSigningAndValidationService(verifiers);
|
||||
JwtSigningAndValidationService service = new DefaultJwtSigningAndValidationService(keyStore);
|
||||
|
||||
return service;
|
||||
|
||||
|
|
|
@ -20,11 +20,7 @@ package org.mitre.openid.connect.view;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.math.BigInteger;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -35,12 +31,8 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.view.AbstractView;
|
||||
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.jwk.JWK;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.Use;
|
||||
import com.nimbusds.jose.util.Base64URL;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
|
@ -58,29 +50,9 @@ public class JwkKeyListView extends AbstractView {
|
|||
|
||||
|
||||
//BiMap<String, PublicKey> keyMap = (BiMap<String, PublicKey>) model.get("keys");
|
||||
Map<String, PublicKey> keys = (Map<String, PublicKey>) model.get("keys");
|
||||
Map<String, JWK> keys = (Map<String, JWK>) model.get("keys");
|
||||
|
||||
List<JWK> jwks = new ArrayList<JWK>();
|
||||
|
||||
for (String keyId : keys.keySet()) {
|
||||
|
||||
PublicKey key = keys.get(keyId);
|
||||
|
||||
if (key instanceof RSAPublicKey) {
|
||||
|
||||
RSAPublicKey rsa = (RSAPublicKey) key;
|
||||
|
||||
BigInteger mod = rsa.getModulus();
|
||||
BigInteger exp = rsa.getPublicExponent();
|
||||
|
||||
// FIXME: this assumes RS256
|
||||
RSAKey rsaKey = new RSAKey(Base64URL.encode(mod.toByteArray()), Base64URL.encode(exp.toByteArray()), Use.SIGNATURE, JWSAlgorithm.RS256, keyId);
|
||||
|
||||
jwks.add(rsaKey);
|
||||
} // TODO: deal with non-RSA key types
|
||||
}
|
||||
|
||||
JWKSet jwkSet = new JWKSet(jwks);
|
||||
JWKSet jwkSet = new JWKSet(new ArrayList<JWK>(keys.values()));
|
||||
|
||||
try {
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
******************************************************************************/
|
||||
package org.mitre.openid.connect.web;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
|
||||
|
@ -23,7 +22,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWK;
|
||||
|
||||
@Controller
|
||||
public class JsonWebKeyEndpoint {
|
||||
|
@ -34,8 +34,8 @@ public class JsonWebKeyEndpoint {
|
|||
@RequestMapping(value = "/jwk", produces = "application/json")
|
||||
public String getJwk(Model m) {
|
||||
|
||||
// map from key id to signer
|
||||
Map<String, PublicKey> keys = jwtService.getAllPublicKeys();
|
||||
// map from key id to key
|
||||
Map<String, JWK> keys = jwtService.getAllPublicKeys();
|
||||
|
||||
// TODO: check if keys are empty, return a 404 here or just an empty list?
|
||||
|
||||
|
|
|
@ -7,9 +7,8 @@
|
|||
<property name="location" value="classpath:keystore.jwks" />
|
||||
</bean>
|
||||
|
||||
<bean id="defaultsignerService"
|
||||
class="org.mitre.jwt.signer.service.impl.DefaultJwtSigningAndValidationService">
|
||||
<property name="keyStore" ref="defaultKeyStore" />
|
||||
<bean id="defaultsignerService" class="org.mitre.jwt.signer.service.impl.DefaultJwtSigningAndValidationService">
|
||||
<constructor-arg name="keyStore" ref="defaultKeyStore" />
|
||||
<property name="defaultSignerKeyId" value="rsa1" />
|
||||
<property name="defaultSigningAlgorithmName" value="RS256" />
|
||||
</bean>
|
||||
|
|
Loading…
Reference in New Issue