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                if (aud.isEmpty()) {
183                        throw new IllegalArgumentException("The aud must not be empty");
184                }
185
186                JSONArray audList = new JSONArray();
187
188                for (Audience a: aud)
189                        audList.add(a.getValue());
190
191                setClaim(AUD_CLAIM_NAME, audList);
192
193                if (exp.before(iat) || exp.equals(iat)) {
194                        throw new IllegalArgumentException("The exp must be after iat");
195                }
196
197                setDateClaim(EXP_CLAIM_NAME, Objects.requireNonNull(exp));
198                setDateClaim(IAT_CLAIM_NAME, Objects.requireNonNull(iat));
199        }
200
201
202        /**
203         * Creates a new ID token claims set from the specified JSON object.
204         *
205         * @param jsonObject The JSON object. Must be verified to represent a
206         *                   valid ID token claims set and not be {@code null}.
207         *
208         * @throws ParseException If the JSON object doesn't represent a valid
209         *                        ID token claims set.
210         */
211        private IDTokenClaimsSet(final JSONObject jsonObject)
212                throws ParseException {
213
214                super(jsonObject);
215
216                if (getStringClaim(ISS_CLAIM_NAME) == null)
217                        throw new ParseException("Missing or invalid iss claim");
218
219                if (getStringClaim(SUB_CLAIM_NAME) == null)
220                        throw new ParseException("Missing or invalid sub claim");
221
222                if (getStringClaim(AUD_CLAIM_NAME) == null && getStringListClaim(AUD_CLAIM_NAME) == null ||
223                    getStringListClaim(AUD_CLAIM_NAME) != null && getStringListClaim(AUD_CLAIM_NAME).isEmpty())
224                        throw new ParseException("Missing or invalid aud claim");
225
226                if (getDateClaim(EXP_CLAIM_NAME) == null)
227                        throw new ParseException("Missing or invalid exp claim");
228
229                if (getDateClaim(IAT_CLAIM_NAME) == null)
230                        throw new ParseException("Missing or invalid iat claim");
231        }
232
233
234        /**
235         * Creates a new ID token claims set from the specified JSON Web Token
236         * (JWT) claims set.
237         *
238         * @param jwtClaimsSet The JWT claims set. Must not be {@code null}.
239         *
240         * @throws ParseException If the JWT claims set doesn't represent a
241         *                        valid ID token claims set.
242         */
243        public IDTokenClaimsSet(final JWTClaimsSet jwtClaimsSet)
244                throws ParseException {
245
246                this(JSONObjectUtils.toJSONObject(jwtClaimsSet));
247        }
248
249
250        /**
251         * Checks if this ID token claims set contains all required claims for
252         * the specified OpenID Connect response type.
253         *
254         * @param responseType     The OpenID Connect response type. Must not
255         *                         be {@code null}.
256         * @param iatAuthzEndpoint Specifies the endpoint where the ID token
257         *                         was issued (required for hybrid flow).
258         *                         {@code true} if the ID token was issued at
259         *                         the authorisation endpoint, {@code false} if
260         *                         the ID token was issued at the token
261         *                         endpoint.
262         *
263         * @return {@code true} if the required claims are contained, else
264         *         {@code false}.
265         */
266        public boolean hasRequiredClaims(final ResponseType responseType, final boolean iatAuthzEndpoint) {
267
268                // Code flow
269                // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
270                if (ResponseType.CODE.equals(responseType)) {
271                        // nonce, c_hash and at_hash not required
272                        return true; // ok
273                }
274
275                // Implicit flow
276                // See http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken
277                if (ResponseType.IDTOKEN.equals(responseType)) {
278
279                        return getNonce() != null;
280
281                }
282
283                if (ResponseType.IDTOKEN_TOKEN.equals(responseType)) {
284
285                        if (getNonce() == null) {
286                                // nonce required
287                                return false;
288                        }
289
290                        return getAccessTokenHash() != null;
291                }
292
293                // Hybrid flow
294                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken
295                if (ResponseType.CODE_IDTOKEN.equals(responseType)) {
296
297                        if (getNonce() == null) {
298                                // nonce required
299                                return false;
300                        }
301
302                        if (! iatAuthzEndpoint) {
303                                // c_hash and at_hash not required when id_token issued at token endpoint
304                                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
305                                return true;
306                        }
307
308                        return getCodeHash() != null;
309
310                }
311
312                if (ResponseType.CODE_TOKEN.equals(responseType)) {
313
314                        if (getNonce() == null) {
315                                // nonce required
316                                return false;
317                        }
318
319                        if (! iatAuthzEndpoint) {
320                                // c_hash and at_hash not required when id_token issued at token endpoint
321                                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
322                                return true;
323                        }
324
325                        return true; // ok
326                }
327
328                if (ResponseType.CODE_IDTOKEN_TOKEN.equals(responseType)) {
329
330                        if (getNonce() == null) {
331                                // nonce required
332                                return false;
333                        }
334
335                        if (! iatAuthzEndpoint) {
336                                // c_hash and at_hash not required when id_token issued at token endpoint
337                                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
338                                return true;
339                        }
340
341                        if (getAccessTokenHash() == null) {
342                                // at_hash required when issued at authz endpoint
343                                return false;
344                        }
345
346                        return getCodeHash() != null;
347
348                }
349
350                throw new IllegalArgumentException("Unsupported response_type: " + responseType);
351        }
352
353
354        /**
355         * Use {@link #hasRequiredClaims(ResponseType, boolean)} instead.
356         *
357         * @param responseType The OpenID Connect response type. Must not be
358         *                     {@code null}.
359         *
360         * @return {@code true} if the required claims are contained, else
361         *         {@code false}.
362         */
363        @Deprecated
364        public boolean hasRequiredClaims(final ResponseType responseType) {
365
366                return hasRequiredClaims(responseType, true);
367        }
368
369
370        /**
371         * Gets the subject authentication time. Corresponds to the
372         * {@code auth_time} claim.
373         *
374         * @return The authentication time, {@code null} if not specified or
375         *         parsing failed.
376         */
377        public Date getAuthenticationTime() {
378
379                return getDateClaim(AUTH_TIME_CLAIM_NAME);
380        }
381
382
383        /**
384         * Sets the subject authentication time. Corresponds to the
385         * {@code auth_time} claim.
386         *
387         * @param authTime The authentication time, {@code null} if not
388         *                 specified.
389         */
390        public void setAuthenticationTime(final Date authTime) {
391
392                setDateClaim(AUTH_TIME_CLAIM_NAME, authTime);
393        }
394
395
396        /**
397         * Gets the ID token nonce. Corresponds to the {@code nonce} claim.
398         *
399         * @return The nonce, {@code null} if not specified or parsing failed.
400         */
401        public Nonce getNonce() {
402
403                String value = getStringClaim(NONCE_CLAIM_NAME);
404                return value != null ? new Nonce(value) : null;
405        }
406
407
408        /**
409         * Sets the ID token nonce. Corresponds to the {@code nonce} claim.
410         *
411         * @param nonce The nonce, {@code null} if not specified.
412         */
413        public void setNonce(final Nonce nonce) {
414
415                setClaim(NONCE_CLAIM_NAME, nonce != null ? nonce.getValue() : null);
416        }
417
418
419        /**
420         * Gets the access token hash. Corresponds to the {@code at_hash}
421         * claim.
422         *
423         * @return The access token hash, {@code null} if not specified or
424         *         parsing failed.
425         */
426        public AccessTokenHash getAccessTokenHash() {
427
428                String value = getStringClaim(AT_HASH_CLAIM_NAME);
429                return value != null ? new AccessTokenHash(value) : null;
430        }
431
432
433        /**
434         * Sets the access token hash. Corresponds to the {@code at_hash}
435         * claim.
436         *
437         * @param atHash The access token hash, {@code null} if not specified.
438         */
439        public void setAccessTokenHash(final AccessTokenHash atHash) {
440
441                setClaim(AT_HASH_CLAIM_NAME, atHash != null ? atHash.getValue() : null);
442        }
443
444
445        /**
446         * Gets the authorisation code hash. Corresponds to the {@code c_hash}
447         * claim.
448         *
449         * @return The authorisation code hash, {@code null} if not specified
450         *         or parsing failed.
451         */
452        public CodeHash getCodeHash() {
453
454                String value = getStringClaim(C_HASH_CLAIM_NAME);
455                return value != null ? new CodeHash(value) : null;
456        }
457
458
459        /**
460         * Sets the authorisation code hash. Corresponds to the {@code c_hash}
461         * claim.
462         *
463         * @param cHash The authorisation code hash, {@code null} if not
464         *              specified.
465         */
466        public void setCodeHash(final CodeHash cHash) {
467
468                setClaim(C_HASH_CLAIM_NAME, cHash != null ? cHash.getValue() : null);
469        }
470        
471        
472        /**
473         * Gets the state hash. Corresponds to the {@code s_hash} claim.
474         *
475         * @return The state hash, {@code null} if not specified or parsing
476         *         failed.
477         */
478        public StateHash getStateHash() {
479                
480                String value = getStringClaim(S_HASH_CLAIM_NAME);
481                return value != null ? new StateHash(value) : null;
482        }
483        
484        
485        /**
486         * Sets the state hash. Corresponds to the {@code s_hash} claim.
487         *
488         * @param sHash The state hash, {@code null} if not specified.
489         */
490        public void setStateHash(final StateHash sHash) {
491                
492                setClaim(S_HASH_CLAIM_NAME, sHash != null ? sHash.getValue() : null);
493        }
494
495
496        /**
497         * Gets the device secret hash. Corresponds to the {@code ds_hash}
498         * claim.
499         *
500         * @return The device secret hash, {@code null} if not specified or
501         *         parsing failed.
502         */
503        public DeviceSecretHash getDeviceSecretHash() {
504
505                String value = getStringClaim(DS_HASH_CLAIM_NAME);
506                return value != null ? new DeviceSecretHash(value) : null;
507        }
508
509
510        /**
511         * Sets the device secret hash. Corresponds to the {@code ds_hash}
512         * claim.
513         *
514         * @param dsHash The device secret hash, {@code null} if not specified.
515         */
516        public void setDeviceSecretHash(final DeviceSecretHash dsHash) {
517
518                setClaim(DS_HASH_CLAIM_NAME, dsHash != null ? dsHash.getValue() : null);
519        }
520
521
522        /**
523         * Gets the Authentication Context Class Reference (ACR). Corresponds
524         * to the {@code acr} claim.
525         *
526         * @return The Authentication Context Class Reference (ACR),
527         *         {@code null} if not specified or parsing failed.
528         */
529        public ACR getACR() {
530
531                String value = getStringClaim(ACR_CLAIM_NAME);
532                return value != null ? new ACR(value) : null;
533        }
534
535
536        /**
537         * Sets the Authentication Context Class Reference (ACR). Corresponds
538         * to the {@code acr} claim.
539         *
540         * @param acr The Authentication Context Class Reference (ACR),
541         *            {@code null} if not specified.
542         */
543        public void setACR(final ACR acr) {
544
545                setClaim(ACR_CLAIM_NAME, acr != null ? acr.getValue() : null);
546        }
547
548
549        /**
550         * Gets the Authentication Methods References (AMRs). Corresponds to
551         * the {@code amr} claim.
552         *
553         * @return The Authentication Methods Reference (AMR) list,
554         *         {@code null} if not specified or parsing failed.
555         */
556        public List<AMR> getAMR() {
557
558                List<String> rawList = getStringListClaim(AMR_CLAIM_NAME);
559
560                if (rawList == null || rawList.isEmpty())
561                        return null;
562
563                List<AMR> amrList = new ArrayList<>(rawList.size());
564
565                for (String s: rawList)
566                        amrList.add(new AMR(s));
567
568                return amrList;
569        }
570
571
572        /**
573         * Sets the Authentication Methods References (AMRs). Corresponds to
574         * the {@code amr} claim.
575         *
576         * @param amr The Authentication Methods Reference (AMR) list,
577         *            {@code null} if not specified.
578         */
579        public void setAMR(final List<AMR> amr) {
580
581                if (amr != null) {
582
583                        List<String> amrList = new ArrayList<>(amr.size());
584
585                        for (AMR a: amr)
586                                amrList.add(a.getValue());
587
588                        setClaim(AMR_CLAIM_NAME, amrList);
589
590                } else {
591                        setClaim(AMR_CLAIM_NAME, null);
592                }
593        }
594
595
596        /**
597         * Gets the authorised party for the ID token. Corresponds to the
598         * {@code azp} claim.
599         *
600         * @return The authorised party, {@code null} if not specified or
601         *         parsing failed.
602         */
603        public AuthorizedParty getAuthorizedParty() {
604
605                String value = getStringClaim(AZP_CLAIM_NAME);
606                return value != null ? new AuthorizedParty(value) : null;
607        }
608
609
610        /**
611         * Sets the authorised party for the ID token. Corresponds to the
612         * {@code azp} claim.
613         *
614         * @param azp The authorised party, {@code null} if not specified.
615         */
616        public void setAuthorizedParty(final AuthorizedParty azp) {
617
618                setClaim(AZP_CLAIM_NAME, azp != null ? azp.getValue() : null);
619        }
620
621
622        /**
623         * Gets the subject's JSON Web Key (JWK) for a self-issued OpenID
624         * Connect provider. Corresponds to the {@code sub_jwk} claim.
625         *
626         * @return The subject's JWK, {@code null} if not specified or parsing
627         *         failed.
628         */
629        public JWK getSubjectJWK() {
630
631                JSONObject jsonObject = getClaim(SUB_JWK_CLAIM_NAME, JSONObject.class);
632
633                if (jsonObject == null)
634                        return null;
635
636                try {
637                        return JWK.parse(jsonObject);
638
639                } catch (java.text.ParseException e) {
640
641                        return null;
642                }
643        }
644
645
646        /**
647         * Sets the subject's JSON Web Key (JWK) for a self-issued OpenID
648         * Connect provider. Corresponds to the {@code sub_jwk} claim.
649         *
650         * @param subJWK The subject's JWK (must be public), {@code null} if
651         *               not specified.
652         */
653        public void setSubjectJWK(final JWK subJWK) {
654
655                if (subJWK != null) {
656
657                        if (subJWK.isPrivate())
658                                throw new IllegalArgumentException("The subject's JSON Web Key (JWK) must be public");
659
660                        setClaim(SUB_JWK_CLAIM_NAME, new JSONObject(subJWK.toJSONObject()));
661
662                } else {
663                        setClaim(SUB_JWK_CLAIM_NAME, null);
664                }
665        }
666
667
668        /**
669         * Parses an ID token claims set from the specified JSON object.
670         *
671         * @param jsonObject The JSON object to parse. Must not be
672         *                   {@code null}.
673         *
674         * @return The ID token claims set.
675         *
676         * @throws ParseException If parsing failed.
677         */
678        public static IDTokenClaimsSet parse(final JSONObject jsonObject)
679                throws ParseException {
680
681                try {
682                        return new IDTokenClaimsSet(jsonObject);
683
684                } catch (IllegalArgumentException e) {
685                        
686                        throw new ParseException(e.getMessage(), e);
687                }
688        }
689
690
691        /**
692         * Parses an ID token claims set from the specified JSON object string.
693         *
694         * @param json The JSON object string to parse. Must not be
695         *             {@code null}.
696         *
697         * @return The ID token claims set.
698         *
699         * @throws ParseException If parsing failed.
700         */
701        public static IDTokenClaimsSet parse(final String json)
702                throws ParseException {
703
704                return parse(JSONObjectUtils.parse(json));
705        }
706}