A. Rac
A. Rac

Reputation: 33

SAML2.0 signature validation failed for SAML Response

I have created SAML2.0 response and signed it using OpenSAML java library. Though SAML created is a valid XML, the signature is not valid (Validated using online SAML tools) and also my SP is not able to verify the signature with the certificate provided. I might be doing something wrong with 'Signature' or certificate in the code. I'm completely new to SAML/SSO/Digital signatures and not sure where to go from here. Please help me in solving this issue. Any advise or guidance would be greatly appreciated.

Below is my code and signed SAMLresponse (I'm not a Java person and new to SAML/SSO, so referred many blogs to develop this code).

    public class SAMLWriter {
static Logger LOGGER = LoggerFactory.getLogger(SAMLWriter.class);
public static void main(String[] args) throws Throwable {
    try {
        SAMLInputContainer input = new SAMLInputContainer();
        input.strIssuer = "http://synesty.com";
        input.strNameID = "UserJohnSmith";
         input.sessionId = "abcdedf1234567";

        Map<String, String> customAttributes = new HashMap<String, String>();
        customAttributes.put("Value", "123456");

        input.attributes = customAttributes;

        Response response = SAMLWriter.buildDefaultResponse(input);

        Signature  signature = createSignature();
        response.setSignature(signature);

        ResponseMarshaller rMarshaller = new ResponseMarshaller();
       Element xmlString = rMarshaller.marshall(response);

       Signer.signObject(signature);

       String originalAssertionString = XMLHelper.prettyPrintXML(xmlString);

         System.out.println(originalAssertionString);

        //LOGGER.debug(originalAssertionString);

    } catch (MarshallingException e) {
        e.printStackTrace();
    }   
}

private static XMLObjectBuilderFactory builderFactory;

public static XMLObjectBuilderFactory getSAMLBuilder() throws ConfigurationException {

    if (builderFactory == null) {
        // OpenSAML 2.3
        DefaultBootstrap.bootstrap();
        builderFactory = Configuration.getBuilderFactory();
    }

    return builderFactory;
}

@SuppressWarnings("rawtypes")
public static Attribute buildStringAttribute(String name, String value, XMLObjectBuilderFactory builderFactory)
        throws ConfigurationException {
    SAMLObjectBuilder attrBuilder = (SAMLObjectBuilder) getSAMLBuilder().getBuilder(Attribute.DEFAULT_ELEMENT_NAME);
    Attribute attrFirstName = (Attribute) attrBuilder.buildObject();
    attrFirstName.setName(name);
    attrFirstName.setFriendlyName(name);
    attrFirstName.setNameFormat("urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified");

    // Set custom Attributes
    XMLObjectBuilder stringBuilder = getSAMLBuilder().getBuilder(XSString.TYPE_NAME);
    XSString attrValueFirstName = (XSString) stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME,    XSString.TYPE_NAME);
    attrValueFirstName.setValue(value);

    attrFirstName.getAttributeValues().add(attrValueFirstName);
    return attrFirstName;
}

/**
 * Helper method which includes some basic SAML fields which are part of almost
 * every SAML Assertion.
 */
@SuppressWarnings("rawtypes")
public static Response buildDefaultResponse(SAMLInputContainer input) {
    try {
        // Create the NameIdentifier
        SAMLObjectBuilder nameIdBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(NameID.DEFAULT_ELEMENT_NAME);
        NameID nameId = (NameID) nameIdBuilder.buildObject();
        nameId.setValue(input.getStrNameID());
        nameId.setNameQualifier(input.getStrNameQualifier());
        // nameId.setFormat(NameID.UNSPECIFIED);
        nameId.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified");

        // Create the SubjectConfirmation

        SAMLObjectBuilder confirmationMethodBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(SubjectConfirmationData.DEFAULT_ELEMENT_NAME);
        SubjectConfirmationData confirmationMethod = (SubjectConfirmationData) confirmationMethodBuilder
                .buildObject();
        DateTime now = new DateTime();
        confirmationMethod.setNotBefore(now);
        confirmationMethod.setNotOnOrAfter(now.plusMinutes(2));
        confirmationMethod.setRecipient("MYCLIENTWEBSITE");

        SAMLObjectBuilder subjectConfirmationBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(SubjectConfirmation.DEFAULT_ELEMENT_NAME);
        SubjectConfirmation subjectConfirmation = (SubjectConfirmation) subjectConfirmationBuilder.buildObject();

        subjectConfirmation.setMethod("urn:oasis:names:tc:SAML:2.0:cm:bearer");
        subjectConfirmation.setSubjectConfirmationData(confirmationMethod);

        // Create the Subject
        SAMLObjectBuilder subjectBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Subject.DEFAULT_ELEMENT_NAME);
        Subject subject = (Subject) subjectBuilder.buildObject();

        subject.setNameID(nameId);
        subject.getSubjectConfirmations().add(subjectConfirmation);

        // Create Authentication Statement
        SAMLObjectBuilder authStatementBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(AuthnStatement.DEFAULT_ELEMENT_NAME);
        AuthnStatement authnStatement = (AuthnStatement) authStatementBuilder.buildObject();
        // authnStatement.setSubject(subject);
        // authnStatement.setAuthenticationMethod(strAuthMethod);
        DateTime now2 = new DateTime();
        authnStatement.setAuthnInstant(now2);
        authnStatement.setSessionIndex(input.getSessionId());
        authnStatement.setSessionNotOnOrAfter(now2.plus(input.getMaxSessionTimeoutInMinutes()));

        SAMLObjectBuilder authContextBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(AuthnContext.DEFAULT_ELEMENT_NAME);
        AuthnContext authnContext = (AuthnContext) authContextBuilder.buildObject();

        SAMLObjectBuilder authContextClassRefBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
        AuthnContextClassRef authnContextClassRef = (AuthnContextClassRef) authContextClassRefBuilder.buildObject();

        authnContextClassRef
                .setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");

        authnContext.setAuthnContextClassRef(authnContextClassRef);
        authnStatement.setAuthnContext(authnContext);

        // Builder Attributes
        SAMLObjectBuilder attrStatementBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(AttributeStatement.DEFAULT_ELEMENT_NAME);
        AttributeStatement attrStatement = (AttributeStatement) attrStatementBuilder.buildObject();


       // Create the attribute statement
        Map attributes = input.getAttributes();
        if (attributes != null) {
            Iterator keySet = attributes.keySet().iterator();
            while (keySet.hasNext()) {
                String key1 = keySet.next().toString();
                String val = attributes.get(key1).toString();
                Attribute attrFirstName = buildStringAttribute(key1, val, getSAMLBuilder());
                attrStatement.getAttributes().add(attrFirstName);
            }
        } 


        SAMLObjectBuilder doNotCacheConditionBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(OneTimeUse.DEFAULT_ELEMENT_NAME);
        Condition condition = (Condition) doNotCacheConditionBuilder.buildObject();


        SAMLObjectBuilder audienceBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Audience.DEFAULT_ELEMENT_NAME);
        Audience audience = (Audience) audienceBuilder.buildObject();
        audience.setAudienceURI("https://my.audience.com");

        SAMLObjectBuilder audienceRestrictionBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(AudienceRestriction.DEFAULT_ELEMENT_NAME);
        AudienceRestriction audienceRestriction = (AudienceRestriction) audienceRestrictionBuilder.buildObject();
        audienceRestriction.getAudiences().add(audience);

        SAMLObjectBuilder conditionsBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Conditions.DEFAULT_ELEMENT_NAME);
        Conditions conditions = (Conditions) conditionsBuilder.buildObject();
        conditions.getConditions().add(condition);
        conditions.getAudienceRestrictions().add(audienceRestriction);

        // Create Issuer
        SAMLObjectBuilder issuerBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
        Issuer issuer = (Issuer) issuerBuilder.buildObject();
        issuer.setValue(input.getStrIssuer());
        issuer.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:entity");

        // Create statusCode
        SAMLObjectBuilder codeBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
        StatusCode statusCode = (StatusCode) codeBuilder.buildObject();
        statusCode.setValue(StatusCode.SUCCESS_URI);

        // Create status
        SAMLObjectBuilder statusBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Status.DEFAULT_ELEMENT_NAME);
        Status status = (Status) statusBuilder.buildObject();
        status.setStatusCode(statusCode);

        // Create the assertion
        SAMLObjectBuilder assertionBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
        Assertion assertion = (Assertion) assertionBuilder.buildObject();
        assertion.setIssuer(issuer);

        assertion.setID(input.getSessionId());
        assertion.setSubject(subject);
        assertion.setIssueInstant(now);
        assertion.setVersion(SAMLVersion.VERSION_20);
        assertion.getAuthnStatements().add(authnStatement);
        assertion.getAttributeStatements().add(attrStatement);
        assertion.setConditions(conditions);

        SAMLObjectBuilder responseBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Response.DEFAULT_ELEMENT_NAME);
        Response response = (Response) responseBuilder.buildObject();

        SAMLObjectBuilder issuBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
        Issuer issuer1 = (Issuer) issuBuilder.buildObject();
        issuer1.setValue(input.getStrIssuer());
        issuer1.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:entity");

        // response.setIssuer(issuer);
        response.setDestination("http://MYCLIENTWEBSITE.com");
        response.setID("abcd123456");
        response.setIssueInstant(now);
        response.setIssuer(issuer1);
        response.setStatus(status);
        response.getAssertions().add(assertion);

        return response;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

 static class SAMLInputContainer {
    private String strIssuer;
    private String strNameID;
    private String strNameQualifier;
    private String sessionId;
    private int maxSessionTimeoutInMinutes = 15; // default is 15 minutes

    private Map<String, String> attributes;

    public String getStrIssuer() {
        return strIssuer;
    }

    public void setStrIssuer(String strIssuer) {
        this.strIssuer = strIssuer;
    }

    public String getStrNameID() {
        return strNameID;
    }

    public void setStrNameID(String strNameID) {
        this.strNameID = strNameID;
    }

    public String getStrNameQualifier() {
        return strNameQualifier;
    }

    public void setStrNameQualifier(String strNameQualifier) {
        this.strNameQualifier = strNameQualifier;
    }

    public void setAttributes(Map<String, String> attributes) {
        this.attributes = attributes;
    }

    public Map<String, String> getAttributes() {
        return attributes;
    }

    public void setSessionId(String sessionId) {
        this.sessionId = sessionId;
    }

    public String getSessionId() {
        return sessionId;
    }

    public void setMaxSessionTimeoutInMinutes(int maxSessionTimeoutInMinutes) {
        this.maxSessionTimeoutInMinutes = maxSessionTimeoutInMinutes;
    }

    public int getMaxSessionTimeoutInMinutes() {
        return maxSessionTimeoutInMinutes;
    }
}

 private static Signature createSignature() throws Throwable {
     KeyStore ks = KeyStore.getInstance("JKS");
     char[] password = "password".toCharArray();
     FileInputStream fis = new FileInputStream("my-custom.jks");

     ks.load(fis, password);
     fis.close();

     String alias = "my-custom-alias";

         PrivateKey key = (PrivateKey) ks.getKey(alias, password);

     SignatureBuilder signatureBuilder = (SignatureBuilder) SAMLWriter.getSAMLBuilder()
             .getBuilder(Signature.DEFAULT_ELEMENT_NAME);
     Signature signature = (Signature) signatureBuilder.buildObject();

     KeyStore.ProtectionParameter protParam = new KeyStore.PasswordProtection(password);

     KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(alias, protParam);

     X509Certificate certificate = (X509Certificate) pkEntry.getCertificate();
     BasicX509Credential credential = new BasicX509Credential();
     credential.setEntityCertificate(certificate);
     credential.setPrivateKey(key);

     signature.setSigningCredential(credential);
     signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
     signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);

    SecurityConfiguration secConfig = Configuration.getGlobalSecurityConfiguration();

    SecurityHelper.prepareSignatureParams(signature, credential, secConfig, null);
    return signature;
 }
}

SAML Response

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:Response Destination="http://MYCLIENTWEBSITE.com" ID="abcd123456"
    IssueInstant="2018-08-12T17:55:28.256Z" Version="2.0"
    xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <saml2:Issuer
        Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">http://synesty.com</saml2:Issuer>
    <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="#abcd123456">
                <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#">
                        <ec:InclusiveNamespaces PrefixList="xs" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    </ds:Transform>
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                <ds:DigestValue>5xbz6J9daVi7sM9pLTuJ0+x0GIo=</ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>f+Ynig8GU6/pqDbnRYxzSCZYTrCUflmXjvgcjfgP5Wxr2qju9gHZszGQny5xgaj+MTkSwF1Y7wREO5f3srVs+Xt7fITUhxyyeop0py/zbbCgT48Vx82eCy5ISo5wGGj6WHVvq6j/UV7UYurAByqZOHKfkKcu551fLkGXaTixAVSeM9tgZ3DAxVn2kkdEvcw8ZIE2GCfOmQApjH9J5I+OB2JuQwGxyEJjYDLVjWd5OrJ/k619BfVwiQ0NMjNGXY3kXCnFNbHOh5k9iYmbmZWl4Kvuf1lZiHpJ/LAcusbk1oyscMZO1d3iKbEDTT8pcV1QnZQ7WLEBzrCoq8mS/DcYmA==</ds:SignatureValue>
        <ds:KeyInfo>
            <ds:X509Data>
                <ds:X509Certificate>MIIDUzCCAjugAwIBAgIEM1ls8DANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJVUzELMAkGA1UE
