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