私たちのタスクは3つの部分に分かれています:個別の署名、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'sを通してこれを行う方法を知っている人にコメントすることを歓迎します:
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を取得することについてのコメントと、理由付けられた批判を待っています。