SignXml в C # не может проверить свою собственную подпись - PullRequest
1 голос
/ 02 июля 2019

Я работаю над заменой устаревшего приложения, которое больше не поддерживается. У меня заменено большинство вещей, кроме метода цифровой подписи. У меня есть реализация в ядре .net, и я немного озадачен тем, почему не удается проверить собственный подписанный документ.

Рассмотрим следующий xml (код грязный, в данный момент выполняется только документ):

<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>

Со следующим 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);

Это приводит к следующему xml (cert, sig, hash, serial являются заполнителями для фактических значений и были отредактированы для публикации здесь):

<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>

Однако попытка проверить его не удалась с помощью следующего кода:

//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

Подпись всегда терпит неудачу. Я рассмотрел множество других вопросов о переполнении стека, и большинство из них связаны с проблемами пробелов (все документы игнорируют пробелы и все источники линеаризуются перед чтением) или добавлением пространства имен "ds" к префиксу подписи во время метода канонизации. Я применил это исправление "ds" и попробовал снова. Он создает следующий xml, который все еще не может быть проверен.

<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 На основании комментариев. Пробовал следующее:

<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);
        }
    }
}

По-прежнему не удается проверить.

Обновление 2 Теперь я получаю следующую ошибку при вызове CheckSignature:

SignatureDescription could not be created for the signature algorithm supplied.

Похоже, что это распространенная проблема в .net 4 с sha256, но я использую sha1 в своем текущем примере, и я использую ядро ​​dotnet. Я тоже пробовал sha256, но та же проблема сохраняется.

1 Ответ

0 голосов
/ 03 июля 2019

Нашел ответы, которые искал.

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);

Элемент, передаваемый конструктору SignedXml (), является контекстом подписи (поэтому элемент SOAP: Header).LoadXml () - это место, где вы фактически загружаете саму подпись (не в конструкторе).Исправление это решило все мои проблемы.

...