【问题标题】:Generate Digital Signature but with a Specific Namespace Prefix ("ds:")生成数字签名,但带有特定的命名空间前缀(“ds:”)
【发布时间】:2015-06-01 17:48:57
【问题描述】:

我对 XML 文件进行数字签名,但需要签名标签包含命名空间前缀“ds”。我搜索了相当多的 google 并发现了许多相同的问题,但没有令人满意的答案。

我尝试将“ds”手动放入文件中,但签名无效。标签“SignatureValue”对标签“SignedInfo”进行签名,因此签名无效。

有人可以告诉我如何生成标签“SignatureValue”的值,以便在添加前缀“ds”后替换签名吗?

【问题讨论】:

  • 这应该让它重新回到视野中:)
  • 请注意,签名放置在 canonicalized 版本的纯文本(放置签名的 XML 元素)上。您需要私钥来生成签名,因此您无法替换签名值本身。诀窍是在不改变规范表示的情况下插入“ds”命名空间,以便签名保持不变。
  • 我认为我个人通过向 MS BizTalk 服务器的用户尖叫以使用生成标准化签名的软件来解决这个问题 :)
  • 在此期间有什么好运气吗,雷纳托?如果您找到答案,请不要忘记发布它!我在这里假设您想保持签名值不变?
  • 是什么让您认为您需要命名空间前缀为ds。这不是 XML 的工作方式。您可能从不懂 XML 的人那里收到了这个要求。 可能要求是关于不理解 XML 的 软件,但此类软件似乎不太可能也足够智能以处理 XML 签名。

标签: c# xml digital-signature prefixes


【解决方案1】:

显然很多人都遇到了同样的问题。在研究了Signature 类的源代码后,我得出结论,微软的目的是帮助我们。 LoadXml() 方法中有硬编码前缀“ds”。因此,可以生成签名,然后向其添加命名空间前缀“ds”,加载修改后的签名并重新计算“SignatureValue”。不幸的是,库中的错误使事情变得比他们需要的更难。带有解决方法和 cmets 的代码如下。

public static void SignXml(XmlDocument xmlDoc, X509Certificate2 cert)
{
        // transformation cert -> key omitted
        RSACryptoServiceProvider key;

        // Create a SignedXml object. 
        SignedXml signedXml = new SignedXml(xmlDoc);

        // Add the key to the SignedXml document. 
        signedXml.SigningKey = key;
        signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
        signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;

        // Create a reference to be signed. 
        Reference reference = new Reference();
        reference.Uri = "#foo";
        reference.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
        // Add an enveloped transformation to the reference. 
        reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
        reference.AddTransform(new XmlDsigExcC14NTransform());
        signedXml.AddReference(reference);

        KeyInfo keyInfo = new KeyInfo();
        KeyInfoX509Data keyInfoData = new KeyInfoX509Data();
        keyInfoData.AddIssuerSerial(cert.IssuerName.Format(false), cert.SerialNumber);
        keyInfo.AddClause(keyInfoData);
        signedXml.KeyInfo = keyInfo;

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

        // Add prefix "ds:" to signature
        XmlElement signature = signedXml.GetXml();
        SetPrefix("ds", signature);

        // Load modified signature back
        signedXml.LoadXml(signature);

        // this is workaround for overcoming a bug in the library
        signedXml.SignedInfo.References.Clear();

        // Recompute the signature
        signedXml.ComputeSignature();
        string recomputedSignature = Convert.ToBase64String(signedXml.SignatureValue);

        // Replace value of the signature with recomputed one
        ReplaceSignature(signature, recomputedSignature);

        // Append the signature to the XML document. 
        xmlDoc.DocumentElement.InsertAfter(xmlDoc.ImportNode(signature, true), xmlDoc.DocumentElement.FirstChild);
    }

    private static void SetPrefix(string prefix, XmlNode node)
    {
        node.Prefix = prefix;
        foreach (XmlNode n in node.ChildNodes)
        {
            SetPrefix(prefix, n);
        }
    }

    private static void ReplaceSignature(XmlElement signature, string newValue)
    {
        if (signature == null) throw new ArgumentNullException(nameof(signature));
        if (signature.OwnerDocument == null) throw new ArgumentException("No owner document", nameof(signature));

        XmlNamespaceManager nsm = new XmlNamespaceManager(signature.OwnerDocument.NameTable);
        nsm.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl);

        XmlNode signatureValue = signature.SelectSingleNode("ds:SignatureValue", nsm);
        if (signatureValue == null)
            throw new Exception("Signature does not contain 'ds:SignatureValue'");

        signatureValue.InnerXml = newValue;
    }

