From 3629ff5e2fe2346444d76e681efcbae5243c0376 Mon Sep 17 00:00:00 2001 From: Justin Richer Date: Thu, 6 Mar 2014 16:44:31 +0000 Subject: [PATCH] added signed authentication assertion support to client, closes #558 --- .../client/OIDCAuthenticationFilter.java | 111 +++++++++++++++++- 1 file changed, 107 insertions(+), 4 deletions(-) diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java index 20c54b235..d174a8252 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java @@ -16,12 +16,14 @@ ******************************************************************************/ package org.mitre.openid.connect.client; -import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.SECRET_BASIC; +import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.*; import java.io.IOException; import java.math.BigInteger; import java.net.URI; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; import java.text.ParseException; import java.util.Date; import java.util.Map; @@ -35,7 +37,9 @@ import javax.servlet.http.HttpSession; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.SystemDefaultHttpClient; import org.mitre.jwt.signer.service.JwtSigningAndValidationService; +import org.mitre.jwt.signer.service.impl.DefaultJwtSigningAndValidationService; import org.mitre.jwt.signer.service.impl.JWKSetCacheService; +import org.mitre.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.model.RegisteredClient; import org.mitre.openid.connect.client.model.IssuerServiceResponse; import org.mitre.openid.connect.client.service.AuthRequestOptionsService; @@ -61,10 +65,19 @@ import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.OctetSequenceKey; +import com.nimbusds.jose.jwk.Use; import com.nimbusds.jose.util.Base64; +import com.nimbusds.jose.util.Base64URL; +import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.ReadOnlyJWTClaimsSet; import com.nimbusds.jwt.SignedJWT; @@ -90,6 +103,9 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi @Autowired private JWKSetCacheService validationServices; + + @Autowired(required=false) + private JwtSigningAndValidationService authenticationSignerService; // modular services to build out client filter private ServerConfigurationService servers; @@ -291,11 +307,61 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi return httpRequest; } }; - } else { //Alternatively use form based auth + } else { + // we're not doing basic auth, figure out what other flavor we have restTemplate = new RestTemplate(factory); - form.add("client_id", clientConfig.getClientId()); - form.add("client_secret", clientConfig.getClientSecret()); + if (SECRET_JWT.equals(clientConfig.getTokenEndpointAuthMethod()) || PRIVATE_KEY.equals(clientConfig.getTokenEndpointAuthMethod())) { + // do a symmetric secret signed JWT for auth + + + JwtSigningAndValidationService signer = null; + JWSAlgorithm alg = clientConfig.getTokenEndpointAuthSigningAlg(); + + if (SECRET_JWT.equals(clientConfig.getTokenEndpointAuthMethod()) && + (alg.equals(JWSAlgorithm.HS256) + || alg.equals(JWSAlgorithm.HS384) + || alg.equals(JWSAlgorithm.HS512))) { + + // generate one based on client secret + signer = getSymmetricValidtor(clientConfig.getClient()); + + } else if (PRIVATE_KEY.equals(clientConfig.getTokenEndpointAuthMethod())) { + + // needs to be wired in to the bean + signer = authenticationSignerService; + } + + if (signer == null) { + throw new AuthenticationServiceException("Couldn't find required signer service for use with private key auth."); + } + + JWTClaimsSet claimsSet = new JWTClaimsSet(); + + claimsSet.setIssuer(clientConfig.getClientId()); + claimsSet.setSubject(clientConfig.getClientId()); + claimsSet.setAudience(Lists.newArrayList(serverConfig.getIssuer())); + + // TODO: make this configurable + Date exp = new Date(System.currentTimeMillis() + (60 * 1000)); // auth good for 60 seconds + claimsSet.setExpirationTime(exp); + + Date now = new Date(System.currentTimeMillis()); + claimsSet.setIssueTime(now); + claimsSet.setNotBeforeTime(now); + + SignedJWT jwt = new SignedJWT(new JWSHeader(alg), claimsSet); + + signer.signJwt(jwt, alg); + + form.add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); + form.add("client_assertion", jwt.serialize()); + } else { + //Alternatively use form based auth + form.add("client_id", clientConfig.getClientId()); + form.add("client_secret", clientConfig.getClientSecret()); + } + } logger.debug("tokenEndpointURI = " + serverConfig.getTokenEndpointUri()); @@ -678,4 +744,41 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi this.authOptions = authOptions; } + /** + * Create a symmetric signing and validation service for the given client + * + * @param client + * @return + */ + private JwtSigningAndValidationService getSymmetricValidtor(ClientDetailsEntity client) { + + // TODO: cache + + if (client == null) { + logger.error("Couldn't create symmetric validator for null client"); + return null; + } + + if (Strings.isNullOrEmpty(client.getClientSecret())) { + logger.error("Couldn't create symmetric validator for client " + client.getClientId() + " without a client secret"); + return null; + } + + try { + + JWK jwk = new OctetSequenceKey(Base64URL.encode(client.getClientSecret()), Use.SIGNATURE, null, client.getClientId(), null, null, null); + Map keys = ImmutableMap.of(client.getClientId(), jwk); + JwtSigningAndValidationService service = new DefaultJwtSigningAndValidationService(keys); + + return service; + + } catch (NoSuchAlgorithmException e) { + logger.error("Couldn't create symmetric validator for client " + client.getClientId(), e); + } catch (InvalidKeySpecException e) { + logger.error("Couldn't create symmetric validator for client " + client.getClientId(), e); + } + + return null; + + } }