How to manually calculate XML signature in C# - c#

I know there is a SignedXml class for signing XML document. However I am trying to calculate the signature value on my own, to understand what exactly is happening. More exactly, I am trying to sign a soap:Body element of SOAP message.
I have manually crated the Signature tag so it matches the template. Also I have succesfully calculated the digest of and inserted this value into DigestValue tag. However I am not able to calculate correct value for the SigantureValue tag.
My approach is:
Canonicalize the SignedInfo tag using excl n14n transform
Hash the canonicalized data using SHA256
Use RSACryptoServiceProvider to sign the hash value
My code looks something like this:
// 1 Canonicalize the SignedInfo tag
XmlDsigExcC14NTransform serializer = new XmlDsigExcC14NTransform();
XmlDocument doc = new XmlDocument();
string toBeCanonicalized = signedInfoTag.OuterXml;
doc.LoadXml(toBeCanonicalized);
serializer.LoadInput(doc);
string c14n = new StreamReader((Stream)serializer.GetOutput(typeof(Stream))).ReadToEnd();
// 2 Hash the SignedInfo tag
SHA256 HashAlg = SHA256.Create();
byte[] hash = HashAlg.ComputeHash(Encoding.UTF8.GetBytes(c14n));
// 3 Sign the hash
byte[] signature;
using (RSACryptoServiceProvider csp = new RSACryptoServiceProvider())
{
csp.ImportParameters(((RSACryptoServiceProvider)mCertificate.PrivateKey).ExportParameters(true));
signature = csp.SignData(Encoding.UTF8.GetBytes(c14n), "SHA256");
}
signValueTag.InnerText = Convert.ToBase64String(signature);
What I am doing wrong?
The example of valid SOAP message is here:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/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="1">
<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="X509-A72D6FD4C41B1F545F14700558816386">MIID6zCCAtOgAwIBAgIEAQAAAzANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJDWjEaMBgGA1UEAwwRR0ZSIEVFVCB0ZXN0IENBIDExLTArBgNVBAoMJEdlbmVyw6FsbsOtIGZpbmFuxI1uw60gxZllZGl0ZWxzdHbDrTAeFw0xNjA1MTkxMjQ1MDJaFw0xODA1MTkxMjQ1MDJaMFMxCzAJBgNVBAYTAkNaMRUwEwYDVQQDDAxDWjEyMTIxMjEyMTgxFzAVBgNVBAoMDk9zb2JhIEZ5emlja8OhMRQwEgYDVQQFEwtUMDAwMDAwMDAwMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMt0eW9n+RB0PSawKSJbtAg3j7e1I5p7P9OvEj0n9raEMI496Zuw7s4VaE8JEX4iowjWhlPIOPljDiAXX6HgZzH4PDps0rFm388KZxj7Ek/ZLyyh5jRovc0Yccfgm3i2huBepk7ZtifZXOZzDEDT0CZsxRpypZJp9PK6SOdj2zPIc11F+prwsGQCDAZsRtama5/W5qn2YUWjjk+4c5Zu3TcknC7bcp1dg7RJ9yMtNiYPY7LNV3uWQhAXZVFpmOpbfYwT1F8H3/UrWDSJ5zrHshICPreMyZ0skU9SUANQ8QQKE6lSlgSs59YaeEmCyGtpttVjN+iR/L9M9FRtq3ZhZz0CAwEAAaOBwTCBvjAeBgNVHREEFzAVgRNlcG9kcG9yYUBmcy5tZmNyLmN6MB8GA1UdIwQYMBaAFHpa/A3L7DamDdppGWaMm++Cw6k0MB0GA1UdDgQWBBQbyTbuGBZdorOZZm7usMraGAJ2STBMBgNVHSAERTBDMEEGCmCGSAFlAwIBMAIwMzAxBggrBgEFBQcCAjAlGiNUZW50byBjZXJ0aWZpa2F0IEpFIFBPVVpFIFRFU1RPVkFDSTAOBgNVHQ8BAf8EBAMCBsAwDQYJKoZIhvcNAQELBQADggEBAC3L8Bm7ZlWui9xWrjM00SlvCokpc2ldGCxNvj4hANaISoLRdPVZAPeLd1X4KsRyxOIazR5oq3EKVZV1ZP3sCF4QFL+SqurkPiBbIrrbABDLyDpf/8DIfyA1x/+zNpN9ul9j9Ca1739P4L1x3wpQcYhEuvSrTiLztndlJb69LXgYZOFfqcBSRedRuMwRdtux9OWkkZjrd9wNHTCDOIfOPaPRRoq5IPHP32shsXaTLvhsT7ktvR5Fr/SQ8CkWq3U6tdcOQRN35ZWyYSyOd5/vkJqK773R/gAeBUE80gpLMdqgVj8HaD7RlHrYyYJEVUI9gbctTfUIJW/9LZ3K78JLXXg=</wsse:BinarySecurityToken>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="SIG-A72D6FD4C41B1F545F147005588163810">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="soap"/>
</ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#id-A72D6FD4C41B1F545F14700558816389">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList=""/>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>iiyihYsFMjO7QxIVCauydehAhjSm5LZlRGm3lT0VFY0=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>cGbhgNKCwrmUPXg2AKgqs1mceNcywK8BvrmmrOH627/3vadzKVnPiTn6ZaLBAcV1pYgTpNvh7RvAa8uZYXmS77YCQcYIOErbWKSTDVwBWv63d8fLm9Ljpx/1/PZrI7zSeIafXTLwPB2Lzt239ylZWPdhfg9XMhS43k4p7u1DZerVeRNSi76Q8u6jIWadDIQkn9mVEbhL5RIRGPoGJBof9QQVk42NHChdESW2RFXG7SSs2VYmdZ+IQUdEC7uPFoT/vxK2My1hGhYhvl6HNbMd5VIz/xMlDPrOCzbLWkA7oqyqSboTCObwkTwD2V20sxn6rb8mtak55zYaGXJldno66g==</ds:SignatureValue>
<ds:KeyInfo Id="KI-A72D6FD4C41B1F545F14700558816387">
<wsse:SecurityTokenReference 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" wsu:Id="STR-A72D6FD4C41B1F545F14700558816388">
<wsse:Reference URI="#X509-A72D6FD4C41B1F545F14700558816386" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</SOAP-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-A72D6FD4C41B1F545F14700558816389">
<Trzba xmlns="http://fs.mfcr.cz/eet/schema/v3">
<Hlavicka dat_odesl="2016-09-19T19:06:37+02:00" prvni_zaslani="false" uuid_zpravy="f5ce1350-e688-4247-b1af-3d2bc592b83c"/>
<Data celk_trzba="34113.00" cerp_zuct="679.00" cest_sluz="5460.00" dan1="-172.39" dan2="-530.73" dan3="975.65" dat_trzby="2016-08-05T00:30:12+02:00" dic_popl="CZ1212121218" id_pokl="/5546/RO24" id_provoz="273" porad_cis="0/6460/ZQ42" pouzit_zboz1="784.00" pouzit_zboz2="967.00" pouzit_zboz3="189.00" rezim="0" urceno_cerp_zuct="324.00" zakl_dan1="-820.92" zakl_dan2="-3538.20" zakl_dan3="9756.46" zakl_nepodl_dph="3036.00"/>
<KontrolniKody>
<pkp cipher="RSA2048" digest="SHA256" encoding="base64">D84gY6RlfUi8dWdhL1zn0LE0s+aqLohtIxY0y88GoG5Ak8pBEH3/Ff2aFW7H6fvRxDMKsvM/VIYtUQxoDEctVGMSU/JDf9Vd0eQwgfLm683p316Sa4BUnVrIsHzwMyYkjpn66I072G2AvOUP4X5UiIYtHTwyMVyp+N/zzay3D7Q619ylDb6puN2iIlLsu+GNSB9DvsQbiLXPH6iK0R9FpR15v2y+0Uhh8NNJKl7O8Us9jbgokrA9gze+erQbhmwTm2nn2+7JGrPDqhyhwWZNLUziGSbC99wJpkEnIs0das/4hFNE3DnLvv4MsXwWCLOUZty6t6DAijlCzQj7KFKw0g==</pkp>
<bkp digest="SHA1" encoding="base16">8F8ABFEB-B76E7064-343A1460-6C6E6D86-B0F99C24</bkp>
</KontrolniKody>
</Trzba>
</soap:Body>
</soap:Envelope>

