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 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 (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 (ResponseType.IDTOKEN.equals(responseType)) { 270 271 return getNonce() != null; 272 273 } 274 275 if (ResponseType.IDTOKEN_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 (ResponseType.CODE_IDTOKEN.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 (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 (ResponseType.CODE_IDTOKEN_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 subject authentication time. Corresponds to the 364 * {@code auth_time} claim. 365 * 366 * @return The authentication time, {@code null} if not specified or 367 * parsing failed. 368 */ 369 public Date getAuthenticationTime() { 370 371 return getDateClaim(AUTH_TIME_CLAIM_NAME); 372 } 373 374 375 /** 376 * Sets the subject authentication time. Corresponds to the 377 * {@code auth_time} claim. 378 * 379 * @param authTime The authentication time, {@code null} if not 380 * specified. 381 */ 382 public void setAuthenticationTime(final Date authTime) { 383 384 setDateClaim(AUTH_TIME_CLAIM_NAME, authTime); 385 } 386 387 388 /** 389 * Gets the ID token nonce. Corresponds to the {@code nonce} claim. 390 * 391 * @return The nonce, {@code null} if not specified or parsing failed. 392 */ 393 public Nonce getNonce() { 394 395 String value = getStringClaim(NONCE_CLAIM_NAME); 396 return value != null ? new Nonce(value) : null; 397 } 398 399 400 /** 401 * Sets the ID token nonce. Corresponds to the {@code nonce} claim. 402 * 403 * @param nonce The nonce, {@code null} if not specified. 404 */ 405 public void setNonce(final Nonce nonce) { 406 407 setClaim(NONCE_CLAIM_NAME, nonce != null ? nonce.getValue() : null); 408 } 409 410 411 /** 412 * Gets the access token hash. Corresponds to the {@code at_hash} 413 * claim. 414 * 415 * @return The access token hash, {@code null} if not specified or 416 * parsing failed. 417 */ 418 public AccessTokenHash getAccessTokenHash() { 419 420 String value = getStringClaim(AT_HASH_CLAIM_NAME); 421 return value != null ? new AccessTokenHash(value) : null; 422 } 423 424 425 /** 426 * Sets the access token hash. Corresponds to the {@code at_hash} 427 * claim. 428 * 429 * @param atHash The access token hash, {@code null} if not specified. 430 */ 431 public void setAccessTokenHash(final AccessTokenHash atHash) { 432 433 setClaim(AT_HASH_CLAIM_NAME, atHash != null ? atHash.getValue() : null); 434 } 435 436 437 /** 438 * Gets the authorisation code hash. Corresponds to the {@code c_hash} 439 * claim. 440 * 441 * @return The authorisation code hash, {@code null} if not specified 442 * or parsing failed. 443 */ 444 public CodeHash getCodeHash() { 445 446 String value = getStringClaim(C_HASH_CLAIM_NAME); 447 return value != null ? new CodeHash(value) : null; 448 } 449 450 451 /** 452 * Sets the authorisation code hash. Corresponds to the {@code c_hash} 453 * claim. 454 * 455 * @param cHash The authorisation code hash, {@code null} if not 456 * specified. 457 */ 458 public void setCodeHash(final CodeHash cHash) { 459 460 setClaim(C_HASH_CLAIM_NAME, cHash != null ? cHash.getValue() : null); 461 } 462 463 464 /** 465 * Gets the state hash. Corresponds to the {@code s_hash} claim. 466 * 467 * @return The state hash, {@code null} if not specified or parsing 468 * failed. 469 */ 470 public StateHash getStateHash() { 471 472 String value = getStringClaim(S_HASH_CLAIM_NAME); 473 return value != null ? new StateHash(value) : null; 474 } 475 476 477 /** 478 * Sets the state hash. Corresponds to the {@code s_hash} claim. 479 * 480 * @param sHash The state hash, {@code null} if not specified. 481 */ 482 public void setStateHash(final StateHash sHash) { 483 484 setClaim(S_HASH_CLAIM_NAME, sHash != null ? sHash.getValue() : null); 485 } 486 487 488 /** 489 * Gets the device secret hash. Corresponds to the {@code ds_hash} 490 * claim. 491 * 492 * @return The device secret hash, {@code null} if not specified or 493 * parsing failed. 494 */ 495 public DeviceSecretHash getDeviceSecretHash() { 496 497 String value = getStringClaim(DS_HASH_CLAIM_NAME); 498 return value != null ? new DeviceSecretHash(value) : null; 499 } 500 501 502 /** 503 * Sets the device secret hash. Corresponds to the {@code ds_hash} 504 * claim. 505 * 506 * @param dsHash The device secret hash, {@code null} if not specified. 507 */ 508 public void setDeviceSecretHash(final DeviceSecretHash dsHash) { 509 510 setClaim(DS_HASH_CLAIM_NAME, dsHash != null ? dsHash.getValue() : null); 511 } 512 513 514 /** 515 * Gets the Authentication Context Class Reference (ACR). Corresponds 516 * to the {@code acr} claim. 517 * 518 * @return The Authentication Context Class Reference (ACR), 519 * {@code null} if not specified or parsing failed. 520 */ 521 public ACR getACR() { 522 523 String value = getStringClaim(ACR_CLAIM_NAME); 524 return value != null ? new ACR(value) : null; 525 } 526 527 528 /** 529 * Sets the Authentication Context Class Reference (ACR). Corresponds 530 * to the {@code acr} claim. 531 * 532 * @param acr The Authentication Context Class Reference (ACR), 533 * {@code null} if not specified. 534 */ 535 public void setACR(final ACR acr) { 536 537 setClaim(ACR_CLAIM_NAME, acr != null ? acr.getValue() : null); 538 } 539 540 541 /** 542 * Gets the Authentication Methods References (AMRs). Corresponds to 543 * the {@code amr} claim. 544 * 545 * @return The Authentication Methods Reference (AMR) list, 546 * {@code null} if not specified or parsing failed. 547 */ 548 public List<AMR> getAMR() { 549 550 List<String> rawList = getStringListClaim(AMR_CLAIM_NAME); 551 552 if (rawList == null || rawList.isEmpty()) 553 return null; 554 555 List<AMR> amrList = new ArrayList<>(rawList.size()); 556 557 for (String s: rawList) 558 amrList.add(new AMR(s)); 559 560 return amrList; 561 } 562 563 564 /** 565 * Sets the Authentication Methods References (AMRs). Corresponds to 566 * the {@code amr} claim. 567 * 568 * @param amr The Authentication Methods Reference (AMR) list, 569 * {@code null} if not specified. 570 */ 571 public void setAMR(final List<AMR> amr) { 572 573 if (amr != null) { 574 575 List<String> amrList = new ArrayList<>(amr.size()); 576 577 for (AMR a: amr) 578 amrList.add(a.getValue()); 579 580 setClaim(AMR_CLAIM_NAME, amrList); 581 582 } else { 583 setClaim(AMR_CLAIM_NAME, null); 584 } 585 } 586 587 588 /** 589 * Gets the authorised party for the ID token. Corresponds to the 590 * {@code azp} claim. 591 * 592 * @return The authorised party, {@code null} if not specified or 593 * parsing failed. 594 */ 595 public AuthorizedParty getAuthorizedParty() { 596 597 String value = getStringClaim(AZP_CLAIM_NAME); 598 return value != null ? new AuthorizedParty(value) : null; 599 } 600 601 602 /** 603 * Sets the authorised party for the ID token. Corresponds to the 604 * {@code azp} claim. 605 * 606 * @param azp The authorised party, {@code null} if not specified. 607 */ 608 public void setAuthorizedParty(final AuthorizedParty azp) { 609 610 setClaim(AZP_CLAIM_NAME, azp != null ? azp.getValue() : null); 611 } 612 613 614 /** 615 * Gets the subject's JSON Web Key (JWK) for a self-issued OpenID 616 * Connect provider. Corresponds to the {@code sub_jwk} claim. 617 * 618 * @return The subject's JWK, {@code null} if not specified or parsing 619 * failed. 620 */ 621 public JWK getSubjectJWK() { 622 623 JSONObject jsonObject = getClaim(SUB_JWK_CLAIM_NAME, JSONObject.class); 624 625 if (jsonObject == null) 626 return null; 627 628 try { 629 return JWK.parse(jsonObject); 630 631 } catch (java.text.ParseException e) { 632 633 return null; 634 } 635 } 636 637 638 /** 639 * Sets the subject's JSON Web Key (JWK) for a self-issued OpenID 640 * Connect provider. Corresponds to the {@code sub_jwk} claim. 641 * 642 * @param subJWK The subject's JWK (must be public), {@code null} if 643 * not specified. 644 */ 645 public void setSubjectJWK(final JWK subJWK) { 646 647 if (subJWK != null) { 648 649 if (subJWK.isPrivate()) 650 throw new IllegalArgumentException("The subject's JSON Web Key (JWK) must be public"); 651 652 setClaim(SUB_JWK_CLAIM_NAME, new JSONObject(subJWK.toJSONObject())); 653 654 } else { 655 setClaim(SUB_JWK_CLAIM_NAME, null); 656 } 657 } 658 659 660 /** 661 * Parses an ID token claims set from the specified JSON object. 662 * 663 * @param jsonObject The JSON object to parse. Must not be 664 * {@code null}. 665 * 666 * @return The ID token claims set. 667 * 668 * @throws ParseException If parsing failed. 669 */ 670 public static IDTokenClaimsSet parse(final JSONObject jsonObject) 671 throws ParseException { 672 673 try { 674 return new IDTokenClaimsSet(jsonObject); 675 676 } catch (IllegalArgumentException e) { 677 678 throw new ParseException(e.getMessage(), e); 679 } 680 } 681 682 683 /** 684 * Parses an ID token claims set from the specified JSON object string. 685 * 686 * @param json The JSON object string to parse. Must not be 687 * {@code null}. 688 * 689 * @return The ID token claims set. 690 * 691 * @throws ParseException If parsing failed. 692 */ 693 public static IDTokenClaimsSet parse(final String json) 694 throws ParseException { 695 696 return parse(JSONObjectUtils.parse(json)); 697 } 698}