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