Skip to content
Advertisement

Why is this XML Validation failing on NameIDFormat from an extended type?

I recently encountered an issue with SAML metadata validation from a customer.

The relevant part of metadata that is failing:

<IDPSSODescriptor protocolSupportEnumeration="urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">

  <Extensions>
    <shibmd:Scope regexp="false">...</shibmd:Scope>
  </Extensions>

  <KeyDescriptor>
    <ds:KeyInfo>
      <ds:X509Data>
        <ds:X509Certificate>
        ...
        </ds:X509Certificate>
      </ds:X509Data>
    </ds:KeyInfo>
  </KeyDescriptor>
    
  <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding" Location="..." index="1"/>
  <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="..." index="2"/>

  <SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest" Location="..."/>
  <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="..."/>
  <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="..."/>
  <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="h..."/>
    
  <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
  <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
</IDPSSODescriptor>

This fails with the folowing error: cvc-complex-type.2.4.a: Invalid content was found starting with element 'NameIDFormat'. One of '{"urn:oasis:names:tc:SAML:2.0:metadata":SingleSignOnService, "urn:oasis:names:tc:SAML:2.0:metadata":NameIDMappingService, "urn:oasis:names:tc:SAML:2.0:metadata":AssertionIDRequestService, "urn:oasis:names:tc:SAML:2.0:metadata":AttributeProfile, "urn:oasis:names:tc:SAML:2.0:assertion":Attribute}' is expected.

The following is the relevant portion of saml-schema-metadata-2.0.xsd:

<complexType name="SSODescriptorType" abstract="true">
  <complexContent>
    <extension base="md:RoleDescriptorType">
      <sequence>
        <element ref="md:ArtifactResolutionService" minOccurs="0" maxOccurs="unbounded"/>
        <element ref="md:SingleLogoutService" minOccurs="0" maxOccurs="unbounded"/>
        <element ref="md:ManageNameIDService" minOccurs="0" maxOccurs="unbounded"/>
        <element ref="md:NameIDFormat" minOccurs="0" maxOccurs="unbounded"/>
      </sequence>
    </extension>
  </complexContent>
</complexType>
<element name="ArtifactResolutionService" type="md:IndexedEndpointType"/>
<element name="SingleLogoutService" type="md:EndpointType"/>
<element name="ManageNameIDService" type="md:EndpointType"/>
<element name="NameIDFormat" type="anyURI"/>

<element name="IDPSSODescriptor" type="md:IDPSSODescriptorType"/>
<complexType name="IDPSSODescriptorType">
  <complexContent>
    <extension base="md:SSODescriptorType">
      <sequence>
        <element ref="md:SingleSignOnService" maxOccurs="unbounded"/>
        <element ref="md:NameIDMappingService" minOccurs="0" maxOccurs="unbounded"/>
        <element ref="md:AssertionIDRequestService" minOccurs="0" maxOccurs="unbounded"/>
        <element ref="md:AttributeProfile" minOccurs="0" maxOccurs="unbounded"/>
        <element ref="saml:Attribute" minOccurs="0" maxOccurs="unbounded"/>
      </sequence>
      <attribute name="WantAuthnRequestsSigned" type="boolean" use="optional"/>
    </extension>
  </complexContent>
</complexType>
<element name="SingleSignOnService" type="md:EndpointType"/>
<element name="NameIDMappingService" type="md:EndpointType"/>
<element name="AssertionIDRequestService" type="md:EndpointType"/>
<element name="AttributeProfile" type="anyURI"/>

I notice that the error message only specifies elements from the IDPSSODescriptorType and not the base SSODescriptorType. Maybe that’s by design?

Regardless, I receive no errors for ArtifactResolutionService which also happens to be define in the base type.

In fact, if I move <element ref="md:NameIDFormat" minOccurs="0" maxOccurs="unbounded"/> in the schema file from the base SSODescriptorType to the IDPSSODescriptorType, and leave everything else, the metadata file passes validation.

I’m using Java 8 with the default implementation of javax.xml.validation.*

private static String[] schemas = {
  "/schema/xml.xsd",
  "/schema/XMLSchema.xsd",
  "/schema/xmldsig-core-schema.xsd",
  "/schema/xenc-schema.xsd",
  "/schema/saml-schema-assertion-2.0.xsd",
  "/schema/saml-schema-metadata-2.0.xsd",
};

public boolean validateXMLSchema(Document document) {
  try {
    SchemaFactory factory =
      SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

    Schema schema = factory.newSchema(getSources().toArray(new Source[0]));

    Validator validator = schema.newValidator();
    validator.validate(new DOMSource(document));
    return true;
  } catch (SAXException e) {
    log.error("Exception: " + e.getMessage());
  } catch (Exception ex) {
    log.debug("Exception: ", ex);
  }

  return false;
}

At this point I’m unsure what would be causing the NameIDFormat element to fail validation, or why the validator isn’t finding it in the base type but does seem to find ArtifactResolutionService.

Advertisement

Answer

I figured out what’s going on. The customer metadata is indeed invalid (according to this schema).

The complexTypes in the schema are specifying sequences. This means the elements MUST be specified in that particular order.

According to the schema, the NameIDFormat elements defined in the SSODescriptorType must be specified before any of the elements defined by the IDPSSODescriptorType. Changing the metadata to the following works:

<IDPSSODescriptor protocolSupportEnumeration="urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">

  <Extensions>
    <shibmd:Scope regexp="false">...</shibmd:Scope>
  </Extensions>

  <KeyDescriptor>
    <ds:KeyInfo>
      <ds:X509Data>
        <ds:X509Certificate>
        ...
        </ds:X509Certificate>
      </ds:X509Data>
    </ds:KeyInfo>
  </KeyDescriptor>

  <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding" Location="..." index="1"/>
  <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="..." index="2"/>

  <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
  <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>

  <SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest" Location="..."/>
  <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="..."/>
  <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="..."/>
  <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="h..."/>
</IDPSSODescriptor>

Now to figure out how I’m going to handle this since it’s unlikely I’ll be able to get the customer to fix their metadata…

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement