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 aud                       The permitted audience (aud) claim.
110         *                                  Must not be empty or {@code null}.
111         *                                  Should be the identity of the
112         *                                  recipient, such as the issuer URI
113         *                                  for an OpenID provider.
114         *
115         * @deprecated Use the constructor with {@link PKIClientX509CertificateBindingVerifier}
116         */
117        @Deprecated
118        public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector,
119                                            final ClientX509CertificateBindingVerifier<T> certBindingVerifier,
120                                            final Set<Audience> aud) {
121
122                claimsSetVerifier = new JWTAuthenticationClaimsSetVerifier(aud);
123                this.certBindingVerifier = certBindingVerifier;
124                this.pkiCertBindingVerifier = null;
125                this.clientCredentialsSelector = Objects.requireNonNull(clientCredentialsSelector);
126                this.expendedJTIChecker = null;
127        }
128
129        
130        /**
131         * Creates a new client authentication verifier without support for
132         * {@code tls_client_auth}. The audience check is
133         * {@link JWTAudienceCheck#LEGACY legacy}.
134         *
135         * @param clientCredentialsSelector The client credentials selector.
136         *                                  Must not be {@code null}.
137         * @param aud                       The permitted audience (aud) claim.
138         *                                  Must not be empty or {@code null}.
139         *                                  Should be the identity of the
140         *                                  recipient, such as the issuer URI
141         *                                  for an OpenID provider.
142         */
143        @Deprecated
144        public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector,
145                                            final Set<Audience> aud) {
146
147                this(clientCredentialsSelector, aud, JWTAudienceCheck.LEGACY);
148        }
149
150
151        /**
152         * Creates a new client authentication verifier without support for
153         * {@code tls_client_auth}.
154         *
155         * @param clientCredentialsSelector The client credentials selector.
156         *                                  Must not be {@code null}.
157         * @param aud                       The permitted audience (aud) claim.
158         *                                  Must not be empty or {@code null}.
159         *                                  Should be the identity of the
160         *                                  recipient, such as the issuer URI
161         *                                  for an OpenID provider.
162         */
163        public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector,
164                                            final Set<Audience> aud,
165                                            final JWTAudienceCheck audCheck) {
166
167                this(clientCredentialsSelector, aud, audCheck, null);
168        }
169
170
171        /**
172         * Creates a new client authentication verifier without support for
173         * {@code tls_client_auth}. The audience check is
174         * {@link JWTAudienceCheck#LEGACY legacy}.
175         *
176         * @param clientCredentialsSelector The client credentials selector.
177         *                                  Must not be {@code null}.
178         * @param aud                       The permitted audience (aud) claim.
179         *                                  Must not be empty or {@code null}.
180         *                                  Should be the identity of the
181         *                                  recipient, such as the issuer URI
182         *                                  for an OpenID provider.
183         * @param expendedJTIChecker        Optional expended JWT ID (jti)
184         *                                  claim checker to prevent JWT
185         *                                  replay, {@code null} if none.
186         */
187        @Deprecated
188        public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector,
189                                            final Set<Audience> aud,
190                                            final ExpendedJTIChecker<T> expendedJTIChecker) {
191
192                this(clientCredentialsSelector, aud, JWTAudienceCheck.LEGACY, expendedJTIChecker);
193        }
194
195
196        /**
197         * Creates a new client authentication verifier without support for
198         * {@code tls_client_auth}.
199         *
200         * @param clientCredentialsSelector The client credentials selector.
201         *                                  Must not be {@code null}.
202         * @param aud                       The permitted audience (aud) claim.
203         *                                  Must not be empty or {@code null}.
204         *                                  Should be the identity of the
205         *                                  recipient, such as the issuer URI
206         *                                  for an OpenID provider.
207         * @param expendedJTIChecker        Optional expended JWT ID (jti)
208         *                                  claim checker to prevent JWT
209         *                                  replay, {@code null} if none.
210         */
211        public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector,
212                                            final Set<Audience> aud,
213                                            final JWTAudienceCheck audCheck,
214                                            final ExpendedJTIChecker<T> expendedJTIChecker) {
215
216                claimsSetVerifier = new JWTAuthenticationClaimsSetVerifier(aud, audCheck, -1L);
217                this.certBindingVerifier = null;
218                this.pkiCertBindingVerifier = null;
219                this.clientCredentialsSelector = Objects.requireNonNull(clientCredentialsSelector);
220                this.expendedJTIChecker = expendedJTIChecker;
221        }
222        
223
224        /**
225         * Creates a new client authentication verifier. The audience check is
226         * {@link JWTAudienceCheck#LEGACY legacy}.
227         *
228         * @param clientCredentialsSelector The client credentials selector.
229         *                                  Must not be {@code null}.
230         * @param pkiCertBindingVerifier    Optional client X.509 certificate
231         *                                  binding verifier for
232         *                                  {@code tls_client_auth},
233         *                                  {@code null} if not supported.
234         * @param aud                       The permitted audience (aud) claim.
235         *                                  Must not be empty or {@code null}.
236         *                                  Should be the identity of the
237         *                                  recipient, such as the issuer URI
238         *                                  for an OpenID provider.
239         */
240        @Deprecated
241        public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector,
242                                            final PKIClientX509CertificateBindingVerifier<T> pkiCertBindingVerifier,
243                                            final Set<Audience> aud) {
244
245                this(clientCredentialsSelector, pkiCertBindingVerifier, aud, JWTAudienceCheck.LEGACY);
246        }
247
248
249        /**
250         * Creates a new client authentication verifier.
251         *
252         * @param clientCredentialsSelector The client credentials selector.
253         *                                  Must not be {@code null}.
254         * @param pkiCertBindingVerifier    Optional client X.509 certificate
255         *                                  binding verifier for
256         *                                  {@code tls_client_auth},
257         *                                  {@code null} if not supported.
258         * @param aud                       The permitted audience (aud) claim.
259         *                                  Must not be empty or {@code null}.
260         *                                  Should be the identity of the
261         *                                  recipient, such as the issuer URI
262         *                                  for an OpenID provider.
263         */
264        public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector,
265                                            final PKIClientX509CertificateBindingVerifier<T> pkiCertBindingVerifier,
266                                            final Set<Audience> aud,
267                                            final JWTAudienceCheck audCheck) {
268
269                this(clientCredentialsSelector, pkiCertBindingVerifier, aud, audCheck, null, -1L);
270        }
271
272
273        /**
274         * Creates a new client authentication verifier. The audience check is
275         * {@link JWTAudienceCheck#LEGACY legacy}.
276         *
277         * @param clientCredentialsSelector The client credentials selector.
278         *                                  Must not be {@code null}.
279         * @param pkiCertBindingVerifier    Optional client X.509 certificate
280         *                                  binding verifier for
281         *                                  {@code tls_client_auth},
282         *                                  {@code null} if not supported.
283         * @param aud                       The permitted audience (aud) claim.
284         *                                  Must not be empty or {@code null}.
285         *                                  Should be the identity of the
286         *                                  recipient, such as the issuer URI
287         *                                  for an OpenID provider.
288         * @param expendedJTIChecker        Optional expended JWT ID (jti)
289         *                                  claim checker to prevent JWT
290         *                                  replay, {@code null} if none.
291         * @param expMaxAhead               The maximum number of seconds the
292         *                                  expiration time (exp) claim can be
293         *                                  ahead of the current time, if zero
294         *                                  or negative this check is disabled.
295         */
296        @Deprecated
297        public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector,
298                                            final PKIClientX509CertificateBindingVerifier<T> pkiCertBindingVerifier,
299                                            final Set<Audience> aud,
300                                            final ExpendedJTIChecker<T> expendedJTIChecker,
301                                            final long expMaxAhead) {
302
303                this(clientCredentialsSelector, pkiCertBindingVerifier, aud, JWTAudienceCheck.LEGACY, expendedJTIChecker, expMaxAhead);
304        }
305
306
307        /**
308         * Creates a new client authentication verifier.
309         *
310         * @param clientCredentialsSelector The client credentials selector.
311         *                                  Must not be {@code null}.
312         * @param pkiCertBindingVerifier    Optional client X.509 certificate
313         *                                  binding verifier for
314         *                                  {@code tls_client_auth},
315         *                                  {@code null} if not supported.
316         * @param aud                       The permitted audience (aud) claim.
317         *                                  Must not be empty or {@code null}.
318         *                                  Should be the identity of the
319         *                                  recipient, such as the issuer URI
320         *                                  for an OpenID provider. When the
321         *                                  audience check is
322         *                                  {@link JWTAudienceCheck#STRICT strict},
323         *                                  the permitted audience must be
324         *                                  single-valued.
325         * @param audCheck                  The type of audience (aud) check.
326         *                                  Must not be {@code null}.
327         * @param expendedJTIChecker        Optional expended JWT ID (jti)
328         *                                  claim checker to prevent JWT
329         *                                  replay, {@code null} if none.
330         * @param expMaxAhead               The maximum number of seconds the
331         *                                  expiration time (exp) claim can be
332         *                                  ahead of the current time, if zero
333         *                                  or negative this check is disabled.
334         */
335        public ClientAuthenticationVerifier(final ClientCredentialsSelector<T> clientCredentialsSelector,
336                                            final PKIClientX509CertificateBindingVerifier<T> pkiCertBindingVerifier,
337                                            final Set<Audience> aud,
338                                            final JWTAudienceCheck audCheck,
339                                            final ExpendedJTIChecker<T> expendedJTIChecker,
340                                            final long expMaxAhead) {
341
342                claimsSetVerifier = new JWTAuthenticationClaimsSetVerifier(aud, audCheck, expMaxAhead);
343                this.certBindingVerifier = null;
344                this.pkiCertBindingVerifier = pkiCertBindingVerifier;
345                this.clientCredentialsSelector = Objects.requireNonNull(clientCredentialsSelector);
346                this.expendedJTIChecker = expendedJTIChecker;
347        }
348
349
350        /**
351         * Returns the client credentials selector.
352         *
353         * @return The client credentials selector.
354         */
355        public ClientCredentialsSelector<T> getClientCredentialsSelector() {
356
357                return clientCredentialsSelector;
358        }
359        
360        
361        /**
362         * Returns the client X.509 certificate binding verifier for use in
363         * {@code tls_client_auth}.
364         *
365         * @return The client X.509 certificate binding verifier, {@code null}
366         *         if not specified.
367         * @deprecated See {@link PKIClientX509CertificateBindingVerifier}
368         */
369        @Deprecated
370        public ClientX509CertificateBindingVerifier<T> getClientX509CertificateBindingVerifier() {
371                
372                return certBindingVerifier;
373        }
374        
375        
376        /**
377         * Returns the client X.509 certificate binding verifier for use in
378         * {@code tls_client_auth}.
379         *
380         * @return The client X.509 certificate binding verifier, {@code null}
381         *         if not specified.
382         */
383        public PKIClientX509CertificateBindingVerifier<T> getPKIClientX509CertificateBindingVerifier() {
384                
385                return pkiCertBindingVerifier;
386        }
387        
388        
389        /**
390         * Returns the permitted audience in JWT authentication assertions.
391         *
392         * @return The permitted audience (aud) claim values.
393         */
394        public Set<Audience> getExpectedAudience() {
395
396                return claimsSetVerifier.getExpectedAudience();
397        }
398
399
400        /**
401         * Returns the configured audience check.
402         *
403         * @return The type of audience (aud) check.
404         */
405        public JWTAudienceCheck getJWTAudienceCheck() {
406
407                return claimsSetVerifier.getAudienceCheck();
408        }
409
410
411        /**
412         * Returns the optional expended JWT ID (jti) claim checker to prevent
413         * JWT replay.
414         *
415         * @return The expended JWT ID (jti) claim checker, {@code null} if
416         *         none.
417         */
418        public ExpendedJTIChecker<T> getExpendedJTIChecker() {
419
420                return expendedJTIChecker;
421        }
422
423
424        private static List<Secret> removeNullOrErased(final List<Secret> secrets) {
425                List<Secret> allSet = ListUtils.removeNullItems(secrets);
426                if (allSet == null) {
427                        return null;
428                }
429                List<Secret> out = new LinkedList<>();
430                for (Secret secret: secrets) {
431                        if (secret.getValue() != null && secret.getValueBytes() != null) {
432                                out.add(secret);
433                        }
434                }
435                return out;
436        }
437
438
439        private void preventJWTReplay(final JWTID jti,
440                                      final ClientID clientID,
441                                      final ClientAuthenticationMethod method,
442                                      final Context<T> context)
443                throws InvalidClientException {
444
445                if (jti == null || getExpendedJTIChecker() == null) {
446                        return;
447                }
448
449                if (getExpendedJTIChecker().isExpended(jti, clientID, method, context)) {
450                        throw new InvalidClientException("Detected JWT ID replay");
451                }
452        }
453
454
455        private void markExpended(final JWTID jti,
456                                  final Date exp,
457                                  final ClientID clientID,
458                                  final ClientAuthenticationMethod method,
459                                  final Context<T> context) {
460
461                if (jti == null || getExpendedJTIChecker() == null) {
462                        return;
463                }
464
465                getExpendedJTIChecker().markExpended(jti, exp, clientID, method, context);
466        }
467
468
469        /**
470         * Verifies a client authentication request.
471         *
472         * @param clientAuth The client authentication. Must not be
473         *                   {@code null}.
474         * @param hints      Optional hints to the verifier, empty set of
475         *                   {@code null} if none.
476         * @param context    Additional context to be passed to the client
477         *                   credentials selector. May be {@code null}.
478         *
479         * @throws InvalidClientException If the client authentication is
480         *                                invalid, typically due to bad
481         *                                credentials.
482         * @throws JOSEException          If authentication failed due to an
483         *                                internal JOSE / JWT processing
484         *                                exception.
485         */
486        public void verify(final ClientAuthentication clientAuth, final Set<Hint> hints, final Context<T> context)
487                throws InvalidClientException, JOSEException {
488
489                if (clientAuth instanceof PlainClientSecret) {
490
491                        List<Secret> secretCandidates = ListUtils.removeNullItems(
492                                clientCredentialsSelector.selectClientSecrets(
493                                        clientAuth.getClientID(),
494                                        clientAuth.getMethod(),
495                                        context
496                                )
497                        );
498
499                        if (CollectionUtils.isEmpty(secretCandidates)) {
500                                throw InvalidClientException.NO_REGISTERED_SECRET;
501                        }
502
503                        PlainClientSecret plainAuth = (PlainClientSecret)clientAuth;
504
505                        for (Secret candidate: secretCandidates) {
506                                
507                                // Constant time, SHA-256 based, unless overridden
508                                if (candidate.equals(plainAuth.getClientSecret())) {
509                                        return; // success
510                                }
511                        }
512
513                        throw InvalidClientException.BAD_SECRET;
514
515                } else if (clientAuth instanceof ClientSecretJWT) {
516
517                        ClientSecretJWT jwtAuth = (ClientSecretJWT) clientAuth;
518
519                        // Check claims first before requesting secret from backend
520                        JWTAuthenticationClaimsSet jwtAuthClaims = jwtAuth.getJWTAuthenticationClaimsSet();
521
522                        preventJWTReplay(jwtAuthClaims.getJWTID(), clientAuth.getClientID(), ClientAuthenticationMethod.CLIENT_SECRET_JWT, context);
523
524                        try {
525                                claimsSetVerifier.verify(jwtAuthClaims.toJWTClaimsSet(), null);
526                        } catch (BadJWTException e) {
527                                throw new InvalidClientException("Bad / expired JWT claims: " + e.getMessage());
528                        }
529
530                        List<Secret> secretCandidates = removeNullOrErased(
531                                clientCredentialsSelector.selectClientSecrets(
532                                        clientAuth.getClientID(),
533                                        clientAuth.getMethod(),
534                                        context
535                                )
536                        );
537
538                        if (CollectionUtils.isEmpty(secretCandidates)) {
539                                throw InvalidClientException.NO_REGISTERED_SECRET;
540                        }
541
542                        SignedJWT assertion = jwtAuth.getClientAssertion();
543
544                        for (Secret candidate : secretCandidates) {
545
546                                boolean valid = assertion.verify(new MACVerifier(candidate.getValueBytes()));
547
548                                if (valid) {
549                                        markExpended(jwtAuthClaims.getJWTID(), jwtAuthClaims.getExpirationTime(), clientAuth.getClientID(), ClientAuthenticationMethod.CLIENT_SECRET_JWT, context);
550                                        return; // success
551                                }
552                        }
553
554                        throw InvalidClientException.BAD_JWT_HMAC;
555
556                } else if (clientAuth instanceof PrivateKeyJWT) {
557                        
558                        PrivateKeyJWT jwtAuth = (PrivateKeyJWT) clientAuth;
559                        
560                        // Check claims first before requesting / retrieving public keys
561                        JWTAuthenticationClaimsSet jwtAuthClaims = jwtAuth.getJWTAuthenticationClaimsSet();
562
563                        preventJWTReplay(jwtAuthClaims.getJWTID(), clientAuth.getClientID(), ClientAuthenticationMethod.PRIVATE_KEY_JWT, context);
564
565                        try {
566                                claimsSetVerifier.verify(jwtAuthClaims.toJWTClaimsSet(), null);
567                        } catch (BadJWTException e) {
568                                throw new InvalidClientException("Bad / expired JWT claims: " + e.getMessage());
569                        }
570                        
571                        List<? extends PublicKey> keyCandidates = ListUtils.removeNullItems(
572                                clientCredentialsSelector.selectPublicKeys(
573                                        jwtAuth.getClientID(),
574                                        jwtAuth.getMethod(),
575                                        jwtAuth.getClientAssertion().getHeader(),
576                                        false,        // don't force refresh if we have a remote JWK set;
577                                        // selector may however do so if it encounters an unknown key ID
578                                        context
579                                )
580                        );
581                        
582                        if (CollectionUtils.isEmpty(keyCandidates)) {
583                                throw InvalidClientException.NO_MATCHING_JWK;
584                        }
585                        
586                        SignedJWT assertion = jwtAuth.getClientAssertion();
587                        
588                        for (PublicKey candidate : keyCandidates) {
589                                
590                                JWSVerifier jwsVerifier = jwsVerifierFactory.createJWSVerifier(
591                                        jwtAuth.getClientAssertion().getHeader(),
592                                        candidate);
593                                
594                                boolean valid = assertion.verify(jwsVerifier);
595                                
596                                if (valid) {
597                                        markExpended(jwtAuthClaims.getJWTID(), jwtAuthClaims.getExpirationTime(), clientAuth.getClientID(), ClientAuthenticationMethod.PRIVATE_KEY_JWT, context);
598                                        return; // success
599                                }
600                        }
601                        
602                        // Second pass
603                        if (hints != null && hints.contains(Hint.CLIENT_HAS_REMOTE_JWK_SET)) {
604                                // Client possibly registered JWK set URL with keys that have no IDs
605                                // force JWK set reload from URL and retry
606                                keyCandidates = ListUtils.removeNullItems(
607                                        clientCredentialsSelector.selectPublicKeys(
608                                                jwtAuth.getClientID(),
609                                                jwtAuth.getMethod(),
610                                                jwtAuth.getClientAssertion().getHeader(),
611                                                true, // force reload of remote JWK set
612                                                context
613                                        )
614                                );
615                                
616                                if (CollectionUtils.isEmpty(keyCandidates)) {
617                                        throw InvalidClientException.NO_MATCHING_JWK;
618                                }
619                                
620                                assertion = jwtAuth.getClientAssertion();
621                                
622                                for (PublicKey candidate : keyCandidates) {
623                                        
624                                        JWSVerifier jwsVerifier = jwsVerifierFactory.createJWSVerifier(
625                                                jwtAuth.getClientAssertion().getHeader(),
626                                                candidate);
627                                        
628                                        boolean valid = assertion.verify(jwsVerifier);
629                                        
630                                        if (valid) {
631                                                markExpended(jwtAuthClaims.getJWTID(), jwtAuthClaims.getExpirationTime(), clientAuth.getClientID(), ClientAuthenticationMethod.PRIVATE_KEY_JWT, context);
632                                                return; // success
633                                        }
634                                }
635                        }
636                        
637                        throw InvalidClientException.BAD_JWT_SIGNATURE;
638                        
639                } else if (clientAuth instanceof SelfSignedTLSClientAuthentication) {
640                        
641                        SelfSignedTLSClientAuthentication tlsClientAuth = (SelfSignedTLSClientAuthentication) clientAuth;
642                        
643                        X509Certificate clientCert = tlsClientAuth.getClientX509Certificate();
644                        
645                        if (clientCert == null) {
646                                // Sanity check
647                                throw new InvalidClientException("Missing client X.509 certificate");
648                        }
649                        
650                        // Self-signed certs bound to registered public key in client jwks / jwks_uri
651                        List<? extends PublicKey> keyCandidates = ListUtils.removeNullItems(
652                                clientCredentialsSelector.selectPublicKeys(
653                                        tlsClientAuth.getClientID(),
654                                        tlsClientAuth.getMethod(),
655                                        null,
656                                        false, // don't force refresh if we have a remote JWK set;
657                                        // selector may however do so if it encounters an unknown key ID
658                                        context
659                                )
660                        );
661                        
662                        if (CollectionUtils.isEmpty(keyCandidates)) {
663                                throw InvalidClientException.NO_MATCHING_JWK;
664                        }
665                        
666                        for (PublicKey candidate : keyCandidates) {
667                                
668                                boolean valid = X509CertificateUtils.publicKeyMatches(clientCert, candidate);
669                                
670                                if (valid) {
671                                        return; // success
672                                }
673                        }
674                        
675                        // Second pass
676                        if (hints != null && hints.contains(Hint.CLIENT_HAS_REMOTE_JWK_SET)) {
677                                // Client possibly registered JWK set URL with keys that have no IDs
678                                // force JWK set reload from URL and retry
679                                keyCandidates = ListUtils.removeNullItems(
680                                        clientCredentialsSelector.selectPublicKeys(
681                                                tlsClientAuth.getClientID(),
682                                                tlsClientAuth.getMethod(),
683                                                null,
684                                                true, // force reload of remote JWK set
685                                                context
686                                        )
687                                );
688                                
689                                if (CollectionUtils.isEmpty(keyCandidates)) {
690                                        throw InvalidClientException.NO_MATCHING_JWK;
691                                }
692                                
693                                for (PublicKey candidate : keyCandidates) {
694                                        
695                                        if (candidate == null) {
696                                                continue; // skip
697                                        }
698                                        
699                                        boolean valid = X509CertificateUtils.publicKeyMatches(clientCert, candidate);
700                                        
701                                        if (valid) {
702                                                return; // success
703                                        }
704                                }
705                        }
706                        
707                        throw InvalidClientException.BAD_SELF_SIGNED_CLIENT_CERTIFICATE;
708                        
709                } else if (clientAuth instanceof PKITLSClientAuthentication) {
710                        
711                        PKITLSClientAuthentication tlsClientAuth = (PKITLSClientAuthentication) clientAuth;
712                        if (pkiCertBindingVerifier != null) {
713                                pkiCertBindingVerifier.verifyCertificateBinding(
714                                                clientAuth.getClientID(),
715                                                tlsClientAuth.getClientX509Certificate(),
716                                                context);
717                                
718                        } else if (certBindingVerifier != null) {
719                                certBindingVerifier.verifyCertificateBinding(
720                                                clientAuth.getClientID(),
721                                                tlsClientAuth.getClientX509CertificateSubjectDN(),
722                                                context);
723                        } else {
724                                throw new InvalidClientException("Mutual TLS client Authentication (tls_client_auth) not supported");
725                        }
726                } else {
727                        throw new RuntimeException("Unexpected client authentication: " + clientAuth.getMethod());
728                }
729        }
730}