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