/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.cxf.rs.security.oidc.rp; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.cxf.rs.security.jose.jwk.JsonWebKey; import org.apache.cxf.rs.security.jose.jwk.JsonWebKeys; import org.apache.cxf.rs.security.jose.jwk.JwkUtils; import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier; import org.apache.cxf.rs.security.jose.jws.JwsUtils; import org.apache.cxf.rs.security.jose.jwt.JwtClaims; import org.apache.cxf.rs.security.jose.jwt.JwtException; import org.apache.cxf.rs.security.jose.jwt.JwtToken; import org.apache.cxf.rs.security.jose.jwt.JwtUtils; import org.apache.cxf.rs.security.oauth2.provider.OAuthJoseJwtConsumer; import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException; import org.apache.cxf.rs.security.oidc.common.IdToken; public class OidcClaimsValidator extends OAuthJoseJwtConsumer { private static final String SELF_ISSUED_ISSUER = "https://self-issued.me"; private String issuerId; private WebClient jwkSetClient; private boolean supportSelfIssuedProvider; private boolean strictTimeValidation; private ConcurrentHashMap<String, JsonWebKey> keyMap = new ConcurrentHashMap<String, JsonWebKey>(); /** * Validate core JWT claims * @param claims the claims * @param clientId OAuth2 client id * @param validateClaimsAlways if set to true then enforce that the claims * to be validated must be set */ public void validateJwtClaims(JwtClaims claims, String clientId, boolean validateClaimsAlways) { // validate the issuer String issuer = claims.getIssuer(); if (issuer == null && validateClaimsAlways) { throw new OAuthServiceException("Invalid issuer"); } if (supportSelfIssuedProvider && issuerId == null && issuer != null && SELF_ISSUED_ISSUER.equals(issuer)) { validateSelfIssuedProvider(claims, clientId, validateClaimsAlways); } else { if (issuer != null && !issuer.equals(issuerId)) { throw new OAuthServiceException("Invalid issuer"); } // validate subject if (claims.getSubject() == null) { throw new OAuthServiceException("Invalid subject"); } // validate authorized party String authorizedParty = (String)claims.getClaim(IdToken.AZP_CLAIM); if (authorizedParty != null && !authorizedParty.equals(clientId)) { throw new OAuthServiceException("Invalid authorized party"); } // validate audience List<String> audiences = claims.getAudiences(); if (StringUtils.isEmpty(audiences) && validateClaimsAlways || !StringUtils.isEmpty(audiences) && !audiences.contains(clientId)) { throw new OAuthServiceException("Invalid audience"); } // If strict time validation: if no issuedTime claim is set then an expiresAt claim must be set // Otherwise: validate only if expiresAt claim is set boolean expiredRequired = validateClaimsAlways || strictTimeValidation && claims.getIssuedAt() == null; try { JwtUtils.validateJwtExpiry(claims, getClockOffset(), expiredRequired); } catch (JwtException ex) { throw new OAuthServiceException("ID Token has expired", ex); } // If strict time validation: If no expiresAt claim is set then an issuedAt claim must be set // Otherwise: validate only if issuedAt claim is set boolean issuedAtRequired = validateClaimsAlways || strictTimeValidation && claims.getExpiryTime() == null; try { JwtUtils.validateJwtIssuedAt(claims, getTtl(), getClockOffset(), issuedAtRequired); } catch (JwtException ex) { throw new OAuthServiceException("Invalid issuedAt claim", ex); } if (strictTimeValidation) { try { JwtUtils.validateJwtNotBefore(claims, getClockOffset(), strictTimeValidation); } catch (JwtException ex) { throw new OAuthServiceException("ID Token can not be used yet", ex); } } } } private void validateSelfIssuedProvider(JwtClaims claims, String clientId, boolean validateClaimsAlways) { } public void setIssuerId(String issuerId) { this.issuerId = issuerId; } public void setJwkSetClient(WebClient jwkSetClient) { this.jwkSetClient = jwkSetClient; } @Override protected JwsSignatureVerifier getInitializedSignatureVerifier(JwtToken jwt) { JsonWebKey key = null; if (supportSelfIssuedProvider && SELF_ISSUED_ISSUER.equals(jwt.getClaim("issuer"))) { String publicKeyJson = (String)jwt.getClaim("sub_jwk"); if (publicKeyJson != null) { JsonWebKey publicKey = JwkUtils.readJwkKey(publicKeyJson); String thumbprint = JwkUtils.getThumbprint(publicKey); if (thumbprint.equals(jwt.getClaim("sub"))) { key = publicKey; } } if (key == null) { throw new SecurityException("Self-issued JWK key is invalid or not available"); } } else { String keyId = jwt.getJwsHeaders().getKeyId(); key = keyId != null ? keyMap.get(keyId) : null; if (key == null && jwkSetClient != null) { JsonWebKeys keys = jwkSetClient.get(JsonWebKeys.class); if (keyId != null) { key = keys.getKey(keyId); } else if (keys.getKeys().size() == 1) { key = keys.getKeys().get(0); } //jwkSetClient returns the most up-to-date keys keyMap.clear(); keyMap.putAll(keys.getKeyIdMap()); } } JwsSignatureVerifier theJwsVerifier = null; if (key != null) { theJwsVerifier = JwsUtils.getSignatureVerifier(key); } else { theJwsVerifier = super.getInitializedSignatureVerifier(jwt.getJwsHeaders()); } if (theJwsVerifier == null) { throw new SecurityException("JWS Verifier is not available"); } return theJwsVerifier; } public void setSupportSelfIssuedProvider(boolean supportSelfIssuedProvider) { this.supportSelfIssuedProvider = supportSelfIssuedProvider; } public void setStrictTimeValidation(boolean strictTimeValidation) { this.strictTimeValidation = strictTimeValidation; } }