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