【讨论】:

    【解决方案2】:

    编辑:你可以看到这篇文章在我的另一个答案中提到的一种算法。

    假设不编写自己的算法来规范化和签署文档是不可能的,一个可能的解决方法是在签名后在签名元素上“注入”命名空间前缀,然后在验证之前将其从它们中删除。

    例如:

    void SignXml(XmlDocument xmlDoc, RSA Key)
    {
        SignedXml signedXml = new SignedXml(xmlDoc);
        signedXml.SigningKey = Key;
    
        Reference reference = new Reference("");
        reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
    
        signedXml.AddReference(reference);
    
        signedXml.ComputeSignature();
    
        XmlElement xmlSignature = signedXml.GetXml();
    
        //Here we set the namespace prefix on the signature element and all child elements to "ds", invalidating the signature.
        AssignNameSpacePrefixToElementTree(xmlSignature, "ds");
    
        xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlSignature, true));
    }
    
    bool VerifyXmlSignature(XmlDocument xmlDoc, RSA Key, string prefix)
    {
        SignedXml signedXml = new SignedXml(xmlDoc);
    
        //Get the <ds:Signature /> element
        XmlElement xmlSignature = (XmlElement)xmlDoc.GetElementsByTagName(prefix + ":Signature")[0];
    
        //Undo what we did after signing
        AssignNameSpacePrefixToElementTree(xmlSignature, "");
    
        //Now it will pass verification.
        signedXml.LoadXml(xmlSignature);
        return signedXml.CheckSignature(Key);
    }
    
    void AssignNameSpacePrefixToElementTree(XmlElement element, string prefix)
    {
        element.Prefix = prefix;
    
        foreach (var child in element.ChildNodes)
        {
            if (child is XmlElement)
                AssignNameSpacePrefixToElementTree(child as XmlElement, prefix);
        }
    }
    

    【讨论】:

    • 我提交了一个新答案,概述了在为 Signature 元素添加前缀后重新计算签名的算法,以及此后如何在 C# 中处理验证。
    【解决方案3】:

    由于您是签署文件的人,这应该很容易做到,但 System.Security.Cryptography.Xml 中的类并不真正支持这一点。

    如果您只需要为 Signature 元素添加前缀,那么,如果 Signature 本身没有被引用(或者如果它是被引用元素的一部分,那么只要使用“http://www.w3.org/2000/09/xmldsig#enveloped-signature”中的转换将其删除即可) 那么您需要做的就是根据更改后的 SignedInfo 元素重新计算 SignatureValue。有关示例,请参见下面的 SignEnveloped 方法。

    但是,您的签名不会通过MSDN's How to: Verify the Digital Signatures of XML Documents 中概述的验证,因为不是通过实际读取文档的 SignedInfo 来计算要检查的 SignatureValue,SignedXml 类似乎会生成一个没有前缀元素的新签名。下面的类围绕着 SignedXmls 看似错误的实现工作,但在其他框架中也可能存在验证问题,而不需要前缀元素。

    public static class XmlSigning
    {
        private static Type tSignedXml = typeof(SignedXml);
        private static ResourceManager SecurityResources = new ResourceManager("system.security", tSignedXml.Assembly);
    
        //these methods from the SignedXml class still work with prefixed Signature elements, but they are private
        private static ParameterExpression thisSignedXmlParam = Expression.Parameter(tSignedXml);
        private static Func<SignedXml, bool> CheckSignatureFormat
            = Expression.Lambda<Func<SignedXml, bool>>(
                Expression.Call(thisSignedXmlParam, tSignedXml.GetMethod("CheckSignatureFormat", BindingFlags.NonPublic | BindingFlags.Instance)),
                thisSignedXmlParam).Compile();
        private static Func<SignedXml, bool> CheckDigestedReferences
            = Expression.Lambda<Func<SignedXml, bool>>(
                Expression.Call(thisSignedXmlParam, tSignedXml.GetMethod("CheckDigestedReferences", BindingFlags.NonPublic | BindingFlags.Instance)),
                thisSignedXmlParam).Compile();
    
        public static void SignEnveloped(XmlDocument xmlDoc, RSACryptoServiceProvider key, string signatureNamespacePrefix)
        {
            SignedXml signedXml = new SignedXml(xmlDoc);
            signedXml.SigningKey = key;
    
            Reference reference = new Reference("");
            reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
    
            signedXml.AddReference(reference);
    
            signedXml.ComputeSignature();
    
            XmlElement xmlSignature = signedXml.GetXml();
    
            if (!string.IsNullOrEmpty(signatureNamespacePrefix))
            {
                //Here we set the namespace prefix on the signature element and all child elements to "ds", invalidating the signature.
                AssignNameSpacePrefixToElementTree(xmlSignature, "ds");
    
                //So let's recompute the SignatureValue based on our new SignatureInfo...
    
                //For XPath
                XmlNamespaceManager namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
                namespaceManager.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); //this prefix is arbitrary and used only for XPath
    
                XmlElement xmlSignedInfo = xmlSignature.SelectSingleNode("ds:SignedInfo", namespaceManager) as XmlElement;
    
                //Canonicalize the SignedInfo element
                XmlDsigC14NTransform transform = new XmlDsigC14NTransform();
                XmlDocument signedInfoDoc = new XmlDocument();
                signedInfoDoc.LoadXml(xmlSignedInfo.OuterXml);
                transform.LoadInput(signedInfoDoc);
    
                //Compute the new SignatureValue
                string signatureValue = Convert.ToBase64String(key.SignData(transform.GetOutput() as MemoryStream, new SHA1CryptoServiceProvider()));
                //Set it in the xml
                XmlElement xmlSignatureValue = xmlSignature.SelectSingleNode("ds:SignatureValue", namespaceManager) as XmlElement;
                xmlSignatureValue.InnerText = signatureValue;
            }
    
            xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlSignature, true));
        }
    
        public static bool CheckSignature(XmlDocument xmlDoc, RSACryptoServiceProvider key)
        {
            if (key == null)
                throw new ArgumentNullException("key");
    
            SignedXml signedXml = new SignedXml(xmlDoc);
    
            //For XPath
            XmlNamespaceManager namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
            namespaceManager.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#"); //this prefix is arbitrary and used only for XPath
    
            XmlElement xmlSignature = xmlDoc.SelectSingleNode("//ds:Signature", namespaceManager) as XmlElement;
    
            signedXml.LoadXml(xmlSignature);
    
            //These are the three methods called in SignedXml's CheckSignature method, but the built-in CheckSignedInfo will not validate prefixed Signature elements
            return CheckSignatureFormat(signedXml) && CheckDigestedReferences(signedXml) && CheckSignedInfo(signedXml, key);
        }
    
        private static bool CheckSignedInfo(SignedXml signedXml, AsymmetricAlgorithm key)
        {
            //Copied from reflected System.Security.Cryptography.Xml.SignedXml
            SignatureDescription signatureDescription = CryptoConfig.CreateFromName(signedXml.SignatureMethod) as SignatureDescription;
            if (signatureDescription == null)
                throw new CryptographicException(SecurityResources.GetString("Cryptography_Xml_SignatureDescriptionNotCreated"));
    
            Type type = Type.GetType(signatureDescription.KeyAlgorithm);
            Type type2 = key.GetType();
            if (type != type2 && !type.IsSubclassOf(type2) && !type2.IsSubclassOf(type))
                return false;
    
            HashAlgorithm hashAlgorithm = signatureDescription.CreateDigest();
            if (hashAlgorithm == null)
                throw new CryptographicException(SecurityResources.GetString("Cryptography_Xml_CreateHashAlgorithmFailed"));
    
            //Except this. The SignedXml class creates and cananicalizes a Signature element without any prefix, rather than using the element from the document provided
            byte[] c14NDigest = GetC14NDigest(signedXml, hashAlgorithm);
    
            AsymmetricSignatureDeformatter asymmetricSignatureDeformatter = signatureDescription.CreateDeformatter(key);
            return asymmetricSignatureDeformatter.VerifySignature(c14NDigest, signedXml.Signature.SignatureValue);
        }
    
        private static byte[] GetC14NDigest(SignedXml signedXml, HashAlgorithm hashAlgorithm)
        {
            Transform canonicalizeTransform = signedXml.SignedInfo.CanonicalizationMethodObject;
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(signedXml.SignedInfo.GetXml().OuterXml);
            canonicalizeTransform.LoadInput(xmlDoc);
            return canonicalizeTransform.GetDigestedOutput(hashAlgorithm);
        }
    
        private static void AssignNameSpacePrefixToElementTree(XmlElement element, string prefix)
        {
            element.Prefix = prefix;
    
            foreach (var child in element.ChildNodes)
            {
                if (child is XmlElement)
                    AssignNameSpacePrefixToElementTree(child as XmlElement, prefix);
            }
        }
    }
    

    【讨论】:

    • Renato,您能否检查一下这是否适合您的情况?虽然我对结果感兴趣,但我没有任何可测试的东西。
    • 我必须进行一些调整才能使其专门用于我的案例,但是,在进行这些调整之后,我能够计算签名,使用前缀更新,并成功验证签名.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-09
    • 2015-06-05
    相关资源
    最近更新 更多