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