11

将应用程序从 3.5 升级到 4.6.2 后 以下代码块不再有效。我得到“格式错误的参考元素”错误,即使它作为 3.5 应用程序工作得很好。代码因上述错误而失败,应该是一个很好的参考。我已经尝试了我能想到的一切都无法让 ASP.Net 版本正常工作。我已经构建了一个测试平台版本作为控制台应用程序,它可以正常工作,直到它到达最后一个失败并显示“无法解析 Uri Signature1.jpg”的引用。我读过 XMLSigner 不接受除 id、ID 和 Id 以外的任何元素作为要查找以匹配引用的元素,但我不相信这种情况,因为它适用于控制台应用程序。

问题的核心是:

  1. 为什么我得到一个“格式错误的参考元素”signedXMl.AddReference(new Reference("#Head01"));
  2. 如何修复对客户签名“src="Signature1.jpg"" 的引用

有问题的功能:

private XmlDocument SignDoc(XmlDocument doc, RSA key, X509Certificate x509cert, ArrayList alSignatures)
    {
        string signatureID = "TamperSealer01";
        Uri uri = new Uri(ConfigurationManager.AppSettings["SomeSetting"]); 
        XmlResolver resolver = new XmlSignatureResolver(uri);
        SignedXml signedXml = new SignedXml(doc);
        signedXml.Signature.Id = signatureID;
        signedXml.Resolver = resolver;

        // Add the key to the SignedXml responseDocument. 
        signedXml.SigningKey = key;

        // Create a new KeyInfo object.
        KeyInfo keyInfo = new KeyInfo();
        keyInfo.AddClause(new RSAKeyValue(key));

        KeyInfoX509Data x509Data = new KeyInfoX509Data(x509cert);

        string subjectName = x509cert.Subject;
        subjectName = subjectName.Replace("S=", "ST=");
        string tmpSubName = subjectName;

        tmpSubName = tmpSubName.Replace("O=A", "O=B");
        tmpSubName = tmpSubName.Replace("CN=A", "CN=B");

        x509Data.AddSubjectName(tmpSubName);
        x509Data.AddIssuerSerial(x509cert.Issuer, x509cert.GetSerialNumberString()); //GetIssuerName
        keyInfo.AddClause(x509Data);
        signedXml.KeyInfo = keyInfo;

        //TIMESTAMP
        XmlElement signaturePropertiesRoot = doc.CreateElement("SignatureProperties", "http://www.w3.org/2000/09/xmldsig#");

        DataObject signatureProperties = new DataObject();
        signatureProperties.Id = "TimeStamp";
        signatureProperties.Data = signaturePropertiesRoot.SelectNodes(".");
        signedXml.AddObject(signatureProperties);

        // and add a reference to the data object
        Reference propertiesRef = new Reference();
        propertiesRef.Uri = "#TimeStamp";
        propertiesRef.Type = "http://www.w3.org/2000/09/xmldsig#SignatureProperties";
        signedXml.AddReference(propertiesRef);

        XmlElement property = doc.CreateElement("SignatureProperty", "http://www.w3.org/2000/09/xmldsig#");
        property.SetAttribute("Id", "TamperSealer01TimeStamp");
        property.SetAttribute("Target", "#" + signedXml.Signature.Id);
        signaturePropertiesRoot.AppendChild(property);

        XmlElement timestamp = doc.CreateElement("DateTimeStamp", "http://www.w3.org/2000/09/xmldsig#");
        timestamp.SetAttribute("DateTime", String.Format("{0:s}Z", DateTime.Now.ToUniversalTime()));
        property.AppendChild(timestamp);
        signedXml.ComputeSignature();

        // References contains three strings "Head01", "Data01", and "View01"
        foreach (string docRef in references) 
        {
            // Create a reference to be signed.
            Reference tempRef = new Reference();
            tempRef.Uri = "#" + docRef;
            Logger.Current.LogInformation("DocRef: #" + docRef + ".");
            // Add the reference to the SignedXml object
            signedXml.AddReference(tempRef);
            signedXml.ComputeSignature(); //Immediately Fails here
        }

        // alSignatures only contains "Signature1.jpg" in this case. Don't yell at me for this crappy code, I didn't write it and plan on fixing it when everything else works.
        int ctr = 0;
        foreach (string str in alSignatures)
        {
            Reference testRef = new Reference();
            Uri relativeUri = new Uri(alSignatures[ctr].ToString(), UriKind.RelativeOrAbsolute);
            Logger.Current.LogInformation("Signature Reference: " + alSignatures[ctr].ToString());
            testRef.Uri = alSignatures[ctr].ToString();
            signedXml.AddReference(testRef);
            ctr += 1;
        }

        // Compute the signature.
        signedXml.ComputeSignature();

        // Get the XML representation of the signature and save it to an XmlElement object.
        XmlElement xmlDigitalSignature = signedXml.GetXml();
        XmlElement signaturesElement = doc.CreateElement("SIGNATURES", "http://www.mismo.org");

        signaturesElement.AppendChild(signedXml.GetXml());
        doc.DocumentElement.AppendChild(signaturesElement);

        key.Clear();
        key.Dispose();
        return doc;
}

它应该签名的 XML 至少是这样的:

<?xml version="1.0" encoding="UTF-8"?>
<DOCUMENT MISMOVersionIdentifier="1.02">
    <HEADER _ID="Head01">
        <SIGNATURE_MODEL>
            <SIGNER AreaIDREF="Borrower1SignatureArea" SectionIDREF="BorrowerSignatures" SignatureIDREF="Borrower1SignatureLine" SignatureType="Image" TargetsIDREFS="View01" _RoleType="Borrower" _SignatureOrderNumber="1" />
            <SIGNER SignatureIDREF="TamperSealer01" SignatureType="DigitalSignature" TargetsIDREFS="Head01 Data01 View01" _RoleType="TamperSealer" _SignatureOrderNumber="1" />
        </SIGNATURE_MODEL>
    </HEADER>
    <DATA _ID="Data01">
        <MAIN>
        </MAIN>
    </DATA>
    <VIEW _ID="View01" _MIMETypeDescription="text/html" _TaggedIndicator="True">
        <html xmlns="http://www.w3.org/1999/xhtml">
            <body>
                <span class="dataEntered" id="BORROWER_Signer-Info">
                    <SIGNATURE_SECTION _ID="BorrowerSignatures">
                        <SIGNATURE_AREA _ID="Borrower1SignatureArea">
                            <p class="right">
                                <SIGNATURE_ABOVE_LINE />
                                <SIGNATURE_IMAGE _EncodingTypeDescription="None" _ID="Borrower1SignatureLine" _MIMEType="image/jpeg">
                                    <img align="right" alt="Signature file is missing - Invalid Document" src="Signature1.jpg" />
                                </SIGNATURE_IMAGE>
                            </p>
                            <p>04/12/2011 12:00 PM</p>
                        </SIGNATURE_AREA>
                    </SIGNATURE_SECTION>
                </span>
            </body>
        </html>
    </VIEW>
</DOCUMENT>
4

2 回答 2

3

相关的代码 -CalculateHashValueSystem.Security.Cryptography.Xml.Reference

// for "Head01" this is "#Head01"
if (this.m_uri[0] == '#')
{
   // idFromLocalUri is set to "Head01"
   string idFromLocalUri = Utils.GetIdFromLocalUri(this.m_uri, out flag);
   ...
   // there is no element with Id="Head01" - so xmlElement is null
   var xmlElement = this.SignedXml.GetIdElement(document, idFromLocalUri);
   ...
   if (xmlElement == null)
   {
     // this is the error you're getting
     throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_InvalidReference"));
   }
}

因此,您在参考验证方面失败了-文档中没有具有此ID的元素-顺便说一句。在您的 xml 中通过实验将“_ID”更改为“Id”解决了这个问题。

好消息是SignedXml该类是可扩展的,您可以重载该XmlElement GetIdElement(XmlDocument document, string idValue)方法以将“_ID”纳入帐户。

// just a sample
class MyCustomSignedXml : SignedXml {
   ...
   override XmlElement GetIdElement(XmlDocument document, string idValue) {
     var element = document.SelectSingleNode($"//*[@_ID='{idValue}']") as XmlElement;
     if (element != null) {
       return element;
     } 

     return base.GetIdElement(document, idValue);
   }
}
于 2018-05-29T10:19:15.840 回答
1

在 Ondrej Svejdar 的提示下,我能够完成这项工作。事实证明,我需要两个课程才能工作。我无法在 UAT 中进行测试,但到目前为止我需要两个类和一个注册表编辑。一个自定义 XmlUrlResolver 允许 DTD 位于单独的位置并指向与外部引用的 XML 相同的文件夹,以及一个修改后的 SignedXml 类来处理 ID。

注册表编辑: https: //support.microsoft.com/en-us/help/3148821/after-you-apply-security-update-3141780-net-framework-applications-enc

修改 SignedXml 类:

    public class CustomIdSignedXml : SignedXml
{
    private static readonly string[] idAttrs = new string[]
    {
    "_id",
    "_Id",
    "_ID"
    };

    public CustomIdSignedXml(XmlDocument doc) : base(doc)
    {
        return;
    }

    public override XmlElement GetIdElement(XmlDocument doc, string id)
    {
        XmlElement idElem = null;
        // check to see if it's a standard ID reference
        //XmlElement idElem = base.GetIdElement(doc, id);
        //if (idElem != null)
        //  return idElem;

        //I get the feeling this is horridly insecure
        XmlElement elementById1 = doc.GetElementById(id);
        if (elementById1 != null) return elementById1;
        // if not, search for custom ids
        foreach (string idAttr in idAttrs)
        {
            idElem = doc.SelectSingleNode("//*[@" + idAttr + "=\"" + id + "\"]") as XmlElement;
            if (idElem != null)
                break;
        }

        return idElem;
    }
}

修改后的 XmlResolver:

public class DTDAndSignatureResolver : XmlUrlResolver
{
    private readonly Uri DTDUri;
    private readonly List<string> XmlExtensions = new List<string>() { ".xml" };
    private readonly List<string> DTDExtensions = new List<string>() { ".dtd", ".ent" };
    private ICredentials credentials;

    public DTDAndSignatureResolver(Uri DTDUri)
    {
        this.DTDUri = DTDUri;
    }

    public override ICredentials Credentials
    {
        set { credentials = value; }
    }

    public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
    {
        if (DTDExtensions.Any(e => absoluteUri.ToString().ToLower().EndsWith(e)) || XmlExtensions.Any(e => absoluteUri.ToString().ToLower().EndsWith(e)))
        {
            return base.GetEntity(absoluteUri, role, ofObjectToReturn); //For DTD/ENT/XML lookup
        }
        else
        {
            return base.GetEntity(DTDUri, null, ofObjectToReturn); //For signature image lookup
        }
    }

    public override Uri ResolveUri(Uri uri, string relativeUri)
    {
        return base.ResolveUri(DTDUri, relativeUri);
    }
}

通过这两项修改,我的 .Net 4.6.2 代码能够验证来自 .Net 3.5 的签名 XML 文档,反之亦然。

于 2018-08-09T16:54:10.420 回答