After banging my head against the wall trying to encrypt, hash, sign, with the certificate keys, I decided to check if there was some source code available for the SignedXml class. Thank God there is a reference source for .NET. So I went to the SignedXml.ComputeSignature method, to see how it worked. TL;DR, here is the code, based on those sources:
// Get signature description (using signature method algorithm http://www.w3.org/2001/04/xmldsig-more#rsa-sha256)
var signatureDescription
= System.Security.Cryptography.CryptoConfig.CreateFromName(System.Security.Cryptography.Xml.SignedXml.XmlDsigRSASHA256Url) as System.Security.Cryptography.SignatureDescription;
if (signatureDescription == null)
throw new System.Security.Cryptography.CryptographicException("SignatureDescriptionNotCreated");
// Get hash algorithm from signature description
System.Security.Cryptography.HashAlgorithm hashAlg = signatureDescription.CreateDigest();
if (hashAlg == null)
throw new System.Security.Cryptography.CryptographicException("CreateHashAlgorithmFailed");
// Load SignedInfo OuterXml in a separate XmlDocument, and canonicalize
var doc = new System.Xml.XmlDocument();
string toBeCanonicalized = signedInfoTag.OuterXml;
doc.LoadXml(toBeCanonicalized);
var transform = new System.Security.Cryptography.Xml.XmlDsigExcC14NTransform();
transform.LoadInput(doc);
byte[] hashvalue = transform.GetDigestedOutput(hashAlg); // hashvalue is not used, but I
// think this process allows to
// use hashAlg to get the
// SignatureValue later
// Create signature formatter using certificate's private key
System.Security.Cryptography.AsymmetricSignatureFormatter asymmetricSignatureFormatter
= signatureDescription.CreateFormatter(mCertificate.PrivateKey);
// Get signature value
byte[] signatureValueBytes = asymmetricSignatureFormatter.CreateSignature(hashAlg);
// Set signature value to node text, as Base64 string
signValueTag.InnerText = Convert.ToBase64String(signatureValueBytes)

Try to use
string toBeCanonicalized = signedInfoTag.InnerXml;
instead of
string toBeCanonicalized = signedInfoTag.OuterXml;
Complete code:
// 1 Canonicalize the SignedInfo tag
XmlDsigExcC14NTransform serializer = new XmlDsigExcC14NTransform();
XmlDocument doc = new XmlDocument();
string toBeCanonicalized = signedInfoTag.InnerXml;
doc.LoadXml(toBeCanonicalized);
serializer.LoadInput(doc);
string c14n = new StreamReader((Stream)serializer.GetOutput(typeof(Stream))).ReadToEnd();
// 2 Hash the SignedInfo tag
SHA256 HashAlg = SHA256.Create();
byte[] hash = HashAlg.ComputeHash(Encoding.UTF8.GetBytes(c14n));
// 3 Sign the hash
byte[] signature;
using (RSACryptoServiceProvider csp = new RSACryptoServiceProvider())
{
csp.ImportParameters(((RSACryptoServiceProvider)mCertificate.PrivateKey)
.ExportParameters(true));
signature = csp.SignData(Encoding.UTF8.GetBytes(c14n), "SHA256");
}
signValueTag.InnerText = Convert.ToBase64String(signature);

Related

Receiving side failing to validate signed XML message

I'm trying to sign some XML with C# according to the spec sheet laid out to us for our test application. We have to:
Canonicalize the message.
Use the Digest algorithm to create digest of the entire message.
Add the digest to the Signed Info element.
Canonicalize the Signed Info.
Input private key and canonicalized signed Info into the specified signing algorithm to generate digital signature.
Add generated digital signature to the signature element.
This is what I currently have.
public static XmlElement Sign(XmlDocument msgDoc)
{
XmlDsigExcC14NTransform transform = new XmlDsigExcC14NTransform();
transform.LoadInput(msgDoc);
msgDoc.Load(transform.GetOutput() as Stream);
var signedXml = new SignedXml(msgDoc)
{
SigningKey = Certificate.GetRSAPrivateKey(),
};
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
var reference = new Reference();
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform(true));
reference.AddTransform(new XmlDsigC14N11Transform()); // Custom transform
reference.Uri = string.Empty;
reference.DigestMethod = SignedXml.XmlDsigSHA256Url;
signedXml.SignedInfo.AddReference(reference);
KeyInfo keyInfo = new KeyInfo();
var keyInfoData = new KeyInfoX509Data();
keyInfoData.AddIssuerSerial(CertManager.Certificate.Issuer, CertManager.Certificate.SerialNumber);
keyInfoData.AddSubjectName(CertManager.Certificate.Subject);
keyInfo.AddClause(keyInfoData);
signedXml.KeyInfo = keyInfo;
signedXml.ComputeSignature();
XmlElement signature = signedXml.GetXml();
// This part until the return was done to ensure we get the 'ds' prefix.
// Code taken from: https://stackoverflow.com/questions/30579938/generate-digital-signature-but-with-a-specific-namespace-prefix-ds
SetPrefix("ds", signature);
signedXml.LoadXml(signature);
signedXml.SignedInfo.References.Clear();
signedXml.ComputeSignature();
ReplaceSignature(signature, Convert.ToBase64String(signedXml.SignatureValue));
XmlNode signatureElement = msgDoc.ImportNode(signature, true);
return msgDoc.DocumentElement.InsertAfter(msgDoc.ImportNode(signature, true), msgDoc.DocumentElement.FirstChild) as XmlElement;
}
The digest algorithm is: http://www.w3.org/2001/04/xmlenc#sha256
The canonicalization algorithm is: http://www.w3.org/2001/10/xml-exc-c14n#
We are using two transforms: http://www.w3.org/2000/09/xmldsig#enveloped-signature and http://www.w3.org/2006/12/xml-c14n11
I've enabled tracing on the other side which is supposed to validate the message and this appears to be the only place where something is failing in the trace log:
[SignedXml#0071d445, VerificationFailure] Verification failed checking SignedInfo.
I've ensured that the Certificate being used in the signing process is my private certificate and the other side is using the public certificate for their verification process.
What else am I missing?
Edit:
We are able to validate messages that we receive from their side. I cannot provide any code to how they validate messages because it's not given to us.
This is an example of what we are sending over:
<Message xmlns="urn:tch">
<AppHdr>
<head:Fr xmlns:head="urn:iso:std:iso:20022:tech:xsd:head.001.001.01">
<head:ID>
<head:MemberId>1234245</head:MemberId>
</head:ID>
</head:Fr>
<head:To xmlns:head="urn:iso:std:iso:20022:tech:xsd:head.001.001.01">
<head:ID>
<head:MemberId>123345345</head:MemberId>
</head:ID>
<head:Date xmlns:head="urn:iso:std:iso:20022:tech:xsd:head.001.001.01">2020-03-16T22:03:24</head:Date>
<head:Sgntr xmlns:head="urn:iso:std:iso:20022:tech:xsd:head.001.001.01">
<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="">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<ds:Transform Algorithm="http://www.w3.org/2006/12/xml-c14n11" />
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
<ds:DigestValue>fV61VLaSYGyW+G+LxZaLfGdOlAVmpJvusoJ792IKblw=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>xlg1Q4hdQoCo+x4rJKWn1UwLwdshonx0dg5Z0bIAsFuBWOJGyVescQBFEZEbUHcgLq2TQZT6PBhH3F2RR4aYTnc9IY0mHRq5rJEpVfXnQZdwCw7SIhkMMIOi5rAgQ5gdLmQlbLWFm8V4+1DTE7QWiCALEkarJQcdU4n9E2rGrSM48H1NFHHC+m8J5eHcGFyRcBwSF712ihGDNvweGhwneR4JeGxyQ3Dc1pSkzkr0oOJDY0vgwIuMZIMvl6Fh98D3QByDadZqFah88uv+TWZy01B9rdfhgCP7gdeiP9fof90jnitQ4c/3XksjieLbWQxWmQ1TPoRQswDuXf731T6kmw==</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509IssuerSerial>
<ds:X509IssuerName>CN=ABC, DC=AAA, DC=BBB, DC=CCC</ds:X509IssuerName>
<ds:X509SerialNumber>75822323454364025267732697561489775234425345</ds:X509SerialNumber>
</ds:X509IssuerSerial>
<ds:X509SubjectName>E=something#somewhere.com, CN=ABC.com, OU=Internet Services, O=AAA, L=Someplace, S=somewhere, C=US</ds:X509SubjectName>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</head:Sgntr>
</AppHdr>
<TestMessage>
<mr:zzzz.002.001.01 xmlns:mr="urn:iso:std:iso:20022:tech:xsd:zzzz.002.001.01">
<mr:Ref>
<mr:Ref>20200316220315134567890122203156779</mr:Ref>
</mr:Ref>
</TestMessage>
</Message>
Edit: If somebody and create an example using the above information for signing I will accept the answer.
To fix the receiver side we are replacing the S= with ST= in the X509SubjectName node.

SignXml in C# fails to verify it's own signature

I am working to replace a legacy application that is no longer being maintained. I have most things replaced except the digital signature method. I have an implementation in .net core and I'm a bit confused as to why it's failing to verify it's own signed document.
Consider the following xml(code is messy, just running a poc at the moment):
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<tns:Body id="Body" xmlns:tns="http://schemas.xmlsoap.org/soap/envelope/">This is a test</tns:Body>
</soap:Envelope>
With the following c#:
var publicCert = new X509Certificate2(File.ReadAllBytes("public.cer"));
var cert = GetSigningCertificate("private.pfx", "super amazing password");
var xml = File.ReadAllText("test.xml");
var doc = new XmlDocument {PreserveWhitespace = false};
doc.LoadXml(xml);
//remove <?xml?> tag
if (doc.FirstChild is XmlDeclaration)
{
doc.RemoveChild(doc.FirstChild);
}
//create the header node: NOTE: This is not on the original document but required by the vendor
var header = new StringBuilder();
header.AppendLine("<SOAP:Header xmlns:SOAP=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:SOAP-SEC=\"http://schemas.xmlsoap.org/soap/security/2000-12\">");
header.AppendLine("<SOAP-SEC:Signature>");
header.AppendLine("</SOAP-SEC:Signature>");
header.AppendLine("</SOAP:Header>");
var headerXml = new XmlDocument {PreserveWhitespace = false};
headerXml.LoadXml(header.ToString());
//add header document to the main document
if (headerXml.DocumentElement != null) doc.DocumentElement?.InsertBefore(doc.ImportNode(headerXml.DocumentElement, true), doc.DocumentElement?.FirstChild);
var domHeader = (XmlElement) doc.FirstChild.FirstChild;
// Create a SignedXml object.
var referenceUri = "#Body";
var signedXml = new SignedXml(domHeader) { SigningKey = cert.PrivateKey }; #NOTE: signing is done at the /SOAP:Header level per the vendor, not at the ParentDocument level
signedXml.SignedInfo.CanonicalizationMethod = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments";
signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
// Create a reference to be signed.
var reference = new Reference
{
Uri = referenceUri,
DigestMethod = "http://www.w3.org/2000/09/xmldsig#sha1"
};
// Add the reference to the SignedXml object.
signedXml.AddReference(reference);
//add key info
var keyInfo = new KeyInfo();
var certInfo = new KeyInfoX509Data();
if (cert.SerialNumber != null) certInfo.AddIssuerSerial(cert.Issuer, cert.SerialNumber);
keyInfo.AddClause(certInfo);
signedXml.KeyInfo = keyInfo;
// Compute the signature.
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
var xmlDigitalSignature = signedXml.GetXml();
//get the SOAP-SEC header and add our signature to it
var soapSecurityList = doc.GetElementsByTagName("Signature", "http://schemas.xmlsoap.org/soap/security/2000-12");
if(soapSecurityList.Count == 0)
{
throw new Exception("Could not find SOAP-SEC header!");
}
var soapSecurity = soapSecurityList.Item(0);
soapSecurity.AppendChild(xmlDigitalSignature);
This produces the following xml(cert, sig,hash,serial are placeholders for the actual values and have been redacted for posting here):
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP:Header xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-SEC="http://schemas.xmlsoap.org/soap/security/2000-12">
<SOAP-SEC:Signature>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="#Body">
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>hash</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>sig</SignatureValue>
<KeyInfo>
<X509Data>
<X509IssuerSerial>
<X509IssuerName>cert</X509IssuerName>
<X509SerialNumber>serial</X509SerialNumber>
</X509IssuerSerial>
</X509Data>
</KeyInfo>
</Signature>
</SOAP-SEC:Signature>
</SOAP:Header>
<tns:Body id="Body" xmlns:tns="http://schemas.xmlsoap.org/soap/envelope/">This is a test</tns:Body>
</soap:Envelope>
However, trying to validate it fails with the following code:
//verify what we just did
var verifiedXml = new XmlDocument {PreserveWhitespace = false};
verifiedXml.LoadXml(doc.InnerXml); //document object from above
var signature = verifiedXml.GetElementsByTagName("Signature", SignedXml.XmlDsigNamespaceUrl);
var validSignature = new SignedXml((XmlElement) signature[0].ParentNode);
return validSignature.CheckSignature(cert, true); //also tried publicCert but no luck
Signature always fails. I have looked at numerous other stack overflow questions and most of them involve whitespace issues(all documents ignore white spaces and all sources are linearized before being read in) or adding "ds" namespace to the signature prefix during the canonicalization method. I implemented that "ds" fix and tried again. It produces the following xml, which still fails to validate.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP:Header xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-SEC="http://schemas.xmlsoap.org/soap/security/2000-12">
<SOAP-SEC:Signature>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" />
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<ds:Reference URI="#Body">
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<ds:DigestValue>hash</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>sig</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509IssuerSerial>
<ds:X509IssuerName>cert</ds:X509IssuerName>
<ds:X509SerialNumber>serial</ds:X509SerialNumber>
</ds:X509IssuerSerial>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</SOAP-SEC:Signature>
</SOAP:Header>
<tns:Body id="Body" xmlns:tns="http://schemas.xmlsoap.org/soap/envelope/">This is a test</tns:Body>
</soap:Envelope>
UPDATE
Based on comments. Tried the following:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<tns:Body id="Body" xmlns:tns="http://schemas.xmlsoap.org/soap/envelope/">This is a test</tns:Body>
</soap:Envelope>
using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Security.Policy;
namespace XmlDsig
{
public class XmlDsig
{
public bool Sign()
{
try
{
var cert = GetSigningCertificate("cert.pfx", "password");
var xml = File.ReadAllText("test.xml");
// Create a new XML document.
var doc = new XmlDocument {PreserveWhitespace = false};
doc.LoadXml(xml);
//remove <?xml?> tag
if (doc.FirstChild is XmlDeclaration)
{
doc.RemoveChild(doc.FirstChild);
}
//create the header node
var header = new StringBuilder();
header.AppendLine("<SOAP:Header xmlns:SOAP=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:SOAP-SEC=\"http://schemas.xmlsoap.org/soap/security/2000-12\">");
header.AppendLine("<SOAP-SEC:Signature>");
header.AppendLine("</SOAP-SEC:Signature>");
header.AppendLine("</SOAP:Header>");
var headerXml = new XmlDocument {PreserveWhitespace = false};
headerXml.LoadXml(header.ToString());
//add header document to the main document
if (headerXml.DocumentElement != null) doc.DocumentElement?.InsertBefore(doc.ImportNode(headerXml.DocumentElement, true), doc.DocumentElement?.FirstChild);
var domHeader = (XmlElement) doc.FirstChild.FirstChild;
SignXmlWithCertificate(domHeader, cert);
var validatorXml = new XmlDocument();
validatorXml.LoadXml(doc.OuterXml);
var sigContext = validatorXml.GetElementsByTagName("Header", "http://schemas.xmlsoap.org/soap/envelope/");
var validator = new SignedXml((XmlElement) sigContext[0]); return validator.CheckSignature(cert, true);
}
catch (Exception e)
{
return false;
// return e.ToString();
}
}
public static void SignXmlWithCertificate(XmlElement assertion, X509Certificate2 cert)
{
var signedXml = new SignedXml(assertion) {SigningKey = cert.PrivateKey};
var reference = new Reference {Uri = "#Body"};
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
signedXml.AddReference(reference);
var keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(cert));
signedXml.KeyInfo = keyInfo;
signedXml.ComputeSignature();
var xmlsig = signedXml.GetXml();
assertion.FirstChild.AppendChild(xmlsig);
}
}
}
Still fails to validate.
Update 2
Now I am getting the following error when i call CheckSignature:
SignatureDescription could not be created for the signature algorithm supplied.
Seems like this is a common problem on .net 4 with sha256, but I'm using sha1 in my current example and i am using dotnet core. I have tried sha256 too but same issue persists.
Found the answers I was looking for.
var verify_xml = new XmlDocument();
verify_xml.LoadXml(doc.OuterXml); //doc = the signed document loaded from disk
var signature = verify_xml.GetElementsByTagName("Signature", "http://www.w3.org/2000/09/xmldsig#");
var verify = new SignedXml((XmlElement)verify_xml.DocumentElement.FirstChild);
verify.LoadXml((XmlElement) signature[0]);
verify.CheckSignature(cert, true);
The Element passed to the SignedXml() constructor is the signing context(so the SOAP:Header element). LoadXml() is where you actually load the signature itself(not in the constructor). Fixing this resolved all my issues.

