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