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