How to sign XML with SHA256 & X.509 correctly?

I want to sign a XML with SHA256 by using a self-signed X.509 certificate. I was inspired by many articles in Stackoverflow and use this code:
X509Certificate2 cert = new X509Certificate2();
X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 cert2 in store.Certificates)
{
if (cert2.Subject == "CN=TestCerificate")
{
cert = cert2;
break;
}
}
store.Close();
CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription), "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
var exportedKeyMaterial = cert.PrivateKey.ToXmlString(true);
var key = new RSACryptoServiceProvider(new CspParameters(24));
key.PersistKeyInCsp = false;
key.FromXmlString(exportedKeyMaterial);
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.Load("test.xml");
SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = key;
signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
Reference reference = new Reference();
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigExcC14NTransform());
reference.Uri = "";
reference.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
signedXml.AddReference(reference);
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(cert));
signedXml.KeyInfo = keyInfo;
signedXml.ComputeSignature();
doc.Save("testSig.xml");
Afterwards, the generated signed XML part of "testSig.xml" looks like this:
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1" />
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>ghOEPeYtAUs5Kb8VMOCIS3f2wIY=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>HANI0GrICbyc5tlmvtU9cB7txdxtuY4uDsntp5XVzaRQbts76ff3Qg==</SignatureValue>
</Signature>
As you can see the XML says that "SHA1" was used to sign the XML instead (see nodes "SignatureMethod" & "DigestMethod"). But I would expect, that the SHA256-URLs have to be stated in these nodes. Is this assumption correct? If yes, does anybody know how to solve this problem? I use C# 4.6.0.
With best regards,
Michael
While you successfully called signedXml.ComputeSignature() that just does the computation, it doesn't insert a signature element into the document (the XmlDocument you pass in the constructor is just what document GetXml()'s node will parent to).
You need to call signedXml.GetXml() (after ComputeSignature) to get the computed ds:Signature element, then insert that into your document.
The fact that your document has a signature node already in it is confusing. My first thought was that test.xml already had that signature in it, but your comment says that it was just some oops code from elsewhere. Lesson learned, I guess :).

