refactored signing and validation, added jwk-based cache, removed keyfetcher, refactored client side class structure
parent
7e64c4bffc
commit
385853fa1f
|
@ -1,4 +1,4 @@
|
|||
package org.mitre.oauth2.filter;
|
||||
package org.mitre.oauth2.introspectingfilter;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
|
@ -1,4 +1,4 @@
|
|||
package org.mitre.oauth2.filter;
|
||||
package org.mitre.oauth2.introspectingfilter;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gson.JsonElement;
|
|
@ -1,4 +1,4 @@
|
|||
package org.mitre.oauth2.filter;
|
||||
package org.mitre.oauth2.introspectingfilter;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
|
@ -43,8 +43,10 @@ import org.apache.http.impl.client.DefaultHttpClient;
|
|||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
|
||||
import org.mitre.jwt.signer.service.impl.DefaultJwtSigningAndValidationService;
|
||||
import org.mitre.jwt.signer.service.impl.JWKSetSigningAndValidationServiceCache;
|
||||
import org.mitre.key.fetch.KeyFetcher;
|
||||
import org.mitre.openid.connect.config.OIDCServerConfiguration;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
@ -86,8 +88,9 @@ public class AbstractOIDCAuthenticationFilter extends
|
|||
|
||||
// Allow for time sync issues by having a window of X seconds.
|
||||
private int timeSkewAllowance = 300;
|
||||
|
||||
private Map<OIDCServerConfiguration, JwtSigningAndValidationService> validationServices = new HashMap<OIDCServerConfiguration, JwtSigningAndValidationService>();
|
||||
|
||||
@Autowired
|
||||
JWKSetSigningAndValidationServiceCache validationServices;
|
||||
|
||||
/**
|
||||
* Builds the redirect_uri that will be sent to the Authorization Endpoint.
|
||||
|
@ -352,7 +355,7 @@ public class AbstractOIDCAuthenticationFilter extends
|
|||
ReadOnlyJWTClaimsSet idClaims = idToken.getJWTClaimsSet();
|
||||
|
||||
// check the signature
|
||||
JwtSigningAndValidationService jwtValidator = getValidatorForServer(serverConfig);
|
||||
JwtSigningAndValidationService jwtValidator = validationServices.get(serverConfig.getJwkSigningUrl());
|
||||
if (jwtValidator != null) {
|
||||
if(!jwtValidator.validateSignature(idToken)) {
|
||||
throw new AuthenticationServiceException("Signature validation failed");
|
||||
|
@ -607,57 +610,6 @@ public class AbstractOIDCAuthenticationFilter extends
|
|||
return getStoredSessionString(session, REDIRECT_URI_SESION_VARIABLE);
|
||||
}
|
||||
|
||||
|
||||
protected JwtSigningAndValidationService getValidatorForServer(OIDCServerConfiguration serverConfig) {
|
||||
|
||||
if(getValidationServices().containsKey(serverConfig)){
|
||||
return validationServices.get(serverConfig);
|
||||
} else {
|
||||
|
||||
KeyFetcher keyFetch = new KeyFetcher();
|
||||
PublicKey signingKey = null;
|
||||
|
||||
if (serverConfig.getJwkSigningUrl() != null) {
|
||||
// prefer the JWK
|
||||
signingKey = keyFetch.retrieveJwkKey(serverConfig.getJwkSigningUrl());
|
||||
} else if (serverConfig.getX509SigningUrl() != null) {
|
||||
// use the x509 only if JWK isn't configured
|
||||
signingKey = keyFetch.retrieveX509Key(serverConfig.getX509SigningUrl());
|
||||
} else {
|
||||
// no keys configured
|
||||
logger.warn("No server key URLs configured for " + serverConfig.getIssuer());
|
||||
}
|
||||
|
||||
if (signingKey != null) {
|
||||
|
||||
// TODO: this assumes RSA
|
||||
JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) signingKey);
|
||||
|
||||
Map<String, JWSVerifier> verifiers = ImmutableMap.of(serverConfig.getIssuer(), verifier);
|
||||
|
||||
JwtSigningAndValidationService service = new DefaultJwtSigningAndValidationService(new HashMap<String, JWSSigner>(), verifiers);
|
||||
|
||||
validationServices.put(serverConfig, service);
|
||||
|
||||
return service;
|
||||
|
||||
} else {
|
||||
// there were either no keys returned or no URLs configured to fetch them, assume no checking on key signatures
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Map<OIDCServerConfiguration, JwtSigningAndValidationService> getValidationServices() {
|
||||
return validationServices;
|
||||
}
|
||||
|
||||
public void setValidationServices(
|
||||
Map<OIDCServerConfiguration, JwtSigningAndValidationService> validationServices) {
|
||||
this.validationServices = validationServices;
|
||||
}
|
||||
|
||||
public int getTimeSkewAllowance() {
|
||||
return timeSkewAllowance;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.mitre.openid.connect.client;
|
||||
package org.mitre.openid.connect.client.keypublisher;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.Map;
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.mitre.openid.connect.client;
|
||||
package org.mitre.openid.connect.client.keypublisher;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.mitre.openid.connect.client;
|
||||
package org.mitre.openid.connect.client.keypublisher;
|
||||
|
||||
import java.util.Locale;
|
||||
|
|
@ -19,6 +19,7 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.security.PublicKey;
|
||||
import java.util.Map;
|
||||
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
|
||||
public interface JwtSigningAndValidationService {
|
||||
|
@ -49,6 +50,12 @@ public interface JwtSigningAndValidationService {
|
|||
*/
|
||||
public void signJwt(SignedJWT jwt) throws NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* Get the default signing algorithm for use when nothing else has been specified.
|
||||
* @return
|
||||
*/
|
||||
JWSAlgorithm getDefaultSigningAlgorithm();
|
||||
|
||||
/**
|
||||
* Sign a jwt using the selected algorithm. The algorithm is selected using the String parameter values specified
|
||||
* in the JWT spec, section 6. I.E., "HS256" means HMAC with SHA-256 and corresponds to our HmacSigner class.
|
||||
|
|
|
@ -28,7 +28,9 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.nimbusds.jose.JOSEException;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.JWSSigner;
|
||||
import com.nimbusds.jose.JWSVerifier;
|
||||
import com.nimbusds.jose.crypto.RSASSAVerifier;
|
||||
|
@ -36,9 +38,6 @@ import com.nimbusds.jwt.SignedJWT;
|
|||
|
||||
public class DefaultJwtSigningAndValidationService implements JwtSigningAndValidationService, InitializingBean {
|
||||
|
||||
@Autowired
|
||||
private ConfigurationPropertiesBean configBean;
|
||||
|
||||
// map of identifier to signer
|
||||
private Map<String, JWSSigner> signers = new HashMap<String, JWSSigner>();
|
||||
// map of identifier to verifier
|
||||
|
@ -46,24 +45,37 @@ public class DefaultJwtSigningAndValidationService implements JwtSigningAndValid
|
|||
|
||||
private static Logger logger = LoggerFactory.getLogger(DefaultJwtSigningAndValidationService.class);
|
||||
|
||||
private String defaultSignerId;
|
||||
|
||||
private JWSAlgorithm defaultAlgorithm;
|
||||
|
||||
/**
|
||||
* default constructor
|
||||
*/
|
||||
public DefaultJwtSigningAndValidationService() {
|
||||
|
||||
}
|
||||
|
||||
public DefaultJwtSigningAndValidationService(Map<String, JWSSigner> signers, Map<String, JWSVerifier> verifiers) {
|
||||
this.signers = signers;
|
||||
this.verifiers = verifiers;
|
||||
}
|
||||
|
||||
public DefaultJwtSigningAndValidationService(Map<String, RSASSASignerVerifierBuilder> builders) {
|
||||
/**
|
||||
* Create a new validation service from the given set of verifiers (no signing)
|
||||
*/
|
||||
public DefaultJwtSigningAndValidationService(Map<String, JWSVerifier> verifiers) {
|
||||
this.verifiers = verifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load this signing and validation service from the given builders (which load keys from keystores)
|
||||
* @param builders
|
||||
*/
|
||||
public void setBuilders(Map<String, RSASSASignerVerifierBuilder> builders) {
|
||||
|
||||
for (Entry<String, RSASSASignerVerifierBuilder> e : builders.entrySet()) {
|
||||
|
||||
JWSSigner signer = e.getValue().buildSigner();
|
||||
signers.put(e.getKey(), signer);
|
||||
if (e.getValue().isDefault()) {
|
||||
defaultSignerId = e.getKey();
|
||||
}
|
||||
|
||||
JWSVerifier verifier = e.getValue().buildVerifier();
|
||||
verifiers.put(e.getKey(), verifier);
|
||||
|
@ -72,6 +84,39 @@ public class DefaultJwtSigningAndValidationService implements JwtSigningAndValid
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the defaultSignerId
|
||||
*/
|
||||
public String getDefaultSignerId() {
|
||||
return defaultSignerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param defaultSignerId the defaultSignerId to set
|
||||
*/
|
||||
public void setDefaultSignerId(String defaultSignerId) {
|
||||
this.defaultSignerId = defaultSignerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public JWSAlgorithm getDefaultSigningAlgorithm() {
|
||||
return defaultAlgorithm;
|
||||
}
|
||||
|
||||
public void setDefaultSigningAlgorithmName(String algName) {
|
||||
defaultAlgorithm = JWSAlgorithm.parse(algName);
|
||||
}
|
||||
|
||||
public String getDefaultSigningAlgorithmName() {
|
||||
if (defaultAlgorithm != null) {
|
||||
return defaultAlgorithm.getName();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
@ -80,41 +125,20 @@ public class DefaultJwtSigningAndValidationService implements JwtSigningAndValid
|
|||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet(){
|
||||
// used for debugging...
|
||||
if (!signers.isEmpty()) {
|
||||
logger.info(this.toString());
|
||||
}
|
||||
|
||||
logger.info("DefaultJwtSigningAndValidationService is open for business");
|
||||
|
||||
logger.info("DefaultJwtSigningAndValidationService is ready: " + this.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the configBean
|
||||
*/
|
||||
public ConfigurationPropertiesBean getConfigBean() {
|
||||
return configBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param configBean the configBean to set
|
||||
*/
|
||||
public void setConfigBean(ConfigurationPropertiesBean configBean) {
|
||||
this.configBean = configBean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sign a jwt in place using the configured default signer.
|
||||
* @throws JOSEException
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
@Override
|
||||
public void signJwt(SignedJWT jwt) {
|
||||
String signerId = configBean.getDefaultJwtSigner();
|
||||
|
||||
JWSSigner signer = signers.get(signerId);
|
||||
if (getDefaultSignerId() == null) {
|
||||
throw new IllegalStateException("Tried to call default signing with no default signer ID set");
|
||||
}
|
||||
|
||||
JWSSigner signer = signers.get(getDefaultSignerId());
|
||||
|
||||
try {
|
||||
jwt.sign(signer);
|
||||
} catch (JOSEException e) {
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.mitre.jwt.signer.service.impl;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.nimbusds.jose.JWK;
|
||||
import com.nimbusds.jose.JWKSet;
|
||||
import com.nimbusds.jose.JWSSigner;
|
||||
import com.nimbusds.jose.JWSVerifier;
|
||||
import com.nimbusds.jose.KeyType;
|
||||
import com.nimbusds.jose.RSAKey;
|
||||
import com.nimbusds.jose.crypto.RSASSAVerifier;
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a
|
||||
*
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public class JWKSetSigningAndValidationServiceCache {
|
||||
|
||||
private Cache<String, JwtSigningAndValidationService> cache;
|
||||
|
||||
public JWKSetSigningAndValidationServiceCache() {
|
||||
this.cache = CacheBuilder.newBuilder()
|
||||
.maximumSize(100)
|
||||
.build(new JWKSetFetcher());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @return
|
||||
* @throws ExecutionException
|
||||
* @see com.google.common.cache.Cache#get(java.lang.Object)
|
||||
*/
|
||||
public JwtSigningAndValidationService get(String key) {
|
||||
try {
|
||||
return cache.get(key);
|
||||
} catch (ExecutionException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
private class JWKSetFetcher extends CacheLoader<String, JwtSigningAndValidationService> {
|
||||
private HttpClient httpClient = new DefaultHttpClient();
|
||||
private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
||||
private RestTemplate restTemplate = new RestTemplate(httpFactory);
|
||||
|
||||
/**
|
||||
* Load the JWK Set and build the appropriate signing service.
|
||||
*/
|
||||
@Override
|
||||
public JwtSigningAndValidationService load(String key) throws Exception {
|
||||
|
||||
String jsonString = restTemplate.getForObject(key, String.class);
|
||||
JWKSet jwkSet = JWKSet.parse(jsonString);
|
||||
|
||||
Map<String, JWSVerifier> verifiers = new HashMap<String, JWSVerifier>();
|
||||
|
||||
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.getExponent().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);
|
||||
|
||||
return service;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -28,6 +28,8 @@ public class RSASSASignerVerifierBuilder {
|
|||
private String alias;
|
||||
private String password;
|
||||
private KeyStore keystore;
|
||||
private boolean defaultSigner = false;
|
||||
|
||||
/**
|
||||
* @return the alias
|
||||
*/
|
||||
|
@ -65,6 +67,18 @@ public class RSASSASignerVerifierBuilder {
|
|||
this.keystore = keystore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the defaultSigner
|
||||
*/
|
||||
public boolean isDefault() {
|
||||
return defaultSigner;
|
||||
}
|
||||
/**
|
||||
* @param defaultSigner the defaultSigner to set
|
||||
*/
|
||||
public void setDefault(boolean defaultSigner) {
|
||||
this.defaultSigner = defaultSigner;
|
||||
}
|
||||
/**
|
||||
* Build the signer as configured from the given keystore, null if it can't be built for some reason
|
||||
* @return
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
package org.mitre.key.fetch;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.mitre.openid.connect.config.OIDCServerConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
public class KeyFetcher {
|
||||
|
||||
private HttpClient httpClient = new DefaultHttpClient();
|
||||
private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
||||
private RestTemplate restTemplate = new RestTemplate(httpFactory);
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(KeyFetcher.class);
|
||||
|
||||
public JsonArray retrieveJwk(OIDCServerConfiguration serverConfig){
|
||||
|
||||
String jsonString = null;
|
||||
|
||||
try {
|
||||
jsonString = restTemplate.getForObject(
|
||||
serverConfig.getTokenEndpointUrl(), String.class);
|
||||
} catch (HttpClientErrorException httpClientErrorException) {
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
"Unable to obtain Access Token.");
|
||||
}
|
||||
|
||||
JsonObject json = (JsonObject) new JsonParser().parse(jsonString);
|
||||
JsonArray getArray = json.getAsJsonArray("jwk");
|
||||
|
||||
return getArray;
|
||||
}
|
||||
|
||||
public PublicKey retrieveX509Key(String x509url) {
|
||||
|
||||
|
||||
PublicKey key = null;
|
||||
|
||||
try {
|
||||
InputStream x509Stream = restTemplate.getForObject(x509url, InputStream.class);
|
||||
CertificateFactory factory = CertificateFactory.getInstance("X.509");
|
||||
X509Certificate cert = (X509Certificate) factory.generateCertificate(x509Stream);
|
||||
key = cert.getPublicKey();
|
||||
} catch (HttpClientErrorException e) {
|
||||
logger.error("HttpClientErrorException in KeyFetcher.java: ", e);
|
||||
} catch (CertificateException e) {
|
||||
logger.error("CertificateException in KeyFetcher.java: ", e);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
public PublicKey retrieveJwkKey(String jwkUrl) {
|
||||
RSAPublicKey pub = null;
|
||||
|
||||
try {
|
||||
String jwkString = restTemplate.getForObject(jwkUrl, String.class);
|
||||
JsonObject json = (JsonObject) new JsonParser().parse(jwkString);
|
||||
JsonArray getArray = json.getAsJsonArray("keys");
|
||||
for(int i = 0; i < getArray.size(); i++) {
|
||||
JsonObject object = getArray.get(i).getAsJsonObject();
|
||||
String algorithm = object.get("alg").getAsString();
|
||||
|
||||
if(algorithm.equals("RSA")){
|
||||
byte[] modulusByte = Base64.decodeBase64(object.get("mod").getAsString());
|
||||
BigInteger modulus = new BigInteger(1, modulusByte);
|
||||
byte[] exponentByte = Base64.decodeBase64(object.get("exp").getAsString());
|
||||
BigInteger exponent = new BigInteger(1, exponentByte);
|
||||
|
||||
RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent);
|
||||
KeyFactory factory = KeyFactory.getInstance("RSA");
|
||||
pub = (RSAPublicKey) factory.generatePublic(spec);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (HttpClientErrorException e) {
|
||||
logger.error("HttpClientErrorException in KeyFetcher.java: ", e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
logger.error("NoSuchAlgorithmException in KeyFetcher.java: ", e);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
logger.error("InvalidKeySpecException in KeyFetcher.java: ", e);
|
||||
}
|
||||
return pub;
|
||||
}
|
||||
|
||||
}
|
|
@ -15,7 +15,6 @@
|
|||
******************************************************************************/
|
||||
package org.mitre.openid.connect.config;
|
||||
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -30,10 +29,6 @@ public class ConfigurationPropertiesBean {
|
|||
|
||||
private String issuer;
|
||||
|
||||
private String defaultJwtSigner;
|
||||
|
||||
private JWSAlgorithm defaultAlgorithm;
|
||||
|
||||
private String adminConsoleTopbarTitle;
|
||||
|
||||
private String logoImageUrl;
|
||||
|
@ -46,17 +41,6 @@ public class ConfigurationPropertiesBean {
|
|||
public ConfigurationPropertiesBean() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the defaultJwtSigner
|
||||
*/
|
||||
public String getDefaultJwtSigner() {
|
||||
return defaultJwtSigner;
|
||||
}
|
||||
|
||||
public void setDefaultJwtSigner(String signer) {
|
||||
defaultJwtSigner = signer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the baseUrl
|
||||
*/
|
||||
|
@ -71,25 +55,6 @@ public class ConfigurationPropertiesBean {
|
|||
issuer = iss;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public JWSAlgorithm getDefaultSigningAlgorithm() {
|
||||
return defaultAlgorithm;
|
||||
}
|
||||
|
||||
public void setDefaultSigningAlgorithmName(String algName) {
|
||||
defaultAlgorithm = JWSAlgorithm.parse(algName);
|
||||
}
|
||||
|
||||
public String getDefaultSigningAlgorithmName() {
|
||||
if (defaultAlgorithm != null) {
|
||||
return defaultAlgorithm.getName();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getAdminConsoleTopbarTitle() {
|
||||
return adminConsoleTopbarTitle;
|
||||
}
|
||||
|
|
|
@ -3,14 +3,11 @@
|
|||
*/
|
||||
package org.mitre.openid.connect.assertion;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mitre.key.fetch.KeyFetcher;
|
||||
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
|
||||
import org.mitre.jwt.signer.service.impl.JWKSetSigningAndValidationServiceCache;
|
||||
import org.mitre.oauth2.exception.ClientNotFoundException;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||
|
@ -24,12 +21,9 @@ import org.springframework.security.core.Authentication;
|
|||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
|
||||
import com.nimbusds.jose.JOSEException;
|
||||
import com.nimbusds.jose.JWSObject;
|
||||
import com.nimbusds.jose.JWSVerifier;
|
||||
import com.nimbusds.jose.crypto.RSASSAVerifier;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.ReadOnlyJWTClaimsSet;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
|
@ -40,7 +34,8 @@ public class JwtBearerAuthenticationProvider implements AuthenticationProvider {
|
|||
private static final Logger logger = LoggerFactory.getLogger(JwtBearerAuthenticationProvider.class);
|
||||
|
||||
// map of verifiers, load keys for clients
|
||||
private Map<ClientDetailsEntity, JWSVerifier> verifiers = new HashMap<ClientDetailsEntity, JWSVerifier>();
|
||||
@Autowired
|
||||
private JWKSetSigningAndValidationServiceCache validators;
|
||||
|
||||
// Allow for time sync issues by having a window of X seconds.
|
||||
private int timeSkewAllowance = 300;
|
||||
|
@ -69,10 +64,12 @@ public class JwtBearerAuthenticationProvider implements AuthenticationProvider {
|
|||
ReadOnlyJWTClaimsSet jwtClaims = jwt.getJWTClaimsSet();
|
||||
|
||||
// check the signature with nimbus
|
||||
JWSVerifier verifier = getVerifierForClient(client);
|
||||
JWSObject jws = JWSObject.parse(jwtAuth.getJwt().toString());
|
||||
if (verifier == null && !jws.verify(verifier)) {
|
||||
throw new AuthenticationServiceException("Invalid signature");
|
||||
if (jwt instanceof SignedJWT) {
|
||||
SignedJWT jws = (SignedJWT)jwt;
|
||||
JwtSigningAndValidationService validator = validators.get(client.getJwkUrl());
|
||||
if (validator == null || !validator.validateSignature(jws)) {
|
||||
throw new AuthenticationServiceException("Invalid signature");
|
||||
}
|
||||
}
|
||||
|
||||
// check the issuer
|
||||
|
@ -125,12 +122,7 @@ public class JwtBearerAuthenticationProvider implements AuthenticationProvider {
|
|||
} catch (ParseException e) {
|
||||
// TODO Auto-generated catch block
|
||||
throw new AuthenticationServiceException("Invalid JWT format");
|
||||
} catch (JOSEException e) {
|
||||
// TODO Auto-generated catch block
|
||||
throw new AuthenticationServiceException("JOSE Error");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,39 +133,5 @@ public class JwtBearerAuthenticationProvider implements AuthenticationProvider {
|
|||
return (JwtBearerAssertionAuthenticationToken.class.isAssignableFrom(authentication));
|
||||
}
|
||||
|
||||
protected JWSVerifier getVerifierForClient(ClientDetailsEntity client) {
|
||||
|
||||
if(verifiers.containsKey(client)){
|
||||
return verifiers.get(client);
|
||||
} else {
|
||||
|
||||
KeyFetcher keyFetch = new KeyFetcher();
|
||||
PublicKey signingKey = null;
|
||||
|
||||
if (client.getJwkUrl() != null) {
|
||||
// prefer the JWK
|
||||
signingKey = keyFetch.retrieveJwkKey(client.getJwkUrl());
|
||||
} else if (client.getX509Url() != null) {
|
||||
// use the x509 only if JWK isn't configured
|
||||
signingKey = keyFetch.retrieveX509Key(client.getX509Url());
|
||||
} else {
|
||||
// no keys configured
|
||||
logger.warn("No server key URLs configured for " + client.getClientId());
|
||||
}
|
||||
|
||||
if (signingKey != null) {
|
||||
|
||||
// TODO: this assumes RSA
|
||||
JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) signingKey);
|
||||
|
||||
verifiers.put(client, verifier);
|
||||
|
||||
return verifier;
|
||||
|
||||
} else {
|
||||
// there were either no keys returned or no URLs configured to fetch them, assume no checking on key signatures
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ public class ConnectTokenEnhancer implements TokenEnhancer {
|
|||
OAuth2AccessTokenEntity token = (OAuth2AccessTokenEntity) accessToken;
|
||||
|
||||
String clientId = authentication.getAuthorizationRequest().getClientId();
|
||||
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
||||
|
||||
JWTClaimsSet claims = new JWTClaimsSet();
|
||||
|
||||
|
@ -73,7 +74,9 @@ public class ConnectTokenEnhancer implements TokenEnhancer {
|
|||
|
||||
claims.setJWTID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it
|
||||
|
||||
SignedJWT signed = new SignedJWT(new JWSHeader(configBean.getDefaultSigningAlgorithm()), claims);
|
||||
// TODO: use client's default signing algorithm
|
||||
|
||||
SignedJWT signed = new SignedJWT(new JWSHeader(jwtService.getDefaultSigningAlgorithm()), claims);
|
||||
|
||||
try {
|
||||
jwtService.signJwt(signed);
|
||||
|
@ -106,8 +109,6 @@ public class ConnectTokenEnhancer implements TokenEnhancer {
|
|||
|
||||
idClaims.setIssueTime(new Date());
|
||||
|
||||
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
||||
|
||||
if (client.getIdTokenValiditySeconds() != null) {
|
||||
Date expiration = new Date(System.currentTimeMillis() + (client.getIdTokenValiditySeconds() * 1000L));
|
||||
idClaims.setExpirationTime(expiration);
|
||||
|
@ -124,7 +125,7 @@ public class ConnectTokenEnhancer implements TokenEnhancer {
|
|||
idClaims.setCustomClaim("nonce", nonce);
|
||||
}
|
||||
|
||||
SignedJWT idToken = new SignedJWT(new JWSHeader(configBean.getDefaultSigningAlgorithm()), idClaims);
|
||||
SignedJWT idToken = new SignedJWT(new JWSHeader(jwtService.getDefaultSigningAlgorithm()), idClaims);
|
||||
|
||||
//TODO: check for client's preferred signer alg and use that
|
||||
|
||||
|
|
|
@ -10,17 +10,19 @@
|
|||
|
||||
<bean id="defaultsignerService"
|
||||
class="org.mitre.jwt.signer.service.impl.DefaultJwtSigningAndValidationService">
|
||||
<constructor-arg name="builders">
|
||||
<property name="builders">
|
||||
<map>
|
||||
<entry key="rsa1">
|
||||
<bean id="rsaSignerBuilder" class="org.mitre.jwt.signer.service.impl.RSASSASignerVerifierBuilder">
|
||||
<property name="keystore" ref="defaultKeystore" />
|
||||
<property name="alias" value="rsa" />
|
||||
<property name="password" value="changeit" />
|
||||
<property name="default" value="true" />
|
||||
</bean>
|
||||
</entry>
|
||||
</map>
|
||||
</constructor-arg>
|
||||
</property>
|
||||
<property name="defaultSigningAlgorithmName" value="RS256" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
|
||||
<bean id="configBean" class="org.mitre.openid.connect.config.ConfigurationPropertiesBean">
|
||||
<property name="issuer" value="http://localhost/" />
|
||||
<property name="defaultJwtSigner" value="rsa1"/>
|
||||
<property name="defaultSigningAlgorithmName" value="RS256" />
|
||||
<property name="adminConsoleCopyrightFooter" value="The MTIRE Corporation" />
|
||||
<property name="logoImageUrl" value="https://id.mitre.org/connect/resources/images/openid_connect_small.png" />
|
||||
<property name="adminConsoleTopbarTitle" value="MITREid Connect" />
|
||||
|
|
Loading…
Reference in New Issue