001package com.nimbusds.openid.connect.sdk.validators;
002
003
004import java.net.MalformedURLException;
005import java.net.URL;
006
007import com.nimbusds.jose.*;
008import com.nimbusds.jose.jwk.JWKSet;
009import com.nimbusds.jose.proc.BadJOSEException;
010import com.nimbusds.jose.proc.JWEKeySelector;
011import com.nimbusds.jose.proc.JWSKeySelector;
012import com.nimbusds.jwt.*;
013import com.nimbusds.jwt.proc.*;
014import com.nimbusds.oauth2.sdk.GeneralException;
015import com.nimbusds.oauth2.sdk.ParseException;
016import com.nimbusds.oauth2.sdk.auth.Secret;
017import com.nimbusds.oauth2.sdk.id.ClientID;
018import com.nimbusds.oauth2.sdk.id.Issuer;
019import com.nimbusds.oauth2.sdk.jose.jwk.*;
020import com.nimbusds.openid.connect.sdk.Nonce;
021import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
022import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
023import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
024import net.jcip.annotations.ThreadSafe;
025
026
027/**
028 * Validator of ID tokens issued by an OpenID Provider (OP).
029 *
030 * <p>Supports processing of ID tokens with the following protection:
031 *
032 * <ul>
033 *     <li>ID tokens signed (JWS) with the OP's RSA or EC key, require the
034 *         OP public JWK set (provided by value or URL) to verify them.
035 *     <li>ID tokens authenticated with a JWS HMAC, require the client's secret
036 *         to verify them.
037 *     <li>Unsecured (plain) ID tokens received at the token endpoint.
038 * </ul>
039 *
040 * <p>Related specifications:
041 *
042 * <ul>
043 *     <li>OpenID Connect Core 1.0, sections 3.1.3.7, 3.2.2.11 and 3.3.2.12.
044 * </ul>
045 */
046@ThreadSafe
047public class IDTokenValidator implements ClockSkewAware {
048
049
050        /**
051         * The default maximum acceptable clock skew for verifying ID token
052         * timestamps, in seconds.
053         */
054        public static final int DEFAULT_MAX_CLOCK_SKEW = 60;
055
056
057        /**
058         * The expected ID token issuer.
059         */
060        private final Issuer expectedIssuer;
061
062
063        /**
064         * The requesting client.
065         */
066        private final ClientID clientID;
067
068
069        /**
070         * The JWS key selector.
071         */
072        private final JWSKeySelector jwsKeySelector;
073
074
075        /**
076         * The JWE key selector.
077         */
078        private final JWEKeySelector jweKeySelector;
079
080
081        /**
082         * The maximum acceptable clock skew, in seconds.
083         */
084        private int maxClockSkew = DEFAULT_MAX_CLOCK_SKEW;
085
086
087        /**
088         * Creates a new validator for unsecured (plain) ID tokens.
089         *
090         * @param expectedIssuer The expected ID token issuer (OpenID
091         *                       Provider). Must not be {@code null}.
092         * @param clientID       The client ID. Must not be {@code null}.
093         */
094        public IDTokenValidator(final Issuer expectedIssuer,
095                                final ClientID clientID) {
096
097                this(expectedIssuer, clientID, (JWSKeySelector) null, (JWEKeySelector) null);
098        }
099
100
101        /**
102         * Creates a new validator for RSA or EC signed ID tokens where the
103         * OpenID Provider's JWK set is specified by value.
104         *
105         * @param expectedIssuer The expected ID token issuer (OpenID
106         *                       Provider). Must not be {@code null}.
107         * @param clientID       The client ID. Must not be {@code null}.
108         * @param expectedJWSAlg The expected RSA or EC JWS algorithm. Must not
109         *                       be {@code null}.
110         * @param jwkSet         The OpenID Provider JWK set. Must not be
111         *                       {@code null}.
112         */
113        public IDTokenValidator(final Issuer expectedIssuer,
114                                final ClientID clientID,
115                                final JWSAlgorithm expectedJWSAlg,
116                                final JWKSet jwkSet) {
117
118                this(expectedIssuer, clientID, new JWSVerificationKeySelector(expectedIssuer, expectedJWSAlg, new ImmutableJWKSet(expectedIssuer, jwkSet)),  null);
119        }
120
121
122        /**
123         * Creates a new validator for RSA or EC signed ID tokens where the
124         * OpenID Provider's JWK set is specified by URL.
125         *
126         * @param expectedIssuer The expected ID token issuer (OpenID
127         *                       Provider). Must not be {@code null}.
128         * @param clientID       The client ID. Must not be {@code null}.
129         * @param expectedJWSAlg The expected RSA or EC JWS algorithm. Must not
130         *                       be {@code null}.
131         * @param jwkSetURI      The OpenID Provider JWK set URL. Must not be
132         *                       {@code null}.
133         */
134        public IDTokenValidator(final Issuer expectedIssuer,
135                                final ClientID clientID,
136                                final JWSAlgorithm expectedJWSAlg,
137                                final URL jwkSetURI) {
138
139                this(expectedIssuer, clientID, new JWSVerificationKeySelector(expectedIssuer, expectedJWSAlg, new RemoteJWKSet(expectedIssuer, jwkSetURI, null)),  null);
140        }
141
142
143        /**
144         * Creates a new validator for HMAC protected ID tokens.
145         *
146         * @param expectedIssuer The expected ID token issuer (OpenID
147         *                       Provider). Must not be {@code null}.
148         * @param clientID       The client ID. Must not be {@code null}.
149         * @param expectedJWSAlg The expected HMAC JWS algorithm. Must not be
150         *                       {@code null}.
151         * @param clientSecret   The client secret. Must not be {@code null}.
152         */
153        public IDTokenValidator(final Issuer expectedIssuer,
154                                final ClientID clientID,
155                                final JWSAlgorithm expectedJWSAlg,
156                                final Secret clientSecret) {
157
158                this(expectedIssuer, clientID, new JWSVerificationKeySelector(expectedIssuer, expectedJWSAlg, new ImmutableClientSecret(clientID, clientSecret)), null);
159        }
160
161
162        /**
163         * Creates a new ID token validator.
164         *
165         * @param expectedIssuer The expected ID token issuer (OpenID
166         *                       Provider). Must not be {@code null}.
167         * @param clientID       The client ID. Must not be {@code null}.
168         * @param jwsKeySelector The key selector for JWS verification,
169         *                       {@code null} if unsecured (plain) ID tokens
170         *                       are expected.
171         * @param jweKeySelector The key selector for JWE decryption,
172         *                       {@code null} if encrypted ID tokens are not
173         *                       expected.
174         */
175        public IDTokenValidator(final Issuer expectedIssuer,
176                                final ClientID clientID,
177                                final JWSKeySelector jwsKeySelector,
178                                final JWEKeySelector jweKeySelector) {
179                if (expectedIssuer == null) {
180                        throw new IllegalArgumentException("The expected ID token issuer must not be null");
181                }
182                this.expectedIssuer = expectedIssuer;
183                if (clientID == null) {
184                        throw new IllegalArgumentException("The client ID must not be null");
185                }
186                this.clientID = clientID;
187                this.jwsKeySelector = jwsKeySelector;
188                this.jweKeySelector = jweKeySelector;
189        }
190
191
192        /**
193         * Returns the expected ID token issuer.
194         *
195         * @return The ID token issuer.
196         */
197        public Issuer getExpectedIssuer() {
198                return expectedIssuer;
199        }
200
201
202        /**
203         * Returns the client ID (the expected ID token audience).
204         *
205         * @return The client ID.
206         */
207        public ClientID getClientID() {
208                return clientID;
209        }
210
211
212        /**
213         * Returns the configured JWS key selector for signed ID token
214         * verification.
215         *
216         * @return The JWS key selector, {@code null} if none.
217         */
218        public JWSKeySelector getJWSKeySelector() {
219                return jwsKeySelector;
220        }
221
222
223        /**
224         * Returns the configured JWE key selector for encrypted ID token
225         * decryption.
226         *
227         * @return The JWE key selector, {@code null}.
228         */
229        public JWEKeySelector getJWEKeySelector() {
230                return jweKeySelector;
231        }
232
233
234        /**
235         * Gets the maximum acceptable clock skew for verifying the ID token
236         * timestamps.
237         *
238         * @return The maximum acceptable clock skew, in seconds. Zero
239         *         indicates none.
240         */
241        @Override
242        public int getMaxClockSkew() {
243
244                return maxClockSkew;
245        }
246
247
248        /**
249         * Sets the maximum acceptable clock skew for verifying the ID token
250         * timestamps.
251         *
252         * @param maxClockSkew The maximum acceptable clock skew, in seconds.
253         *                     Zero indicates none. Must not be negative.
254         */
255        @Override
256        public void setMaxClockSkew(final int maxClockSkew) {
257
258                this.maxClockSkew = maxClockSkew;
259        }
260
261
262        /**
263         * Validates the specified ID token.
264         *
265         * @param idToken       The ID token. Must not be {@code null}.
266         * @param expectedNonce The expected nonce, {@code null} if none.
267         *
268         * @return The claims set of the verified ID token.
269         *
270         * @throws BadJOSEException If the ID token is invalid or expired.
271         * @throws JOSEException    If an internal JOSE exception was
272         *                          encountered.
273         */
274        public IDTokenClaimsSet validate(final JWT idToken, final Nonce expectedNonce)
275                throws BadJOSEException, JOSEException {
276
277                if (idToken instanceof PlainJWT) {
278                        return validate((PlainJWT)idToken, expectedNonce);
279                } else if (idToken instanceof SignedJWT) {
280                        return validate((SignedJWT) idToken, expectedNonce);
281                } else if (idToken instanceof EncryptedJWT) {
282                        return validate((EncryptedJWT) idToken, expectedNonce);
283                } else {
284                        throw new JOSEException("Unexpected JWT type: " + idToken.getClass());
285                }
286        }
287
288
289        /**
290         * Verifies the specified unsecured (plain) ID token.
291         *
292         * @param idToken       The ID token. Must not be {@code null}.
293         * @param expectedNonce The expected nonce, {@code null} if none.
294         *
295         * @return The claims set of the verified ID token.
296         *
297         * @throws BadJOSEException If the ID token is invalid or expired.
298         * @throws JOSEException    If an internal JOSE exception was
299         *                          encountered.
300         */
301        private IDTokenClaimsSet validate(final PlainJWT idToken, final Nonce expectedNonce)
302                throws BadJOSEException, JOSEException {
303
304                if (jwsKeySelector != null) {
305                        throw new BadJWTException("Signed ID token expected");
306                }
307
308                JWTClaimsSet jwtClaimsSet;
309
310                try {
311                        jwtClaimsSet = idToken.getJWTClaimsSet();
312                } catch (java.text.ParseException e) {
313                        throw new BadJWTException(e.getMessage(), e);
314                }
315
316                JWTClaimsVerifier claimsVerifier = new IDTokenClaimsVerifier(expectedIssuer, clientID, expectedNonce, maxClockSkew);
317                claimsVerifier.verify(jwtClaimsSet);
318                return toIDTokenClaimsSet(jwtClaimsSet);
319        }
320
321
322        /**
323         * Verifies the specified signed ID token.
324         *
325         * @param idToken       The ID token. Must not be {@code null}.
326         * @param expectedNonce The expected nonce, {@code null} if none.
327         *
328         * @return The claims set of the verified ID token.
329         *
330         * @throws BadJOSEException If the ID token is invalid or expired.
331         * @throws JOSEException    If an internal JOSE exception was
332         *                          encountered.
333         */
334        private IDTokenClaimsSet validate(final SignedJWT idToken, final Nonce expectedNonce)
335                throws BadJOSEException, JOSEException {
336
337                if (jwsKeySelector == null) {
338                        throw new BadJWTException("Verification of signed JWTs not configured");
339                }
340
341                ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor();
342                jwtProcessor.setJWSKeySelector(jwsKeySelector);
343                jwtProcessor.setJWTClaimsVerifier(new IDTokenClaimsVerifier(expectedIssuer, clientID, expectedNonce, maxClockSkew));
344                JWTClaimsSet jwtClaimsSet = jwtProcessor.process(idToken, null);
345                return toIDTokenClaimsSet(jwtClaimsSet);
346        }
347
348
349        /**
350         * Verifies the specified signed and encrypted ID token.
351         *
352         * @param idToken       The ID token. Must not be {@code null}.
353         * @param expectedNonce The expected nonce, {@code null} if none.
354         *
355         * @return The claims set of the verified ID token.
356         *
357         * @throws BadJOSEException If the ID token is invalid or expired.
358         * @throws JOSEException    If an internal JOSE exception was
359         *                          encountered.
360         */
361        private IDTokenClaimsSet validate(final EncryptedJWT idToken, final Nonce expectedNonce)
362                throws BadJOSEException, JOSEException {
363
364                if (jweKeySelector == null) {
365                        throw new BadJWTException("Decryption of JWTs not configured");
366                }
367                if (jwsKeySelector == null) {
368                        throw new BadJWTException("Verification of signed JWTs not configured");
369                }
370
371                ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor();
372                jwtProcessor.setJWSKeySelector(jwsKeySelector);
373                jwtProcessor.setJWEKeySelector(jweKeySelector);
374                jwtProcessor.setJWTClaimsVerifier(new IDTokenClaimsVerifier(expectedIssuer, clientID, expectedNonce, maxClockSkew));
375
376                JWTClaimsSet jwtClaimsSet = jwtProcessor.process(idToken, null);
377
378                return toIDTokenClaimsSet(jwtClaimsSet);
379        }
380
381
382        /**
383         * Converts a JWT claims set to ID token claims set.
384         *
385         * @param jwtClaimsSet The JWT claims set. Must not be {@code null}.
386         *
387         * @return The ID token claims set.
388         *
389         * @throws JOSEException If conversion failed.
390         */
391        private static IDTokenClaimsSet toIDTokenClaimsSet(final JWTClaimsSet jwtClaimsSet)
392                throws JOSEException {
393
394                try {
395                        return new IDTokenClaimsSet(jwtClaimsSet);
396                } catch (ParseException e) {
397                        // Claims set must be verified at this point
398                        throw new JOSEException(e.getMessage(), e);
399                }
400        }
401
402
403        /**
404         * Creates a key selector for JWS verification.
405         *
406         * @param opMetadata The OpenID Provider metadata. Must not be
407         *                   {@code null}.
408         * @param clientInfo The Relying Party metadata. Must not be
409         *                   {@code null}.
410         *
411         * @return The JWS key selector.
412         *
413         * @throws GeneralException If the supplied OpenID Provider metadata or
414         *                          Relying Party metadata are missing a
415         *                          required parameter or inconsistent.
416         */
417        private static JWSKeySelector createJWSKeySelector(final OIDCProviderMetadata opMetadata,
418                                                           final OIDCClientInformation clientInfo)
419                throws GeneralException {
420
421                final Issuer expectedIssuer = opMetadata.getIssuer();
422                final ClientID clientID = clientInfo.getID();
423                final JWSAlgorithm expectedJWSAlg = clientInfo.getOIDCMetadata().getIDTokenJWSAlg();
424
425                if (opMetadata.getIDTokenJWSAlgs() == null) {
426                        throw new GeneralException("Missing OpenID Provider id_token_signing_alg_values_supported parameter");
427                }
428
429                if (! opMetadata.getIDTokenJWSAlgs().contains(expectedJWSAlg)) {
430                        throw new GeneralException("The OpenID Provider doesn't support " + expectedJWSAlg + " ID tokens");
431                }
432
433                if (Algorithm.NONE.equals(expectedJWSAlg)) {
434                        // Skip creation of JWS key selector, plain ID tokens expected
435                        return null;
436
437                } else if (JWSAlgorithm.Family.RSA.contains(expectedJWSAlg) || JWSAlgorithm.Family.EC.contains(expectedJWSAlg)) {
438
439                        JWKSource jwkSource;
440
441                        if (clientInfo.getOIDCMetadata().getJWKSet() != null) {
442                                // The JWK set is specified by value
443                                jwkSource = new ImmutableJWKSet(clientID, clientInfo.getOIDCMetadata().getJWKSet());
444                        } else if (clientInfo.getOIDCMetadata().getJWKSetURI() != null) {
445                                // The JWK set is specified by URL reference
446                                URL jwkSetURL;
447                                try {
448                                        jwkSetURL = clientInfo.getOIDCMetadata().getJWKSetURI().toURL();
449                                } catch (MalformedURLException e) {
450                                        throw new GeneralException("Invalid jwk set URI: " + e.getMessage(), e);
451                                }
452                                jwkSource = new RemoteJWKSet(clientID, jwkSetURL, null);
453                        } else {
454                                throw new GeneralException("Missing JWK set source");
455                        }
456
457                        return new JWSVerificationKeySelector(expectedIssuer, expectedJWSAlg, jwkSource);
458
459                } else if (JWSAlgorithm.Family.HMAC_SHA.contains(expectedJWSAlg)) {
460
461                        Secret clientSecret = clientInfo.getSecret();
462                        if (clientSecret == null) {
463                                throw new GeneralException("Missing client secret");
464                        }
465
466                        return new JWSVerificationKeySelector(expectedIssuer, expectedJWSAlg, new ImmutableClientSecret(clientID, clientSecret));
467
468                } else {
469                        throw new GeneralException("Unsupported JWS algorithm: " + expectedJWSAlg);
470                }
471        }
472
473
474        /**
475         * Creates a key selector for JWE decryption.
476         *
477         * @param opMetadata      The OpenID Provider metadata. Must not be
478         *                        {@code null}.
479         * @param clientInfo      The Relying Party metadata. Must not be
480         *                        {@code null}.
481         * @param clientJWKSource The client private JWK source, {@code null}
482         *                        if encrypted ID tokens are not expected.
483         *
484         * @return The JWE key selector.
485         *
486         * @throws GeneralException If the supplied OpenID Provider metadata or
487         *                          Relying Party metadata are missing a
488         *                          required parameter or inconsistent.
489         */
490        private static JWEKeySelector createJWEKeySelector(final OIDCProviderMetadata opMetadata,
491                                                           final OIDCClientInformation clientInfo,
492                                                           final JWKSource clientJWKSource)
493                throws GeneralException {
494
495                final JWEAlgorithm expectedJWEAlg = clientInfo.getOIDCMetadata().getIDTokenJWEAlg();
496                final EncryptionMethod expectedJWEEnc = clientInfo.getOIDCMetadata().getIDTokenJWEEnc();
497
498                if (expectedJWEAlg == null) {
499                        // Encrypted ID tokens not expected
500                        return null;
501                }
502
503                if (expectedJWEEnc == null) {
504                        throw new GeneralException("Missing required ID token JWE encryption method for " + expectedJWEAlg);
505                }
506
507                if (opMetadata.getIDTokenJWEAlgs() == null || ! opMetadata.getIDTokenJWEAlgs().contains(expectedJWEAlg)) {
508                        throw new GeneralException("The OpenID Provider doesn't support " + expectedJWEAlg + " ID tokens");
509                }
510
511                if (opMetadata.getIDTokenJWEEncs() == null || ! opMetadata.getIDTokenJWEEncs().contains(expectedJWEEnc)) {
512                        throw new GeneralException("The OpenID Provider doesn't support " + expectedJWEAlg + " / " + expectedJWEEnc + " ID tokens");
513                }
514
515                return new JWEDecryptionKeySelector(clientInfo.getID(), expectedJWEAlg, expectedJWEEnc, clientJWKSource);
516        }
517
518
519        /**
520         * Creates a new ID token validator for the specified OpenID Provider
521         * metadata and OpenID Relying Party registration.
522         *
523         * @param opMetadata      The OpenID Provider metadata. Must not be
524         *                        {@code null}.
525         * @param clientInfo      The OpenID Relying Party registration. Must
526         *                        not be {@code null}.
527         * @param clientJWKSource The client private JWK source, {@code null}
528         *                        if encrypted ID tokens are not expected.
529         *
530         * @return The ID token validator.
531         *
532         * @throws GeneralException If the supplied OpenID Provider metadata or
533         *                          Relying Party metadata are missing a
534         *                          required parameter or inconsistent.
535         */
536        public static IDTokenValidator create(final OIDCProviderMetadata opMetadata,
537                                              final OIDCClientInformation clientInfo,
538                                              final JWKSource clientJWKSource)
539                throws GeneralException {
540
541                // Create JWS key selector, unless id_token alg = none
542                final JWSKeySelector jwsKeySelector = createJWSKeySelector(opMetadata, clientInfo);
543
544                // Create JWE key selector if encrypted ID tokens are expected
545                final JWEKeySelector jweKeySelector = createJWEKeySelector(opMetadata, clientInfo, clientJWKSource);
546
547                return new IDTokenValidator(opMetadata.getIssuer(), clientInfo.getID(), jwsKeySelector, jweKeySelector);
548        }
549}