AlexMAS / GostCryptography

.NET driver for ViPNet CSP and CryptoPro CSP
MIT License
128 stars 41 forks source link

Поддержка префиксов в xmldsig #42

Open VVSoft opened 2 years ago

VVSoft commented 2 years ago

Доброго дня!

Спасибо за продукт! Очень выручаете!

Хотел попросить рассмотреть возможность поддержки работы с префиксом на блок Signature лично столкнулся с проблемой на примере сервиса ГИИС ДМДК https://dmdk.ru они отступают от стандарта и требуют префикс причем только "ds" пока решил временным костылём но хочется видеть интегрированное в библиотеку, более изящное и лаконичное решение так как не имею опыта работы с xml прикладываю то, что у меня работает.

Работающий код:

` public static void SignDmdkXml(string requestFileName, string requestSignedFileName, X509Certificate2 certificate, bool saveFormat = true) { // Подгружаем документ var xdoc = new XmlDocument { PreserveWhitespace = saveFormat };

            xdoc.Load(requestFileName);

            // Создание подписчика XML-документа
            var signedXml = new PrefixedSignedXml(xdoc,"ds")
            {
                // Установка ключа для создания подписи
                SigningKey = certificate.PrivateKey,
                SignedInfo =
                {
                    // Установка алгоритма нормализации узла SignedInfo (в соответствии с методическими рекомендациями СМЭВ)
                    CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl,
                    // Установка алгоритма хэширования (в соответствии с методическими рекомендациями СМЭВ)
                    SignatureMethod = CPSignedXml.XmlDsigGost3410_2012_256Url
                }
            };

            // Ссылка на узел, который нужно подписать, с указанием алгоритма хэширования
            var dataReference = new Reference
            {
                Uri = "#body",
                DigestMethod = CPSignedXml.XmlDsigGost3411_2012_256Url
            };

            // Метод преобразования, применяемый к данным перед их подписью (в соответствии с методическими рекомендациями СМЭВ)
            dataReference.AddTransform(new XmlDsigExcC14NTransform());
            dataReference.AddTransform(new XmlDsigSmevTransform());

            signedXml.SafeCanonicalizationMethods.Add("urn://smev-gov-ru/xmldsig/transform");

            // Установка ссылки на узел
            signedXml.AddReference(dataReference);

            // Установка информации о сертификате, который использовался для создания подписи
            var keyInfo = new KeyInfo();
            keyInfo.AddClause(new KeyInfoX509Data(certificate));
            signedXml.KeyInfo = keyInfo;

            // Вычисление подписи
            signedXml.ComputeSignature();

            // Получение XML-представления подписи
            var signatureXml = signedXml.GetXml();

            //// Добавление подписи в исходный документ
            xdoc.GetElementsByTagName("ns:CallerSignature")[0].AppendChild(xdoc.ImportNode(signatureXml, true));

            // Охраняем документ в выходной файл
            if (!saveFormat)
            {
                var settings = new XmlWriterSettings
                {
                    Indent = false,
                    NewLineChars = Empty
                };

                using var writer = XmlWriter.Create(requestSignedFileName, settings);
                xdoc.Save(writer);
            }
            else
                xdoc.Save(requestSignedFileName);
        }
    }

`

Использую обвертку над XmlSigned:

` ///

/// Thx https://stackoverflow.com/a/12343267 /// public class PrefixedSignedXml : SignedXml { private readonly string _prefix;

    public PrefixedSignedXml(XmlDocument document, string prefix)
        : base(document)
    {
        _prefix = prefix;
    }

    public void ComputeSignature()
    {
        BuildDigestedReferences();
        var signingKey = SigningKey;
        if (signingKey == null)
        {
            throw new CryptographicException("Cryptography_Xml_LoadKeyFailed");
        }
        if (SignedInfo.SignatureMethod == null)
        {
            if (signingKey is not DSA)
            {
                if (signingKey is not RSA)
                {
                    throw new CryptographicException("Cryptography_Xml_CreatedKeyFailed");
                }

                SignedInfo.SignatureMethod ??= "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
            }
            else
            {
                SignedInfo.SignatureMethod = "http://www.w3.org/2000/09/xmldsig#dsa-sha1";
            }
        }

        if (CryptoConfig.CreateFromName(SignedInfo.SignatureMethod) is not SignatureDescription description)
        {
            throw new CryptographicException("Cryptography_Xml_SignatureDescriptionNotCreated");
        }
        var hash = description.CreateDigest();
        if (hash == null)
        {
            throw new CryptographicException("Cryptography_Xml_CreateHashAlgorithmFailed");
        }
        GetC14NDigest(hash, _prefix);
        m_signature.SignatureValue = description.CreateFormatter(signingKey).CreateSignature(hash);
    }

    public new XmlElement GetXml()
    {
        var e = base.GetXml();
        SetPrefix(_prefix, e);
        return e;
    }

    //Отражательно вызывать закрытый метод SignedXml.BuildDigestedReferences
    private void BuildDigestedReferences()
    {
        var t = typeof(SignedXml);
        var m = t.GetMethod("BuildDigestedReferences", BindingFlags.NonPublic | BindingFlags.Instance);
        m?.Invoke(this, new object[] { });
    }

    private void GetC14NDigest(HashAlgorithm hash, string prefix)
    {
        //string securityUrl = (this.m_containingDocument == null) ? null : this.m_containingDocument.BaseURI;
        //XmlResolver xmlResolver = new XmlSecureResolver(new XmlUrlResolver(), securityUrl);
        var document = new XmlDocument
        {
            PreserveWhitespace = true
        };
        var e = SignedInfo.GetXml();
        document.AppendChild(document.ImportNode(e, true));
        //CanonicalXmlNodeList namespaces = (this.m_context == null) ? null : Utils.GetPropagatedAttributes(this.m_context);
        //Utils.AddNamespaces(document.DocumentElement, namespaces);

        var canonicalizationMethodObject = SignedInfo.CanonicalizationMethodObject;
        //canonicalizationMethodObject.Resolver = xmlResolver;
        //canonicalizationMethodObject.BaseURI = securityUrl;
        SetPrefix(prefix, document.DocumentElement); //мы устанавливаем префикс перед вычислением хеша (иначе подпись не будет действительной)
        canonicalizationMethodObject.LoadInput(document);
        canonicalizationMethodObject.GetDigestedOutput(hash);
    }

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

`