CBMCUEExDjAMBgNVBAcTBUV4dG9uMQ0wCwYDVQQKEwRzZWxmMQ0wCwYDVQQLEwRzZWxmMRAwDgYD
VQQDEwdoIG1hcmFtMB4XDTE4MDcxMTAzMjAyNloXDTI4MDcwODAzMjAyNlowWjELMAkGA1UEBhMC
VVMxCzAJBgNVBAgTAlBBMQ4wDAYDVQQHEwVFeHRvbjENMAsGA1UEChMEc2VsZjENMAsGA1UECxME
c2VsZjEQMA4GA1UEAxMHaCBtYXJhbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIts
MsABm9v2JgYq9H5fBFlR2Y04VbGIM9dEgq7kXfteHzNB5zPyIBEh/CpIuxKfkg0cftMhglL5aFGR
PgbfMMZ3w+zZDWNg5SY2O8WkcBBcC55GNX5bgZ0uYYuefiqqKOIh8QFZYI0sNW8eV+rJduyNNOgR
4ZO9yS950FBhmrmjE/b4blNMAnUH8q0MRjDzF/3vBcrQueLLCfCgxK/5Gv48XsiQNCYhGMEP1RM+
aX466hITH9IBri7bKNyh5REMYZCCHH3N6H80gmVxJRce5DVXHg9hqr+eSsnM0izSyy3GmkNvrAQZ
U7ZAc5pfOzUHhtN6jlkfSEDUDY5UUMn4SOsCAwEAAaMhMB8wHQYDVR0OBBYEFBbMWQqbqcpkvHh2
tpO5fMX1QH3IMA0GCSqGSIb3DQEBCwUAA4IBAQBGxJnq8Py5BA0UXijgaxdZn9ggaIz+IvknOpYg
ExCj5bG12sHLjAKXjDWpuB/9gX2SbrE3LsyHD2MBUqnVTfO2ZsWgiBGTfd2Wl6Yy1AuoMIigHu2A
xtu5G6/DeD/KEKYkQj9GcUg/rNAMBfKKSRb8Pif77AOQPoJgbSb8gxsKy8K3b7KjJtPAOgkQ0hDW
y9eRrbaV8Zcj77MPs7UppvjpI0n7FhyzJp1zpZJitYm978CrVs/qARos9VPAs7WnrOH2LR92+OaY
pSN/d0pXYRRI0fGzw8grpgy9kcV9SD6IApc3K5qoq1DskRZVvO1QVYO+QNAkM9Xca6vTDWOJ9qC5</ds:X509Certificate>
            </ds:X509Data>
        </ds:KeyInfo>
    </ds:Signature>
    <saml2p:Status>
        <saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </saml2p:Status>
    <saml2:Assertion ID="abcdedf1234567"
        IssueInstant="2018-08-12T17:55:28.256Z" Version="2.0" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
        <saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://synesty.com</saml2:Issuer>
        <saml2:Subject>
            <saml2:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified">UserJohnSmith</saml2:NameID>
            <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml2:SubjectConfirmationData
                    NotBefore="2018-08-12T17:55:28.256Z"
                    NotOnOrAfter="2018-08-12T17:57:28.256Z" Recipient="MYCLIENTWEBSITE"/>
            </saml2:SubjectConfirmation>
        </saml2:Subject>
        <saml2:Conditions>
            <saml2:OneTimeUse/>
            <saml2:AudienceRestriction>
                <saml2:Audience>https://my.audience.com</saml2:Audience>
            </saml2:AudienceRestriction>
        </saml2:Conditions>
        <saml2:AuthnStatement AuthnInstant="2018-08-12T17:55:28.659Z"
            SessionIndex="abcdedf1234567" SessionNotOnOrAfter="2018-08-12T17:55:28.674Z">
            <saml2:AuthnContext>
                <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>
            </saml2:AuthnContext>
        </saml2:AuthnStatement>
        <saml2:AttributeStatement>
            <saml2:Attribute FriendlyName="Value" Name="Value" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified">
                <saml2:AttributeValue
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">123456</saml2:AttributeValue>
            </saml2:Attribute>
        </saml2:AttributeStatement>
    </saml2:Assertion>
</saml2p:Response>

Upvotes: 3

Views: 9089

Answers (1)

Richard Tingle
Richard Tingle

Reputation: 17226

I believe that the SAML signature is sensitive to whitespace. As such it shouldn't be prettyprinted after it has been signed.

Replace

String originalAssertionString = XMLHelper.prettyPrintXML(xmlString);

With

String originalAssertionString = XMLHelper.nodeToString(xmlString);

Upvotes: 4

Related Questions