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