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