using Hcs.GostXades.Abstractions; using Hcs.GostXades.Helpers; using Microsoft.Xades; using System; using System.Linq; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Xml; namespace Hcs.GostXades { public class XadesBesSignedXml : XadesSignedXml { public ICertificateMatcher CertificateMatcher { get; set; } public ICryptoProvider CryptoProvider { get; set; } /// Документ, который необходимо подписать с помощью XAdES-BES public XadesBesSignedXml(XmlDocument document) : base(document) { } /// Документ, подписанный с помощью XAdES-BES /// Id подписанного элемента public XadesBesSignedXml(XmlDocument document, string elementId) : base(document, elementId) { var elementToVerify = FindElement(elementId, document); var signatureNodeList = FindSignatureNodes(elementToVerify); if (!signatureNodeList.Any()) { throw new InvalidOperationException($"Элемент с Id {elementId} не содержит подписи"); } if (signatureNodeList.Length > 1) { throw new InvalidOperationException($"Элемент с {elementId} подписан более одного раза"); } var signatureNode = (XmlElement)signatureNodeList[0]; var standart = GetSignatureStandart(signatureNode); if (standart != KnownSignatureStandard.Xades) { throw new InvalidOperationException($"Элемент с Id {elementId} подписан не по стандарту XADES-BES"); } LoadXml(signatureNode); } /// /// Валидация XAdES-BES подписи /// public void Validate() { X509Certificate2 matchedCert; try { matchedCert = CertificateMatcher.GetSignatureCertificate(this); } catch (InvalidOperationException ex) { throw new XadesBesValidationException(ex.Message, ex); } if (matchedCert == null) { throw new XadesBesValidationException("XML подпись неверна"); } ValidateCertificate(matchedCert); ValidateAdditionalProperties(); } /// /// Подписывает XML-документ c помощью XAdES-BES /// /// Сертификат, с помощью которого производится подпись /// Пароль от контейнера закрытого ключа используемого сертификата public void ComputeSignature(X509Certificate2 certificate, string privateKeyPassword) { var signatureId = $"xmldsig-{Guid.NewGuid().ToString().ToLower()}"; SigningKey = CryptoProvider.GetAsymmetricAlgorithm(certificate, privateKeyPassword); Signature.Id = signatureId; SignatureValueId = $"{signatureId}-sigvalue"; var reference = CryptoProvider.GetReference(SignedElementId, signatureId); AddReference(reference); SignedInfo.CanonicalizationMethod = XmlDsigCanonicalizationUrl; SignedInfo.SignatureMethod = CryptoProvider.SignatureMethod; var xadesInfo = new XadesInfo(certificate); KeyInfo = KeyInfoHelper.Create(certificate); var xadesObject = CryptoProvider.GetXadesObject(xadesInfo, signatureId); AddXadesObject(xadesObject); ComputeSignature(); HashAlgorithm hashAlgorithm; GetSignedInfoHash(out hashAlgorithm); var formatter = CryptoProvider.GetSignatureFormatter(certificate); var signedHash = formatter.CreateSignature(hashAlgorithm.Hash); Signature.SignatureValue = signedHash; } public XmlElement FindElement(string elementId, XmlDocument xmlDocument) { var elementToVerify = GetIdElement(xmlDocument, elementId); if (elementToVerify == null) { throw new InvalidOperationException($"Элемент с Id {elementId} не найден"); } return elementToVerify; } private KnownSignatureStandard GetSignatureStandart(XmlElement signatureElement) { return GetXadesObjectElement(signatureElement) == null ? KnownSignatureStandard.XmlDsig : KnownSignatureStandard.Xades; } private static XmlNode[] FindSignatureNodes(XmlElement elementToVerify) { var items = elementToVerify.ChildNodes .OfType() .Where(x => string.Equals(x.LocalName, SignatureTagName, StringComparison.InvariantCulture) && string.Equals(x.NamespaceURI, XmlDsigNamespaceUrl, StringComparison.InvariantCultureIgnoreCase)) .ToArray(); return items; } private void ValidateAdditionalProperties() { var signedSignatureProperties = SignedSignatureProperties; var signedDataObjectProperties = SignedDataObjectProperties; // XAdES 1.4.2 clause G.2.2.6 Checking SignaturePolicyIdentifier ThrowIfTrue(signedSignatureProperties?.SignaturePolicyIdentifier != null, "SignaturePolicyIdentifier"); // XAdES 1.4.2 clause G.2.2.8 Checking DataObjectFormat ThrowIfTrue(signedDataObjectProperties?.DataObjectFormatCollection.IsNotEmpty(), "DataObjectFormat"); // XAdES 1.4.2 clause G.2.2.9 Checking CommitmentTypeIndication ThrowIfTrue(signedDataObjectProperties?.CommitmentTypeIndicationCollection.IsNotEmpty(), "CommitmentTypeIndication"); // XAdES 1.4.2 clause G.2.2.11 Checking SignerRole var signerRole = signedSignatureProperties?.SignerRole; ThrowIfTrue(signerRole?.ClaimedRoles != null || signerRole?.CertifiedRoles != null, "SignerRole"); // XAdES 1.4.2 clause G.2.2.16.1.1 Checking AllDataObjectsTimeStamp ThrowIfTrue(signedDataObjectProperties?.AllDataObjectsTimeStampCollection.IsNotEmpty(), "AllDataObjectsTimeStamp"); // XAdES 1.4.2 clause G.2.2.16.1.2 Checking IndividualDataObjectsTimeStamp ThrowIfTrue(signedDataObjectProperties?.IndividualDataObjectsTimeStampCollection.IsNotEmpty(), "IndividualDataObjectsTimeStamp"); } private static void ValidateCertificate(X509Certificate2 certificate) { if (DateTime.Now > certificate.NotAfter || DateTime.Now < certificate.NotBefore) { throw new XadesBesValidationException("Срок действия сертификата истек или еще не наступил"); } var isCertValid = certificate.Verify(); if (!isCertValid) { throw new XadesBesValidationException("Неверный сертификат"); } } private static void ThrowIfTrue(bool? condition, string tagName) { if (condition == true) { throw new XadesBesValidationException($"Свойство xades:{tagName} не поддерживается"); } } private const string SignatureTagName = "Signature"; } }