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