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