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