function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
KGrausKGraus 

XML Signature

I'm trying to sign an XML document in one of my Apex classes.  Does Apex have any built in classes for signing XML documents?  Ie, like using the javax.xml.crypto.dsig.XMLSignature class or the Apache Santuario library?  I've looked at the Crypto.sign function, but I don't think it does what I need it to do (or if it does, please let me know!).

 

Thanks for any suggestions!

 

Kelly 

KGrausKGraus

Does anyone have any ideas for this?  Are there any web services out there that do this?  Or would allow me to write a custom java class that they would run on their servers?  I'm trying to avoid having to write and host my own web service just to sign xml documents.

 

Thanks! 

GuyClairboisGuyClairbois

Hi KGraus,

I managed to product a valid XML Signature from APEX today. If you're still interested, let me know and I can post some more info here.

Guy

KGrausKGraus

Hi GuyClairbois,

 

Yes, I'm still very interested in this!  We're currently running a signing service on our own server, but I would love to integrate it all in APEX.

 

Thanks!

 

Kelly

GuyClairboisGuyClairbois

Hi Kelly,

 

key thing to keep in mind is that Salesforce.com does not offer a canonicalization method (there's an Idea on this here http://success.salesforce.com/search?type=Idea&keywords=canonicalization so make sure to vote on it)... So I made sure I deliver my XML canonicalized. I tested this with a small java canonicalizer script that compares the non-canonicalized input string with the canonicalized output string. If both are equal, you know that you are delivering a canonicalized string.

 

Also I noticed that for very large XML dom trees, APEX performance is not optimal. That's why I switched to a self-generated XML string instead of using the built-in XML dom functions. However, the below example should also be applicable to the dom structure.

 

Here's my class for generating the signature:

 

	public static String generateXMLSignature(String docBodyString){
		// 1. Apply the Transforms, as determined by the application, to the data object.
		// (no transforms are done, since the body should be delivered in canonicalized form (Force.com offers no canonicalization algorithm))
		
		// 2. Calculate the digest value over the resulting data object. 
		
		Blob bodyDigest = Crypto.generateDigest('SHA1',Blob.valueOf(docBodyString));
		
		// 3. Create a Reference element, including the (optional) identification of the data object, any (optional) transform elements, the digest algorithm and the DigestValue.
		// (Note, it is the canonical form of these references that are signed and validated in next steps.)

		String referenceString = '';
		referenceString += '';
		referenceString += '<ds:Reference URI="#msgBody">';
		referenceString += '<ds:Transforms>';
		referenceString += '<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform>';
		referenceString += '<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform>';
		referenceString += '</ds:Transforms>';
		referenceString += '<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>';
		referenceString += '<ds:DigestValue>'+EncodingUtil.base64Encode(bodyDigest)+'</ds:DigestValue>';
		referenceString += '</ds:Reference>';		 
		
		// 4. Create SignedInfo element with SignatureMethod, CanonicalizationMethod and Reference(s).
		String signedInfoString = '';
		signedInfoString += '<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">';
		signedInfoString += '<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod>';
		signedInfoString += '<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod>';
		signedInfoString += referenceString;
		signedInfoString += '</ds:SignedInfo>';
		
		// 5. Canonicalize and then calculate the SignatureValue over SignedInfo based on algorithms specified in SignedInfo.
		// (no canonicalization is done, since the signedinfo element should be delivered in canonicalized form (Force.com offers no canonicalization algorithm))

		String algorithmName = 'RSA-SHA1';
		// decrypted private key pkcs8 format from certificate
		String key = '[decryptedPrivateKeypkcs8formatfromcertificate]';
		Blob privateKey = EncodingUtil.base64Decode(key);
		
		Blob siBlob = Blob.valueOf(signedInfoString);
		
		Blob signatureValue = Crypto.sign(algorithmName, siBlob, privateKey);
		String signatureValueString = EncodingUtil.base64Encode(signatureValue);

		// 6. Construct the Signature element that includes SignedInfo, Object(s) (if desired, encoding may be different than that used for signing), KeyInfo (if required), and SignatureValue. 
		String signatureString = '';
		
		signatureString += '<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">';
		signatureString += signedInfoString; 
		signatureString += '<ds:SignatureValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#">'+signatureValueString+'</ds:SignatureValue>';
		signatureString += '<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">';
		signatureString += '<ds:X509Data xmlns:ds="http://www.w3.org/2000/09/xmldsig#">';
		signatureString += '<ds:X509Certificate xmlns:ds="http://www.w3.org/2000/09/xmldsig#">[the certificate used for signing]</ds:X509Certificate>';
		signatureString += '</ds:X509Data>';
		signatureString += '</ds:KeyInfo>';
		signatureString += '</ds:Signature>';
		
		return signatureString;
	}	

 As you can see I am generated the complete XML signature string, including the manually canonicalized SignedInfo bit. I then append this to my existing SOAP message (e.g. in the Header element). Also you need to add a reference to the element being signed, in this case I added the parameter id="msgBody" to my body element.

 

A tool which helped me a lot in getting the structure right is Oxygen XLM Editor. It can also verify the signature for you.

 

Next step would be to make this work for he xlm dom structure.  However I didn't attempt this, as my messages are huge and performance was not adequate.

 

I hope this helps you on your way,

Let me know if you have any additional questions.

Guy

Lee ShaLee Sha

Hi,

 

I was looking for it everywhere then I finally found your post.  I have a lot of questions further.  Please see you can help.

 

1) The solution that you provided Is for Apex callout or http callout using a self-formed soap xml?

