Search code examples
javasamloneloginopensaml

SAML - How to Correctly Sign both Response(Message) and Assertion


I'm using Java8, and I tried both OpenSAML(v3) and OneLogin but none of them worked.

  • Validation Check Tool : https://samltool.io/
  • Signing Assertion Only : Valid
  • Signing Response Only : Valid
  • Signing Both : Response Signature Invalid

Using this site I tried

  • Sign Both : Valid
  • Sign Assertion > Copy then Sign Response : Valid
  • Sign Assertion via Code > Sign Response : Response Signature Invalid

Response Signature Inavlid Message looks like this "XMLJS0013: Cryptographic error: Invalid digest for uri '$REFERENCE_URI'. Calculated digest is $CALCULATED_DIGESTVALUE but the xml to validate supplies digest $MY_DIGESTVALUE"

When I manually replace $MY_DIGESTVALUE with $CALCULATED_DIGESTVALUE, the error message changes to "The calculated signature does not match the signature of the message."

/* Methods */
// For OneLogin
private Object stringTOobject(String samlStr) throws Exception {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(true);
    DocumentBuilder builder = factory.newDocumentBuilder();
    Document document = builder.parse(new ByteArrayInputStream(samlStr.getBytes("UTF-8")));  
    Element samlElement = document.getDocumentElement();
    Unmarshaller unmarshaller = XMLObjectProviderRegistrySupport.getUnmarshallerFactory().getUnmarshaller(samlElement);
    return unmarshaller.unmarshall(samlElement);
}
// For OpenSAML
private <T> T createObject(Class<T> clazz) throws Exception {
    QName defaultElementName = (QName) clazz.getDeclaredField("DEFAULT_ELEMENT_NAME").get(null);
    Object object = SAMLutil.getSAMLBuilder().getBuilder(defaultElementName).buildObject(defaultElementName);
    return clazz.cast(object);
}
private KeyInfo getKeyInfo(BasicX509Credential basicCredential, HashMap reqMap) throws Exception {
    KeyInfo result = null;
    KeyName keyName = createObject(KeyName.class);
    keyName.setValue($KeyID);
    X509KeyInfoGeneratorFactory kiFactory = new X509KeyInfoGeneratorFactory();
    kiFactory.setEmitEntityCertificate(true);
    result = kiFactory.newInstance().generate(basicCredential);
    result.getKeyNames().add(keyName);
    // To remove linebreaks in Certificate
    result.getX509Datas().get(0).getX509Certificates().get(0).setValue($X509CertificateString);
    return result;
}
private Signature getSig(HashMap reqMap) throws Exception {
    Signature result = createObject(Signature.class);
    BasicX509Credential basicCredential = getBasicX509Credential($X509CertificateString);
    basicCredential.setPrivateKey($PrivateKey);
    KeyInfo keyInfo = getKeyInfo(basicCredential, reqMap);
    result.setKeyInfo(keyInfo);
    result.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
    result.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1);
    result.setSigningCredential(basicCredential);
    return result;
}

/* OneLogin */
// 1. Sign Assertion > Turn signed string back to Assertion
AssertionMarshaller aMarshaller = new AssertionMarshaller();
String astStr = Util.addSign(aMarshaller.marshall(assertion), privateKey, cert, Constants.RSA_SHA1);
assertion = (Assertion) stringTOobject(astStr);
// 2. Add Assertion into Response
response.getAssertions().add(assertion);
// 3. Sign Response > Turn signed string back to Response
ResponseMarshaller marshaller = new ResponseMarshaller();
String resStr = Util.addSign(marshaller.marshall(response), privateKey, cert, Constants.RSA_SHA1);
response = (Response) stringTOobject(resStr);
// 4. To XMLString
String samlStr = SerializeSupport.nodeToString(marshaller.marshall(response));

/* OpenSAML */
// 1. Sign Assertion
Signature signature = getSig(reqMap);
assertion.setSignature(signature);
((SAMLObjectContentReference)signature.getContentReferences().get(0)).setDigestAlgorithm(SignatureConstants.ALGO_ID_DIGEST_SHA1);
AssertionMarshaller aMarshaller = new AssertionMarshaller();
aMarshaller.marshall(assertion);
Signer.signObject(signature);
// 2. Add Assertion into Response
response.getAssertions().add(assertion);
// 3. Sign Response
Signature signature = getSig(reqMap);
response.setSignature(signature);
((SAMLObjectContentReference)signature.getContentReferences().get(0)).setDigestAlgorithm(SignatureConstants.ALGO_ID_DIGEST_SHA1);
ResponseMarshaller marshaller = new ResponseMarshaller();
marshaller.marshall(response);
Signer.signObject(signature);
// 4. To XMLString
String samlStr = SerializeSupport.nodeToString(marshaller.marshall(response));

What am I missing?

Any help would be really appreciated.


Solution

  • Ok it was the line break in assertion that was making this whole issue. I just replaced the special characters and it magically works so well now.. wow.... The tones of time for nothing... wow.....

    astStr = astStr.replace("&#13;", "").replace("&#xD;", "");