0
votes

I am able to create a SAMLResponse using OpenSAML, but as a sanity check I wanted to validate the signature. The recipient wants the Assertion to be signed but not the Response, which looks just fine:

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:Response ID="1111111111" Version="2.0"
    xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
    <saml2:Assertion Version="2.0"
        xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
        <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
            <ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"
                    xmlns:ds="http://www.w3.org/2000/09/xmldsig#" />
                <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"
                    xmlns:ds="http://www.w3.org/2000/09/xmldsig#" />
                <ds:Reference URI="" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                    <ds:Transforms xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                        <ds:Transform
                            Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"
                            xmlns:ds="http://www.w3.org/2000/09/xmldsig#" />
                        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"
                            xmlns:ds="http://www.w3.org/2000/09/xmldsig#" />
                    </ds:Transforms>
                    <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"
                        xmlns:ds="http://www.w3.org/2000/09/xmldsig#" />
                    <ds:DigestValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#">H1M/MSp4KUwKryB9c99XvE6PWaU=
                    </ds:DigestValue>
                </ds:Reference>
            </ds:SignedInfo>
            <ds:SignatureValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                npeqj35bPh06G6WcSBzfpaDEqVUzhEOgqARKB6NIowGcAoD6yHpmDYTJ0nXvS31cRFYxqWtCVIrX
                e7abo3mpwfLI7FjpEawP+8IEaMi++b0hN1Xri/bprMNjxB7kldDGJc2WkbdVRAAIknQetxytd9gB
                WGhP97zLyGPVK23orH/JF09bFgoqGPgd/14fuY5ksOJuFfuZD4rsCOxxicYrfMsJDnlUfz8n18Xw
                Jj6vX/0SDoDknMz9h4kh1TU4tus0FvIAPYLNB7QV7Iarpd+5Q9R8mj+NJEviFH/DZlqE/NYrxRNt
                iinOgOnz1x3dtnDr2O/BHIW55mlBFi0L6wJ7ow==
            </ds:SignatureValue>
        </ds:Signature>
    </saml2:Assertion>
</saml2p:Response>

If I validate the signature before marshalling the response, it's valid:

protected static void testSignature(Credential credential) throws Exception {
    DefaultBootstrap.bootstrap();
    SignatureBuilder builder = new SignatureBuilder();
    Signature signature = builder.buildObject(); 
    signature.setSigningCredential(credential);
    signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1);
    signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);

    Assertion assertion = new AssertionBuilder().buildObject();
    assertion.setVersion(SAMLVersion.VERSION_20);   
    assertion.setSignature(signature);

    AssertionMarshaller aMarsh = new AssertionMarshaller();
    aMarsh.marshall(assertion);
    Signer.signObject(signature);

    Response response = new ResponseBuilder().buildObject();
    response.setVersion(SAMLVersion.VERSION_20);
    response.setID("1111111111");
    response.getAssertions().add(assertion);

    Signature sig = response.getAssertions().get(0).getSignature();

    SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();        
    profileValidator.validate(sig);      
    SignatureValidator sigValidator = new SignatureValidator(credential);               
    sigValidator.validate(sig); //valid

}

However if I marshall the response by adding the following code, it comes back invalid even if I set the signature before marshalling:

    ...

    Signature sig = response.getAssertions().get(0).getSignature();

    ResponseMarshaller rMarsh = new ResponseMarshaller();
    Element plain = rMarsh.marshall(response);


    SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();        
    profileValidator.validate(sig);      
    SignatureValidator sigValidator = new SignatureValidator(credential);               
    sigValidator.validate(sig); //invalid

Lastly I tried to unmarshall the marshalled response, then read and validate the signature, and it still comes back invalid even though I can verify the response is the same as before marshalling/unmarshalling.

    ...

    ResponseMarshaller rMarsh = new ResponseMarshaller();
    Element plain = rMarsh.marshall(response);
    String samlResponse1 = XMLHelper.nodeToString(plain);

    UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
    Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(plain);
    XMLObject responseXmlObj = unmarshaller.unmarshall(plain);
    Response newResponse = (Response) responseXmlObj;


    plain = rMarsh.marshall(newResponse);
    String samlResponse2 = XMLHelper.nodeToString(plain);
    System.out.println(StringUtils.equals(samlResponse1, samlResponse2));  //true

    Signature sig = newResponse.getAssertions().get(0).getSignature();


    SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();        
    profileValidator.validate(sig);      
    SignatureValidator sigValidator = new SignatureValidator(credential);               
    sigValidator.validate(sig);  //invalid

What's going on? Is there something I'm missing here?

1
Does the original SAMLResponse have line breaks? If yes, what are the line break characters? For me, most of the times it is because line break characters are not persisted as-is between processing step, which is Marshalling in this case. - Thuan

1 Answers

0
votes

Solved this. The Assertion requires an ID value that is different from the Response ID. I just needed to add the following line of code and it worked.

assertion.setID("1111111112");