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

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 :).

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.

c# Signing XML Document: What is in the x509 certificate

I'm incorporating a software license system into a program of mine.
The client asks for a license, sends me the data I need, then I generate an XML file, being the license file that my program must check at the clients side.
So I generate a x509 certificate on my development computer (win10) and sign the license file.
X509Store store = new X509Store(StoreName, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = store.Certificates;
X509Certificate2Collection currentCerts = certCollection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
X509Certificate2Collection signingCert = currentCerts.Find(X509FindType.FindBySubjectDistinguishedName, $"CN={CertificateName}", false);
X509Certificate2 cert = signingCert[0]; // has private and public
store.Close();
CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription), "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
var exportedKeyMaterial = signingCert[0].PrivateKey.ToXmlString(true);
var key = new RSACryptoServiceProvider(new CspParameters(24));
key.PersistKeyInCsp = true;
key.FromXmlString(exportedKeyMaterial);
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
// LicenseInfo is the info about her computer etc. my client provides, eg. by e-mail
using (TextReader sr = new StringReader(LicenseInfo))
{
doc.Load(sr);
}
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();
XmlElement sig = signedXml.GetXml();
doc.DocumentElement.AppendChild(doc.ImportNode(sig,true));
if (doc.FirstChild is XmlDeclaration)
{
doc.RemoveChild(doc.FirstChild);
}
// and here it finally is: the license!
LicenseText = doc.OuterXml;
The signature goes into the license file which will then look like this, after signing:
<License>
<MailAddress>....</MailAddress>
<LicenseNr>8309847560354</LicenseNr>
<RequestTimeStamp>8/15/2019 11:28:47 AM</RequestTimeStamp>
<ExpirationTimeStamp>8/14/2020 11:28:47 AM</ExpirationTimeStamp>
<X5>78-BB-2F-65-4blabla45-A8-B5-4D-82</X5>
<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/2001/04/xmldsig-more#rsa-sha256" />
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
<DigestValue>Gg9WLq12JSYFh6tatRTCnGbzEzMg8PidcXK4iWwPcXg=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>JdC09BIeHcw8...q24JFVseBZWXOo==</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIC8zCCAd+gAwIBAgIQgwuqv2X..........oP7f5f</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
</License>
What is in that <X509Certificate/> ?
My public key?
If so, I do not need to include my public key in the program as an embeded resource because it will be in my clients license file, that must be processed anyway...
Correct?
The will not include my private key, I hope...?
Here is how i (try to) verify the license validity (not including the expiration date, only seeing if the license file was not tampered with)
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(Name); // the license file
SignedXml signedXml = new SignedXml(xmlDocument);
XmlNodeList nodeList = xmlDocument.GetElementsByTagName("Signature");
signedXml.LoadXml((XmlElement)nodeList[0]);
RSACryptoServiceProvider Key = new RSACryptoServiceProvider(new CspParameters(24));
Key.ToXmlString(false);
return signedXml.CheckSignature(Resources.PubKey.FromXmlString()); // ????? or what?
So:
1: What is in the X509Certificate in the signature?
2: Verification does not work. Any suggestions?
3: Do I include my public key in the program that checks the license, or is the signature itself the source?
PS:
Please, don't point out that this will not give me 100% security. I know.
My questions are about X509, not about guarding against theft. That is another, much broader, issue. It's interesting, programming this stuff and learning.

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.

Adding KeyInfo reference in SOAP request

So I'm having a very similar issue to this post here. SOAP KeyInfo values
I am wanting to add a reference within KeyInfo but can't seem to find a way to do it through code.
Here is what the expected output should be:
<KeyInfo>
<wsse:SecurityTokenReference>
<wsse:Reference URI="#SecurityTest" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
</wsse:SecurityTokenReference>
</KeyInfo>
And I do have this above where it is trying to reference from:
<wsse:BinarySecurityToken ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="SecurityTest">Base64CertStuffBlahblah
</wsse:BinarySecurityToken>
Every attempt at creating the KeyInfo portion only allows me to insert an item, like a key, to fill in this part in but I just want a reference. This code is what I've been working with but is not creating what I want at the moment.
//This creates a X509 clause but it's as far as I've got.
//The "keyInfoData" needs to be of a different type to allow custom reference?
var signer = new SignedXmlWithId(doc) {SigningKey = Key};
KeyInfo keyInfo = new KeyInfo();
KeyInfoX509Data keyInfoData = new KeyInfoX509Data();
keyInfoData.AddCertificate(cert);
keyInfo.AddClause(keyInfoData);
signer.KeyInfo = keyInfo;
Thanks for looking, any help would be appreciated.
So this piece of code lets me add what I want into the KeyInfo portion.
KeyInfo keyInfo = new KeyInfo();
XmlElement x = doc.CreateElement("wsse","SecurityTokenReference", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
XmlElement y = doc.CreateElement("wsse","Reference", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
y.SetAttribute("URI","#SecurityTest");
y.SetAttribute("ValueType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3");
x.AppendChild(y);
var keyInfoData = new KeyInfoNode(x);
keyInfo.AddClause(keyInfoData);
signer.KeyInfo = keyInfo;
This produces the following result:
<KeyInfo>
<wsse:SecurityTokenReference>
<wsse:Reference URI="#SecurityTest" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" />
</wsse:SecurityTokenReference>
</KeyInfo>
This didn't seem to fix my issue though the SOAP now "looks" correct. Maybe it will help someone else.

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>

Categories