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}