Search code examples
javaxmldigital-signatureopensaml

sign a detached signature using OpenSaml


<soap:Envelope
xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<env:Header
    xmlns:env="http://www.w3.org/2003/05/soap-envelope">
    <wsse:Security
        xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
        xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soap:mustUnderstand="true">
        <ds:Signature
            xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="SIG-B9561BC9E482A7482717165370736895">
            <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-sha512"/>
                <ds:Reference URI="#id-B9561BC9E482A7482717165370736684">
                    <ds:Transforms>
                        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                        </ds:Transform>
                    </ds:Transforms>
                    <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"/>
                    <ds:DigestValue>some value</ds:DigestValue>
                </ds:Reference>
            </ds:SignedInfo>
            <ds:SignatureValue>some value </ds:SignatureValue>
        </ds:Signature>
    </wsse:Security>
</env:Header>
<soap:Body
    xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="id-B9561BC9E482A7482717165370736684">
    <Test/>
</soap:Body>
</soap:Envelope>

I have the requirement to sign the soap body as shown above.

I believe this is called detached signing.

I want to use opensaml and the implementation till now looks like this:

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

Then marshall the signature and call Signer.signObject(signature);. Reference: https://blog.samlsecurity.com/2012/11/signing-with-opensaml.html

I also added content reference pointing to the soap body i want to sign

Element bodyElement = (Element) doc.getDocumentElement().getElementsByTagName("SOAP-Body").item(0);
        String bodyId = "body123"; // Unique reference ID
        bodyElement.setAttribute("Id", bodyId);
        bodyElement.setIdAttribute("Id", true);

        DocumentInternalIDContentReference uriContentReference = new DocumentInternalIDContentReference( bodyId);
        uriContentReference.setDigestAlgorithm(SignatureConstants.ALGO_ID_DIGEST_SHA256);
        uriContentReference.getTransforms().add(SignatureConstants.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);

        signature.getContentReferences().add(uriContentReference);

But I get this error:

Exception in thread "main" org.opensaml.xmlsec.signature.support.SignatureException: Signature computation error
    at org.opensaml.xmlsec.signature.support.impl.provider.ApacheSantuarioSignerProviderImpl.signObject(ApacheSantuarioSignerProviderImpl.java:62)
    at org.opensaml.xmlsec.signature.support.Signer.signObject(Signer.java:73)
    at com.amazon.bancomatpayremittancelambda.service.OpenSAMLDetachedSignature.main(OpenSAMLDetachedSignature.java:98)
Caused by: org.apache.xml.security.signature.ReferenceNotInitializedException: Cannot resolve element with ID body123
Original Exception was org.apache.xml.security.signature.ReferenceNotInitializedException: Cannot resolve element with ID body123
Original Exception was org.apache.xml.security.signature.ReferenceNotInitializedException: Cannot resolve element with ID body123
Original Exception was org.apache.xml.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID body123
    at org.apache.xml.security.signature.Reference.calculateDigest(Reference.java:744)
    at org.apache.xml.security.signature.Reference.generateDigestValue(Reference.java:405)
    at org.apache.xml.security.signature.Manifest.generateDigestValues(Manifest.java:205)
    at org.apache.xml.security.signature.XMLSignature.sign(XMLSignature.java:631)
    at org.opensaml.xmlsec.signature.support.impl.provider.ApacheSantuarioSignerProviderImpl.signObject(ApacheSantuarioSignerProviderImpl.java:59)
    ... 2 more

tl;dr

How can I create the above xml request using openSaml?


Solution

  • I was able to figure out how to sign a detached internal signature by looking at some UTs in openSaml library :

     // 📝 Create Signature object
     Signature signature = (Signature) builderFactory.getBuilder(Signature.DEFAULT_ELEMENT_NAME)
                    .buildObject(Signature.DEFAULT_ELEMENT_NAME);
    
    // set signature properties
     signature.setSigningCredential(credential);
     signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
     signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
    
    //Since you want to sign the body within the same document create a reference to it with transforms and digest algorithm
    
    DocumentInternalIDContentReference uriContentReference = new DocumentInternalIDContentReference( "body123");
            uriContentReference.setDigestAlgorithm(SignatureConstants.ALGO_ID_DIGEST_SHA256);
            uriContentReference.getTransforms().add(SignatureConstants.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
            uriContentReference.getTransforms().add(SignatureConstants.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
    
    //add the above content reference to the signature
    signature.getContentReferences().add(uriContentReference);
    
    //add the signature to the header
    header.getUnknownXMLObjects().add(signature);
    
    // Assemble the envelope
      soapEnvelope.setHeader(header);
      soapEnvelope.setBody(bodyElement);
    
    //Now the most important part is marshalling the right part of the xml. In my case since this was detached signature, I needed to marshall the document containing the signature and the body to be signed
     Marshaller envelopeMarshaller = marshallerFactory.getMarshaller(soapEnvelope);
     Element envelopeElement = envelopeMarshaller.marshall(soapEnvelope);
    
    //finally sign the object
     Signer.signObject(signature);
    

    This created the request as expected.