2) What is the docBodyString passed to the method?  Is it the self-formed soap xml string?

3) How can I get the decrypted private key pkcs8 format from the certificate?

4) I have pasted my Apex code.  Have I used the reference "msgBody" correctly?

5) Where would I insert the xml signature in the below code snippet?

 

    public void doModelCallout() {

        String url = 'https://www.submissionservice.com:15314/SubmissionServiceSOAP';
   
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint(url);
        req.setMethod('POST');
        req.setHeader('Content-Type', 'text/xml');
       
        String reqXML = '<?xml version="1.0" encoding="utf-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:m0="http://ins.us.com/CommonHeaderV7" xmlns:m1="http://ins.us.com/SubmissionServiceFromDTDsV1">';
        reqXML +=    '<SOAP-ENV:Header>';
        reqXML +=     '<m:GetSubmissionHeaderRequest xmlns:m="http://ins.us.com/SubmissionServiceDataV1">';
        reqXML +=      '<m0:RequestHeader>';
        reqXML +=       '<m0:ServiceRequestContext>';
        reqXML +=        '<m0:ServiceInvoker>'+EncodingUtil.urlEncode('String','UTF-8')+'</m0:ServiceInvoker>';
        reqXML +=        '<m0:ServiceProvider>'+EncodingUtil.urlEncode('String','UTF-8')+'</m0:ServiceProvider>';
        reqXML +=        '<m0:RequestMessageId>'+EncodingUtil.urlEncode('String','UTF-8')+'</m0:RequestMessageId>';
        reqXML +=        '<m0:Timestamp>'+EncodingUtil.urlEncode('2001-12-17T09:30:47Z','UTF-8')+'</m0:Timestamp>';
  reqXML +=       '</m0:ServiceRequestContext>';       
        reqXML +=      '</m0:RequestHeader>';
        reqXML +=     '</m:GetSubmissionHeaderRequest>';
        reqXML +=    '</SOAP-ENV:Header>';
        reqXML +=    '<SOAP-ENV:Body id="msgBody">';
        reqXML +=     '<m:GetSubmissionRequest xmlns:m="http://ins.us.com/SubmissionServiceDataV1">';
        reqXML +=      '<m:SubmissionServiceXML>';
        reqXML +=       '<m1:Identity>';
        reqXML +=        '<m1:application_id>'+EncodingUtil.urlEncode('EST','UTF-8')+'</m1:application_id>';
        reqXML +=        '<m1:user_id>'+EncodingUtil.urlEncode('EST','UTF-8')+'</m1:user_id>';
        reqXML +=        '<m1:password/>';
        reqXML +=        '<m1:secured_id/>';
        reqXML +=        '<m1:newpassword/>';                       
        reqXML +=       '</m1:Identity>';
        reqXML +=       '<m:Request>';
        reqXML +=        '<m:Arguments>';
        reqXML +=         '<m:getSubmission>';
        reqXML +=          '<m1:SearchNumber>'+EncodingUtil.urlEncode('37655736','UTF-8')+'</m1:SearchNumber>';
        reqXML +=         '</m:getSubmission>';
        reqXML +=        '</m:Arguments>';
        reqXML +=       '</m:Request>';
        reqXML +=      '</m:SubmissionServiceXML>';
        reqXML +=     '</m:GetSubmissionRequest>';
        reqXML +=    '</SOAP-ENV:Body>';
        reqXML +=   '</SOAP-ENV:Envelope>';      
       
        req.setBody(reqXML);
        req.setClientCertificateName('SampleCert');//CA-signed


        try {
            HttpResponse res = h.send(req);
          
            modelOutputMessage = res.getBody();

        }
        catch (Exception ex) {
            modelOutputMessage = ex.GetMessage();
        }      
    }

 

