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