XML signature & digest verification
.NET SignedXml class was only designed to adhere to the XMLDSIG specification. It has no knowledge of SAML, SAML 2.0, SOAP or any other higher level XML protocol. If you are using SignedXml for any other protocol other than XMLDSIG, the scenario is not supported.
SignedXml.CheckSignature method determines whether the signature property verifies for the specified key.
Ideally the CheckSignature method does two things:
1. Matches Signature
2. Validates digest value
How to match the signature? See code below.
For example consider the string named xmlString which has the signed XML.
We can do the following to get the SignedXml object say verifier.
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.LoadXml(xmlString);
SignedXml verifier = new SignedXml();
Next, the SignedXml class must be given the value of the signature it is to validate. This can be done by looking for elements with the tag name of Signature. See code below:
verifier.LoadXml(doc.GetElementsByTagName("ds:Signature")[0] as XmlElement);
// Get the certificate from the XML. Say it is rawCertData (byte array)
X509Certificate2 x509 = new X509Certificate2();
x509.Import(rawCertData);
// Get the public key
AsymmetricAlgorithm key = x509.PublicKey.Key;
// =================================================================================
if (key == null)
throw new ArgumentNullException("key");
SignatureDescription signatureDescription = CryptoConfig.CreateFromName(verifier.SignatureMethod) as SignatureDescription;
if (signatureDescription == null)
throw new CryptographicException("123456789"); // "123456789" is an arbitrary value.
// Let's see if the key corresponds with the SignatureMethod
Type ta = Type.GetType(signatureDescription.KeyAlgorithm);
Type tb = key.GetType();
if ((ta != tb) && !ta.IsSubclassOf(tb) && !tb.IsSubclassOf(ta))
// Signature method key mismatch
return;
HashAlgorithm hashAlgorithm = signatureDescription.CreateDigest();
if (hashAlgorithm == null)
throw new CryptographicException("123456789");
//====================================================
byte[] digestedSignedInfo = null;
string baseUri = (doc == null ? null : doc.BaseURI);
XmlResolver resolver = new XmlSecureResolver(new XmlUrlResolver(), baseUri);
XmlDocument docProcessed = PreProcessElementInput(verifier.SignedInfo.GetXml(), resolver, baseUri);
Transform c14nMethodTransform = verifier.SignedInfo.CanonicalizationMethodObject;
c14nMethodTransform.Resolver = resolver;
c14nMethodTransform.LoadInput(docProcessed);
digestedSignedInfo = c14nMethodTransform.GetDigestedOutput(hashAlgorithm);
//====================================================
AsymmetricSignatureDeformatter asymmetricSignatureDeformatter = signatureDescription.CreateDeformatter(key);
// Verify the signature
bool isSignatureOK = asymmetricSignatureDeformatter.VerifySignature(digestedSignedInfo, verifier.SignatureValue);
if (!isSignatureOK)
{
Console.WriteLine("Signature invalid.");
}
Here is the code to verify the digest value.
string ObjId = ((System.Security.Cryptography.Xml.Reference)(verifier.SignedInfo.References[0])).Uri.Replace("#", "");
XmlElement xRefElement = verifier.GetIdElement(doc, ObjId);
XmlDocument xRefElementDoc = new XmlDocument();
xRefElementDoc.PreserveWhitespace = true;
xRefElementDoc.LoadXml(xRefElement.OuterXml);
// Get the digest value - customers code
byte[] rgb1 = ((System.Security.Cryptography.Xml.Reference)(verifier.SignedInfo.References[0])).DigestValue;
string strDigestValue = Convert.ToBase64String(rgb1);
// Verify the digest
bIsValid = VerifyDigest(xRefElementDoc, strDigestValue);
if (!bIsValid)
{
Console.WriteLine("The computed digest & the digest present in the XML does not match.");
}
bool VerifyDigest(XmlDocument xAssertionDoc, string strDigestValue)
{
// if xmlsignature is available in the XML then remove the signature node
xAssertionDoc.DocumentElement.RemoveChild(xAssertionDoc.GetElementsByTagName("ds:Signature")[0]);
//create c14n instance and load in xml file
XmlDsigC14NTransform c14n = new XmlDsigC14NTransform(false);
// Loading the Assetion Node into the canonicalization
c14n.LoadInput(xAssertionDoc);
//get canonalised stream
Stream canonalisedStream = (Stream)c14n.GetOutput(typeof(Stream));
//Creating SHA1 object to get Hash
SHA1 sha1 = new SHA1CryptoServiceProvider();
Byte[] output = sha1.ComputeHash(canonalisedStream);
//Getting the Base64 version of digest Value computed
string xmlDigestValue = Convert.ToBase64String(output);
// If Computed and original digest value matches then return true else false.
if (xmlDigestValue == strDigestValue)
{
Console.WriteLine("The computed hash matches with the Digest contained in the XML.");
return true;
}
return false;
}
Reference:
https://msdn.microsoft.com/en-us/library/kd4wwa16(v=vs.110).aspx