Thanks for you time in advance.

GuyClairboisGuyClairbois

Hi Lee Sha,

Posting some additional information that I also provided in a private post:

 

Here's where I create the XML string (only part of the code, as an example). This will depend on the required structure for your xml message:

        String docOpenEnvString = '';
        String docOpenHeaderString = '';
        String docClosingHeaderString = '';
        String docOpenBodyString = '';
        String docSignatureString = '';
        String docClosingBodyString = '';
        String docClosingEnvString = '';
        String soapNS = 'http://schemas.xmlsoap.org/soap/envelope/';
        String xsi = 'http://www.w3.org/2001/XMLSchema-instance';
        String serviceNS = 'http://companyx.com/sco_generic/services'; 

		// add xml tag        
        docOpenEnvString += '<?xml version="1.0" encoding="UTF-8"?>';
        // open envelope
        docOpenEnvString += '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.xmlsoap.org/soap/envelope/ http://schemas.xmlsoap.org/soap/envelope/">';
        // put header (as long as msg is not signed)
        docOpenHeaderString += '<soapenv:Header>';
        docClosingHeaderString += '</soapenv:Header>';
        // open Body and Service
        docOpenBodyString += '<soapenv:Body xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" Id="msgBody"><ns0:EndUserDealService xmlns:ns0="http://companyx.com/sco_generic/services">';
        // add header
        docOpenBodyString += '<header><RequesterInfo><ID></ID><Name></Name><Version></Version><System></System></RequesterInfo><ResponderInfo><ID></ID><Name></Name><Version></Version><System></System></ResponderInfo><Flags><IsRetry>false</IsRetry><IsTest>false</IsTest><IsInternal>false</IsInternal></Flags><Identification><MessageID></MessageID><CorrelationID></CorrelationID><DocumentID></DocumentID><ProcessTypeID></ProcessTypeID></Identification><Context><ServiceUser></ServiceUser><Domain></Domain><Channel></Channel><BusinessFunction></BusinessFunction><Environment></Environment></Context></header>';
        // open Request
        docOpenBodyString += '<request>';
        
        // close Request
        docClosingBodyString += '</request>';
        // close Body and Service
        docClosingBodyString += '</ns0:EndUserDealService></soapenv:Body>';
        // close envelope
        docClosingEnvString += '</soapenv:Envelope>';
        
    	String docBodyString = docOpenBodyString + docContentString + docClosingBodyString;
    	
    	// 2. ADD SIGNATURE
    	docSignatureString = generateXMLSignature(docBodyString);
        String docString = docOpenEnvString + docOpenHeaderString + docSignatureString + docClosingHeaderString + docBodyString + docClosingEnvString;
     
    

 

 

here's where I send the message:

	            HttpRequest req = new HttpRequest();
	            req.setMethod('POST');

	            req.setEndpoint('https://api-tst.test.nl/partner');
	            req.setHeader('Content-Type', 'application/soap+xml');
	            req.setHeader('SOAPAction', 'https://api-tst.test.nl/partner');
	            req.setTimeout(120000);
	                
                    req.setBody(requestString);
	                
	            Http http = new Http();
	            HttpResponse res = http.send(req);

Combined with the signature code, this should get you a valid message. However, it might take a while before you get the exact match (took me a couple of days..)

 

Also you can use the SoapUI tool to test and compare with any valid xml file you might have (maybe the webService provider can provide you with a valid example including signature).

 

This should answer questions 1) and 2).

 

3) I used OpenSSL for this. Find the steps here on Jeff Douglas's (awesome) blog: http://blog.jeffdouglas.com/2010/07/06/using-rsa-sha1-with-salesforce-crypto-class/

4) looks like you did. Also see my code.

5) basically it depends on what your endpoint allows. In my example I put it in the <Header> but it can also be put in other locations (see the general XML Signature documentation for that, e.g. here: http://www.xml.com/pub/a/2001/08/08/xmldsig.html)

 

Hope this helps! Let me know if you have more questions.

Guy

Lee ShaLee Sha

Hi Guy,

 

Thank you for the details.  It is making sense for me now.

I have one question still.  I have a CA-signed certificate uploaded to Salesforce.com using Certificate and Key Management.  When I download this, I get a public certificate which I convert to X509 format .cer file.  I shared this with the External Web Service for implementing 2-way SSL.  Should I be generating decrypted private key PKCS#8 format from this .cer file?  In that case there are no steps mentioned to split the private key from it.

 

Or do I need to create a separate self-signed certificate using open SSL and then split cert file and private key?  In this case, I have to share two certificates with the External Web Service - one which is CA-signed and the other which is self-signed generated using openssl.  Is that right?

 

I have a limitation: My External Web Service does not accept self signed certificates.  How could I overcome this?  I thought the same CA-signed certificate will handle transport level and message level security.

 

Please help.

 

Lee Sha.

 

 

GuyClairboisGuyClairbois

I'd say the principle is right; you should be able to do both with 1 and the same CA-signed certificate.

 

I also used a CA-signed certificate for generating my keys. I just don't remember exactly how I got from the certificate to the .pem file. I think I followed some steps similar to these: http://nl.globalsign.com/en/support/ssl+certificates/microsoft/all+windows+servers/export+private+key+or+certificate/

 

Otherwise some further googling should get you going..

Good luck!

 

Guy

 

 

Lee ShaLee Sha

Hi Guy,

 

With Certificate and Key Management, we cannot have access to Private Key due to security policy.  Hence, I created a new certificate from outside of Salesforce and got hold of the Private key.  This is the only way you would have also done it.

 

Did you implement 2 Way SSL as well?  In that case, did you use the setClientCertificate method of HttpRequest class?  As per the definition, it expects to pass the Private key in base64 encoded format and the password.  I am doing the same and it returns handshake failure.  In case you implemented 2 Way SSL, can you tell me what did you pass in this method?

 

 

        String url = 'https://www.service.com:12324/SubServiceSOAP';
       
        //RSA Private Key
        String CACertificateStr = 'MIIEpQIBAAKCAQEA7K070P2t2GcCx4IGw52uYswVlhE+g8LmTingnWlb9WQRlRrc...';
       
        //Base64 encoded Private Key
        CACertificateStr = EncodingUtil.base64Encode(Blob.valueOf(CACertificateStr));  
   
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint(url);
        req.setMethod('POST');
        req.setHeader('Content-Type','application/soap+xml');
        req.setHeader('SOAPAction', url);

 

        req.setClientCertificate(CACertificateStr, '');
        
        String reqXML = 'Soap XML with signature';
       
        req.setBody(reqXML);
       
        HttpResponse res = h.send(req);

 

 

Thanks,

Lee Sha.

GuyClairboisGuyClairbois

Yes, the certificate I used was from an external source also.

 

We did not implement 2 way SSL. You might find some more information in other discussions/blogs. Sorry but can't help you with that.

 

However we do have plans on implementing it in the near future so if you manage to resolve your problem, it would be great if you can post it here..

 

Best,

Guy

 

 

Lee ShaLee Sha

Sure.  This has been going on for over a month now and I have to somehow find a solution.

sandeep varsandeep var

Hi Guy,

 

this is a very helpful post i was looking all around but caught hold of this here. i used your approach but data power firewall is saying hash values does not match the digest value. more over String docBodyString = docOpenBodyString + docContentString + docClosingBodyString;

 

i am not able to get where did docContentstring came from.

GuyClairboisGuyClairbois

Hi Sandeep,

Getting the hash value to match is the hardest part of it all, as I already mentioned in my posts.

Some caveats I remember I struggled with:

- did you properly unencrypt the private key?

- did you properly canonicalize all your data?

Tips on this are in my earlier posts.

 

docContentString is the actual xml content that I am sending. It consists of a set of xml data in a certain structure, e.g.:

<deal>
 <price>15.54</price>
 <quantity>100</quantity>
</deal>

 

sandeep varsandeep var

Thanks Guy,

 

i did the following commands in OPENSSL for genearation certificate and private key

 

private key and CSR(certificate signing request)


req -new -newkey rsa:2048 -nodes -keyout localhost.key -out localhost.csr

 

For Self Signed Certificate:

 

x509 -req -days 365 -in localhost.csr -signkey localhost.key -out localhost.crt

 

pkcs8 decrypted private key 

 

pkcs8 -topk8 -nocrypt -in localhost.key  -out pkcs8.pem

 

below is the code :

 

public class serviceClient{

public static String generateXMLSignature(String docBodyString){
// 1. Apply the Transforms, as determined by the application, to the data object.
// (no transforms are done, since the body should be delivered in canonicalized form (Force.com offers no canonicalization algorithm))

// 2. Calculate the digest value over the resulting data object.
//String dBS= docBodyString;


Blob bodyDigest = Crypto.generateDigest('SHA1',Blob.valueOf(docBodyString));
system.debug('Testing*****'+bodyDigest);
// 3. Create a Reference element, including the (optional) identification of the data object, any (optional) transform elements, the digest algorithm and the DigestValue.
// (Note, it is the canonical form of these references that are signed and validated in next steps.)

String referenceString = '';
referenceString += '';
referenceString += '<ds:Reference URI="#id-1367626116">';
referenceString += '<ds:Transforms>';
//referenceString += '<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform>';
referenceString += '<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform>';
referenceString += '</ds:Transforms>';
referenceString += '<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>';
referenceString += '<ds:DigestValue>'+EncodingUtil.base64Encode(bodyDigest)+'</ds:DigestValue>';
referenceString += '</ds:Reference>';

// 4. Create SignedInfo element with SignatureMethod, CanonicalizationMethod and Reference(s).
String signedInfoString = '';
signedInfoString += '<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">';
signedInfoString += '<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod>';
signedInfoString += '<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod>';
signedInfoString += referenceString;
signedInfoString += '</ds:SignedInfo>';

// 5. Canonicalize and then calculate the SignatureValue over SignedInfo based on algorithms specified in SignedInfo.
// (no canonicalization is done, since the signedinfo element should be delivered in canonicalized form (Force.com offers no canonicalization algorithm))

String algorithmName = 'RSA-SHA1';
// decrypted private key pkcs8 format from certificate
String key = 'PKCS8 DECRPTED PRIVATE KEY';

Blob privateKey = EncodingUtil.base64Decode(key);

Blob siBlob = Blob.valueOf(signedInfoString);

Blob signatureValue = Crypto.sign(algorithmName, siBlob, privateKey);
String signatureValueString = EncodingUtil.base64Encode(signatureValue);
//String signatureValueString = EncodingUtil.base64Encode(privateKey);
// 6. Construct the Signature element that includes SignedInfo, Object(s) (if desired, encoding may be different than that used for signing), KeyInfo (if required), and SignatureValue.
String signatureString = '';
signatureString += '<wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">';
signatureString += '<wsse:BinarySecurityToken EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" wsu:Id="CertId-1828792" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">CERTIFICATE</wsse:BinarySecurityToken>';
signatureString += '<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">';
signatureString += signedInfoString;
signatureString += '<ds:SignatureValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#">'+signatureValueString+'</ds:SignatureValue>';
signatureString += '<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">';
signatureString += '<wsse:SecurityTokenReference wsu:Id="STRId-308417122" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><wsse:Reference URI="#CertId-1828792" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/></wsse:SecurityTokenReference>';
//signatureString += '</ds:X509Data>';
signatureString += '</ds:KeyInfo>';
signatureString += '</ds:Signature>';
signatureString += '</wsse:Security>';
return signatureString;
}
public static void doModelCallout() {

String url = 'https://ws-stga.bcbsfl.com:443/cip/1.0/FloridaBlueMemberEmailUpdate';
Http h = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint(url);
req.setMethod('POST');
req.setHeader('Content-Type','application/soap+xml');
req.setHeader('SOAPAction', url);

String ApplicationId = '270';
String TransactionId='1231323131';
Date TransactionDate= System.Today();
//String
String docOpenEnvString = '';
String docOpenHeaderString = '';
String docClosingHeaderString = '';
String docOpenBodyString = '';
String docSignatureString = '';
String docClosingBodyString = '';
String docClosingEnvString = '';
String soapNS = 'http://schemas.xmlsoap.org/soap/envelope/';
String xsi = 'http://www.w3.org/2001/XMLSchema-instance';
String serviceNS = 'http://essent.nl/sco_generic_b2b/services';

// add xml tag
docOpenEnvString += '<?xml version="1.0" encoding="UTF-8"?>';
// open envelope
docOpenEnvString += '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mem="http://MemberUpdater.crm.floridablue.com" xsi:schemaLocation="http://schemas.xmlsoap.org/soap/envelope/ http://schemas.xmlsoap.org/soap/envelope/">';
// put header (as long as msg is not signed)
docOpenHeaderString += '<soapenv:Header>';
docClosingHeaderString += '</soapenv:Header>';

 

// open Body and Service

// open Request
docOpenBodyString += '<soapenv:Body wsu:Id="id-1367626116" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">..............</soapenv:Body>';

// close Request
// docClosingBodyString += '</request>';
// close Body and Service
// docClosingBodyString += '</ns0:EndUserDealService></soapenv:Body>';
// close envelope
docClosingEnvString += '</soapenv:Envelope>';

String docBodyString = docOpenBodyString + docClosingBodyString;

// 2. ADD SIGNATURE
docSignatureString = generateXMLSignature(docBodyString);
String docString = docOpenEnvString + docOpenHeaderString + docSignatureString + docClosingHeaderString + docBodyString + docClosingEnvString;

 

req.setBody(docString);

HttpResponse res = h.send(req);
System.Debug(res);

}

}

 

I am doing anything wrong in this, please let me know thanks.

 

GuyClairboisGuyClairbois

Hi Sandeep,

 

I can't judge by every detail if what you're doing is correct or not, but the general principle looks fine. I was at the same stage as you are, struggling with getting my signature accepted. What I did was:

- use a canonicalization tool to check whether my string was canonicalized (this is very important, as the remote server does the same to your message before calculating if the signature is valid). If not, modify the string until it is canonicalized (using the right canonicalization method)

- use xml tools like Oxygen XML to validate your xml and have that tool generate a signature (using your private key). Then check if the remote server accepts that signature and also check if your code generates the same signature.

 

I hope this helps. 

 

 

sandeep varsandeep var

Thanks a lot Guy, i am failing how to use the XML oxygen tool, seriously if you can provide me the steps you used to generate the self signed certificate and required keys that would be helpful.

 

Thanks in advance.

GuyClairboisGuyClairbois

Hi Sandeep,

 

I did not use a self-signed certificate but a certificate purchased from GlobalSign. But the process should be more or less equal (I'm not a certificates expert, though).

 

For generating the keys, did you follow these steps pointed out by Jeff Douglas?

http://blog.jeffdouglas.com/2010/07/06/using-rsa-sha1-with-salesforce-crypto-class/

 

Oleksandr VlasenkoOleksandr Vlasenko

Hi Guy,

How did you canonicalize the SignedInfo.
I'm trying to do this but nothing is coming out:

// 5. Canonicalize and then calculate the SignatureValue over SignedInfo based on algorithms specified in SignedInfo.
		// (no canonicalization is done, since the signedinfo element should be delivered in canonicalized form (Force.com offers no canonicalization algorithm))

		String algorithmName = 'RSA-SHA1';
		// decrypted private key pkcs8 format from certificate
		String key = '[decryptedPrivateKeypkcs8formatfromcertificate]';
		Blob privateKey = EncodingUtil.base64Decode(key);
		
		Blob siBlob = Blob.valueOf(signedInfoString);
		
		Blob signatureValue = Crypto.sign(algorithmName, siBlob, privateKey);
		String signatureValueString = EncodingUtil.base64Encode(signatureValue);


Only with this utility http://www.signfiles.com/xml-signer/ I can create valid SignatureValue. I have used Oxygen XML to canonicalize but it didn't help.

Do you have any ideas?
 

Tim ByrnesTim Byrnes
I know this is 5+ years old, but I'm crossing my fingers that someone is still set to receive emails when replies are posted...

I am going through these exact steps for a callout and am having the same problems as Oleksandr above...

The DigestValue I'm generating in Salesforce perfectly matches the DigestValue that SoapUI generated for my sample data, but the SignedInfo node hash is giving me a completely different value for the SignatureValue node and I'm completely stuck.

My canonicalized string perfectly matches what SC14N (https://www.cryptosys.net/sc14n/) tells me it should be, and a manual reviewing of the rules (https://www.di-mgt.com.au/xmldsig-c14n.html) seems to agree.  I've also tried a litany of variations just in case....  Every single attempt has results in:
The signature or decryption was invalid; nested exception is org.apache.ws.security.WSSecurityException

Are they any tips that anyone here can share that might help?  Thanks...