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