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 net.minidev.json.JSONArray;
024import net.minidev.json.JSONObject;
025
026import com.nimbusds.jose.jwk.JWK;
027import com.nimbusds.jwt.JWTClaimsSet;
028import com.nimbusds.oauth2.sdk.ParseException;
029import com.nimbusds.oauth2.sdk.ResponseType;
030import com.nimbusds.oauth2.sdk.id.Audience;
031import com.nimbusds.oauth2.sdk.id.Issuer;
032import com.nimbusds.oauth2.sdk.id.Subject;
033import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
034import com.nimbusds.openid.connect.sdk.Nonce;
035
036
037/**
038 * ID token claims set, serialisable to a JSON object.
039 *
040 * <p>Example ID token claims set:
041 *
042 * <pre>
043 * {
044 *   "iss"       : "https://server.example.com",
045 *   "sub"       : "24400320",
046 *   "aud"       : "s6BhdRkqt3",
047 *   "nonce"     : "n-0S6_WzA2Mj",
048 *   "exp"       : 1311281970,
049 *   "iat"       : 1311280970,
050 *   "auth_time" : 1311280969,
051 *   "acr"       : "urn:mace:incommon:iap:silver",
052 *   "at_hash"   : "MTIzNDU2Nzg5MDEyMzQ1Ng"
053 * }
054 * </pre>
055 *
056 * <p>Related specifications:
057 *
058 * <ul>
059 *     <li>OpenID Connect Core 1.0, section 2.
060 *     <li>OpenID Connect Front-Channel Logout 1.0, section 3 (draft 02).
061 *     <li>Financial Services – Financial API - Part 2: Read and Write API
062 *         Security Profile, section 5.1.
063 * </ul>
064 */
065public class IDTokenClaimsSet extends CommonOIDCTokenClaimsSet {
066
067
068        /**
069         * The expiration time claim name.
070         */
071        public static final String EXP_CLAIM_NAME = "exp";
072
073
074        /**
075         * The subject authentication time claim name.
076         */
077        public static final String AUTH_TIME_CLAIM_NAME = "auth_time";
078
079
080        /**
081         * The nonce claim name.
082         */
083        public static final String NONCE_CLAIM_NAME = "nonce";
084
085
086        /**
087         * The access token hash claim name.
088         */
089        public static final String AT_HASH_CLAIM_NAME = "at_hash";
090
091
092        /**
093         * The authorisation code hash claim name.
094         */
095        public static final String C_HASH_CLAIM_NAME = "c_hash";
096        
097        
098        /**
099         * The state hash claim name.
100         */
101        public static final String S_HASH_CLAIM_NAME = "s_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(EXP_CLAIM_NAME);
137                claimNames.add(AUTH_TIME_CLAIM_NAME);
138                claimNames.add(NONCE_CLAIM_NAME);
139                claimNames.add(AT_HASH_CLAIM_NAME);
140                claimNames.add(C_HASH_CLAIM_NAME);
141                claimNames.add(S_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 (new 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 (new ResponseType("id_token").equals(responseType)) {
270
271                        return getNonce() != null;
272
273                }
274
275                if (new ResponseType("id_token", "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 (new ResponseType("code", "id_token").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 (new 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 (new ResponseType("code", "id_token", "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 ID token expiration time. Corresponds to the {@code exp}
364         * claim.
365         *
366         * @return The expiration time.
367         */
368        public Date getExpirationTime() {
369
370                return getDateClaim(EXP_CLAIM_NAME);
371        }
372
373
374        /**
375         * Gets the subject authentication time. Corresponds to the
376         * {@code auth_time} claim.
377         *
378         * @return The authentication time, {@code null} if not specified or
379         *         parsing failed.
380         */
381        public Date getAuthenticationTime() {
382
383                return getDateClaim(AUTH_TIME_CLAIM_NAME);
384        }
385
386
387        /**
388         * Sets the subject authentication time. Corresponds to the
389         * {@code auth_time} claim.
390         *
391         * @param authTime The authentication time, {@code null} if not
392         *                 specified.
393         */
394        public void setAuthenticationTime(final Date authTime) {
395
396                setDateClaim(AUTH_TIME_CLAIM_NAME, authTime);
397        }
398
399
400        /**
401         * Gets the ID token nonce. Corresponds to the {@code nonce} claim.
402         *
403         * @return The nonce, {@code null} if not specified or parsing failed.
404         */
405        public Nonce getNonce() {
406
407                String value = getStringClaim(NONCE_CLAIM_NAME);
408                return value != null ? new Nonce(value) : null;
409        }
410
411
412        /**
413         * Sets the ID token nonce. Corresponds to the {@code nonce} claim.
414         *
415         * @param nonce The nonce, {@code null} if not specified.
416         */
417        public void setNonce(final Nonce nonce) {
418
419                setClaim(NONCE_CLAIM_NAME, nonce != null ? nonce.getValue() : null);
420        }
421
422
423        /**
424         * Gets the access token hash. Corresponds to the {@code at_hash}
425         * claim.
426         *
427         * @return The access token hash, {@code null} if not specified or
428         *         parsing failed.
429         */
430        public AccessTokenHash getAccessTokenHash() {
431
432                String value = getStringClaim(AT_HASH_CLAIM_NAME);
433                return value != null ? new AccessTokenHash(value) : null;
434        }
435
436
437        /**
438         * Sets the access token hash. Corresponds to the {@code at_hash}
439         * claim.
440         *
441         * @param atHash The access token hash, {@code null} if not specified.
442         */
443        public void setAccessTokenHash(final AccessTokenHash atHash) {
444
445                setClaim(AT_HASH_CLAIM_NAME, atHash != null ? atHash.getValue() : null);
446        }
447
448
449        /**
450         * Gets the authorisation code hash. Corresponds to the {@code c_hash}
451         * claim.
452         *
453         * @return The authorisation code hash, {@code null} if not specified
454         *         or parsing failed.
455         */
456        public CodeHash getCodeHash() {
457
458                String value = getStringClaim(C_HASH_CLAIM_NAME);
459                return value != null ? new CodeHash(value) : null;
460        }
461
462
463        /**
464         * Sets the authorisation code hash. Corresponds to the {@code c_hash}
465         * claim.
466         *
467         * @param cHash The authorisation code hash, {@code null} if not
468         *              specified.
469         */
470        public void setCodeHash(final CodeHash cHash) {
471
472                setClaim(C_HASH_CLAIM_NAME, cHash != null ? cHash.getValue() : null);
473        }
474        
475        
476        /**
477         * Gets the state hash. Corresponds to the {@code s_hash} claim.
478         *
479         * @return The state hash, {@code null} if not specified or parsing
480         *         failed.
481         */
482        public StateHash getStateHash() {
483                
484                String value = getStringClaim(S_HASH_CLAIM_NAME);
485                return value != null ? new StateHash(value) : null;
486        }
487        
488        
489        /**
490         * Sets the state hash. Corresponds to the {@code s_hash} claim.
491         *
492         * @param sHash The state hash, {@code null} if not specified.
493         */
494        public void setStateHash(final StateHash sHash) {
495                
496                setClaim(S_HASH_CLAIM_NAME, sHash != null ? sHash.getValue() : null);
497        }
498
499
500        /**
501         * Gets the Authentication Context Class Reference (ACR). Corresponds
502         * to the {@code acr} claim.
503         *
504         * @return The Authentication Context Class Reference (ACR),
505         *         {@code null} if not specified or parsing failed.
506         */
507        public ACR getACR() {
508
509                String value = getStringClaim(ACR_CLAIM_NAME);
510                return value != null ? new ACR(value) : null;
511        }
512
513
514        /**
515         * Sets the Authentication Context Class Reference (ACR). Corresponds
516         * to the {@code acr} claim.
517         *
518         * @param acr The Authentication Context Class Reference (ACR),
519         *            {@code null} if not specified.
520         */
521        public void setACR(final ACR acr) {
522
523                setClaim(ACR_CLAIM_NAME, acr != null ? acr.getValue() : null);
524        }
525
526
527        /**
528         * Gets the Authentication Methods References (AMRs). Corresponds to
529         * the {@code amr} claim.
530         *
531         * @return The Authentication Methods Reference (AMR) list,
532         *         {@code null} if not specified or parsing failed.
533         */
534        public List<AMR> getAMR() {
535
536                List<String> rawList = getStringListClaim(AMR_CLAIM_NAME);
537
538                if (rawList == null || rawList.isEmpty())
539                        return null;
540
541                List<AMR> amrList = new ArrayList<>(rawList.size());
542
543                for (String s: rawList)
544                        amrList.add(new AMR(s));
545
546                return amrList;
547        }
548
549
550        /**
551         * Sets the Authentication Methods References (AMRs). Corresponds to
552         * the {@code amr} claim.
553         *
554         * @param amr The Authentication Methods Reference (AMR) list,
555         *            {@code null} if not specified.
556         */
557        public void setAMR(final List<AMR> amr) {
558
559                if (amr != null) {
560
561                        List<String> amrList = new ArrayList<>(amr.size());
562
563                        for (AMR a: amr)
564                                amrList.add(a.getValue());
565
566                        setClaim(AMR_CLAIM_NAME, amrList);
567
568                } else {
569                        setClaim(AMR_CLAIM_NAME, null);
570                }
571        }
572
573
574        /**
575         * Gets the authorised party for the ID token. Corresponds to the
576         * {@code azp} claim.
577         *
578         * @return The authorised party, {@code null} if not specified or
579         *         parsing failed.
580         */
581        public AuthorizedParty getAuthorizedParty() {
582
583                String value = getStringClaim(AZP_CLAIM_NAME);
584                return value != null ? new AuthorizedParty(value) : null;
585        }
586
587
588        /**
589         * Sets the authorised party for the ID token. Corresponds to the
590         * {@code azp} claim.
591         *
592         * @param azp The authorised party, {@code null} if not specified.
593         */
594        public void setAuthorizedParty(final AuthorizedParty azp) {
595
596                setClaim(AZP_CLAIM_NAME, azp != null ? azp.getValue() : null);
597        }
598
599
600        /**
601         * Gets the subject's JSON Web Key (JWK) for a self-issued OpenID
602         * Connect provider. Corresponds to the {@code sub_jwk} claim.
603         *
604         * @return The subject's JWK, {@code null} if not specified or parsing
605         *         failed.
606         */
607        public JWK getSubjectJWK() {
608
609                JSONObject jsonObject = getClaim(SUB_JWK_CLAIM_NAME, JSONObject.class);
610
611                if (jsonObject == null)
612                        return null;
613
614                try {
615                        return JWK.parse(jsonObject);
616
617                } catch (java.text.ParseException e) {
618
619                        return null;
620                }
621        }
622
623
624        /**
625         * Sets the subject's JSON Web Key (JWK) for a self-issued OpenID
626         * Connect provider. Corresponds to the {@code sub_jwk} claim.
627         *
628         * @param subJWK The subject's JWK (must be public), {@code null} if
629         *               not specified.
630         */
631        public void setSubjectJWK(final JWK subJWK) {
632
633                if (subJWK != null) {
634
635                        if (subJWK.isPrivate())
636                                throw new IllegalArgumentException("The subject's JSON Web Key (JWK) must be public");
637
638                        setClaim(SUB_JWK_CLAIM_NAME, new JSONObject(subJWK.toJSONObject()));
639
640                } else {
641                        setClaim(SUB_JWK_CLAIM_NAME, null);
642                }
643        }
644
645
646        /**
647         * Parses an ID token claims set from the specified JSON object.
648         *
649         * @param jsonObject The JSON object to parse. Must not be
650         *                   {@code null}.
651         *
652         * @return The ID token claims set.
653         *
654         * @throws ParseException If parsing failed.
655         */
656        public static IDTokenClaimsSet parse(final JSONObject jsonObject)
657                throws ParseException {
658
659                try {
660                        return new IDTokenClaimsSet(jsonObject);
661
662                } catch (IllegalArgumentException e) {
663                        
664                        throw new ParseException(e.getMessage(), e);
665                }
666        }
667
668
669        /**
670         * Parses an ID token claims set from the specified JSON object string.
671         *
672         * @param json The JSON object string to parse. Must not be
673         *             {@code null}.
674         *
675         * @return The ID token claims set.
676         *
677         * @throws ParseException If parsing failed.
678         */
679        public static IDTokenClaimsSet parse(final String json)
680                throws ParseException {
681
682                return parse(JSONObjectUtils.parse(json));
683        }
684}