Генерация цифровой подписи, но с определенным префиксом пространства имен («ds:»)

12

Я цифровую подпись XML файлов, но теги подписи содержат префикс пространства имен "ds" . Я исследовал довольно Google и нашел многие из тех же вопросов, но не удовлетворительный ответ.

Я попытался поместить "ds" вручную в файл, но подпись становится недействительной. Тег "SignatureValue" подписывает тег "SignedInfo", поэтому подпись становится недействительной.

Может ли кто-нибудь показать мне, как я генерирую значение тега "SignatureValue", поэтому я могу заменить подпись после добавления префикса "ds" ?

  • 6
    Это должно пнуть его обратно в поле зрения :)
  • 0
    Обратите внимание, что подпись помещена в канонизированную версию открытого текста (элемент XML, на котором размещена подпись). Вам нужен закрытый ключ для генерации подписи, поэтому вы не можете заменить само значение подписи. Хитрость заключается в том, чтобы вставить пространство имен "ds" без изменения канонического представления, чтобы сигнатура осталась прежней.
Показать ещё 6 комментариев
Теги:
digital-signature

3 ответа

10
Лучший ответ

По-видимому, многие люди столкнулись с одной и той же проблемой. После изучения исходного кода класса Signature я пришел к выводу, что Microsoft стремится помочь нам. В методе LoadXml() есть жестко закодированный префикс "ds". Таким образом, можно сгенерировать сигнатуру, а затем добавить к ней префикс пространства имен "ds", загрузить измененную подпись и пересчитать "SignatureValue". Несчастливо ошибка в библиотеке делает вещи немного более трудными чем они должны быть. Код с обходным путем и комментариями ниже.

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

Поскольку вы подписываете документы, это должно быть достаточно легко сделать, но классы в System.Security.Cryptography.Xml на самом деле не поддерживают это.

Если вам нужны только элементы подписи, которые должны быть префиксными, то при условии, что подпись не ссылается сама по себе (или если она является частью ссылочного элемента, то до тех пор, пока она удаляется с помощью преобразования, как в " http://www.w3.org/2000/09/xmldsig#enveloped-signature" ), тогда вам нужно всего лишь пересчитать SignatureValue на основе измененного элемента SignedInfo. Ниже приведен пример метода SignEnveloped.

Однако ваша подпись не пройдет проверку, изложенную в MSDN Практическое руководство. Проверка Цифровые подписи XML-документов, поскольку вместо того, чтобы вычислять SignatureValue для проверки, фактически прочитав документ SignedInfo, класс 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 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 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);
        }
    }
}
  • 0
    Ренато, не могли бы вы проверить, правильно ли это работает в вашей ситуации? Хотя я заинтересован в результате, мне нечего проверять.
  • 0
    Мне пришлось внести несколько корректировок, чтобы заставить его работать конкретно с моим делом, однако после внесения этих корректировок я смог вычислить подпись, обновить с помощью префикса и успешно проверить подпись.
3

Изменить: Вы можете увидеть алгоритм, на который этот пост ссылается в моем другом ответе.

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

Например:

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);
    }
}
  • 0
    Я представил новый ответ с изложением алгоритма пересчета сигнатуры после добавления префикса элементов подписи и того, как впоследствии можно будет выполнить проверку в C #.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню