All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nimbusds.oauth2.sdk.auth.verifier.ClientAuthenticationVerifier Maven / Gradle / Ivy

Go to download

OAuth 2.0 SDK with OpenID Connection extensions for developing client and server applications.

There is a newer version: 11.20.1
Show newest version
/*
 * oauth2-oidc-sdk
 *
 * Copyright 2012-2016, Connect2id Ltd and contributors.
 *
 * Licensed 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 com.nimbusds.oauth2.sdk.auth.verifier;


import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory;
import com.nimbusds.jose.proc.JWSVerifierFactory;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.jwt.proc.BadJWTException;
import com.nimbusds.oauth2.sdk.auth.*;
import com.nimbusds.oauth2.sdk.id.Audience;
import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.oauth2.sdk.id.JWTID;
import com.nimbusds.oauth2.sdk.util.CollectionUtils;
import com.nimbusds.oauth2.sdk.util.ListUtils;
import com.nimbusds.oauth2.sdk.util.X509CertificateUtils;
import net.jcip.annotations.ThreadSafe;

import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;


/**
 * Client authentication verifier.
 *
 * 

Related specifications: * *

    *
  • OAuth 2.0 (RFC 6749), sections 2.3.1 and 3.2.1. *
  • OpenID Connect Core 1.0, section 9. *
  • JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and * Authorization Grants (RFC 7523). *
  • OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound * Access Tokens (RFC 8705), section 2. *
*/ @ThreadSafe public class ClientAuthenticationVerifier { /** * The client credentials selector. */ private final ClientCredentialsSelector clientCredentialsSelector; /** * Optional client X.509 certificate binding verifier for * {@code tls_client_auth}. * @deprecated Replaced by pkiCertBindingVerifier */ @Deprecated private final ClientX509CertificateBindingVerifier certBindingVerifier; /** * Optional client X.509 certificate binding verifier for * {@code tls_client_auth}. */ private final PKIClientX509CertificateBindingVerifier pkiCertBindingVerifier; /** * The JWT assertion claims set verifier. */ private final JWTAuthenticationClaimsSetVerifier claimsSetVerifier; /** * Optional expended JWT ID (jti) checker. */ private final ExpendedJTIChecker expendedJTIChecker; /** * JWS verifier factory for private_key_jwt authentication. */ private final JWSVerifierFactory jwsVerifierFactory = new DefaultJWSVerifierFactory(); /** * Creates a new client authentication verifier. * * @param clientCredentialsSelector The client credentials selector. * Must not be {@code null}. * @param certBindingVerifier Optional client X.509 certificate * binding verifier for * {@code tls_client_auth}, * {@code null} if not supported. * @param expectedAudience The permitted audience (aud) claim * values in JWT authentication * assertions. Must not be empty or * {@code null}. Should typically * contain the token endpoint URI and * for OpenID provider it may also * include the issuer URI. * * @deprecated Use the constructor with {@link PKIClientX509CertificateBindingVerifier} */ @Deprecated public ClientAuthenticationVerifier(final ClientCredentialsSelector clientCredentialsSelector, final ClientX509CertificateBindingVerifier certBindingVerifier, final Set expectedAudience) { claimsSetVerifier = new JWTAuthenticationClaimsSetVerifier(expectedAudience); if (clientCredentialsSelector == null) { throw new IllegalArgumentException("The client credentials selector must not be null"); } this.certBindingVerifier = certBindingVerifier; this.pkiCertBindingVerifier = null; this.clientCredentialsSelector = clientCredentialsSelector; this.expendedJTIChecker = null; } /** * Creates a new client authentication verifier without support for * {@code tls_client_auth}. * * @param clientCredentialsSelector The client credentials selector. * Must not be {@code null}. * @param expectedAudience The permitted audience (aud) claim * values in JWT authentication * assertions. Must not be empty or * {@code null}. Should typically * contain the token endpoint URI and * for OpenID provider it may also * include the issuer URI. */ public ClientAuthenticationVerifier(final ClientCredentialsSelector clientCredentialsSelector, final Set expectedAudience) { this(clientCredentialsSelector, expectedAudience, null); } /** * Creates a new client authentication verifier without support for * {@code tls_client_auth}. * * @param clientCredentialsSelector The client credentials selector. * Must not be {@code null}. * @param expectedAudience The permitted audience (aud) claim * values in JWT authentication * assertions. Must not be empty or * {@code null}. Should typically * contain the token endpoint URI and * for OpenID provider it may also * include the issuer URI. * @param expendedJTIChecker Optional expended JWT ID (jti) * claim checker to prevent JWT * replay, {@code null} if none. */ public ClientAuthenticationVerifier(final ClientCredentialsSelector clientCredentialsSelector, final Set expectedAudience, final ExpendedJTIChecker expendedJTIChecker) { claimsSetVerifier = new JWTAuthenticationClaimsSetVerifier(expectedAudience); if (clientCredentialsSelector == null) { throw new IllegalArgumentException("The client credentials selector must not be null"); } this.certBindingVerifier = null; this.pkiCertBindingVerifier = null; this.clientCredentialsSelector = clientCredentialsSelector; this.expendedJTIChecker = expendedJTIChecker; } /** * Creates a new client authentication verifier. * * @param clientCredentialsSelector The client credentials selector. * Must not be {@code null}. * @param pkiCertBindingVerifier Optional client X.509 certificate * binding verifier for * {@code tls_client_auth}, * {@code null} if not supported. * @param expectedAudience The permitted audience (aud) claim * values in JWT authentication * assertions. Must not be empty or * {@code null}. Should typically * contain the token endpoint URI and * for OpenID provider it may also * include the issuer URI. */ public ClientAuthenticationVerifier(final ClientCredentialsSelector clientCredentialsSelector, final PKIClientX509CertificateBindingVerifier pkiCertBindingVerifier, final Set expectedAudience) { this(clientCredentialsSelector, pkiCertBindingVerifier, expectedAudience, null, -1L); } /** * Creates a new client authentication verifier. * * @param clientCredentialsSelector The client credentials selector. * Must not be {@code null}. * @param pkiCertBindingVerifier Optional client X.509 certificate * binding verifier for * {@code tls_client_auth}, * {@code null} if not supported. * @param expectedAudience The permitted audience (aud) claim * values in JWT authentication * assertions. Must not be empty or * {@code null}. Should typically * contain the token endpoint URI and * for OpenID provider it may also * include the issuer URI. * @param expendedJTIChecker Optional expended JWT ID (jti) * claim checker to prevent JWT * replay, {@code null} if none. * @param expMaxAhead The maximum number of seconds the * expiration time (exp) claim can be * ahead of the current time, if zero * or negative this check is disabled. */ public ClientAuthenticationVerifier(final ClientCredentialsSelector clientCredentialsSelector, final PKIClientX509CertificateBindingVerifier pkiCertBindingVerifier, final Set expectedAudience, final ExpendedJTIChecker expendedJTIChecker, final long expMaxAhead) { claimsSetVerifier = new JWTAuthenticationClaimsSetVerifier(expectedAudience, expMaxAhead); if (clientCredentialsSelector == null) { throw new IllegalArgumentException("The client credentials selector must not be null"); } this.certBindingVerifier = null; this.pkiCertBindingVerifier = pkiCertBindingVerifier; this.clientCredentialsSelector = clientCredentialsSelector; this.expendedJTIChecker = expendedJTIChecker; } /** * Returns the client credentials selector. * * @return The client credentials selector. */ public ClientCredentialsSelector getClientCredentialsSelector() { return clientCredentialsSelector; } /** * Returns the client X.509 certificate binding verifier for use in * {@code tls_client_auth}. * * @return The client X.509 certificate binding verifier, {@code null} * if not specified. * @deprecated See {@link PKIClientX509CertificateBindingVerifier} */ @Deprecated public ClientX509CertificateBindingVerifier getClientX509CertificateBindingVerifier() { return certBindingVerifier; } /** * Returns the client X.509 certificate binding verifier for use in * {@code tls_client_auth}. * * @return The client X.509 certificate binding verifier, {@code null} * if not specified. */ public PKIClientX509CertificateBindingVerifier getPKIClientX509CertificateBindingVerifier() { return pkiCertBindingVerifier; } /** * Returns the permitted audience values in JWT authentication * assertions. * * @return The permitted audience (aud) claim values. */ public Set getExpectedAudience() { return claimsSetVerifier.getExpectedAudience(); } /** * Returns the optional expended JWT ID (jti) claim checker to prevent * JWT replay. * * @return The expended JWT ID (jti) claim checker, {@code null} if * none. */ public ExpendedJTIChecker getExpendedJTIChecker() { return expendedJTIChecker; } private static List removeNullOrErased(final List secrets) { List allSet = ListUtils.removeNullItems(secrets); if (allSet == null) { return null; } List out = new LinkedList<>(); for (Secret secret: secrets) { if (secret.getValue() != null && secret.getValueBytes() != null) { out.add(secret); } } return out; } private void preventJWTReplay(final JWTID jti, final ClientID clientID, final ClientAuthenticationMethod method, final Context context) throws InvalidClientException { if (jti == null || getExpendedJTIChecker() == null) { return; } if (getExpendedJTIChecker().isExpended(jti, clientID, method, context)) { throw new InvalidClientException("Detected JWT ID replay"); } } private void markExpended(final JWTID jti, final Date exp, final ClientID clientID, final ClientAuthenticationMethod method, final Context context) { if (jti == null || getExpendedJTIChecker() == null) { return; } getExpendedJTIChecker().markExpended(jti, exp, clientID, method, context); } /** * Verifies a client authentication request. * * @param clientAuth The client authentication. Must not be * {@code null}. * @param hints Optional hints to the verifier, empty set of * {@code null} if none. * @param context Additional context to be passed to the client * credentials selector. May be {@code null}. * * @throws InvalidClientException If the client authentication is * invalid, typically due to bad * credentials. * @throws JOSEException If authentication failed due to an * internal JOSE / JWT processing * exception. */ public void verify(final ClientAuthentication clientAuth, final Set hints, final Context context) throws InvalidClientException, JOSEException { if (clientAuth instanceof PlainClientSecret) { List secretCandidates = ListUtils.removeNullItems( clientCredentialsSelector.selectClientSecrets( clientAuth.getClientID(), clientAuth.getMethod(), context ) ); if (CollectionUtils.isEmpty(secretCandidates)) { throw InvalidClientException.NO_REGISTERED_SECRET; } PlainClientSecret plainAuth = (PlainClientSecret)clientAuth; for (Secret candidate: secretCandidates) { // Constant time, SHA-256 based, unless overridden if (candidate.equals(plainAuth.getClientSecret())) { return; // success } } throw InvalidClientException.BAD_SECRET; } else if (clientAuth instanceof ClientSecretJWT) { ClientSecretJWT jwtAuth = (ClientSecretJWT) clientAuth; // Check claims first before requesting secret from backend JWTAuthenticationClaimsSet jwtAuthClaims = jwtAuth.getJWTAuthenticationClaimsSet(); preventJWTReplay(jwtAuthClaims.getJWTID(), clientAuth.getClientID(), ClientAuthenticationMethod.CLIENT_SECRET_JWT, context); try { claimsSetVerifier.verify(jwtAuthClaims.toJWTClaimsSet(), null); } catch (BadJWTException e) { throw new InvalidClientException("Bad / expired JWT claims: " + e.getMessage()); } List secretCandidates = removeNullOrErased( clientCredentialsSelector.selectClientSecrets( clientAuth.getClientID(), clientAuth.getMethod(), context ) ); if (CollectionUtils.isEmpty(secretCandidates)) { throw InvalidClientException.NO_REGISTERED_SECRET; } SignedJWT assertion = jwtAuth.getClientAssertion(); for (Secret candidate : secretCandidates) { boolean valid = assertion.verify(new MACVerifier(candidate.getValueBytes())); if (valid) { markExpended(jwtAuthClaims.getJWTID(), jwtAuthClaims.getExpirationTime(), clientAuth.getClientID(), ClientAuthenticationMethod.CLIENT_SECRET_JWT, context); return; // success } } throw InvalidClientException.BAD_JWT_HMAC; } else if (clientAuth instanceof PrivateKeyJWT) { PrivateKeyJWT jwtAuth = (PrivateKeyJWT) clientAuth; // Check claims first before requesting / retrieving public keys JWTAuthenticationClaimsSet jwtAuthClaims = jwtAuth.getJWTAuthenticationClaimsSet(); preventJWTReplay(jwtAuthClaims.getJWTID(), clientAuth.getClientID(), ClientAuthenticationMethod.PRIVATE_KEY_JWT, context); try { claimsSetVerifier.verify(jwtAuthClaims.toJWTClaimsSet(), null); } catch (BadJWTException e) { throw new InvalidClientException("Bad / expired JWT claims: " + e.getMessage()); } List keyCandidates = ListUtils.removeNullItems( clientCredentialsSelector.selectPublicKeys( jwtAuth.getClientID(), jwtAuth.getMethod(), jwtAuth.getClientAssertion().getHeader(), false, // don't force refresh if we have a remote JWK set; // selector may however do so if it encounters an unknown key ID context ) ); if (CollectionUtils.isEmpty(keyCandidates)) { throw InvalidClientException.NO_MATCHING_JWK; } SignedJWT assertion = jwtAuth.getClientAssertion(); for (PublicKey candidate : keyCandidates) { JWSVerifier jwsVerifier = jwsVerifierFactory.createJWSVerifier( jwtAuth.getClientAssertion().getHeader(), candidate); boolean valid = assertion.verify(jwsVerifier); if (valid) { markExpended(jwtAuthClaims.getJWTID(), jwtAuthClaims.getExpirationTime(), clientAuth.getClientID(), ClientAuthenticationMethod.PRIVATE_KEY_JWT, context); return; // success } } // Second pass if (hints != null && hints.contains(Hint.CLIENT_HAS_REMOTE_JWK_SET)) { // Client possibly registered JWK set URL with keys that have no IDs // force JWK set reload from URL and retry keyCandidates = ListUtils.removeNullItems( clientCredentialsSelector.selectPublicKeys( jwtAuth.getClientID(), jwtAuth.getMethod(), jwtAuth.getClientAssertion().getHeader(), true, // force reload of remote JWK set context ) ); if (CollectionUtils.isEmpty(keyCandidates)) { throw InvalidClientException.NO_MATCHING_JWK; } assertion = jwtAuth.getClientAssertion(); for (PublicKey candidate : keyCandidates) { JWSVerifier jwsVerifier = jwsVerifierFactory.createJWSVerifier( jwtAuth.getClientAssertion().getHeader(), candidate); boolean valid = assertion.verify(jwsVerifier); if (valid) { markExpended(jwtAuthClaims.getJWTID(), jwtAuthClaims.getExpirationTime(), clientAuth.getClientID(), ClientAuthenticationMethod.PRIVATE_KEY_JWT, context); return; // success } } } throw InvalidClientException.BAD_JWT_SIGNATURE; } else if (clientAuth instanceof SelfSignedTLSClientAuthentication) { SelfSignedTLSClientAuthentication tlsClientAuth = (SelfSignedTLSClientAuthentication) clientAuth; X509Certificate clientCert = tlsClientAuth.getClientX509Certificate(); if (clientCert == null) { // Sanity check throw new InvalidClientException("Missing client X.509 certificate"); } // Self-signed certs bound to registered public key in client jwks / jwks_uri List keyCandidates = ListUtils.removeNullItems( clientCredentialsSelector.selectPublicKeys( tlsClientAuth.getClientID(), tlsClientAuth.getMethod(), null, false, // don't force refresh if we have a remote JWK set; // selector may however do so if it encounters an unknown key ID context ) ); if (CollectionUtils.isEmpty(keyCandidates)) { throw InvalidClientException.NO_MATCHING_JWK; } for (PublicKey candidate : keyCandidates) { boolean valid = X509CertificateUtils.publicKeyMatches(clientCert, candidate); if (valid) { return; // success } } // Second pass if (hints != null && hints.contains(Hint.CLIENT_HAS_REMOTE_JWK_SET)) { // Client possibly registered JWK set URL with keys that have no IDs // force JWK set reload from URL and retry keyCandidates = ListUtils.removeNullItems( clientCredentialsSelector.selectPublicKeys( tlsClientAuth.getClientID(), tlsClientAuth.getMethod(), null, true, // force reload of remote JWK set context ) ); if (CollectionUtils.isEmpty(keyCandidates)) { throw InvalidClientException.NO_MATCHING_JWK; } for (PublicKey candidate : keyCandidates) { if (candidate == null) { continue; // skip } boolean valid = X509CertificateUtils.publicKeyMatches(clientCert, candidate); if (valid) { return; // success } } } throw InvalidClientException.BAD_SELF_SIGNED_CLIENT_CERTIFICATE; } else if (clientAuth instanceof PKITLSClientAuthentication) { PKITLSClientAuthentication tlsClientAuth = (PKITLSClientAuthentication) clientAuth; if (pkiCertBindingVerifier != null) { pkiCertBindingVerifier.verifyCertificateBinding( clientAuth.getClientID(), tlsClientAuth.getClientX509Certificate(), context); } else if (certBindingVerifier != null) { certBindingVerifier.verifyCertificateBinding( clientAuth.getClientID(), tlsClientAuth.getClientX509CertificateSubjectDN(), context); } else { throw new InvalidClientException("Mutual TLS client Authentication (tls_client_auth) not supported"); } } else { throw new RuntimeException("Unexpected client authentication: " + clientAuth.getMethod()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy