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.federation.entities; 019 020 021import java.util.Date; 022import java.util.LinkedList; 023import java.util.List; 024 025import net.minidev.json.JSONObject; 026 027import com.nimbusds.jose.jwk.JWKSet; 028import com.nimbusds.jwt.JWTClaimsSet; 029import com.nimbusds.oauth2.sdk.ParseException; 030import com.nimbusds.oauth2.sdk.as.AuthorizationServerMetadata; 031import com.nimbusds.oauth2.sdk.client.ClientMetadata; 032import com.nimbusds.oauth2.sdk.id.Identifier; 033import com.nimbusds.oauth2.sdk.id.Issuer; 034import com.nimbusds.oauth2.sdk.id.Subject; 035import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 036import com.nimbusds.oauth2.sdk.util.MapUtils; 037import com.nimbusds.openid.connect.sdk.claims.CommonClaimsSet; 038import com.nimbusds.openid.connect.sdk.federation.policy.MetadataPolicy; 039import com.nimbusds.openid.connect.sdk.federation.policy.language.PolicyViolationException; 040import com.nimbusds.openid.connect.sdk.federation.trust.constraints.TrustChainConstraints; 041import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; 042import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata; 043 044 045/** 046 * Federation entity statement claims set, serialisable to a JSON object. 047 * 048 * <p>Example claims set: 049 * 050 * <pre> 051 * { 052 * "iss": "https://feide.no", 053 * "sub": "https://ntnu.no", 054 * "iat": 1516239022, 055 * "exp": 1516298022, 056 * "crit": ["jti"], 057 * "jti": "7l2lncFdY6SlhNia", 058 * "policy_language_crit": ["regexp"], 059 * "metadata_policy": { 060 * "openid_provider": { 061 * "issuer": {"value": "https://ntnu.no"}, 062 * "organization_name": {"value": "NTNU"}, 063 * "id_token_signing_alg_values_supported": 064 * {"subset_of": ["RS256", "RS384", "RS512"]}, 065 * "op_policy_uri": { 066 * "regexp": "^https:\/\/[\w-]+\.example\.com\/[\w-]+\.html"} 067 * }, 068 * "openid_relying_party": { 069 * "organization_name": {"value": "NTNU"}, 070 * "grant_types_supported": { 071 * "subset_of": ["authorization_code", "implicit"]}, 072 * "scopes": { 073 * "subset_of": ["openid", "profile", "email", "phone"]} 074 * } 075 * }, 076 * "constraints": { 077 * "max_path_length": 2 078 * } 079 * "jwks": { 080 * "keys": [ 081 * { 082 * "alg": "RS256", 083 * "e": "AQAB", 084 * "ext": true, 085 * "key_ops": ["verify"], 086 * "kid": "key1", 087 * "kty": "RSA", 088 * "n": "pnXBOusEANuug6ewezb9J_...", 089 * "use": "sig" 090 * } 091 * ] 092 * }, 093 * "authority_hints": [ 094 * "https://edugain.org/federation" 095 * ] 096 * } 097 * </pre> 098 * 099 * <p>Related specifications: 100 * 101 * <ul> 102 * <li>OpenID Connect Federation 1.0, section 2.1. 103 * </ul> 104 */ 105public class EntityStatementClaimsSet extends CommonClaimsSet { 106 107 108 /** 109 * The expiration time claim name. 110 */ 111 public static final String EXP_CLAIM_NAME = "exp"; 112 113 114 /** 115 * The JWK set claim name. 116 */ 117 public static final String JWKS_CLAIM_NAME = "jwks"; 118 119 120 /** 121 * The authority hints claim name. 122 */ 123 public static final String AUTHORITY_HINTS_CLAIM_NAME = "authority_hints"; 124 125 126 /** 127 * The metadata claim name. 128 */ 129 public static final String METADATA_CLAIM_NAME = "metadata"; 130 131 132 /** 133 * The metadata policy claim name. 134 */ 135 public static final String METADATA_POLICY_CLAIM_NAME = "metadata_policy"; 136 137 138 /** 139 * The constraints claim name. 140 */ 141 public static final String CONSTRAINTS_CLAIM_NAME = "constraints"; 142 143 144 /** 145 * The critical claim name. 146 */ 147 public static final String CRITICAL_CLAIM_NAME = "crit"; 148 149 150 /** 151 * The policy critical claim name. 152 */ 153 public static final String POLICY_LANGUAGE_CRITICAL_CLAIM_NAME = "policy_language_crit"; 154 155 156 /** 157 * Creates a new federation entity statement claims set with the 158 * minimum required claims. 159 * 160 * @param iss The issuer. Must not be {@code null}. 161 * @param sub The subject. Must not be {@code null}. 162 * @param iat The issue time. Must not be {@code null}. 163 * @param exp The expiration time. Must not be {@code null}. 164 * @param jwks The entity public JWK set, {@code null} if not required. 165 */ 166 public EntityStatementClaimsSet(final Issuer iss, 167 final Subject sub, 168 final Date iat, 169 final Date exp, 170 final JWKSet jwks) { 171 172 this(new EntityID(iss.getValue()), new EntityID(sub.getValue()), iat, exp, jwks); 173 } 174 175 176 /** 177 * Creates a new federation entity statement claims set with the 178 * minimum required claims. 179 * 180 * @param iss The issuer. Must not be {@code null}. 181 * @param sub The subject. Must not be {@code null}. 182 * @param iat The issue time. Must not be {@code null}. 183 * @param exp The expiration time. Must not be {@code null}. 184 * @param jwks The entity public JWK set, {@code null} if not required. 185 */ 186 public EntityStatementClaimsSet(final EntityID iss, 187 final EntityID sub, 188 final Date iat, 189 final Date exp, 190 final JWKSet jwks) { 191 192 setClaim(ISS_CLAIM_NAME, iss.getValue()); 193 setClaim(SUB_CLAIM_NAME, sub.getValue()); 194 setDateClaim(IAT_CLAIM_NAME, iat); 195 setDateClaim(EXP_CLAIM_NAME, exp); 196 if (jwks != null) { 197 setClaim(JWKS_CLAIM_NAME, jwks.toJSONObject(true)); // public JWKs only 198 } 199 } 200 201 202 /** 203 * Creates a new federation entity statement claims set from the 204 * specified JWT claims set. 205 * 206 * @param jwtClaimsSet The JWT claims set. Must not be {@code null}. 207 * 208 * @throws ParseException If the JWT claims set doesn't represent a 209 * valid federation entity statement claims set. 210 */ 211 public EntityStatementClaimsSet(final JWTClaimsSet jwtClaimsSet) 212 throws ParseException { 213 214 super(jwtClaimsSet.toJSONObject()); 215 216 validateRequiredClaimsPresence(); 217 } 218 219 220 /** 221 * Validates this claims set for having all minimum required claims for 222 * an entity statement. If a {@link #isSelfStatement() selt-statement} 223 * check for the {@link #hasMetadata() presence of metadata}. If 224 * {@link #getCriticalExtensionClaims() critical extension claims} are 225 * listed their presence is also checked. 226 * 227 * @throws ParseException If the validation failed and a required claim 228 * is missing. 229 */ 230 public void validateRequiredClaimsPresence() 231 throws ParseException { 232 233 if (getIssuer() == null) { 234 throw new ParseException("Missing iss (issuer) claim"); 235 } 236 237 EntityID.parse(getIssuer()); // ensure URI 238 239 if (getSubject() == null) { 240 throw new ParseException("Missing sub (subject) claim"); 241 } 242 243 EntityID.parse(getSubject()); // ensure URI 244 245 if (getIssueTime() == null) { 246 throw new ParseException("Missing iat (issued-at) claim"); 247 } 248 249 if (getExpirationTime() == null) { 250 throw new ParseException("Missing exp (expiration) claim"); 251 } 252 253 // jwks always required for self-statements 254 if (isSelfStatement() && getJWKSet() == null) { 255 throw new ParseException("Missing jwks (JWK set) claim"); 256 } 257 258 if (isSelfStatement() && ! hasMetadata()) { 259 throw new ParseException("Missing required metadata claim for self-statement"); 260 } 261 262 List<String> crit = getCriticalExtensionClaims(); 263 264 if (crit != null) { 265 for (String claimName: crit) { 266 if (getClaim(claimName) == null) { 267 throw new ParseException("Missing critical " + claimName + " claim"); 268 } 269 } 270 } 271 } 272 273 274 /** 275 * Returns {@code true} if this is a self-statement (issuer and subject 276 * match). 277 * 278 * @return {@code true} for a self-statement, {@code false} if not. 279 */ 280 public boolean isSelfStatement() { 281 282 Issuer issuer = getIssuer(); 283 Subject subject = getSubject(); 284 285 return issuer != null && subject != null && issuer.getValue().equals(subject.getValue()); 286 } 287 288 289 /** 290 * Returns the issuer as entity ID. 291 * 292 * @return The issuer as entity ID. 293 */ 294 public EntityID getIssuerEntityID() { 295 296 return new EntityID(getIssuer().getValue()); 297 } 298 299 300 /** 301 * Returns the subject as entity ID. 302 * 303 * @return The subject as entity ID. 304 */ 305 public EntityID getSubjectEntityID() { 306 307 return new EntityID(getSubject().getValue()); 308 } 309 310 311 /** 312 * Gets the entity statement expiration time. Corresponds to the 313 * {@code exp} claim. 314 * 315 * @return The expiration time, {@code null} if not specified or 316 * parsing failed. 317 */ 318 public Date getExpirationTime() { 319 320 return getDateClaim(EXP_CLAIM_NAME); 321 } 322 323 324 /** 325 * Gets the entity JWK set. 326 * 327 * @return The entity JWK set, {@code null} if not specified or parsing 328 * failed. 329 */ 330 public JWKSet getJWKSet() { 331 332 JSONObject jwkSetJSONObject = getJSONObjectClaim(JWKS_CLAIM_NAME); 333 if (jwkSetJSONObject == null) { 334 return null; 335 } 336 try { 337 return JWKSet.parse(jwkSetJSONObject); 338 } catch (java.text.ParseException e) { 339 return null; 340 } 341 } 342 343 344 /** 345 * Gets the entity IDs of the intermediate entities or trust anchors. 346 * 347 * @return The entity IDs, {@code null} or empty list for a trust 348 * anchor, or if parsing failed. 349 */ 350 public List<EntityID> getAuthorityHints() { 351 352 List<String> strings = getStringListClaim(AUTHORITY_HINTS_CLAIM_NAME); 353 354 if (strings == null) { 355 return null; 356 } 357 358 List<EntityID> trustChain = new LinkedList<>(); 359 for (String s: strings) { 360 trustChain.add(new EntityID(s)); 361 } 362 return trustChain; 363 } 364 365 366 /** 367 * Sets the entity IDs of the intermediate entities or trust anchors. 368 * 369 * @param trustChain The entity IDs, {@code null} or empty list for a 370 * trust anchor. 371 */ 372 public void setAuthorityHints(final List<EntityID> trustChain) { 373 374 if (trustChain != null) { 375 setClaim(AUTHORITY_HINTS_CLAIM_NAME, Identifier.toStringList(trustChain)); 376 } else { 377 setClaim(AUTHORITY_HINTS_CLAIM_NAME, null); 378 } 379 } 380 381 382 /** 383 * Returns {@code true} if a metadata field is present. 384 * 385 * @return {@code true} if for a metadata field for an OpenID relying 386 * party, OpenID provider, OAuth authorisation server, OAuth 387 * client, OAuth protected resource or a federation entity is 388 * present. 389 */ 390 public boolean hasMetadata() { 391 392 JSONObject metadataObject = getJSONObjectClaim(METADATA_CLAIM_NAME); 393 394 if (MapUtils.isEmpty(metadataObject)) { 395 return false; 396 } 397 398 if (metadataObject.get(FederationMetadataType.OPENID_RELYING_PARTY.getValue()) != null) return true; 399 if (metadataObject.get(FederationMetadataType.OPENID_PROVIDER.getValue()) != null) return true; 400 if (metadataObject.get(FederationMetadataType.OAUTH_AUTHORIZATION_SERVER.getValue()) != null) return true; 401 if (metadataObject.get(FederationMetadataType.OAUTH_CLIENT.getValue()) != null) return true; 402 if (metadataObject.get(FederationMetadataType.OAUTH_RESOURCE.getValue()) != null) return true; 403 if (metadataObject.get(FederationMetadataType.FEDERATION_ENTITY.getValue()) != null) return true; 404 405 return false; 406 } 407 408 409 /** 410 * Gets the metadata for the specified type. Use a typed getter, such 411 * as {@link #getRPMetadata}, when available. 412 * 413 * @param type The type. Must not be {@code null}. 414 * 415 * @return The metadata, {@code null} if not specified. 416 */ 417 public JSONObject getMetadata(final FederationMetadataType type) { 418 419 JSONObject o = getJSONObjectClaim(METADATA_CLAIM_NAME); 420 421 if (o == null) { 422 return null; 423 } 424 425 try { 426 return JSONObjectUtils.getJSONObject(o, type.getValue(), null); 427 } catch (com.nimbusds.oauth2.sdk.ParseException e) { 428 return null; 429 } 430 } 431 432 433 /** 434 * Sets the metadata for the specified type. Use a typed setter, such 435 * as {@link #setRPMetadata}, when available. 436 * 437 * @param type The type. Must not be {@code null}. 438 * @param metadata The metadata, {@code null} if not specified. 439 */ 440 public void setMetadata(final FederationMetadataType type, final JSONObject metadata) { 441 442 JSONObject o = getJSONObjectClaim(METADATA_CLAIM_NAME); 443 444 if (o == null) { 445 if (metadata == null) { 446 return; // nothing to clear 447 } 448 o = new JSONObject(); 449 } 450 451 o.put(type.getValue(), metadata); 452 453 setClaim(METADATA_CLAIM_NAME, o); 454 } 455 456 457 /** 458 * Gets the OpenID relying party metadata if present for this entity. 459 * 460 * @return The RP metadata, {@code null} if not specified or if parsing 461 * failed. 462 */ 463 public OIDCClientMetadata getRPMetadata() { 464 465 JSONObject o = getMetadata(FederationMetadataType.OPENID_RELYING_PARTY); 466 467 if (o == null) { 468 return null; 469 } 470 471 try { 472 return OIDCClientMetadata.parse(o); 473 } catch (com.nimbusds.oauth2.sdk.ParseException e) { 474 return null; 475 } 476 } 477 478 479 /** 480 * Sets the OpenID relying party metadata if present for this entity. 481 * 482 * @param rpMetadata The RP metadata, {@code null} if not specified. 483 */ 484 public void setRPMetadata(final OIDCClientMetadata rpMetadata) { 485 486 JSONObject o = rpMetadata != null ? rpMetadata.toJSONObject() : null; 487 setMetadata(FederationMetadataType.OPENID_RELYING_PARTY, o); 488 } 489 490 491 /** 492 * Gets the OpenID provider metadata if present for this entity. 493 * 494 * @return The OP metadata, {@code null} if not specified or if parsing 495 * failed. 496 */ 497 public OIDCProviderMetadata getOPMetadata() { 498 499 JSONObject o = getMetadata(FederationMetadataType.OPENID_PROVIDER); 500 501 if (o == null) { 502 return null; 503 } 504 505 try { 506 return OIDCProviderMetadata.parse(o); 507 } catch (com.nimbusds.oauth2.sdk.ParseException e) { 508 return null; 509 } 510 } 511 512 513 /** 514 * Gets the OpenID provider metadata if present for this entity. 515 * 516 * @param opMetadata The OP metadata, {@code null} if not specified. 517 */ 518 public void setOPMetadata(final OIDCProviderMetadata opMetadata) { 519 520 JSONObject o = opMetadata != null ? opMetadata.toJSONObject() : null; 521 setMetadata(FederationMetadataType.OPENID_PROVIDER, o); 522 } 523 524 525 /** 526 * Gets the OAuth 2.0 client metadata if present for this entity. 527 * 528 * @return The client metadata, {@code null} if not specified or if 529 * parsing failed. 530 */ 531 public ClientMetadata getOAuthClientMetadata() { 532 533 JSONObject o = getMetadata(FederationMetadataType.OAUTH_CLIENT); 534 535 if (o == null) { 536 return null; 537 } 538 539 try { 540 return ClientMetadata.parse(o); 541 } catch (com.nimbusds.oauth2.sdk.ParseException e) { 542 return null; 543 } 544 } 545 546 547 /** 548 * Sets the OAuth 2.0 client metadata if present for this entity. 549 * 550 * @param clientMetadata The client metadata, {@code null} if not 551 * specified. 552 */ 553 public void setOAuthClientMetadata(final ClientMetadata clientMetadata) { 554 555 JSONObject o = clientMetadata != null ? clientMetadata.toJSONObject() : null; 556 setMetadata(FederationMetadataType.OAUTH_CLIENT, o); 557 } 558 559 560 /** 561 * Gets the OAuth 2.0 authorisation server metadata if present for this 562 * entity. 563 * 564 * @return The AS metadata, {@code null} if not specified or if parsing 565 * failed. 566 */ 567 public AuthorizationServerMetadata getASMetadata() { 568 569 JSONObject o = getMetadata(FederationMetadataType.OAUTH_AUTHORIZATION_SERVER); 570 571 if (o == null) { 572 return null; 573 } 574 575 try { 576 return AuthorizationServerMetadata.parse(o); 577 } catch (com.nimbusds.oauth2.sdk.ParseException e) { 578 return null; 579 } 580 } 581 582 583 /** 584 * Sets the OAuth 2.0 authorisation server metadata if present for this 585 * entity. 586 * 587 * @param asMetadata The AS metadata, {@code null} if not specified. 588 */ 589 public void setASMetadata(final AuthorizationServerMetadata asMetadata) { 590 591 JSONObject o = asMetadata != null ? asMetadata.toJSONObject() : null; 592 setMetadata(FederationMetadataType.OAUTH_AUTHORIZATION_SERVER, o); 593 } 594 595 596 /** 597 * Gets the federation entity metadata if present for this entity. 598 * 599 * @return The federation entity metadata, {@code null} if not 600 * specified or if parsing failed. 601 */ 602 public FederationEntityMetadata getFederationEntityMetadata() { 603 604 JSONObject o = getMetadata(FederationMetadataType.FEDERATION_ENTITY); 605 606 if (o == null) { 607 return null; 608 } 609 610 try { 611 return FederationEntityMetadata.parse(o); 612 } catch (com.nimbusds.oauth2.sdk.ParseException e) { 613 return null; 614 } 615 } 616 617 618 /** 619 * Sets the federation entity metadata if present for this entity. 620 * 621 * @param entityMetadata The federation entity metadata, {@code null} 622 * if not specified. 623 */ 624 public void setFederationEntityMetadata(final FederationEntityMetadata entityMetadata) { 625 626 JSONObject o = entityMetadata != null ? entityMetadata.toJSONObject() : null; 627 setMetadata(FederationMetadataType.FEDERATION_ENTITY, o); 628 } 629 630 631 /** 632 * Gets the complete metadata policy JSON object. 633 * 634 * @return The metadata policy JSON object, {@code null} if not 635 * specified or if parsing failed. 636 */ 637 public JSONObject getMetadataPolicyJSONObject() { 638 639 return getJSONObjectClaim(METADATA_POLICY_CLAIM_NAME); 640 } 641 642 643 /** 644 * Sets the complete metadata policy JSON object. 645 * 646 * @param metadataPolicy The metadata policy JSON object, {@code null} 647 * if not specified. 648 */ 649 public void setMetadataPolicyJSONObject(final JSONObject metadataPolicy) { 650 651 setClaim(METADATA_POLICY_CLAIM_NAME, metadataPolicy); 652 } 653 654 655 /** 656 * Gets the metadata policy for the specified type. 657 * 658 * @param type The type. Must not be {@code null}. 659 * 660 * @return The metadata policy, {@code null} or if JSON parsing failed. 661 * 662 * @throws PolicyViolationException On a policy violation. 663 */ 664 public MetadataPolicy getMetadataPolicy(final FederationMetadataType type) 665 throws PolicyViolationException { 666 667 JSONObject o = getMetadataPolicyJSONObject(); 668 669 if (o == null) { 670 return null; 671 } 672 673 try { 674 JSONObject policyJSONObject = JSONObjectUtils.getJSONObject(o, type.getValue(), null); 675 if (policyJSONObject == null) { 676 return null; 677 } 678 return MetadataPolicy.parse(policyJSONObject); 679 } catch (ParseException e) { 680 return null; 681 } 682 } 683 684 685 /** 686 * Sets the metadata policy for the specified type. 687 * 688 * @param type The type. Must not be {@code null}. 689 * @param metadataPolicy The metadata policy, {@code null} if not 690 * specified. 691 */ 692 public void setMetadataPolicy(final FederationMetadataType type, final MetadataPolicy metadataPolicy) { 693 694 JSONObject o = getMetadataPolicyJSONObject(); 695 696 if (o == null) { 697 if (metadataPolicy == null) { 698 return; // nothing to clear 699 } 700 o = new JSONObject(); 701 } 702 703 if (metadataPolicy != null) { 704 o.put(type.getValue(), metadataPolicy.toJSONObject()); 705 } else { 706 o.remove(type.getValue()); 707 } 708 709 if (o.isEmpty()) { 710 o = null; 711 } 712 setMetadataPolicyJSONObject(o); 713 } 714 715 716 /** 717 * Gets the trust chain constraints for subordinate entities. 718 * 719 * @return The trust chain constraints, {@code null} if not specified 720 * or if parsing failed. 721 */ 722 public TrustChainConstraints getConstraints() { 723 724 JSONObject o = getJSONObjectClaim(CONSTRAINTS_CLAIM_NAME); 725 726 if (o == null) { 727 return null; 728 } 729 730 try { 731 return TrustChainConstraints.parse(o); 732 } catch (com.nimbusds.oauth2.sdk.ParseException e) { 733 return null; 734 } 735 } 736 737 738 /** 739 * Sets the trust chain constraint for subordinate entities. 740 * 741 * @param constraints The trust chain constraints, {@code null} if not 742 * specified. 743 */ 744 public void setConstraints(final TrustChainConstraints constraints) { 745 746 if (constraints != null) { 747 setClaim(CONSTRAINTS_CLAIM_NAME, constraints.toJSONObject()); 748 } else { 749 setClaim(CONSTRAINTS_CLAIM_NAME, null); 750 } 751 } 752 753 754 /** 755 * Gets the names of the critical extension claims. 756 * 757 * @return The names of the critical extension claims, {@code null} if 758 * not specified or if parsing failed. 759 */ 760 public List<String> getCriticalExtensionClaims() { 761 762 return getStringListClaim(CRITICAL_CLAIM_NAME); 763 } 764 765 766 /** 767 * Sets the names of the critical extension claims. 768 * 769 * @param claimNames The names of the critical extension claims, 770 * {@code null} if not specified. Must not be an 771 * empty list. 772 */ 773 public void setCriticalExtensionClaims(final List<String> claimNames) { 774 775 if (claimNames != null && claimNames.isEmpty()) { 776 throw new IllegalArgumentException("The critical extension claim names must not be empty"); 777 } 778 779 setClaim(CRITICAL_CLAIM_NAME, claimNames); 780 } 781 782 783 /** 784 * Gets the names of the critical policy extensions. 785 * 786 * @return The names of the critical policy extensions or if parsing 787 * failed. 788 */ 789 public List<String> getCriticalPolicyExtensions() { 790 791 return getStringListClaim(POLICY_LANGUAGE_CRITICAL_CLAIM_NAME); 792 } 793 794 795 /** 796 * Sets the names of the critical policy extensions. 797 * 798 * @param extNames The names of the critical policy extensions, 799 * {@code null} if not specified. Must not be an empty 800 * list. 801 */ 802 public void setCriticalPolicyExtensions(final List<String> extNames) { 803 804 if (extNames != null && extNames.isEmpty()) { 805 throw new IllegalArgumentException("The critical policy extension names must not be empty"); 806 } 807 808 setClaim(POLICY_LANGUAGE_CRITICAL_CLAIM_NAME, extNames); 809 } 810}