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