From 2d7e8610867dd24a5aa9609baad25d7cee53f1ff Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Tue, 31 Mar 2015 14:25:26 -0400 Subject: [PATCH] jwks parsing --- .../oidc/KeycloakOIDCIdentityProvider.java | 13 +++-- .../KeycloakOIDCIdentityProviderFactory.java | 14 +---- .../broker/oidc/OIDCIdentityProvider.java | 54 +++++++++++++++---- .../oidc/OIDCIdentityProviderConfig.java | 24 +++++++-- .../oidc/OIDCIdentityProviderFactory.java | 34 +++++++++++- .../main/java/org/keycloak/jose/jwk/JWK.java | 22 ++++++++ .../java/org/keycloak/jose/jwk/JWKParser.java | 22 ++++++-- .../realm-identity-provider-oidc.html | 14 +++++ .../realm-identity-provider-saml.html | 30 +++++------ 9 files changed, 176 insertions(+), 51 deletions(-) mode change 100644 => 100755 core/src/main/java/org/keycloak/jose/jwk/JWK.java mode change 100644 => 100755 core/src/main/java/org/keycloak/jose/jwk/JWKParser.java diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java index 152823f73e..47847dc68d 100755 --- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java +++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java @@ -4,17 +4,20 @@ import org.keycloak.broker.oidc.util.SimpleHttp; import org.keycloak.constants.AdapterConstants; import org.keycloak.events.EventBuilder; import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.models.RealmModel; import org.keycloak.models.UserSessionModel; import org.keycloak.representations.adapters.action.AdminAction; import org.keycloak.representations.adapters.action.LogoutAction; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.util.JsonSerialization; +import org.keycloak.util.PemUtils; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.core.Response; import java.io.IOException; +import java.security.PublicKey; /** * @author Bill Burke @@ -40,10 +43,12 @@ public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider { @Path(AdapterConstants.K_LOGOUT) public Response backchannelLogout(String input) { JWSInput token = new JWSInput(input); - String signingCert = getConfig().getSigningCertificate(); - if (signingCert != null && !signingCert.trim().equals("")) { - if (!token.verify(getConfig().getSigningCertificate())) { - return Response.status(400).build(); } + PublicKey key = getExternalIdpKey(); + if (key != null) { + if (!verify(token, key)) { + logger.warn("Failed to verify logout request"); + return Response.status(400).build(); + } } LogoutAction action = null; try { diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java index 9c46cd8d4b..6a47135faf 100755 --- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java +++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java @@ -50,19 +50,7 @@ public class KeycloakOIDCIdentityProviderFactory extends AbstractIdentityProvide @Override public Map parseConfig(InputStream inputStream) { - OIDCConfigurationRepresentation rep = null; - try { - rep = JsonSerialization.readValue(inputStream, OIDCConfigurationRepresentation.class); - } catch (IOException e) { - throw new RuntimeException("failed to load openid connect metadata", e); - } - OIDCIdentityProviderConfig config = new OIDCIdentityProviderConfig(new IdentityProviderModel()); - config.setIssuer(rep.getIssuer()); - config.setLogoutUrl(rep.getLogoutEndpoint()); - config.setAuthorizationUrl(rep.getAuthorizationEndpoint()); - config.setTokenUrl(rep.getTokenEndpoint()); - config.setUserInfoUrl(rep.getUserinfoEndpoint()); - return config.getConfig(); + return OIDCIdentityProviderFactory.parseOIDCConfig(inputStream); } } diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java index b4058b1409..a1ea53b0a5 100755 --- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java +++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java @@ -19,6 +19,7 @@ package org.keycloak.broker.oidc; import org.codehaus.jackson.JsonNode; import org.jboss.logging.Logger; +import org.keycloak.RSATokenVerifier; import org.keycloak.broker.oidc.util.SimpleHttp; import org.keycloak.broker.provider.AuthenticationRequest; import org.keycloak.broker.provider.FederatedIdentity; @@ -28,6 +29,7 @@ import org.keycloak.events.EventBuilder; import org.keycloak.events.EventGroup; import org.keycloak.events.EventType; import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.models.RealmModel; import org.keycloak.models.UserSessionModel; import org.keycloak.representations.AccessTokenResponse; @@ -38,6 +40,7 @@ import org.keycloak.services.resources.IdentityBrokerService; import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.resources.flows.Flows; import org.keycloak.util.JsonSerialization; +import org.keycloak.util.PemUtils; import javax.ws.rs.GET; import javax.ws.rs.Path; @@ -47,6 +50,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import java.io.IOException; +import java.security.PublicKey; import java.util.Map; /** @@ -74,6 +78,28 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider parseConfig(InputStream inputStream) { + return parseOIDCConfig(inputStream); + } + + protected static Map parseOIDCConfig(InputStream inputStream) { OIDCConfigurationRepresentation rep = null; try { rep = JsonSerialization.readValue(inputStream, OIDCConfigurationRepresentation.class); @@ -62,7 +74,27 @@ public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory config.setAuthorizationUrl(rep.getAuthorizationEndpoint()); config.setTokenUrl(rep.getTokenEndpoint()); config.setUserInfoUrl(rep.getUserinfoEndpoint()); - return config.getConfig(); + if (rep.getJwksUri() != null) { + String uri = rep.getJwksUri(); + String keySetString = null; + try { + keySetString = SimpleHttp.doGet(uri).asString(); + JSONWebKeySet keySet = JsonSerialization.readValue(keySetString, JSONWebKeySet.class); + for (JWK jwk : keySet.getKeys()) { + JWKParser parse = JWKParser.create(jwk); + if (parse.getJwk().getPublicKeyUse().equals(JWK.SIG_USE)) { + PublicKey key = parse.toPublicKey(); + config.setPublicKeySignatureVerifier(KeycloakModelUtils.getPemFromKey(key)); + config.setValidateSignature(true); + break; + } + } + } catch (IOException e) { + throw new RuntimeException("F ailed to query JWKSet from: " + uri, e); + } + + } + return config.getConfig(); } } diff --git a/core/src/main/java/org/keycloak/jose/jwk/JWK.java b/core/src/main/java/org/keycloak/jose/jwk/JWK.java old mode 100644 new mode 100755 index d292f41977..6b2cd9ca48 --- a/core/src/main/java/org/keycloak/jose/jwk/JWK.java +++ b/core/src/main/java/org/keycloak/jose/jwk/JWK.java @@ -1,7 +1,12 @@ package org.keycloak.jose.jwk; +import org.codehaus.jackson.annotate.JsonAnyGetter; +import org.codehaus.jackson.annotate.JsonAnySetter; import org.codehaus.jackson.annotate.JsonProperty; +import java.util.HashMap; +import java.util.Map; + /** * @author Stian Thorgersen */ @@ -15,6 +20,9 @@ public class JWK { public static final String PUBLIC_KEY_USE = "use"; + public static final String SIG_USE = "sig"; + public static final String ENCRYPTION_USE = "enc"; + @JsonProperty(KEY_ID) private String keyId; @@ -27,6 +35,9 @@ public class JWK { @JsonProperty(PUBLIC_KEY_USE) private String publicKeyUse; + protected Map otherClaims = new HashMap(); + + public String getKeyId() { return keyId; } @@ -59,4 +70,15 @@ public class JWK { this.publicKeyUse = publicKeyUse; } + @JsonAnyGetter + public Map getOtherClaims() { + return otherClaims; + } + + @JsonAnySetter + public void setOtherClaims(String name, Object value) { + otherClaims.put(name, value); + } + + } diff --git a/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java b/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java old mode 100644 new mode 100755 index 38f02d83cb..b498cc15a2 --- a/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java +++ b/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java @@ -17,29 +17,41 @@ public class JWKParser { private static TypeReference> typeRef = new TypeReference>() {}; - private Map values; + private JWK jwk; private JWKParser() { } + public JWKParser(JWK jwk) { + this.jwk = jwk; + } + public static JWKParser create() { return new JWKParser(); } + public static JWKParser create(JWK jwk) { + return new JWKParser(jwk); + } + public JWKParser parse(String jwk) { try { - this.values = JsonSerialization.mapper.readValue(jwk, typeRef); + this.jwk = JsonSerialization.mapper.readValue(jwk, JWK.class); return this; } catch (Exception e) { throw new RuntimeException(e); } } + public JWK getJwk() { + return jwk; + } + public PublicKey toPublicKey() { - String algorithm = values.get(JWK.KEY_TYPE); + String algorithm = jwk.getKeyType(); if (RSAPublicJWK.RSA.equals(algorithm)) { - BigInteger modulus = new BigInteger(1, Base64Url.decode(values.get(RSAPublicJWK.MODULUS))); - BigInteger publicExponent = new BigInteger(1, Base64Url.decode(values.get(RSAPublicJWK.PUBLIC_EXPONENT))); + BigInteger modulus = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.MODULUS).toString())); + BigInteger publicExponent = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.PUBLIC_EXPONENT).toString())); try { return KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, publicExponent)); diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html index b2a90917d1..6019f2c5a1 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html @@ -132,6 +132,20 @@ +
+ +
+ +
+ +
+
+ +
+