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