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 com.nimbusds.jose.JOSEException; 022import com.nimbusds.jose.JWSVerifier; 023import com.nimbusds.jose.crypto.MACVerifier; 024import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory; 025import com.nimbusds.jose.proc.JWSVerifierFactory; 026import com.nimbusds.jwt.SignedJWT; 027import com.nimbusds.jwt.proc.BadJWTException; 028import com.nimbusds.oauth2.sdk.auth.*; 029import com.nimbusds.oauth2.sdk.id.Audience; 030import com.nimbusds.oauth2.sdk.id.ClientID; 031import com.nimbusds.oauth2.sdk.id.JWTID; 032import com.nimbusds.oauth2.sdk.util.CollectionUtils; 033import com.nimbusds.oauth2.sdk.util.ListUtils; 034import com.nimbusds.oauth2.sdk.util.X509CertificateUtils; 035import net.jcip.annotations.ThreadSafe; 036 037import java.security.PublicKey; 038import java.security.cert.X509Certificate; 039import java.util.Date; 040import java.util.LinkedList; 041import java.util.List; 042import java.util.Set; 043 044 045/** 046 * Client authentication verifier. 047 * 048 * <p>Related specifications: 049 * 050 * <ul> 051 * <li>OAuth 2.0 (RFC 6749), sections 2.3.1 and 3.2.1. 052 * <li>OpenID Connect Core 1.0, section 9. 053 * <li>JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and 054 * Authorization Grants (RFC 7523). 055 * <li>OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound 056 * Access Tokens (RFC 8705), section 2. 057 * </ul> 058 */ 059@ThreadSafe 060public class ClientAuthenticationVerifier<T> { 061 062 063 /** 064 * The client credentials selector. 065 */ 066 private final ClientCredentialsSelector<T> clientCredentialsSelector; 067 068 069 /** 070 * Optional client X.509 certificate binding verifier for 071 * {@code tls_client_auth}. 072 * @deprecated Replaced by pkiCertBindingVerifier 073 */ 074 @Deprecated 075 private final ClientX509CertificateBindingVerifier<T> certBindingVerifier; 076 077 078 /** 079 * Optional client X.509 certificate binding verifier for 080 * {@code tls_client_auth}. 081 */ 082 private final PKIClientX509CertificateBindingVerifier<T> pkiCertBindingVerifier; 083 084 085 /** 086 * The JWT assertion claims set verifier. 087 */ 088 private final JWTAuthenticationClaimsSetVerifier claimsSetVerifier; 089 090 091 /** 092 * Optional expended JWT ID (jti) checker. 093 */ 094 private final ExpendedJTIChecker<T> expendedJTIChecker; 095 096 097 /** 098 * JWS verifier factory for private_key_jwt authentication. 099 */ 100 private final JWSVerifierFactory jwsVerifierFactory = new DefaultJWSVerifierFactory(); 101 102 103 /** 104 * Creates a new client authentication verifier. 105 * 106 * @param clientCredentialsSelector The client credentials selector. 107 * Must not be {@code null}. 108 * @param certBindingVerifier Optional client X.509 certificate 109 * binding verifier for 110 * {@code tls_client_auth}, 111 * {@code null} if not supported. 112 * @param expectedAudience The permitted audience (aud) claim 113 * values in JWT authentication 114 * assertions. Must not be empty or 115 * {@code null}. Should typically 116 * contain the token endpoint URI and 117 * for OpenID provider it may also 118 * include the issuer URI. 119 * 120 * @deprecated Use the constructor with {@link PKIClientX509CertificateBindingVerifier} 121 */ 122 @Deprecated 123 public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector, 124 final ClientX509CertificateBindingVerifier<T> certBindingVerifier, 125 final Set<Audience> expectedAudience) { 126 127 claimsSetVerifier = new JWTAuthenticationClaimsSetVerifier(expectedAudience); 128 129 if (clientCredentialsSelector == null) { 130 throw new IllegalArgumentException("The client credentials selector must not be null"); 131 } 132 133 this.certBindingVerifier = certBindingVerifier; 134 this.pkiCertBindingVerifier = null; 135 136 this.clientCredentialsSelector = clientCredentialsSelector; 137 138 this.expendedJTIChecker = null; 139 } 140 141 142 /** 143 * Creates a new client authentication verifier without support for 144 * {@code tls_client_auth}. 145 * 146 * @param clientCredentialsSelector The client credentials selector. 147 * Must not be {@code null}. 148 * @param expectedAudience The permitted audience (aud) claim 149 * values in JWT authentication 150 * assertions. Must not be empty or 151 * {@code null}. Should typically 152 * contain the token endpoint URI and 153 * for OpenID provider it may also 154 * include the issuer URI. 155 */ 156 public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector, 157 final Set<Audience> expectedAudience) { 158 159 this(clientCredentialsSelector, expectedAudience, null); 160 } 161 162 163 /** 164 * Creates a new client authentication verifier without support for 165 * {@code tls_client_auth}. 166 * 167 * @param clientCredentialsSelector The client credentials selector. 168 * Must not be {@code null}. 169 * @param expectedAudience The permitted audience (aud) claim 170 * values in JWT authentication 171 * assertions. Must not be empty or 172 * {@code null}. Should typically 173 * contain the token endpoint URI and 174 * for OpenID provider it may also 175 * include the issuer URI. 176 * @param expendedJTIChecker Optional expended JWT ID (jti) 177 * claim checker to prevent JWT 178 * replay, {@code null} if none. 179 */ 180 public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector, 181 final Set<Audience> expectedAudience, 182 final ExpendedJTIChecker<T> expendedJTIChecker) { 183 184 claimsSetVerifier = new JWTAuthenticationClaimsSetVerifier(expectedAudience); 185 186 if (clientCredentialsSelector == null) { 187 throw new IllegalArgumentException("The client credentials selector must not be null"); 188 } 189 190 this.certBindingVerifier = null; 191 this.pkiCertBindingVerifier = null; 192 193 this.clientCredentialsSelector = clientCredentialsSelector; 194 195 this.expendedJTIChecker = expendedJTIChecker; 196 } 197 198 199 /** 200 * Creates a new client authentication verifier. 201 * 202 * @param clientCredentialsSelector The client credentials selector. 203 * Must not be {@code null}. 204 * @param pkiCertBindingVerifier Optional client X.509 certificate 205 * binding verifier for 206 * {@code tls_client_auth}, 207 * {@code null} if not supported. 208 * @param expectedAudience The permitted audience (aud) claim 209 * values in JWT authentication 210 * assertions. Must not be empty or 211 * {@code null}. Should typically 212 * contain the token endpoint URI and 213 * for OpenID provider it may also 214 * include the issuer URI. 215 */ 216 public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector, 217 final PKIClientX509CertificateBindingVerifier<T> pkiCertBindingVerifier, 218 final Set<Audience> expectedAudience) { 219 220 this(clientCredentialsSelector, pkiCertBindingVerifier, expectedAudience, null); 221 } 222 223 224 /** 225 * Creates a new client authentication verifier. 226 * 227 * @param clientCredentialsSelector The client credentials selector. 228 * Must not be {@code null}. 229 * @param pkiCertBindingVerifier Optional client X.509 certificate 230 * binding verifier for 231 * {@code tls_client_auth}, 232 * {@code null} if not supported. 233 * @param expectedAudience The permitted audience (aud) claim 234 * values in JWT authentication 235 * assertions. Must not be empty or 236 * {@code null}. Should typically 237 * contain the token endpoint URI and 238 * for OpenID provider it may also 239 * include the issuer URI. 240 * @param expendedJTIChecker Optional expended JWT ID (jti) 241 * claim checker to prevent JWT 242 * replay, {@code null} if none. 243 */ 244 public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector, 245 final PKIClientX509CertificateBindingVerifier<T> pkiCertBindingVerifier, 246 final Set<Audience> expectedAudience, 247 final ExpendedJTIChecker<T> expendedJTIChecker) { 248 249 claimsSetVerifier = new JWTAuthenticationClaimsSetVerifier(expectedAudience); 250 251 if (clientCredentialsSelector == null) { 252 throw new IllegalArgumentException("The client credentials selector must not be null"); 253 } 254 255 this.certBindingVerifier = null; 256 this.pkiCertBindingVerifier = pkiCertBindingVerifier; 257 258 this.clientCredentialsSelector = clientCredentialsSelector; 259 260 this.expendedJTIChecker = expendedJTIChecker; 261 } 262 263 264 /** 265 * Returns the client credentials selector. 266 * 267 * @return The client credentials selector. 268 */ 269 public ClientCredentialsSelector<T> getClientCredentialsSelector() { 270 271 return clientCredentialsSelector; 272 } 273 274 275 /** 276 * Returns the client X.509 certificate binding verifier for use in 277 * {@code tls_client_auth}. 278 * 279 * @return The client X.509 certificate binding verifier, {@code null} 280 * if not specified. 281 * @deprecated See {@link PKIClientX509CertificateBindingVerifier} 282 */ 283 @Deprecated 284 public ClientX509CertificateBindingVerifier<T> getClientX509CertificateBindingVerifier() { 285 286 return certBindingVerifier; 287 } 288 289 290 /** 291 * Returns the client X.509 certificate binding verifier for use in 292 * {@code tls_client_auth}. 293 * 294 * @return The client X.509 certificate binding verifier, {@code null} 295 * if not specified. 296 */ 297 public PKIClientX509CertificateBindingVerifier<T> getPKIClientX509CertificateBindingVerifier() { 298 299 return pkiCertBindingVerifier; 300 } 301 302 303 /** 304 * Returns the permitted audience values in JWT authentication 305 * assertions. 306 * 307 * @return The permitted audience (aud) claim values. 308 */ 309 public Set<Audience> getExpectedAudience() { 310 311 return claimsSetVerifier.getExpectedAudience(); 312 } 313 314 315 /** 316 * Returns the optional expended JWT ID (jti) claim checker to prevent 317 * JWT replay. 318 * 319 * @return The expended JWT ID (jti) claim checker, {@code null} if 320 * none. 321 */ 322 public ExpendedJTIChecker<T> getExpendedJTIChecker() { 323 324 return expendedJTIChecker; 325 } 326 327 328 private static List<Secret> removeNullOrErased(final List<Secret> secrets) { 329 List<Secret> allSet = ListUtils.removeNullItems(secrets); 330 if (allSet == null) { 331 return null; 332 } 333 List<Secret> out = new LinkedList<>(); 334 for (Secret secret: secrets) { 335 if (secret.getValue() != null && secret.getValueBytes() != null) { 336 out.add(secret); 337 } 338 } 339 return out; 340 } 341 342 343 private void preventJWTReplay(final JWTID jti, 344 final ClientID clientID, 345 final ClientAuthenticationMethod method, 346 final Context<T> context) 347 throws InvalidClientException { 348 349 if (jti == null || getExpendedJTIChecker() == null) { 350 return; 351 } 352 353 if (getExpendedJTIChecker().isExpended(jti, clientID, method, context)) { 354 throw new InvalidClientException("Detected JWT ID replay"); 355 } 356 } 357 358 359 private void markExpended(final JWTID jti, 360 final Date exp, 361 final ClientID clientID, 362 final ClientAuthenticationMethod method, 363 final Context<T> context) { 364 365 if (jti == null || getExpendedJTIChecker() == null) { 366 return; 367 } 368 369 getExpendedJTIChecker().markExpended(jti, exp, clientID, method, context); 370 } 371 372 373 /** 374 * Verifies a client authentication request. 375 * 376 * @param clientAuth The client authentication. Must not be 377 * {@code null}. 378 * @param hints Optional hints to the verifier, empty set of 379 * {@code null} if none. 380 * @param context Additional context to be passed to the client 381 * credentials selector. May be {@code null}. 382 * 383 * @throws InvalidClientException If the client authentication is 384 * invalid, typically due to bad 385 * credentials. 386 * @throws JOSEException If authentication failed due to an 387 * internal JOSE / JWT processing 388 * exception. 389 */ 390 public void verify(final ClientAuthentication clientAuth, final Set<Hint> hints, final Context<T> context) 391 throws InvalidClientException, JOSEException { 392 393 if (clientAuth instanceof PlainClientSecret) { 394 395 List<Secret> secretCandidates = ListUtils.removeNullItems( 396 clientCredentialsSelector.selectClientSecrets( 397 clientAuth.getClientID(), 398 clientAuth.getMethod(), 399 context 400 ) 401 ); 402 403 if (CollectionUtils.isEmpty(secretCandidates)) { 404 throw InvalidClientException.NO_REGISTERED_SECRET; 405 } 406 407 PlainClientSecret plainAuth = (PlainClientSecret)clientAuth; 408 409 for (Secret candidate: secretCandidates) { 410 411 // Constant time, SHA-256 based, unless overridden 412 if (candidate.equals(plainAuth.getClientSecret())) { 413 return; // success 414 } 415 } 416 417 throw InvalidClientException.BAD_SECRET; 418 419 } else if (clientAuth instanceof ClientSecretJWT) { 420 421 ClientSecretJWT jwtAuth = (ClientSecretJWT) clientAuth; 422 423 // Check claims first before requesting secret from backend 424 JWTAuthenticationClaimsSet jwtAuthClaims = jwtAuth.getJWTAuthenticationClaimsSet(); 425 426 preventJWTReplay(jwtAuthClaims.getJWTID(), clientAuth.getClientID(), ClientAuthenticationMethod.CLIENT_SECRET_JWT, context); 427 428 try { 429 claimsSetVerifier.verify(jwtAuthClaims.toJWTClaimsSet(), null); 430 } catch (BadJWTException e) { 431 throw new InvalidClientException("Bad / expired JWT claims: " + e.getMessage()); 432 } 433 434 List<Secret> secretCandidates = removeNullOrErased( 435 clientCredentialsSelector.selectClientSecrets( 436 clientAuth.getClientID(), 437 clientAuth.getMethod(), 438 context 439 ) 440 ); 441 442 if (CollectionUtils.isEmpty(secretCandidates)) { 443 throw InvalidClientException.NO_REGISTERED_SECRET; 444 } 445 446 SignedJWT assertion = jwtAuth.getClientAssertion(); 447 448 for (Secret candidate : secretCandidates) { 449 450 boolean valid = assertion.verify(new MACVerifier(candidate.getValueBytes())); 451 452 if (valid) { 453 markExpended(jwtAuthClaims.getJWTID(), jwtAuthClaims.getExpirationTime(), clientAuth.getClientID(), ClientAuthenticationMethod.CLIENT_SECRET_JWT, context); 454 return; // success 455 } 456 } 457 458 throw InvalidClientException.BAD_JWT_HMAC; 459 460 } else if (clientAuth instanceof PrivateKeyJWT) { 461 462 PrivateKeyJWT jwtAuth = (PrivateKeyJWT) clientAuth; 463 464 // Check claims first before requesting / retrieving public keys 465 JWTAuthenticationClaimsSet jwtAuthClaims = jwtAuth.getJWTAuthenticationClaimsSet(); 466 467 preventJWTReplay(jwtAuthClaims.getJWTID(), clientAuth.getClientID(), ClientAuthenticationMethod.PRIVATE_KEY_JWT, context); 468 469 try { 470 claimsSetVerifier.verify(jwtAuthClaims.toJWTClaimsSet(), null); 471 } catch (BadJWTException e) { 472 throw new InvalidClientException("Bad / expired JWT claims: " + e.getMessage()); 473 } 474 475 List<? extends PublicKey> keyCandidates = ListUtils.removeNullItems( 476 clientCredentialsSelector.selectPublicKeys( 477 jwtAuth.getClientID(), 478 jwtAuth.getMethod(), 479 jwtAuth.getClientAssertion().getHeader(), 480 false, // don't force refresh if we have a remote JWK set; 481 // selector may however do so if it encounters an unknown key ID 482 context 483 ) 484 ); 485 486 if (CollectionUtils.isEmpty(keyCandidates)) { 487 throw InvalidClientException.NO_MATCHING_JWK; 488 } 489 490 SignedJWT assertion = jwtAuth.getClientAssertion(); 491 492 for (PublicKey candidate : keyCandidates) { 493 494 JWSVerifier jwsVerifier = jwsVerifierFactory.createJWSVerifier( 495 jwtAuth.getClientAssertion().getHeader(), 496 candidate); 497 498 boolean valid = assertion.verify(jwsVerifier); 499 500 if (valid) { 501 markExpended(jwtAuthClaims.getJWTID(), jwtAuthClaims.getExpirationTime(), clientAuth.getClientID(), ClientAuthenticationMethod.PRIVATE_KEY_JWT, context); 502 return; // success 503 } 504 } 505 506 // Second pass 507 if (hints != null && hints.contains(Hint.CLIENT_HAS_REMOTE_JWK_SET)) { 508 // Client possibly registered JWK set URL with keys that have no IDs 509 // force JWK set reload from URL and retry 510 keyCandidates = ListUtils.removeNullItems( 511 clientCredentialsSelector.selectPublicKeys( 512 jwtAuth.getClientID(), 513 jwtAuth.getMethod(), 514 jwtAuth.getClientAssertion().getHeader(), 515 true, // force reload of remote JWK set 516 context 517 ) 518 ); 519 520 if (CollectionUtils.isEmpty(keyCandidates)) { 521 throw InvalidClientException.NO_MATCHING_JWK; 522 } 523 524 assertion = jwtAuth.getClientAssertion(); 525 526 for (PublicKey candidate : keyCandidates) { 527 528 JWSVerifier jwsVerifier = jwsVerifierFactory.createJWSVerifier( 529 jwtAuth.getClientAssertion().getHeader(), 530 candidate); 531 532 boolean valid = assertion.verify(jwsVerifier); 533 534 if (valid) { 535 markExpended(jwtAuthClaims.getJWTID(), jwtAuthClaims.getExpirationTime(), clientAuth.getClientID(), ClientAuthenticationMethod.PRIVATE_KEY_JWT, context); 536 return; // success 537 } 538 } 539 } 540 541 throw InvalidClientException.BAD_JWT_SIGNATURE; 542 543 } else if (clientAuth instanceof SelfSignedTLSClientAuthentication) { 544 545 SelfSignedTLSClientAuthentication tlsClientAuth = (SelfSignedTLSClientAuthentication) clientAuth; 546 547 X509Certificate clientCert = tlsClientAuth.getClientX509Certificate(); 548 549 if (clientCert == null) { 550 // Sanity check 551 throw new InvalidClientException("Missing client X.509 certificate"); 552 } 553 554 // Self-signed certs bound to registered public key in client jwks / jwks_uri 555 List<? extends PublicKey> keyCandidates = ListUtils.removeNullItems( 556 clientCredentialsSelector.selectPublicKeys( 557 tlsClientAuth.getClientID(), 558 tlsClientAuth.getMethod(), 559 null, 560 false, // don't force refresh if we have a remote JWK set; 561 // selector may however do so if it encounters an unknown key ID 562 context 563 ) 564 ); 565 566 if (CollectionUtils.isEmpty(keyCandidates)) { 567 throw InvalidClientException.NO_MATCHING_JWK; 568 } 569 570 for (PublicKey candidate : keyCandidates) { 571 572 boolean valid = X509CertificateUtils.publicKeyMatches(clientCert, candidate); 573 574 if (valid) { 575 return; // success 576 } 577 } 578 579 // Second pass 580 if (hints != null && hints.contains(Hint.CLIENT_HAS_REMOTE_JWK_SET)) { 581 // Client possibly registered JWK set URL with keys that have no IDs 582 // force JWK set reload from URL and retry 583 keyCandidates = ListUtils.removeNullItems( 584 clientCredentialsSelector.selectPublicKeys( 585 tlsClientAuth.getClientID(), 586 tlsClientAuth.getMethod(), 587 null, 588 true, // force reload of remote JWK set 589 context 590 ) 591 ); 592 593 if (CollectionUtils.isEmpty(keyCandidates)) { 594 throw InvalidClientException.NO_MATCHING_JWK; 595 } 596 597 for (PublicKey candidate : keyCandidates) { 598 599 if (candidate == null) { 600 continue; // skip 601 } 602 603 boolean valid = X509CertificateUtils.publicKeyMatches(clientCert, candidate); 604 605 if (valid) { 606 return; // success 607 } 608 } 609 } 610 611 throw InvalidClientException.BAD_SELF_SIGNED_CLIENT_CERTIFICATE; 612 613 } else if (clientAuth instanceof PKITLSClientAuthentication) { 614 615 PKITLSClientAuthentication tlsClientAuth = (PKITLSClientAuthentication) clientAuth; 616 if (pkiCertBindingVerifier != null) { 617 pkiCertBindingVerifier.verifyCertificateBinding( 618 clientAuth.getClientID(), 619 tlsClientAuth.getClientX509Certificate(), 620 context); 621 622 } else if (certBindingVerifier != null) { 623 certBindingVerifier.verifyCertificateBinding( 624 clientAuth.getClientID(), 625 tlsClientAuth.getClientX509CertificateSubjectDN(), 626 context); 627 } else { 628 throw new InvalidClientException("Mutual TLS client Authentication (tls_client_auth) not supported"); 629 } 630 } else { 631 throw new RuntimeException("Unexpected client authentication: " + clientAuth.getMethod()); 632 } 633 } 634}