使用Crypto PRO在C#中验证电子签名

继续关于电子签名(以下称为电子签名)主题的对话,应该说是关于验证。 在上一篇文章中,我分析了任务中比较困难的部分-创建签名。 本文比较简单。 大多数代码是Crypto PRO .NET SDK中的示例的改编。 我们将根据GOST R 34.10-2001和GOST R 34.10-2012首先检查所有签名,为此,我们需要CRYPTO PRO。

我们的任务分为三个部分:单独的签名,PDF中的签名和MS Word中的签名。

验证分离的签名:

//dataFileRawBytes -     ContentInfo contentInfo = new ContentInfo(dataFileRawBytes); SignedCms signedCms = new SignedCms(contentInfo, true); //signatureFileRawBytes -    signedCms.Decode(signatureFileRawBytes); if (signedCms.SignerInfos.Count == 0) { //     } foreach (SignerInfo signerInfo in signedCms.SignerInfos) { //   DateTime? signDate = (signerInfo.SignedAttributes .Cast<CryptographicAttributeObject>() .FirstOrDefault(x => x.Oid.Value == "1.2.840.113549.1.9.5") ?.Values[0] as Pkcs9SigningTime)?.SigningTime; bool valid; try { signerInfo.CheckSignature(true); valid = true; } catch (CryptographicException exc) { valid = false; } //   .     X509Certificate2 certificate = signerInfo.Certificate; 

注释都包含在代码中,我只请您注意获取证书,稍后我们将需要它,因为 我们将单独检查证书。

好吧,不要忘记将所有内容包装在try-catch和其他使用中。 在示例中,我故意不这样做是为了减小音量