Create SAML 2.0 response with signed and encrypted Assertion using c#

Hi I am new to SAML and SSO techniques. I am trying to create a valid SAML reponse with signed and encrypted Assertion. I have created a SAML response which is signed but i am not able to encrypte the assertion and create the tag. My Generated SAML response is,
<samlp:Response xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_88a4cf19-6f41-46ee-9dc3-98ac80168bd9" Version="2.0" IssueInstant="2015-03-26T11:43:13.4468624Z" Destination="Test1" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<saml:Issuer>Test</saml:Issuer>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="#_49bc8835-7c9a-4ee2-8087-7cfcbe48375f">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>My4iQVO1Oy3i6jV+Jlp0czX0mpA=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>GWfdKMCHbiqq6OhyHQ0y2LoDQkmC95fs3SKWyPMzu6jSjbf6vrMRFCrlch+DU1k3+sfsj1tFkJNMPKpxZIx2XksjnEQv3Hdqy7oPSoGiODmrky7CTKEdYbCQqu6a8dwNBLNQTClYAgDz/m5yfbFlJNPy9TtsCl2l1R/qg6dzVkA=</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIBwTCCAS6gAwIBAgIQr71oSHfrBKpKpRUTWmBFCzAJBgUrDgMCHQUAMCMxITAfBgNVBAMeGABCAEYASQBcAGQAcwBwAGUAaQBnAGgAdDAeFw0xMDAyMTkxMzI4MzlaFw0xMTAyMTkxOTI4MzlaMCMxITAfBgNVBAMeGABCAEYASQBcAGQAcwBwAGUAaQBnAGgAdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxXmIj8FBaL+94B/fNsBcoNZZraicGsm5+8VtWQIaGdM65q6vgDSQAg4zOkTQCqKh2vlN2NHSZb2XjcrUTWm2Vb279dvkOZfZ1mdQeLjM2LbXvrY4e7qK1dhZy9gZ3Mhvuk3cKPwwPsLNFifOt6OsS8ZzK7/PC+uUKznZtRsCAwEAATAJBgUrDgMCHQUAA4GBADGP1MjZm28GdYy3mQGprHQNDn8fIyBQvhwVwl4SVPxYDTKG7OsUC/QDUzy8vGXm+9qd2Es5creZS1DTAweC60JsJLdmp631FnbG4xJOCRHbR0HWyruhGkN6wPJ0RyJbdUrAcEPG4cfcYwl3oBeL48MfUD56UC0jSfBezUvnOMBX</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
</samlp:Status>
<saml:Assertion Version="2.0" ID="_49bc8835-7c9a-4ee2-8087-7cfcbe48375f" IssueInstant="2015-03-26T11:43:13.6835615Z">
<saml:Issuer>Test</saml:Issuer>
<saml:Subject>
<saml:NameID NameQualifier="TestDomain">TestSubject</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2015-03-26T11:48:13.7304370Z" Recipient="Test1" />
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2015-03-26T11:43:13.6835615Z" NotOnOrAfter="2015-03-26T11:48:13.6835615Z">
<saml:AudienceRestriction>
<saml:Audience>TestDomain</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2015-03-26T11:43:13.6835615Z">
<saml:AuthnContext>
<saml:AuthnContextClassRef>AuthnContextClassRef</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="UserId" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xmlns:q1="http://www.w3.org/2001/XMLSchema" p7:type="q1:string" xmlns:p7="http://www.w3.org/2001/XMLSchema-instance">1000001</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="UserName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xmlns:q2="http://www.w3.org/2001/XMLSchema" p7:type="q2:string" xmlns:p7="http://www.w3.org/2001/XMLSchema-instance">Manish Pandey</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
Can any one suggest any method to accomplish this?
it depends what kind of encryption you want to use... Basically you would have to encrypt signed assertion and replace assertion node with EncryptedAssertion node.
I recommend using both symmetric and asymmetric encryption.
Using Symmetric key to encrypt the whole of assertion node and then use asymmetric (i.e. certificate public/private key to encrypt symmetric key). I have used our client's certificate public key to encrypt symmetric key so only they can decrypt using their private key.
I have also specified certificate I want to use in web.config...
string samlResponseXml = '<SAML Response Message>'
XmlDocument loginResponseXmlDocument = new XmlDocument();
loginResponseXmlDocument.LoadXml(samlResponseXml);
// Add name spaces
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(loginResponseXmlDocument.NameTable);
namespaceManager.AddNamespace("samlp", "urn:oasis:names:tc:SAML:2.0:protocol");
namespaceManager.AddNamespace("saml", "urn:oasis:names:tc:SAML:2.0:assertion");
namespaceManager.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#");
namespaceManager.AddNamespace("xenc", "http://www.w3.org/2001/04/xmlenc#");
Encrypt(loginResponseXmlDocument, namespaceManager);
private void Encrypt(XmlDocument document, XmlNamespaceManager namespaceManager)
{
// create symmetric key
var key = new RijndaelManaged();
key.BlockSize = 128;
key.KeySize = 256;
key.Padding = PaddingMode.ISO10126;
key.Mode = CipherMode.CBC;
XmlElement assertion = (XmlElement)document.SelectSingleNode("/samlp:Response/saml:Assertion", namespaceManager);
EncryptedXml eXml = new EncryptedXml();
byte[] encryptedElement = eXml.EncryptData(assertion, key, false);
EncryptedData edElement = new EncryptedData
{
Type = EncryptedXml.XmlEncAES256Url
};
const string encryptionMethod = EncryptedXml.XmlEncAES256Url;
edElement.EncryptionMethod = new EncryptionMethod(encryptionMethod);
edElement.CipherData.CipherValue = encryptedElement;
// edElement = EncryptedData
// Now encrypt symmetric key
string certificateDn = ConfigurationManager.AppSettings["CertificateDN"];
X509Certificate2 x509Certificate = GetCertificate(certificateDn);
RSACryptoServiceProvider rsa = x509Certificate.PublicKey.Key as RSACryptoServiceProvider;
byte[] cryptedData = rsa.Encrypt(key.Key, false);
//string data = Convert.ToBase64String(cryptedData);
//byte[] encryptedKey = EncryptedXml.EncryptKey(key.Key, rsa, true);
EncryptedKey ek = new EncryptedKey();
ek.CipherData = new CipherData(cryptedData);
ek.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncRSA15Url);
//EncryptedData edkey = new EncryptedData();
//string data1 = Convert.ToBase64String(encryptedKey);
//edkey.CipherData.CipherValue = System.Text.Encoding.Unicode.GetBytes(data);
rsa.Clear();
XmlDocument encryptedAssertion = new XmlDocument();
// Add name spaces
XmlDeclaration xmlDeclaration = encryptedAssertion.CreateXmlDeclaration("1.0", "UTF-8", null);
XmlElement encryptedRoot = encryptedAssertion.DocumentElement;
encryptedAssertion.InsertBefore(xmlDeclaration, encryptedRoot);
XmlElement encryptedAssertionElement = encryptedAssertion.CreateElement("saml", "EncryptedAssertion", "urn:oasis:names:tc:SAML:2.0:assertion");
encryptedAssertion.AppendChild(encryptedAssertionElement);
string xml = edElement.GetXml().OuterXml;
XmlElement element = AddPrefix(xml, "xenc", "http://www.w3.org/2001/04/xmlenc#");
var encryptedDataNode = encryptedAssertion.ImportNode(element, true);
encryptedAssertionElement.AppendChild(encryptedDataNode);
xml = ek.GetXml().OuterXml;
element = AddPrefix(xml, "xenc", "http://www.w3.org/2001/04/xmlenc#");
var encryptedKeyNode = encryptedAssertion.ImportNode(element, true);
encryptedAssertionElement.AppendChild(encryptedKeyNode);
var root = document.DocumentElement;
var node = root.OwnerDocument.ImportNode(encryptedAssertionElement, true);
root.RemoveChild(assertion);
root.AppendChild(node);
}
Once assertion is encrypted.. this is what it will look like:
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Destination="https://aviva-rpt.distribution-technology.com" ID="_fba2f5af-a430-8001-5cb8-9714f3aeb4bc"
IssueInstant="2015-02-04T16:32:33.446Z" Version="2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
<saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://www.client.sds</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
</samlp:Status>
<saml:EncryptedAssertion>
<xenc:EncryptedData>
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
<xenc:CipherData>
<xenc:CipherValue>zjAgkZ=</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
<xenc:EncryptedKey>
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
<xenc:CipherData>
<xenc:CipherValue>DyA22==</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedKey>
</saml:EncryptedAssertion>
</samlp:Response>

