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.claims;
019
020
021import java.util.*;
022
023import com.nimbusds.jose.jwk.JWK;
024import com.nimbusds.jwt.JWTClaimsSet;
025import com.nimbusds.oauth2.sdk.ParseException;
026import com.nimbusds.oauth2.sdk.ResponseType;
027import com.nimbusds.oauth2.sdk.id.Audience;
028import com.nimbusds.oauth2.sdk.id.Issuer;
029import com.nimbusds.oauth2.sdk.id.Subject;
030import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
031import com.nimbusds.openid.connect.sdk.Nonce;
032import net.minidev.json.JSONArray;
033import net.minidev.json.JSONObject;
034
035
036/**
037 * ID token claims set, serialisable to a JSON object.
038 *
039 * <p>Example ID token claims set:
040 *
041 * <pre>
042 * {
043 *   "iss"       : "https://server.example.com",
044 *   "sub"       : "24400320",
045 *   "aud"       : "s6BhdRkqt3",
046 *   "nonce"     : "n-0S6_WzA2Mj",
047 *   "exp"       : 1311281970,
048 *   "iat"       : 1311280970,
049 *   "auth_time" : 1311280969,
050 *   "acr"       : "urn:mace:incommon:iap:silver",
051 *   "at_hash"   : "MTIzNDU2Nzg5MDEyMzQ1Ng"
052 * }
053 * </pre>
054 *
055 * <p>Related specifications:
056 *
057 * <ul>
058 *     <li>OpenID Connect Core 1.0, section 2.
059 *     <li>OpenID Connect Front-Channel Logout 1.0, section 3 (draft 02).
060 *     <li>Financial Services – Financial API - Part 2: Read and Write API
061 *         Security Profile, section 5.1.
062 * </ul>
063 */
064public class IDTokenClaimsSet extends CommonClaimsSet {
065
066
067        /**
068         * The expiration time claim name.
069         */
070        public static final String EXP_CLAIM_NAME = "exp";
071
072
073        /**
074         * The subject authentication time claim name.
075         */
076        public static final String AUTH_TIME_CLAIM_NAME = "auth_time";
077
078
079        /**
080         * The nonce claim name.
081         */
082        public static final String NONCE_CLAIM_NAME = "nonce";
083
084
085        /**
086         * The access token hash claim name.
087         */
088        public static final String AT_HASH_CLAIM_NAME = "at_hash";
089
090
091        /**
092         * The authorisation code hash claim name.
093         */
094        public static final String C_HASH_CLAIM_NAME = "c_hash";
095        
096        
097        /**
098         * The state hash claim name.
099         */
100        public static final String S_HASH_CLAIM_NAME = "s_hash";
101
102
103        /**
104         * The ACR claim name.
105         */
106        public static final String ACR_CLAIM_NAME = "acr";
107
108
109        /**
110         * The AMRs claim name.
111         */
112        public static final String AMR_CLAIM_NAME = "amr";
113
114
115        /**
116         * The authorised party claim name.
117         */
118        public static final String AZP_CLAIM_NAME = "azp";
119
120
121        /**
122         * The subject JWK claim name.
123         */
124        public static final String SUB_JWK_CLAIM_NAME = "sub_jwk";
125
126
127        /**
128         * The names of the standard top-level ID token claims.
129         */
130        private static final Set<String> stdClaimNames = new LinkedHashSet<>();
131
132
133        static {
134                stdClaimNames.add(ISS_CLAIM_NAME);
135                stdClaimNames.add(SUB_CLAIM_NAME);
136                stdClaimNames.add(AUD_CLAIM_NAME);
137                stdClaimNames.add(EXP_CLAIM_NAME);
138                stdClaimNames.add(IAT_CLAIM_NAME);
139                stdClaimNames.add(AUTH_TIME_CLAIM_NAME);
140                stdClaimNames.add(NONCE_CLAIM_NAME);
141                stdClaimNames.add(AT_HASH_CLAIM_NAME);
142                stdClaimNames.add(C_HASH_CLAIM_NAME);
143                stdClaimNames.add(S_HASH_CLAIM_NAME);
144                stdClaimNames.add(ACR_CLAIM_NAME);
145                stdClaimNames.add(AMR_CLAIM_NAME);
146                stdClaimNames.add(AZP_CLAIM_NAME);
147                stdClaimNames.add(SUB_JWK_CLAIM_NAME);
148                stdClaimNames.add(SID_CLAIM_NAME);
149        }
150
151
152        /**
153         * Gets the names of the standard top-level ID token claims.
154         *
155         * @return The names of the standard top-level ID token claims
156         *         (read-only set).
157         */
158        public static Set<String> getStandardClaimNames() {
159
160                return Collections.unmodifiableSet(stdClaimNames);
161        }
162
163
164        /**
165         * Creates a new minimal ID token claims set. Note that the ID token
166         * may require additional claims to be present depending on the
167         * original OpenID Connect authorisation request.
168         *
169         * @param iss The issuer. Must not be {@code null}.
170         * @param sub The subject. Must not be {@code null}.
171         * @param aud The audience. Must not be {@code null}.
172         * @param exp The expiration time. Must not be {@code null}.
173         * @param iat The issue time. Must not be {@code null}.
174         */
175        public IDTokenClaimsSet(final Issuer iss,
176                                final Subject sub,
177                                final List<Audience> aud,
178                                final Date exp,
179                                final Date iat) {
180
181                setClaim(ISS_CLAIM_NAME, iss.getValue());
182                setClaim(SUB_CLAIM_NAME, sub.getValue());
183
184                JSONArray audList = new JSONArray();
185
186                for (Audience a: aud)
187                        audList.add(a.getValue());
188
189                setClaim(AUD_CLAIM_NAME, audList);
190
191                setDateClaim(EXP_CLAIM_NAME, exp);
192                setDateClaim(IAT_CLAIM_NAME, iat);
193        }
194
195
196        /**
197         * Creates a new ID token claims set from the specified JSON object.
198         *
199         * @param jsonObject The JSON object. Must be verified to represent a
200         *                   valid ID token claims set and not be {@code null}.
201         *
202         * @throws ParseException If the JSON object doesn't represent a valid
203         *                        ID token claims set.
204         */
205        private IDTokenClaimsSet(final JSONObject jsonObject)
206                throws ParseException {
207
208                super(jsonObject);
209
210                if (getStringClaim(ISS_CLAIM_NAME) == null)
211                        throw new ParseException("Missing or invalid \"iss\" claim");
212
213                if (getStringClaim(SUB_CLAIM_NAME) == null)
214                        throw new ParseException("Missing or invalid \"sub\" claim");
215
216                if (getStringClaim(AUD_CLAIM_NAME) == null && getStringListClaim(AUD_CLAIM_NAME) == null ||
217                    getStringListClaim(AUD_CLAIM_NAME) != null && getStringListClaim(AUD_CLAIM_NAME).isEmpty())
218                        throw new ParseException("Missing or invalid \"aud\" claim");
219
220                if (getDateClaim(EXP_CLAIM_NAME) == null)
221                        throw new ParseException("Missing or invalid \"exp\" claim");
222
223                if (getDateClaim(IAT_CLAIM_NAME) == null)
224                        throw new ParseException("Missing or invalid \"iat\" claim");
225        }
226
227
228        /**
229         * Creates a new ID token claims set from the specified JSON Web Token
230         * (JWT) claims set.
231         *
232         * @param jwtClaimsSet The JWT claims set. Must not be {@code null}.
233         *
234         * @throws ParseException If the JWT claims set doesn't represent a
235         *                        valid ID token claims set.
236         */
237        public IDTokenClaimsSet(final JWTClaimsSet jwtClaimsSet)
238                throws ParseException {
239
240                this(jwtClaimsSet.toJSONObject());
241        }
242
243
244        /**
245         * Checks if this ID token claims set contains all required claims for
246         * the specified OpenID Connect response type.
247         *
248         * @param responseType     The OpenID Connect response type. Must not
249         *                         be {@code null}.
250         * @param iatAuthzEndpoint Specifies the endpoint where the ID token
251         *                         was issued (required for hybrid flow).
252         *                         {@code true} if the ID token was issued at
253         *                         the authorisation endpoint, {@code false} if
254         *                         the ID token was issued at the token
255         *                         endpoint.
256         *
257         * @return {@code true} if the required claims are contained, else
258         *         {@code false}.
259         */
260        public boolean hasRequiredClaims(final ResponseType responseType, final boolean iatAuthzEndpoint) {
261
262                // Code flow
263                // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
264                if (new ResponseType("code").equals(responseType)) {
265                        // nonce, c_hash and at_hash not required
266                        return true; // ok
267                }
268
269                // Implicit flow
270                // See http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken
271                if (new ResponseType("id_token").equals(responseType)) {
272
273                        return getNonce() != null;
274
275                }
276
277                if (new ResponseType("id_token", "token").equals(responseType)) {
278
279                        if (getNonce() == null) {
280                                // nonce required
281                                return false;
282                        }
283
284                        return getAccessTokenHash() != null;
285                }
286
287                // Hybrid flow
288                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken
289                if (new ResponseType("code", "id_token").equals(responseType)) {
290
291                        if (getNonce() == null) {
292                                // nonce required
293                                return false;
294                        }
295
296                        if (! iatAuthzEndpoint) {
297                                // c_hash and at_hash not required when id_token issued at token endpoint
298                                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
299                                return true;
300                        }
301
302                        return getCodeHash() != null;
303
304                }
305
306                if (new ResponseType("code", "token").equals(responseType)) {
307
308                        if (getNonce() == null) {
309                                // nonce required
310                                return false;
311                        }
312
313                        if (! iatAuthzEndpoint) {
314                                // c_hash and at_hash not required when id_token issued at token endpoint
315                                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
316                                return true;
317                        }
318
319                        return true; // ok
320                }
321
322                if (new ResponseType("code", "id_token", "token").equals(responseType)) {
323
324                        if (getNonce() == null) {
325                                // nonce required
326                                return false;
327                        }
328
329                        if (! iatAuthzEndpoint) {
330                                // c_hash and at_hash not required when id_token issued at token endpoint
331                                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
332                                return true;
333                        }
334
335                        if (getAccessTokenHash() == null) {
336                                // at_hash required when issued at authz endpoint
337                                return false;
338                        }
339
340                        return getCodeHash() != null;
341
342                }
343
344                throw new IllegalArgumentException("Unsupported response_type: " + responseType);
345        }
346
347
348        /**
349         * Use {@link #hasRequiredClaims(ResponseType, boolean)} instead.
350         *
351         * @param responseType The OpenID Connect response type. Must not be
352         *                     {@code null}.
353         *
354         * @return {@code true} if the required claims are contained, else
355         *         {@code false}.
356         */
357        @Deprecated
358        public boolean hasRequiredClaims(final ResponseType responseType) {
359
360                return hasRequiredClaims(responseType, true);
361        }
362
363
364        /**
365         * Gets the ID token expiration time. Corresponds to the {@code exp}
366         * claim.
367         *
368         * @return The expiration time.
369         */
370        public Date getExpirationTime() {
371
372                return getDateClaim(EXP_CLAIM_NAME);
373        }
374
375
376        /**
377         * Gets the subject authentication time. Corresponds to the
378         * {@code auth_time} claim.
379         *
380         * @return The authentication time, {@code null} if not specified or
381         *         parsing failed.
382         */
383        public Date getAuthenticationTime() {
384
385                return getDateClaim(AUTH_TIME_CLAIM_NAME);
386        }
387
388
389        /**
390         * Sets the subject authentication time. Corresponds to the
391         * {@code auth_time} claim.
392         *
393         * @param authTime The authentication time, {@code null} if not
394         *                 specified.
395         */
396        public void setAuthenticationTime(final Date authTime) {
397
398                setDateClaim(AUTH_TIME_CLAIM_NAME, authTime);
399        }
400
401
402        /**
403         * Gets the ID token nonce. Corresponds to the {@code nonce} claim.
404         *
405         * @return The nonce, {@code null} if not specified or parsing failed.
406         */
407        public Nonce getNonce() {
408
409                String value = getStringClaim(NONCE_CLAIM_NAME);
410                return value != null ? new Nonce(value) : null;
411        }
412
413
414        /**
415         * Sets the ID token nonce. Corresponds to the {@code nonce} claim.
416         *
417         * @param nonce The nonce, {@code null} if not specified.
418         */
419        public void setNonce(final Nonce nonce) {
420
421                setClaim(NONCE_CLAIM_NAME, nonce != null ? nonce.getValue() : null);
422        }
423
424
425        /**
426         * Gets the access token hash. Corresponds to the {@code at_hash}
427         * claim.
428         *
429         * @return The access token hash, {@code null} if not specified or
430         *         parsing failed.
431         */
432        public AccessTokenHash getAccessTokenHash() {
433
434                String value = getStringClaim(AT_HASH_CLAIM_NAME);
435                return value != null ? new AccessTokenHash(value) : null;
436        }
437
438
439        /**
440         * Sets the access token hash. Corresponds to the {@code at_hash}
441         * claim.
442         *
443         * @param atHash The access token hash, {@code null} if not specified.
444         */
445        public void setAccessTokenHash(final AccessTokenHash atHash) {
446
447                setClaim(AT_HASH_CLAIM_NAME, atHash != null ? atHash.getValue() : null);
448        }
449
450
451        /**
452         * Gets the authorisation code hash. Corresponds to the {@code c_hash}
453         * claim.
454         *
455         * @return The authorisation code hash, {@code null} if not specified
456         *         or parsing failed.
457         */
458        public CodeHash getCodeHash() {
459
460                String value = getStringClaim(C_HASH_CLAIM_NAME);
461                return value != null ? new CodeHash(value) : null;
462        }
463
464
465        /**
466         * Sets the authorisation code hash. Corresponds to the {@code c_hash}
467         * claim.
468         *
469         * @param cHash The authorisation code hash, {@code null} if not
470         *              specified.
471         */
472        public void setCodeHash(final CodeHash cHash) {
473
474                setClaim(C_HASH_CLAIM_NAME, cHash != null ? cHash.getValue() : null);
475        }
476        
477        
478        /**
479         * Gets the state hash. Corresponds to the {@code s_hash} claim.
480         *
481         * @return The state hash, {@code null} if not specified or parsing
482         *         failed.
483         */
484        public StateHash getStateHash() {
485                
486                String value = getStringClaim(S_HASH_CLAIM_NAME);
487                return value != null ? new StateHash(value) : null;
488        }
489        
490        
491        /**
492         * Sets the state hash. Corresponds to the {@code s_hash} claim.
493         *
494         * @param sHash The state hash, {@code null} if not specified.
495         */
496        public void setStateHash(final StateHash sHash) {
497                
498                setClaim(S_HASH_CLAIM_NAME, sHash != null ? sHash.getValue() : null);
499        }
500
501
502        /**
503         * Gets the Authentication Context Class Reference (ACR). Corresponds
504         * to the {@code acr} claim.
505         *
506         * @return The Authentication Context Class Reference (ACR),
507         *         {@code null} if not specified or parsing failed.
508         */
509        public ACR getACR() {
510
511                String value = getStringClaim(ACR_CLAIM_NAME);
512                return value != null ? new ACR(value) : null;
513        }
514
515
516        /**
517         * Sets the Authentication Context Class Reference (ACR). Corresponds
518         * to the {@code acr} claim.
519         *
520         * @param acr The Authentication Context Class Reference (ACR),
521         *            {@code null} if not specified.
522         */
523        public void setACR(final ACR acr) {
524
525                setClaim(ACR_CLAIM_NAME, acr != null ? acr.getValue() : null);
526        }
527
528
529        /**
530         * Gets the Authentication Methods References (AMRs). Corresponds to
531         * the {@code amr} claim.
532         *
533         * @return The Authentication Methods Reference (AMR) list,
534         *         {@code null} if not specified or parsing failed.
535         */
536        public List<AMR> getAMR() {
537
538                List<String> rawList = getStringListClaim(AMR_CLAIM_NAME);
539
540                if (rawList == null || rawList.isEmpty())
541                        return null;
542
543                List<AMR> amrList = new ArrayList<>(rawList.size());
544
545                for (String s: rawList)
546                        amrList.add(new AMR(s));
547
548                return amrList;
549        }
550
551
552        /**
553         * Sets the Authentication Methods References (AMRs). Corresponds to
554         * the {@code amr} claim.
555         *
556         * @param amr The Authentication Methods Reference (AMR) list,
557         *            {@code null} if not specified.
558         */
559        public void setAMR(final List<AMR> amr) {
560
561                if (amr != null) {
562
563                        List<String> amrList = new ArrayList<>(amr.size());
564
565                        for (AMR a: amr)
566                                amrList.add(a.getValue());
567
568                        setClaim(AMR_CLAIM_NAME, amrList);
569
570                } else {
571                        setClaim(AMR_CLAIM_NAME, null);
572                }
573        }
574
575
576        /**
577         * Gets the authorised party for the ID token. Corresponds to the
578         * {@code azp} claim.
579         *
580         * @return The authorised party, {@code null} if not specified or
581         *         parsing failed.
582         */
583        public AuthorizedParty getAuthorizedParty() {
584
585                String value = getStringClaim(AZP_CLAIM_NAME);
586                return value != null ? new AuthorizedParty(value) : null;
587        }
588
589
590        /**
591         * Sets the authorised party for the ID token. Corresponds to the
592         * {@code azp} claim.
593         *
594         * @param azp The authorised party, {@code null} if not specified.
595         */
596        public void setAuthorizedParty(final AuthorizedParty azp) {
597
598                setClaim(AZP_CLAIM_NAME, azp != null ? azp.getValue() : null);
599        }
600
601
602        /**
603         * Gets the subject's JSON Web Key (JWK) for a self-issued OpenID
604         * Connect provider. Corresponds to the {@code sub_jwk} claim.
605         *
606         * @return The subject's JWK, {@code null} if not specified or parsing
607         *         failed.
608         */
609        public JWK getSubjectJWK() {
610
611                JSONObject jsonObject = getClaim(SUB_JWK_CLAIM_NAME, JSONObject.class);
612
613                if (jsonObject == null)
614                        return null;
615
616                try {
617                        return JWK.parse(jsonObject);
618
619                } catch (java.text.ParseException e) {
620
621                        return null;
622                }
623        }
624
625
626        /**
627         * Sets the subject's JSON Web Key (JWK) for a self-issued OpenID
628         * Connect provider. Corresponds to the {@code sub_jwk} claim.
629         *
630         * @param subJWK The subject's JWK (must be public), {@code null} if
631         *               not specified.
632         */
633        public void setSubjectJWK(final JWK subJWK) {
634
635                if (subJWK != null) {
636
637                        if (subJWK.isPrivate())
638                                throw new IllegalArgumentException("The subject's JSON Web Key (JWK) must be public");
639
640                        setClaim(SUB_JWK_CLAIM_NAME, subJWK.toJSONObject());
641
642                } else {
643                        setClaim(SUB_JWK_CLAIM_NAME, null);
644                }
645        }
646
647
648        /**
649         * Parses an ID token claims set from the specified JSON object string.
650         *
651         * @param json The JSON object string to parse. Must not be
652         *             {@code null}.
653         *
654         * @return The ID token claims set.
655         *
656         * @throws ParseException If parsing failed.
657         */
658        public static IDTokenClaimsSet parse(final String json)
659                throws ParseException {
660
661                JSONObject jsonObject = JSONObjectUtils.parse(json);
662
663                try {
664                        return new IDTokenClaimsSet(jsonObject);
665
666                } catch (IllegalArgumentException e) {
667
668                        throw new ParseException(e.getMessage(), e);
669                }
670        }
671}