PDF中的签名验证。 在这里,我们需要iTextSharp(撰写本文时为5.5.13的当前版本):

  using (MemoryStream fileStream = new MemoryStream(dataFileRawBytes)) using (PdfReader pdfReader = new PdfReader(fileStream)) { AcroFields acroFields = pdfReader.AcroFields; //    List<string> signatureNames = acroFields.GetSignatureNames(); if (!signatureNames.Any()) { //   } foreach (string signatureName in signatureNames) { //       PdfDictionary singleSignature = acroFields.GetSignatureDictionary(signatureName); PdfString asString1 = singleSignature.GetAsString(PdfName.CONTENTS); byte[] signatureBytes = asString1.GetOriginalBytes(); RandomAccessFileOrArray safeFile = pdfReader.SafeFile; PdfArray asArray = singleSignature.GetAsArray(PdfName.BYTERANGE); using ( Stream stream = new RASInputStream( new RandomAccessSourceFactory().CreateRanged( safeFile.CreateSourceView(), asArray.AsLongArray()))) { using (MemoryStream ms = new MemoryStream((int)stream.Length)) { stream.CopyTo(ms); byte[] data = ms.GetBuffer(); ContentInfo contentInfo = new ContentInfo(data); SignedCms signedCms = new SignedCms(contentInfo, true); signedCms.Decode(signatureBytes); bool checkResult; //    ,    try { signedCms.CheckSignature(true); checkResult = true; } catch (Exception) { checkResult = false; } foreach (SignerInfo signerInfo in signedCms.SignerInfos) { //   DateTime? signDate = (signerInfo.SignedAttributes .Cast<CryptographicAttributeObject>() .FirstOrDefault(x => x.Oid.Value == "1.2.840.113549.1.9.5") ?.Values[0] as Pkcs9SigningTime)?.SigningTime; //  X509Certificate2 certificate = signerInfo.Certificate; } } } } } 

同样,没有什么特别要评论的。 除非我不得不说Oid,否则“ 1.2.840.113549.1.9.5”是签署日期的Oid。

我们列表中的最后一个是docx,也许是最简单的选择:

  using (MemoryStream fileStream = new MemoryStream(dataFileRawBytes)) using (Package filePackage = Package.Open(fileStream)) { PackageDigitalSignatureManager digitalSignatureManager = new PackageDigitalSignatureManager(filePackage); if (!digitalSignatureManager.IsSigned) { //    } foreach (PackageDigitalSignature signature in digitalSignatureManager.Signatures) { DateTime? signDate = signature.SigningTime; bool checkResult = signature.Verify() == VerifyResult.Success; //      X509Certificate2 certificate = new X509Certificate2(signature.Signer); } } 

现在,我们将解析证书并验证整个证书链。 因此,该程序集应该从有权访问网络的用户的下面进行工作。

然后地狱开始,因为 我不知道如何通过Oid获取有关证书所有者的信息,因此我将解析该字符串。 大声笑:马戏团开始了。

但认真地说,欢迎您对那些知道如何通过Oid做到这一点的人发表评论:

  private static void FillElectronicSignature(X509Certificate2 certificate) { foreach (KeyValuePair<string, string> item in ParseCertificatesSubject(certificate.Subject)) { switch (item.Key) { case "C": string certificatesCountryName = item.Value; break; case "S": string certificatesState = item.Value; break; case "L": string certificatesLocality = item.Value; break; case "O": string certificatesOrganizationName = item.Value; break; case "OU": string certificatesOrganizationalUnitName = item.Value; break; case "CN": string certificatesCommonName = item.Value; break; case "E": string certificatesEmail = item.Value; break; case "STREET": string certificatesStreet = item.Value; break; //  ,  Window ,     ,   ,  INN //       deploy    // ,   -  case "": case "INN": case "1.2.643.3.131.1.1": string certificatesInn = item.Value; break; //  case "": case "OGRN": case "1.2.643.100.1": string certificatesOgrn = item.Value; break; //  case "": case "SNILS": case "1.2.643.100.3": string certificatesSnils = item.Value; break; case "SN": string certificatesOwnerLastName = item.Value; break; case "G": string certificatesOwnerFirstName = item.Value; break; //    default           } } DateTime certificateNotBefore = certificate.NotBefore; DateTime certificateNotAfter = certificate.NotAfter; string certificatesSerialNumber = certificate.SerialNumber; if (!certificate.Verify()) { //   using (X509Chain x509Chain = new X509Chain()) { x509Chain.Build(certificate); //    X509ChainStatus[] statuses = x509Chain.ChainStatus; //      int,    int certificatesErrorCode = statuses.Aggregate(X509ChainStatusFlags.NoError, (acc, chainStatus) => acc | chainStatus.Status, result => (int)result); } } } /// <summary> ///        /// </summary> private static Dictionary<string, string> ParseCertificatesSubject(string subject) { Dictionary<string, string> result = new Dictionary<string, string>(); //  ,     int quotationMarksCount = 0; //    "  " bool isKey = true; //    string key = string.Empty; //    string value = string.Empty; for (int i = 0; i < subject.Length; i++) { char c = subject[i]; if (isKey && c == '=') { isKey = false; continue; } if (isKey) key += c; else { if (c == '"') quotationMarksCount++; bool isItemEnd = (c == ',' && subject.Length >= i + 1 && subject[i + 1] == ' '); bool isLastChar = subject.Length == i + 1; if ((isItemEnd && quotationMarksCount % 2 == 0) || isLastChar) { if (isItemEnd) i++; if (isLastChar) value += c; isKey = true; if (value.StartsWith("\"") && value.EndsWith("\"")) value = value.Substring(1, value.Length - 2); value = value.Replace("\"\"", "\""); result.Add(key, value); key = string.Empty; value = string.Empty; quotationMarksCount = 0; continue; } value += c; } } return result; } 

代码越短越好,以更好地理解其本质。

总的来说,这就是全部,我正在等待有关从证书中获取Oid的评论以及任何有理由的批评。

Source: https://habr.com/ru/post/zh-CN426645/


All Articles