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 extends AbstractJWTValidator implements ClockSkewAware {
067
068
069        /**
070         * Creates a new validator for unsecured (plain) ID tokens.
071         *
072         * @param expectedIssuer The expected ID token issuer (OpenID
073         *                       Provider). Must not be {@code null}.
074         * @param clientID       The client ID. Must not be {@code null}.
075         */
076        public IDTokenValidator(final Issuer expectedIssuer,
077                                final ClientID clientID) {
078
079                this(expectedIssuer, clientID, (JWSKeySelector) null, null);
080        }
081
082
083        /**
084         * Creates a new validator for RSA or EC signed ID tokens where the
085         * OpenID Provider's JWK set is specified by value.
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         * @param expectedJWSAlg The expected RSA or EC JWS algorithm. Must not
091         *                       be {@code null}.
092         * @param jwkSet         The OpenID Provider JWK set. Must not be
093         *                       {@code null}.
094         */
095        public IDTokenValidator(final Issuer expectedIssuer,
096                                final ClientID clientID,
097                                final JWSAlgorithm expectedJWSAlg,
098                                final JWKSet jwkSet) {
099
100                this(expectedIssuer, clientID, new JWSVerificationKeySelector(expectedJWSAlg, new ImmutableJWKSet(jwkSet)),  null);
101        }
102
103
104        /**
105         * Creates a new validator for RSA or EC signed ID tokens where the
106         * OpenID Provider's JWK set is specified by URL.
107         *
108         * @param expectedIssuer The expected ID token issuer (OpenID
109         *                       Provider). Must not be {@code null}.
110         * @param clientID       The client ID. Must not be {@code null}.
111         * @param expectedJWSAlg The expected RSA or EC JWS algorithm. Must not
112         *                       be {@code null}.
113         * @param jwkSetURI      The OpenID Provider JWK set URL. Must not be
114         *                       {@code null}.
115         */
116        public IDTokenValidator(final Issuer expectedIssuer,
117                                final ClientID clientID,
118                                final JWSAlgorithm expectedJWSAlg,
119                                final URL jwkSetURI) {
120
121                this(expectedIssuer, clientID, expectedJWSAlg, jwkSetURI, null);
122        }
123
124
125        /**
126         * Creates a new validator for RSA or EC signed ID tokens where the
127         * OpenID Provider's JWK set is specified by URL. Permits setting of a
128         * specific resource retriever (HTTP client) for the JWK set.
129         *
130         * @param expectedIssuer    The expected ID token issuer (OpenID
131         *                          Provider). Must not be {@code null}.
132         * @param clientID          The client ID. Must not be {@code null}.
133         * @param expectedJWSAlg    The expected RSA or EC JWS algorithm. Must
134         *                          not be {@code null}.
135         * @param jwkSetURI         The OpenID Provider JWK set URL. Must not
136         *                          be {@code null}.
137         * @param resourceRetriever For retrieving the OpenID Connect Provider
138         *                          JWK set from the specified URL. If
139         *                          {@code null} the
140         *                          {@link com.nimbusds.jose.util.DefaultResourceRetriever
141         *                          default retriever} will be used, with
142         *                          preset HTTP connect timeout, HTTP read
143         *                          timeout and entity size limit.
144         */
145        public IDTokenValidator(final Issuer expectedIssuer,
146                                final ClientID clientID,
147                                final JWSAlgorithm expectedJWSAlg,
148                                final URL jwkSetURI,
149                                final ResourceRetriever resourceRetriever) {
150
151                this(expectedIssuer, clientID, new JWSVerificationKeySelector(expectedJWSAlg, new RemoteJWKSet(jwkSetURI, resourceRetriever)),  null);
152        }
153
154
155        /**
156         * Creates a new validator for HMAC protected ID tokens.
157         *
158         * @param expectedIssuer The expected ID token issuer (OpenID
159         *                       Provider). Must not be {@code null}.
160         * @param clientID       The client ID. Must not be {@code null}.
161         * @param expectedJWSAlg The expected HMAC JWS algorithm. Must not be
162         *                       {@code null}.
163         * @param clientSecret   The client secret. Must not be {@code null}.
164         */
165        public IDTokenValidator(final Issuer expectedIssuer,
166                                final ClientID clientID,
167                                final JWSAlgorithm expectedJWSAlg,
168                                final Secret clientSecret) {
169
170                this(expectedIssuer, clientID, new JWSVerificationKeySelector(expectedJWSAlg, new ImmutableSecret(clientSecret.getValueBytes())), null);
171        }
172
173
174        /**
175         * Creates a new ID token validator.
176         *
177         * @param expectedIssuer The expected ID token issuer (OpenID
178         *                       Provider). Must not be {@code null}.
179         * @param clientID       The client ID. Must not be {@code null}.
180         * @param jwsKeySelector The key selector for JWS verification,
181         *                       {@code null} if unsecured (plain) ID tokens
182         *                       are expected.
183         * @param jweKeySelector The key selector for JWE decryption,
184         *                       {@code null} if encrypted ID tokens are not
185         *                       expected.
186         */
187        public IDTokenValidator(final Issuer expectedIssuer,
188                                final ClientID clientID,
189                                final JWSKeySelector jwsKeySelector,
190                                final JWEKeySelector jweKeySelector) {
191                
192                super(expectedIssuer, clientID, jwsKeySelector, jweKeySelector);
193        }
194
195
196        /**
197         * Validates the specified ID token.
198         *
199         * @param idToken       The ID token. Must not be {@code null}.
200         * @param expectedNonce The expected nonce, {@code null} if none.
201         *
202         * @return The claims set of the verified ID token.
203         *
204         * @throws BadJOSEException If the ID token is invalid or expired.
205         * @throws JOSEException    If an internal JOSE exception was
206         *                          encountered.
207         */
208        public IDTokenClaimsSet validate(final JWT idToken, final Nonce expectedNonce)
209                throws BadJOSEException, JOSEException {
210
211                if (idToken instanceof PlainJWT) {
212                        return validate((PlainJWT)idToken, expectedNonce);
213                } else if (idToken instanceof SignedJWT) {
214                        return validate((SignedJWT) idToken, expectedNonce);
215                } else if (idToken instanceof EncryptedJWT) {
216                        return validate((EncryptedJWT) idToken, expectedNonce);
217                } else {
218                        throw new JOSEException("Unexpected JWT type: " + idToken.getClass());
219                }
220        }
221
222
223        /**
224         * Verifies the specified unsecured (plain) ID token.
225         *
226         * @param idToken       The ID token. Must not be {@code null}.
227         * @param expectedNonce The expected nonce, {@code null} if none.
228         *
229         * @return The claims set of the verified ID token.
230         *
231         * @throws BadJOSEException If the ID token is invalid or expired.
232         * @throws JOSEException    If an internal JOSE exception was
233         *                          encountered.
234         */
235        private IDTokenClaimsSet validate(final PlainJWT idToken, final Nonce expectedNonce)
236                throws BadJOSEException, JOSEException {
237
238                if (getJWSKeySelector() != null) {
239                        throw new BadJWTException("Signed ID token expected");
240                }
241
242                JWTClaimsSet jwtClaimsSet;
243
244                try {
245                        jwtClaimsSet = idToken.getJWTClaimsSet();
246                } catch (java.text.ParseException e) {
247                        throw new BadJWTException(e.getMessage(), e);
248                }
249
250                JWTClaimsSetVerifier<?> claimsVerifier = new IDTokenClaimsVerifier(getExpectedIssuer(), getClientID(), expectedNonce, getMaxClockSkew());
251                claimsVerifier.verify(jwtClaimsSet, null);
252                return toIDTokenClaimsSet(jwtClaimsSet);
253        }
254
255
256        /**
257         * Verifies the specified signed ID token.
258         *
259         * @param idToken       The ID token. Must not be {@code null}.
260         * @param expectedNonce The expected nonce, {@code null} if none.
261         *
262         * @return The claims set of the verified ID token.
263         *
264         * @throws BadJOSEException If the ID token is invalid or expired.
265         * @throws JOSEException    If an internal JOSE exception was
266         *                          encountered.
267         */
268        private IDTokenClaimsSet validate(final SignedJWT idToken, final Nonce expectedNonce)
269                throws BadJOSEException, JOSEException {
270
271                if (getJWSKeySelector() == null) {
272                        throw new BadJWTException("Verification of signed JWTs not configured");
273                }
274
275                ConfigurableJWTProcessor<?> jwtProcessor = new DefaultJWTProcessor();
276                jwtProcessor.setJWSKeySelector(getJWSKeySelector());
277                jwtProcessor.setJWTClaimsSetVerifier(new IDTokenClaimsVerifier(getExpectedIssuer(), getClientID(), expectedNonce, getMaxClockSkew()));
278                JWTClaimsSet jwtClaimsSet = jwtProcessor.process(idToken, null);
279                return toIDTokenClaimsSet(jwtClaimsSet);
280        }
281
282
283        /**
284         * Verifies the specified signed and encrypted ID token.
285         *
286         * @param idToken       The ID token. Must not be {@code null}.
287         * @param expectedNonce The expected nonce, {@code null} if none.
288         *
289         * @return The claims set of the verified ID token.
290         *
291         * @throws BadJOSEException If the ID token is invalid or expired.
292         * @throws JOSEException    If an internal JOSE exception was
293         *                          encountered.
294         */
295        private IDTokenClaimsSet validate(final EncryptedJWT idToken, final Nonce expectedNonce)
296                throws BadJOSEException, JOSEException {
297
298                if (getJWEKeySelector() == null) {
299                        throw new BadJWTException("Decryption of JWTs not configured");
300                }
301                if (getJWSKeySelector() == null) {
302                        throw new BadJWTException("Verification of signed JWTs not configured");
303                }
304
305                ConfigurableJWTProcessor<?> jwtProcessor = new DefaultJWTProcessor();
306                jwtProcessor.setJWSKeySelector(getJWSKeySelector());
307                jwtProcessor.setJWEKeySelector(getJWEKeySelector());
308                jwtProcessor.setJWTClaimsSetVerifier(new IDTokenClaimsVerifier(getExpectedIssuer(), getClientID(), expectedNonce, getMaxClockSkew()));
309
310                JWTClaimsSet jwtClaimsSet = jwtProcessor.process(idToken, null);
311
312                return toIDTokenClaimsSet(jwtClaimsSet);
313        }
314
315
316        /**
317         * Converts a JWT claims set to an ID token claims set.
318         *
319         * @param jwtClaimsSet The JWT claims set. Must not be {@code null}.
320         *
321         * @return The ID token claims set.
322         *
323         * @throws JOSEException If conversion failed.
324         */
325        private static IDTokenClaimsSet toIDTokenClaimsSet(final JWTClaimsSet jwtClaimsSet)
326                throws JOSEException {
327
328                try {
329                        return new IDTokenClaimsSet(jwtClaimsSet);
330                } catch (ParseException e) {
331                        // Claims set must be verified at this point
332                        throw new JOSEException(e.getMessage(), e);
333                }
334        }
335
336
337        /**
338         * Creates a key selector for JWS verification.
339         *
340         * @param opMetadata The OpenID Provider metadata. Must not be
341         *                   {@code null}.
342         * @param clientInfo The Relying Party metadata. Must not be
343         *                   {@code null}.
344         *
345         * @return The JWS key selector.
346         *
347         * @throws GeneralException If the supplied OpenID Provider metadata or
348         *                          Relying Party metadata are missing a
349         *                          required parameter or inconsistent.
350         */
351        protected static JWSKeySelector createJWSKeySelector(final OIDCProviderMetadata opMetadata,
352                                                             final OIDCClientInformation clientInfo)
353                throws GeneralException {
354
355                final JWSAlgorithm expectedJWSAlg = clientInfo.getOIDCMetadata().getIDTokenJWSAlg();
356
357                if (opMetadata.getIDTokenJWSAlgs() == null) {
358                        throw new GeneralException("Missing OpenID Provider id_token_signing_alg_values_supported parameter");
359                }
360
361                if (! opMetadata.getIDTokenJWSAlgs().contains(expectedJWSAlg)) {
362                        throw new GeneralException("The OpenID Provider doesn't support " + expectedJWSAlg + " ID tokens");
363                }
364
365                if (Algorithm.NONE.equals(expectedJWSAlg)) {
366                        // Skip creation of JWS key selector, plain ID tokens expected
367                        return null;
368
369                } else if (JWSAlgorithm.Family.RSA.contains(expectedJWSAlg) || JWSAlgorithm.Family.EC.contains(expectedJWSAlg)) {
370
371                        URL jwkSetURL;
372                        try {
373                                jwkSetURL = opMetadata.getJWKSetURI().toURL();
374                        } catch (MalformedURLException e) {
375                                throw new GeneralException("Invalid jwk set URI: " + e.getMessage(), e);
376                        }
377                        JWKSource jwkSource = new RemoteJWKSet(jwkSetURL); // TODO specify HTTP response limits
378
379                        return new JWSVerificationKeySelector(expectedJWSAlg, jwkSource);
380
381                } else if (JWSAlgorithm.Family.HMAC_SHA.contains(expectedJWSAlg)) {
382
383                        Secret clientSecret = clientInfo.getSecret();
384                        if (clientSecret == null) {
385                                throw new GeneralException("Missing client secret");
386                        }
387                        return new JWSVerificationKeySelector(expectedJWSAlg, new ImmutableSecret(clientSecret.getValueBytes()));
388
389                } else {
390                        throw new GeneralException("Unsupported JWS algorithm: " + expectedJWSAlg);
391                }
392        }
393
394
395        /**
396         * Creates a key selector for JWE decryption.
397         *
398         * @param opMetadata      The OpenID Provider metadata. Must not be
399         *                        {@code null}.
400         * @param clientInfo      The Relying Party metadata. Must not be
401         *                        {@code null}.
402         * @param clientJWKSource The client private JWK source, {@code null}
403         *                        if encrypted ID tokens are not expected.
404         *
405         * @return The JWE key selector.
406         *
407         * @throws GeneralException If the supplied OpenID Provider metadata or
408         *                          Relying Party metadata are missing a
409         *                          required parameter or inconsistent.
410         */
411        protected static JWEKeySelector createJWEKeySelector(final OIDCProviderMetadata opMetadata,
412                                                             final OIDCClientInformation clientInfo,
413                                                             final JWKSource clientJWKSource)
414                throws GeneralException {
415
416                final JWEAlgorithm expectedJWEAlg = clientInfo.getOIDCMetadata().getIDTokenJWEAlg();
417                final EncryptionMethod expectedJWEEnc = clientInfo.getOIDCMetadata().getIDTokenJWEEnc();
418
419                if (expectedJWEAlg == null) {
420                        // Encrypted ID tokens not expected
421                        return null;
422                }
423
424                if (expectedJWEEnc == null) {
425                        throw new GeneralException("Missing required ID token JWE encryption method for " + expectedJWEAlg);
426                }
427
428                if (opMetadata.getIDTokenJWEAlgs() == null || ! opMetadata.getIDTokenJWEAlgs().contains(expectedJWEAlg)) {
429                        throw new GeneralException("The OpenID Provider doesn't support " + expectedJWEAlg + " ID tokens");
430                }
431
432                if (opMetadata.getIDTokenJWEEncs() == null || ! opMetadata.getIDTokenJWEEncs().contains(expectedJWEEnc)) {
433                        throw new GeneralException("The OpenID Provider doesn't support " + expectedJWEAlg + " / " + expectedJWEEnc + " ID tokens");
434                }
435
436                return new JWEDecryptionKeySelector(expectedJWEAlg, expectedJWEEnc, clientJWKSource);
437        }
438
439
440        /**
441         * Creates a new ID token validator for the specified OpenID Provider
442         * metadata and OpenID Relying Party registration.
443         *
444         * @param opMetadata      The OpenID Provider metadata. Must not be
445         *                        {@code null}.
446         * @param clientInfo      The OpenID Relying Party registration. Must
447         *                        not be {@code null}.
448         * @param clientJWKSource The client private JWK source, {@code null}
449         *                        if encrypted ID tokens are not expected.
450         *
451         * @return The ID token validator.
452         *
453         * @throws GeneralException If the supplied OpenID Provider metadata or
454         *                          Relying Party metadata are missing a
455         *                          required parameter or inconsistent.
456         */
457        public static IDTokenValidator create(final OIDCProviderMetadata opMetadata,
458                                              final OIDCClientInformation clientInfo,
459                                              final JWKSource clientJWKSource)
460                throws GeneralException {
461
462                // Create JWS key selector, unless id_token alg = none
463                final JWSKeySelector jwsKeySelector = createJWSKeySelector(opMetadata, clientInfo);
464
465                // Create JWE key selector if encrypted ID tokens are expected
466                final JWEKeySelector jweKeySelector = createJWEKeySelector(opMetadata, clientInfo, clientJWKSource);
467
468                return new IDTokenValidator(opMetadata.getIssuer(), clientInfo.getID(), jwsKeySelector, jweKeySelector);
469        }
470}