001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.oauth2.sdk.auth.verifier; 019 020 021import java.security.PublicKey; 022import java.security.cert.X509Certificate; 023import java.util.List; 024import java.util.Set; 025 026import com.nimbusds.jose.JOSEException; 027import com.nimbusds.jose.JWSVerifier; 028import com.nimbusds.jose.crypto.MACVerifier; 029import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory; 030import com.nimbusds.jose.proc.JWSVerifierFactory; 031import com.nimbusds.jwt.SignedJWT; 032import com.nimbusds.jwt.proc.BadJWTException; 033import com.nimbusds.oauth2.sdk.auth.*; 034import com.nimbusds.oauth2.sdk.id.Audience; 035import com.nimbusds.oauth2.sdk.util.CollectionUtils; 036import com.nimbusds.oauth2.sdk.util.X509CertificateUtils; 037import net.jcip.annotations.ThreadSafe; 038 039 040/** 041 * Client authentication verifier. 042 * 043 * <p>Related specifications: 044 * 045 * <ul> 046 * <li>OAuth 2.0 (RFC 6749), sections 2.3.1 and 3.2.1. 047 * <li>OpenID Connect Core 1.0, section 9. 048 * <li>JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and 049 * Authorization Grants (RFC 7523). 050 * <li>OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound 051 * Access Tokens (RFC 8705), section 2. 052 * </ul> 053 */ 054@ThreadSafe 055public class ClientAuthenticationVerifier<T> { 056 057 058 /** 059 * The client credentials selector. 060 */ 061 private final ClientCredentialsSelector<T> clientCredentialsSelector; 062 063 064 /** 065 * Optional client X.509 certificate binding verifier for 066 * {@code tls_client_auth}. 067 * @deprecated Replaced by pkiCertBindingVerifier 068 */ 069 @Deprecated 070 private final ClientX509CertificateBindingVerifier<T> certBindingVerifier; 071 072 073 /** 074 * Optional client X.509 certificate binding verifier for 075 * {@code tls_client_auth}. 076 */ 077 private final PKIClientX509CertificateBindingVerifier<T> pkiCertBindingVerifier; 078 079 080 /** 081 * The JWT assertion claims set verifier. 082 */ 083 private final JWTAuthenticationClaimsSetVerifier claimsSetVerifier; 084 085 086 /** 087 * JWS verifier factory for private_key_jwt authentication. 088 */ 089 private final JWSVerifierFactory jwsVerifierFactory = new DefaultJWSVerifierFactory(); 090 091 092 /** 093 * Creates a new client authentication verifier. 094 * 095 * @param clientCredentialsSelector The client credentials selector. 096 * Must not be {@code null}. 097 * @param certBindingVerifier Optional client X.509 certificate 098 * binding verifier for 099 * {@code tls_client_auth}, 100 * {@code null} if not supported. 101 * @param expectedAudience The permitted audience (aud) claim 102 * values in JWT authentication 103 * assertions. Must not be empty or 104 * {@code null}. Should typically 105 * contain the token endpoint URI and 106 * for OpenID provider it may also 107 * include the issuer URI. 108 * @deprecated Use the constructor with {@link PKIClientX509CertificateBindingVerifier} 109 */ 110 @Deprecated 111 public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector, 112 final ClientX509CertificateBindingVerifier<T> certBindingVerifier, 113 final Set<Audience> expectedAudience) { 114 115 claimsSetVerifier = new JWTAuthenticationClaimsSetVerifier(expectedAudience); 116 117 if (clientCredentialsSelector == null) { 118 throw new IllegalArgumentException("The client credentials selector must not be null"); 119 } 120 121 this.certBindingVerifier = certBindingVerifier; 122 this.pkiCertBindingVerifier = null; 123 124 this.clientCredentialsSelector = clientCredentialsSelector; 125 } 126 127 128 /** 129 * Creates a new client authentication verifier without support for 130 * {@code tls_client_auth}. 131 * 132 * @param clientCredentialsSelector The client credentials selector. 133 * Must not be {@code null}. 134 * @param expectedAudience The permitted audience (aud) claim 135 * values in JWT authentication 136 * assertions. Must not be empty or 137 * {@code null}. Should typically 138 * contain the token endpoint URI and 139 * for OpenID provider it may also 140 * include the issuer URI. 141 */ 142 public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector, 143 final Set<Audience> expectedAudience) { 144 145 claimsSetVerifier = new JWTAuthenticationClaimsSetVerifier(expectedAudience); 146 147 if (clientCredentialsSelector == null) { 148 throw new IllegalArgumentException("The client credentials selector must not be null"); 149 } 150 151 this.certBindingVerifier = null; 152 this.pkiCertBindingVerifier = null; 153 154 this.clientCredentialsSelector = clientCredentialsSelector; 155 } 156 157 158 /** 159 * Creates a new client authentication verifier. 160 * 161 * @param clientCredentialsSelector The client credentials selector. 162 * Must not be {@code null}. 163 * @param pkiCertBindingVerifier Optional client X.509 certificate 164 * binding verifier for 165 * {@code tls_client_auth}, 166 * {@code null} if not supported. 167 * @param expectedAudience The permitted audience (aud) claim 168 * values in JWT authentication 169 * assertions. Must not be empty or 170 * {@code null}. Should typically 171 * contain the token endpoint URI and 172 * for OpenID provider it may also 173 * include the issuer URI. 174 */ 175 public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector, 176 final PKIClientX509CertificateBindingVerifier<T> pkiCertBindingVerifier, 177 final Set<Audience> expectedAudience) { 178 179 claimsSetVerifier = new JWTAuthenticationClaimsSetVerifier(expectedAudience); 180 181 if (clientCredentialsSelector == null) { 182 throw new IllegalArgumentException("The client credentials selector must not be null"); 183 } 184 185 this.certBindingVerifier = null; 186 this.pkiCertBindingVerifier = pkiCertBindingVerifier; 187 188 this.clientCredentialsSelector = clientCredentialsSelector; 189 } 190 191 192 /** 193 * Returns the client credentials selector. 194 * 195 * @return The client credentials selector. 196 */ 197 public ClientCredentialsSelector<T> getClientCredentialsSelector() { 198 199 return clientCredentialsSelector; 200 } 201 202 203 /** 204 * Returns the client X.509 certificate binding verifier for use in 205 * {@code tls_client_auth}. 206 * 207 * @return The client X.509 certificate binding verifier, {@code null} 208 * if not specified. 209 * @deprecated See {@link PKIClientX509CertificateBindingVerifier} 210 */ 211 @Deprecated 212 public ClientX509CertificateBindingVerifier<T> getClientX509CertificateBindingVerifier() { 213 214 return certBindingVerifier; 215 } 216 217 218 /** 219 * Returns the client X.509 certificate binding verifier for use in 220 * {@code tls_client_auth}. 221 * 222 * @return The client X.509 certificate binding verifier, {@code null} 223 * if not specified. 224 */ 225 public PKIClientX509CertificateBindingVerifier<T> getPKIClientX509CertificateBindingVerifier() { 226 227 return pkiCertBindingVerifier; 228 } 229 230 231 /** 232 * Returns the permitted audience values in JWT authentication 233 * assertions. 234 * 235 * @return The permitted audience (aud) claim values. 236 */ 237 public Set<Audience> getExpectedAudience() { 238 239 return claimsSetVerifier.getExpectedAudience(); 240 } 241 242 243 /** 244 * Verifies a client authentication request. 245 * 246 * @param clientAuth The client authentication. Must not be 247 * {@code null}. 248 * @param hints Optional hints to the verifier, empty set of 249 * {@code null} if none. 250 * @param context Additional context to be passed to the client 251 * credentials selector. May be {@code null}. 252 * 253 * @throws InvalidClientException If the client authentication is 254 * invalid, typically due to bad 255 * credentials. 256 * @throws JOSEException If authentication failed due to an 257 * internal JOSE / JWT processing 258 * exception. 259 */ 260 public void verify(final ClientAuthentication clientAuth, final Set<Hint> hints, final Context<T> context) 261 throws InvalidClientException, JOSEException { 262 263 if (clientAuth instanceof PlainClientSecret) { 264 265 List<Secret> secretCandidates = clientCredentialsSelector.selectClientSecrets( 266 clientAuth.getClientID(), 267 clientAuth.getMethod(), 268 context); 269 270 if (CollectionUtils.isEmpty(secretCandidates)) { 271 throw InvalidClientException.NO_REGISTERED_SECRET; 272 } 273 274 PlainClientSecret plainAuth = (PlainClientSecret)clientAuth; 275 276 for (Secret candidate: secretCandidates) { 277 // constant time, SHA-256 based 278 if (plainAuth.getClientSecret().equalsSHA256Based(candidate)) { 279 return; // success 280 } 281 } 282 283 throw InvalidClientException.BAD_SECRET; 284 285 } else if (clientAuth instanceof ClientSecretJWT) { 286 287 ClientSecretJWT jwtAuth = (ClientSecretJWT) clientAuth; 288 289 // Check claims first before requesting secret from backend 290 try { 291 claimsSetVerifier.verify(jwtAuth.getJWTAuthenticationClaimsSet().toJWTClaimsSet()); 292 } catch (BadJWTException e) { 293 throw new InvalidClientException("Bad / expired JWT claims: " + e.getMessage()); 294 } 295 296 List<Secret> secretCandidates = clientCredentialsSelector.selectClientSecrets( 297 clientAuth.getClientID(), 298 clientAuth.getMethod(), 299 context); 300 301 if (CollectionUtils.isEmpty(secretCandidates)) { 302 throw InvalidClientException.NO_REGISTERED_SECRET; 303 } 304 305 SignedJWT assertion = jwtAuth.getClientAssertion(); 306 307 for (Secret candidate : secretCandidates) { 308 309 boolean valid = assertion.verify(new MACVerifier(candidate.getValueBytes())); 310 311 if (valid) { 312 return; // success 313 } 314 } 315 316 throw InvalidClientException.BAD_JWT_HMAC; 317 318 } else if (clientAuth instanceof PrivateKeyJWT) { 319 320 PrivateKeyJWT jwtAuth = (PrivateKeyJWT) clientAuth; 321 322 // Check claims first before requesting / retrieving public keys 323 try { 324 claimsSetVerifier.verify(jwtAuth.getJWTAuthenticationClaimsSet().toJWTClaimsSet()); 325 } catch (BadJWTException e) { 326 throw new InvalidClientException("Bad / expired JWT claims: " + e.getMessage()); 327 } 328 329 List<? extends PublicKey> keyCandidates = clientCredentialsSelector.selectPublicKeys( 330 jwtAuth.getClientID(), 331 jwtAuth.getMethod(), 332 jwtAuth.getClientAssertion().getHeader(), 333 false, // don't force refresh if we have a remote JWK set; 334 // selector may however do so if it encounters an unknown key ID 335 context); 336 337 if (CollectionUtils.isEmpty(keyCandidates)) { 338 throw InvalidClientException.NO_MATCHING_JWK; 339 } 340 341 SignedJWT assertion = jwtAuth.getClientAssertion(); 342 343 for (PublicKey candidate : keyCandidates) { 344 345 if (candidate == null) { 346 continue; // skip 347 } 348 349 JWSVerifier jwsVerifier = jwsVerifierFactory.createJWSVerifier( 350 jwtAuth.getClientAssertion().getHeader(), 351 candidate); 352 353 boolean valid = assertion.verify(jwsVerifier); 354 355 if (valid) { 356 return; // success 357 } 358 } 359 360 // Second pass 361 if (hints != null && hints.contains(Hint.CLIENT_HAS_REMOTE_JWK_SET)) { 362 // Client possibly registered JWK set URL with keys that have no IDs 363 // force JWK set reload from URL and retry 364 keyCandidates = clientCredentialsSelector.selectPublicKeys( 365 jwtAuth.getClientID(), 366 jwtAuth.getMethod(), 367 jwtAuth.getClientAssertion().getHeader(), 368 true, // force reload of remote JWK set 369 context); 370 371 if (CollectionUtils.isEmpty(keyCandidates)) { 372 throw InvalidClientException.NO_MATCHING_JWK; 373 } 374 375 assertion = jwtAuth.getClientAssertion(); 376 377 for (PublicKey candidate : keyCandidates) { 378 379 if (candidate == null) { 380 continue; // skip 381 } 382 383 JWSVerifier jwsVerifier = jwsVerifierFactory.createJWSVerifier( 384 jwtAuth.getClientAssertion().getHeader(), 385 candidate); 386 387 boolean valid = assertion.verify(jwsVerifier); 388 389 if (valid) { 390 return; // success 391 } 392 } 393 } 394 395 throw InvalidClientException.BAD_JWT_SIGNATURE; 396 397 } else if (clientAuth instanceof SelfSignedTLSClientAuthentication) { 398 399 SelfSignedTLSClientAuthentication tlsClientAuth = (SelfSignedTLSClientAuthentication) clientAuth; 400 401 X509Certificate clientCert = tlsClientAuth.getClientX509Certificate(); 402 403 if (clientCert == null) { 404 // Sanity check 405 throw new InvalidClientException("Missing client X.509 certificate"); 406 } 407 408 // Self-signed certs bound to registered public key in client jwks / jwks_uri 409 List<? extends PublicKey> keyCandidates = clientCredentialsSelector.selectPublicKeys( 410 tlsClientAuth.getClientID(), 411 tlsClientAuth.getMethod(), 412 null, 413 false, // don't force refresh if we have a remote JWK set; 414 // selector may however do so if it encounters an unknown key ID 415 context); 416 417 if (CollectionUtils.isEmpty(keyCandidates)) { 418 throw InvalidClientException.NO_MATCHING_JWK; 419 } 420 421 for (PublicKey candidate : keyCandidates) { 422 423 if (candidate == null) { 424 continue; // skip 425 } 426 427 boolean valid = X509CertificateUtils.publicKeyMatches(clientCert, candidate); 428 429 if (valid) { 430 return; // success 431 } 432 } 433 434 // Second pass 435 if (hints != null && hints.contains(Hint.CLIENT_HAS_REMOTE_JWK_SET)) { 436 // Client possibly registered JWK set URL with keys that have no IDs 437 // force JWK set reload from URL and retry 438 keyCandidates = clientCredentialsSelector.selectPublicKeys( 439 tlsClientAuth.getClientID(), 440 tlsClientAuth.getMethod(), 441 null, 442 true, // force reload of remote JWK set 443 context); 444 445 if (CollectionUtils.isEmpty(keyCandidates)) { 446 throw InvalidClientException.NO_MATCHING_JWK; 447 } 448 449 for (PublicKey candidate : keyCandidates) { 450 451 if (candidate == null) { 452 continue; // skip 453 } 454 455 boolean valid = X509CertificateUtils.publicKeyMatches(clientCert, candidate); 456 457 if (valid) { 458 return; // success 459 } 460 } 461 } 462 463 throw InvalidClientException.BAD_SELF_SIGNED_CLIENT_CERTIFICATE; 464 465 } else if (clientAuth instanceof PKITLSClientAuthentication) { 466 467 PKITLSClientAuthentication tlsClientAuth = (PKITLSClientAuthentication) clientAuth; 468 if (pkiCertBindingVerifier != null) { 469 pkiCertBindingVerifier.verifyCertificateBinding( 470 clientAuth.getClientID(), 471 tlsClientAuth.getClientX509Certificate(), 472 context); 473 474 } else if (certBindingVerifier != null) { 475 certBindingVerifier.verifyCertificateBinding( 476 clientAuth.getClientID(), 477 tlsClientAuth.getClientX509CertificateSubjectDN(), 478 context); 479 } else { 480 throw new InvalidClientException("Mutual TLS client Authentication (tls_client_auth) not supported"); 481 } 482 } else { 483 throw new RuntimeException("Unexpected client authentication: " + clientAuth.getMethod()); 484 } 485 } 486}