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.X509CertificateUtils; 036import net.jcip.annotations.ThreadSafe; 037import org.apache.commons.collections4.CollectionUtils; 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 (draft-ietf-oauth-mtls-07), 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 */ 068 private final ClientX509CertificateBindingVerifier<T> certBindingVerifier; 069 070 071 /** 072 * The JWT assertion claims set verifier. 073 */ 074 private final JWTAuthenticationClaimsSetVerifier claimsSetVerifier; 075 076 077 /** 078 * JWS verifier factory for private_key_jwt authentication. 079 */ 080 private final JWSVerifierFactory jwsVerifierFactory = new DefaultJWSVerifierFactory(); 081 082 083 /** 084 * Creates a new client authentication verifier. 085 * 086 * @param clientCredentialsSelector The client credentials selector. 087 * Must not be {@code null}. 088 * @param certBindingVerifier Optional client X.509 certificate 089 * binding verifier for 090 * {@code tls_client_auth}, 091 * {@code null} if not supported. 092 * @param expectedAudience The permitted audience (aud) claim 093 * values in JWT authentication 094 * assertions. Must not be empty or 095 * {@code null}. Should typically 096 * contain the token endpoint URI and 097 * for OpenID provider it may also 098 * include the issuer URI. 099 */ 100 public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector, 101 final ClientX509CertificateBindingVerifier<T> certBindingVerifier, 102 final Set<Audience> expectedAudience) { 103 104 claimsSetVerifier = new JWTAuthenticationClaimsSetVerifier(expectedAudience); 105 106 if (clientCredentialsSelector == null) { 107 throw new IllegalArgumentException("The client credentials selector must not be null"); 108 } 109 110 this.certBindingVerifier = certBindingVerifier; 111 112 this.clientCredentialsSelector = clientCredentialsSelector; 113 } 114 115 116 /** 117 * Returns the client credentials selector. 118 * 119 * @return The client credentials selector. 120 */ 121 public ClientCredentialsSelector<T> getClientCredentialsSelector() { 122 123 return clientCredentialsSelector; 124 } 125 126 127 /** 128 * Returns the client X.509 certificate binding verifier for use in 129 * {@code tls_client_auth}. 130 * 131 * @return The client X.509 certificate binding verifier, {@code null} 132 * if not specified. 133 */ 134 public ClientX509CertificateBindingVerifier<T> getClientX509CertificateBindingVerifier() { 135 136 return certBindingVerifier; 137 } 138 139 140 /** 141 * Returns the permitted audience values in JWT authentication 142 * assertions. 143 * 144 * @return The permitted audience (aud) claim values. 145 */ 146 public Set<Audience> getExpectedAudience() { 147 148 return claimsSetVerifier.getExpectedAudience(); 149 } 150 151 152 /** 153 * Verifies a client authentication request. 154 * 155 * @param clientAuth The client authentication. Must not be 156 * {@code null}. 157 * @param hints Optional hints to the verifier, empty set of 158 * {@code null} if none. 159 * @param context Additional context to be passed to the client 160 * credentials selector. May be {@code null}. 161 * 162 * @throws InvalidClientException If the client authentication is 163 * invalid, typically due to bad 164 * credentials. 165 * @throws JOSEException If authentication failed due to an 166 * internal JOSE / JWT processing 167 * exception. 168 */ 169 public void verify(final ClientAuthentication clientAuth, final Set<Hint> hints, final Context<T> context) 170 throws InvalidClientException, JOSEException { 171 172 if (clientAuth instanceof PlainClientSecret) { 173 174 List<Secret> secretCandidates = clientCredentialsSelector.selectClientSecrets( 175 clientAuth.getClientID(), 176 clientAuth.getMethod(), 177 context); 178 179 if (CollectionUtils.isEmpty(secretCandidates)) { 180 throw InvalidClientException.NO_REGISTERED_SECRET; 181 } 182 183 PlainClientSecret plainAuth = (PlainClientSecret)clientAuth; 184 185 for (Secret candidate: secretCandidates) { 186 // constant time, SHA-256 based 187 if (plainAuth.getClientSecret().equalsSHA256Based(candidate)) { 188 return; // success 189 } 190 } 191 192 throw InvalidClientException.BAD_SECRET; 193 194 } else if (clientAuth instanceof ClientSecretJWT) { 195 196 ClientSecretJWT jwtAuth = (ClientSecretJWT) clientAuth; 197 198 // Check claims first before requesting secret from backend 199 try { 200 claimsSetVerifier.verify(jwtAuth.getJWTAuthenticationClaimsSet().toJWTClaimsSet()); 201 } catch (BadJWTException e) { 202 throw new InvalidClientException("Bad / expired JWT claims: " + e.getMessage()); 203 } 204 205 List<Secret> secretCandidates = clientCredentialsSelector.selectClientSecrets( 206 clientAuth.getClientID(), 207 clientAuth.getMethod(), 208 context); 209 210 if (CollectionUtils.isEmpty(secretCandidates)) { 211 throw InvalidClientException.NO_REGISTERED_SECRET; 212 } 213 214 SignedJWT assertion = jwtAuth.getClientAssertion(); 215 216 for (Secret candidate : secretCandidates) { 217 218 boolean valid = assertion.verify(new MACVerifier(candidate.getValueBytes())); 219 220 if (valid) { 221 return; // success 222 } 223 } 224 225 throw InvalidClientException.BAD_JWT_HMAC; 226 227 } else if (clientAuth instanceof PrivateKeyJWT) { 228 229 PrivateKeyJWT jwtAuth = (PrivateKeyJWT) clientAuth; 230 231 // Check claims first before requesting / retrieving public keys 232 try { 233 claimsSetVerifier.verify(jwtAuth.getJWTAuthenticationClaimsSet().toJWTClaimsSet()); 234 } catch (BadJWTException e) { 235 throw new InvalidClientException("Bad / expired JWT claims: " + e.getMessage()); 236 } 237 238 List<? extends PublicKey> keyCandidates = clientCredentialsSelector.selectPublicKeys( 239 jwtAuth.getClientID(), 240 jwtAuth.getMethod(), 241 jwtAuth.getClientAssertion().getHeader(), 242 false, // don't force refresh if we have a remote JWK set; 243 // selector may however do so if it encounters an unknown key ID 244 context); 245 246 if (CollectionUtils.isEmpty(keyCandidates)) { 247 throw InvalidClientException.NO_MATCHING_JWK; 248 } 249 250 SignedJWT assertion = jwtAuth.getClientAssertion(); 251 252 for (PublicKey candidate : keyCandidates) { 253 254 if (candidate == null) { 255 continue; // skip 256 } 257 258 JWSVerifier jwsVerifier = jwsVerifierFactory.createJWSVerifier( 259 jwtAuth.getClientAssertion().getHeader(), 260 candidate); 261 262 boolean valid = assertion.verify(jwsVerifier); 263 264 if (valid) { 265 return; // success 266 } 267 } 268 269 // Second pass 270 if (hints != null && hints.contains(Hint.CLIENT_HAS_REMOTE_JWK_SET)) { 271 // Client possibly registered JWK set URL with keys that have no IDs 272 // force JWK set reload from URL and retry 273 keyCandidates = clientCredentialsSelector.selectPublicKeys( 274 jwtAuth.getClientID(), 275 jwtAuth.getMethod(), 276 jwtAuth.getClientAssertion().getHeader(), 277 true, // force reload of remote JWK set 278 context); 279 280 if (CollectionUtils.isEmpty(keyCandidates)) { 281 throw InvalidClientException.NO_MATCHING_JWK; 282 } 283 284 assertion = jwtAuth.getClientAssertion(); 285 286 for (PublicKey candidate : keyCandidates) { 287 288 if (candidate == null) { 289 continue; // skip 290 } 291 292 JWSVerifier jwsVerifier = jwsVerifierFactory.createJWSVerifier( 293 jwtAuth.getClientAssertion().getHeader(), 294 candidate); 295 296 boolean valid = assertion.verify(jwsVerifier); 297 298 if (valid) { 299 return; // success 300 } 301 } 302 } 303 304 throw InvalidClientException.BAD_JWT_SIGNATURE; 305 306 } else if (clientAuth instanceof SelfSignedTLSClientAuthentication) { 307 308 SelfSignedTLSClientAuthentication tlsClientAuth = (SelfSignedTLSClientAuthentication) clientAuth; 309 310 X509Certificate clientCert = tlsClientAuth.getClientX509Certificate(); 311 312 if (clientCert == null) { 313 // Sanity check 314 throw new InvalidClientException("Missing client X.509 certificate"); 315 } 316 317 // Self-signed certs bound to registered public key in client jwks / jwks_uri 318 List<? extends PublicKey> keyCandidates = clientCredentialsSelector.selectPublicKeys( 319 tlsClientAuth.getClientID(), 320 tlsClientAuth.getMethod(), 321 null, 322 false, // don't force refresh if we have a remote JWK set; 323 // selector may however do so if it encounters an unknown key ID 324 context); 325 326 if (CollectionUtils.isEmpty(keyCandidates)) { 327 throw InvalidClientException.NO_MATCHING_JWK; 328 } 329 330 for (PublicKey candidate : keyCandidates) { 331 332 if (candidate == null) { 333 continue; // skip 334 } 335 336 boolean valid = X509CertificateUtils.hasValidSignature(clientCert, candidate); 337 338 if (valid) { 339 return; // success 340 } 341 } 342 343 // Second pass 344 if (hints != null && hints.contains(Hint.CLIENT_HAS_REMOTE_JWK_SET)) { 345 // Client possibly registered JWK set URL with keys that have no IDs 346 // force JWK set reload from URL and retry 347 keyCandidates = clientCredentialsSelector.selectPublicKeys( 348 tlsClientAuth.getClientID(), 349 tlsClientAuth.getMethod(), 350 null, 351 true, // force reload of remote JWK set 352 context); 353 354 if (CollectionUtils.isEmpty(keyCandidates)) { 355 throw InvalidClientException.NO_MATCHING_JWK; 356 } 357 358 for (PublicKey candidate : keyCandidates) { 359 360 if (candidate == null) { 361 continue; // skip 362 } 363 364 boolean valid = X509CertificateUtils.hasValidSignature(clientCert, candidate); 365 366 if (valid) { 367 return; // success 368 } 369 } 370 } 371 372 throw InvalidClientException.BAD_SELF_SIGNED_CLIENT_CERTIFICATE; 373 374 } else if (clientAuth instanceof TLSClientAuthentication) { 375 376 if (certBindingVerifier == null) { 377 throw new InvalidClientException("Mutual TLS client Authentication (tls_client_auth) not supported"); 378 } 379 380 TLSClientAuthentication tlsClientAuth = (TLSClientAuthentication) clientAuth; 381 382 certBindingVerifier.verifyCertificateBinding( 383 clientAuth.getClientID(), 384 tlsClientAuth.getClientX509CertificateSubjectDN(), 385 context); 386 387 } else { 388 throw new RuntimeException("Unexpected client authentication: " + clientAuth.getMethod()); 389 } 390 } 391}