Specifying Reference URI of #Body while digitally signing SOAP request - using WCF

I am using a WCF client to talk to a non-WCF web service.
This web service requires that the body of the SOAP message is signed, however, I am having trouble generating a valid SOAP request.
I have implemented a ClientMessageInspector which inherits from IClientMessageInspector, where I modify the message in the BeforeSendRequest method to add the XML digital signature. I use the SignedXML class to do this.
I am using the IBM Web Services Validation Tool for WSDL and SOAP to check whether my digital signature verifies.
My problem is that when I specify a full namespaced reference in the Reference URI, the IBM tools that I'm using say that I have a valid signature. From the XML Digital Signature specifications, I should just be able to reference the attribute, without the namespace, however, when I do this, I don't get a valid digital signature.
This is the SOAP request I am currently generating, which my tools say has a valid signature, but the web service doesn't like it:
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:soapsec="http://schemas.xmlsoap.org/soap/security/2000-12"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<s:Header>
<soapsec:Signature>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="http://schemas.xmlsoap.org/soap/security/2000-12#Body">
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>4mt5wluUTu5tpR2d5UemVSLvqTs=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>UZ7HzfE3GxIY9hg...</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIEkTCCA3mgAwIBAgIQCu...</X509Certificate>
</X509Data>
<KeyValue>
<RSAKeyValue>
<Modulus>0C3e9HDx5Yq6FLUxIgjJ...</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</KeyValue>
</KeyInfo>
</Signature>
</soapsec:Signature>
</s:Header>
<s:Body soapsec:id="Body">
.... SOAP Body Here ...
</s:Body>
</s:Envelope>
This is the SOAP request I want to be generating, but my tools say this has an invalid signature, and the web service also tells me the signature is invalid:
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:soapsec="http://schemas.xmlsoap.org/soap/security/2000-12"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<s:Header>
<soapsec:Signature>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="#Body">
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>4mt5wluUTu5tpR2d5UemVSLvqTs=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>UZ7HzfE3GxIY9hg...</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIEkTCCA3mgAwIBAgIQCu...</X509Certificate>
</X509Data>
<KeyValue>
<RSAKeyValue>
<Modulus>0C3e9HDx5Yq6FLUxIgjJ...</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</KeyValue>
</KeyInfo>
</Signature>
</soapsec:Signature>
</s:Header>
<s:Body soapsec:id="Body">
.... SOAP Body Here ...
</s:Body>
</s:Envelope>
And here is the code I have in BeforeSendRequest to create the signature, and modify the message accordingly:
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.LoadXml(request.ToString());
// Add the required namespaces to the SOAP Envelope element, if I don't do this, the web service I'm calling returns an error
string soapSecNS = "http://schemas.xmlsoap.org/soap/security/2000-12";
string soapEnvNS = "http://www.w3.org/2003/05/soap-envelope";
//Get the header element, so that we can add the digital signature to it
XmlNode headerNode = doc.GetElementsByTagName("Header", soapEnvNS)[0];
// Set the ID attribute on the body element, so that we can reference it later
XmlNode bodyNode = doc.GetElementsByTagName("Body", soapEnvNS)[0];
((XmlElement)bodyNode).RemoveAllAttributes();
((XmlElement)bodyNode).SetAttribute("id", soapSecNS, "Body");
XmlWriterSettings settings2 = new XmlWriterSettings();
settings2.Encoding = new System.Text.UTF8Encoding(false);
// Load the certificate we want to use for signing
SignedXmlWithId signedXml = new SignedXmlWithId(doc);
X509Certificate2 cert = new X509Certificate2("C:\\myCertificate.pfx", "myPassword");
signedXml.SigningKey = cert.PrivateKey;
//Populate the KeyInfo element correctly, with the public cert and public key
Signature sigElement = signedXml.Signature;
KeyInfoX509Data x509Data = new KeyInfoX509Data(cert);
sigElement.KeyInfo.AddClause(x509Data);
RSAKeyValue rsaKeyValue = new RSAKeyValue((RSA)cert.PublicKey.Key);
sigElement.KeyInfo.AddClause(rsaKeyValue);
// Create a reference to be signed, only sign the body of the SOAP request, which we have given an
// ID attribute to, in order to reference it correctly here
Reference reference = new Reference();
reference.Uri = soapSecNS + "#Body";
// Add the reference to the SignedXml object.
signedXml.AddReference(reference);
// Compute the signature.
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
XmlElement xmlDigitalSignature = signedXml.GetXml();
XmlElement soapSignature = doc.CreateElement("Signature", soapSecNS);
soapSignature.Prefix = "soapsec";
soapSignature.AppendChild(xmlDigitalSignature);
headerNode.AppendChild(soapSignature);
// Make sure the byte order mark doesn't get written out
XmlDictionaryReaderQuotas quotas = new XmlDictionaryReaderQuotas();
Encoding encoderWithoutBOM = new System.Text.UTF8Encoding(false);
System.IO.MemoryStream ms = new System.IO.MemoryStream(encoderWithoutBOM.GetBytes(doc.InnerXml));
XmlDictionaryReader xdr = XmlDictionaryReader.CreateTextReader(ms, encoderWithoutBOM, quotas, null);
//Create the new message, that has the digital signature in the header
Message newMessage = Message.CreateMessage(xdr, System.Int32.MaxValue, request.Version);
request = newMessage;
return null;
}
Does anybody know how I can set the Reference URI to #Body, but also have a valid XML Signature?
Since I was trying to generate a signature for a SOAP request, I got around this by subclassing SignedXml, overriding GetIdElement, so that I can return whatever element it is that I'm looking for. In this case, the id element will belong to the namespace http://schemas.xmlsoap.org/soap/security/2000-12
public class SignedXmlWithId : SignedXml
{
public SignedXmlWithId(XmlDocument xml)
: base(xml)
{
}
public SignedXmlWithId(XmlElement xmlElement)
: base(xmlElement)
{
}
public override XmlElement GetIdElement(XmlDocument doc, string id)
{
// check to see if it's a standard ID reference
XmlElement idElem = base.GetIdElement(doc, id);
if (idElem == null)
{
// I've just hardcoded it for the time being, but should be using an XPath expression here, and the id that is passed in
idElem = (XmlElement)doc.GetElementsByTagName("Body", "http://schemas.xmlsoap.org/soap/security/2000-12")[0];
}
return idElem;
}
}
I've also realised that using tools such as IBM Web Services Validation Tool for WSDL and SOAP to validate the digital signature of the SOAP request doesn't work. Instead I'm using the following method to verify signatures:
public static bool verifyDigitalSignatureForString(string msgAsString)
{
XmlDocument verifyDoc = new XmlDocument();
verifyDoc.PreserveWhitespace = true;
verifyDoc.LoadXml(msgAsString);
SignedXmlWithId verifyXml = new SignedXmlWithId(verifyDoc);
// Find the "Signature" node and create a new
// XmlNodeList object.
XmlNodeList nodeList = verifyDoc.GetElementsByTagName("Signature");
// Load the signature node.
verifyXml.LoadXml((XmlElement)nodeList[0]);
if (verifyXml.CheckSignature())
{
Console.WriteLine("Digital signature is valid");
return true;
}
else
{
Console.WriteLine("Digital signature is not valid");
return false;
}
}

Categories