Hot questions for Using Azure in opensaml

Question:

Hi i got this errormessage:

net.shibboleth.utilities.java.support.logic.ConstraintViolationException: Signature was null

On validating a SAML response from Azure AD.

For test purpose i saved a response file as xml and found a tag:

<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
            <ds:SignatureMethod
                Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
            <ds:Reference URI="#_97031c65-0139-4047-a416-9495df5d6ed7">
                <ds:Transforms>
                    <ds:Transform
                        Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
                <ds:DigestValue>
                    KMaFHRt8inqVYsMGKnAryKUTQUbYGPUDPxdvj6T08OQ=
                </ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>
           .....
        </ds:SignatureValue>
        <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
            <X509Data>
                <X509Certificate>
                    ....
                </X509Certificate>
            </X509Data>
        </KeyInfo>
    </ds:Signature>

i unmarshall the XML response:

InitializationService.initialize();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
String content = new String(Files.readAllBytes(Paths.get("saml_response_azure.xml")));
Document document = docBuilder.parse(new ByteArrayInputStream(content.trim().getBytes()));

Element element = document.getDocumentElement();
Unmarshaller unmarshaller = XMLObjectProviderRegistrySupport.getUnmarshallerFactory().getUnmarshaller(element);
Response response = (Response) unmarshaller.unmarshall(element);

And error is dropped at:

Signature signature = response.getAssertions().get(0).getSignature() // returns null

SAMLSignatureProfileValidator profValidator = new SAMLSignatureProfileValidator();
profValidator.validate(signature);

Answer:

Ok, I think I found it, it looks like you did not add any implementation dependencies to you POM. When I use your POM and include this dependency, I get the signature object.

  <dependency>
     <groupId>org.opensaml</groupId>
     <artifactId>opensaml-saml-impl</artifactId>
     <version>3.2.0</version>
 </dependency>

The modular structure of the dependencies is a big difference from version 2 of OpenSAML.

Question:

i want to send a SAML request to my IDP (Azure AD) but ia m not sure how to send the request at all.

First i used OpenSAML to build an AuthRequest. Which i encoded as a String.

Now i wanted to use ApacheHttpClient to send the request and read the response and i am not sure if OpenSAML provides http sending methods at all so my idea was to use Apaches HttpClient for this for now.

String encodedAuthRequest = generateAuthRequest();
String url = "http://myidp/samlendpoint";
CloseableHttpClient client = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(url);

// add request header
request.addHeader("User-Agent", USER_AGENT);

// what is to add else?

HttpResponse response = client.execute(request);

I am stuck now since i am not sure how to setup the request, does it need to be a query parameter like ?saml=.... in GET or do i have to put the encoded saml response in the body as POST..

Can someone help or clarify these issue?

Update from Guillaumes answer:

I have this from the IDPs MetaData:

<IDPSSODescriptor>
    <SingleSignOnService
        Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
        Location="https://myidp/saml2" />
    <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
        Location="https://myidp/saml2" />

Answer:

Depends on which binding you are supposed to use. The IdP documentation or metadata should mention that. There are several:

  • Redirect Binding (using a GET), by far the most common for Requests
  • POST Binding
  • Artifact Binding (more complex, but I have never seen it used for Requests)
  • ...

I suppose that Redirect Binding will be used in your case (EDIT: you added the metadata from your IdP, it mentions that you can use both Redirect and POST bindings). It is described here: https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf page 15.

Short version: your must first use the DEFLATE algorithm to compress your XML Request, encode it using base64, encode it using URL encoding, then pass it as a query parameter named SAMLRequest

?SAMLRequest=<your url-encoded base64-encoded deflated authnrequest>

https://en.wikipedia.org/wiki/SAML_2.0#SP_Redirect_Request.3B_IdP_POST_Response

Question:

I am using Opensaml to generate a saml2 authentication request for azure

<?xml version="1.0" encoding="UTF-8"?>
<samlp:AuthnRequest AssertionConsumerServiceURL="https://myserver.de/_saml/validate/azure"
ForceAuthn="false" ID="0" IsPassive="false" IssueInstant="2016-11-28T09:46:43.215Z"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<samlp:Issuer xmlns:samlp="urn:oasis:names:tc:SAML:2.0:assertion">issuerid</samlp:Issuer>
<saml2p:NameIDPolicy AllowCreate="true"
    Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
    SPNameQualifier="Isser" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" />
<saml2p:RequestedAuthnContext Comparison="exact"
    xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
    <saml:AuthnContextClassRef xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
</saml2p:RequestedAuthnContext>
</samlp:AuthnRequest>

The XML was encoded with org.opensaml.xml.io.Marshaller and java.util.zip.DeflaterOutputStream.DeflaterOutputStream to Base64 and i verified it by using some online saml decoders for correctnes.

The error i get from azure however is:

AADSTS75005: The request is not a valid Saml2 protocol message.

Anyone can help?


Answer:

In the element 'AuthnRequest' you define the attribute 'ID' as '0'. This is not a valid value of the atomic type 'xs:ID'.

I quote the technical rule of Oasis document:

The xs:ID simple type is used to declare SAML identifiers for assertions, requests, and responses. Values declared to be of type xs:ID in this specification MUST satisfy the following properties in addition to those imposed by the definition of the xs:ID type itself:

  • Any party that assigns an identifier MUST ensure that there is negligible probability that that party or any other party will accidentally assign the same identifier to a different data object.
  • Where a data object declares that it has a particular identifier, there MUST be exactly one such declaration.

The mechanism by which a SAML system entity ensures that the identifier is unique is left to the implementation. In the case that a random or pseudorandom technique is employed, the probability of two randomly chosen identifiers being identical MUST be less than or equal to 2-128 and SHOULD be less than or equal to 2-160.

You should use one of the existing way to generate the ID.

Question:

i have Response in XML format from my IDP and want to use opensaml2 to validate it. How can it be done?


Answer:

According to the OpenSAML2 offical docs (doc1 & doc2), you can try to use the code below to validate the saml xml response with OpenSAML.

// Initialize the library
DefaultBootstrap.bootstrap(); 

// Get parser pool manager
BasicParserPool ppMgr = new BasicParserPool();
ppMgr.setNamespaceAware(true);

// Get org.w3c.dom.Document Object from response
HttpURLConnection req = (HttpURLConnection) new URL("<saml-xml-url>").openConnection();
// Add some necessary headers for the request
// req.addRequestProperty("...", "...");
// ...
InputStream in = req.getInputStream();
Document inCommonMDDoc = ppMgr.parse(in);

// Get the DOMSource from org.w3c.dom.Document Object
DOMSource domSource=new DOMSource(document);  

//Add an extension schema via the code SAMLSchemaBuilder.addExtensionSchema(String schema) if necessary
Schema schema = SAMLSchemaBuilder.getSAML11Schema();

// Get a Validator instance.
Validator validator = schema.newValidator();
try {
    validator.validate(domSource);
    System.out.println("Result : Valid!");
} catch(Exception e) {
    System.out.println("Result : Invalid!");
}