JSでPDFに署名し、Crypto PROを使用してC#で署名を貼り付ける

だから。 タスクが来ました。 ブラウザーを使用して、ユーザーを電子署名(以降EPと呼びます)でPDFに署名するように招待します。 ユーザーは、証明書、公開鍵および秘密鍵を含むトークンを持っている必要があります。 次に、サーバー上で、PDFドキュメントに署名を挿入する必要があります。 その後、署名の有効性を確認する必要があります。 ASP.NETと、それに応じてバックエンドとしてC#を使用します。



すべてのソルトは、CAdES-Xロングタイプ1形式の署名と、ロシアのGOST R 34.10-2001、GOST R 34.10-2012などを使用する必要があるということです。 さらに、複数の署名が存在する可能性があります。つまり、ユーザーはファイルに交互に署名することができます。 ただし、以前の署名は有効なままでなければなりません。



問題を解決する過程で、彼らは私たちを複雑にし、クライアントに送信されるデータの量を減らすことにしました。 ドキュメントのハッシュのみを送信し、ドキュメント自体は送信しません。



ソースコードでは、このトピックにとってほとんど意味のない瞬間を省略します。暗号に関することだけを残します。 JSコードは、JSエンジンがPromiseおよびFunction Generatorをサポートする通常のブラウザーにのみ提供します。 誰がIEのために自分で書く必要があると思います(「やりたくない」必要がありました)。



必要なもの:



  1. ユーザーはキーペアと証明書を受け取る必要があります。
  2. ユーザーは、Crypto PROからプラグインをインストールする必要があります。 これがないと、JSツールを使用して暗号化プロバイダーと連携できなくなります。


備考:



  1. テストについては、テストCrypto PRO CAによって発行された証明書と、従業員の1人が受け取った通常のトークンがありました(執筆時点で、Crypto PROの年間ライセンスと2つの証明書を持つ〜1500r、ただし「新しい」および「古い」GOST)
  2. 彼らはプラグインがViPNetで動作することができると言っていますが、私はそれをテストしていません。


ここで、サーバーに署名可能なPDFがあると仮定します。

Crypto PROからページにスクリプトを追加します。



<script src="/Scripts/cadesplugin_api.js" type="text/javascript"></script>
      
      





次に、cadespluginオブジェクトが形成されるまで待つ必要があります



 window.cadespluginLoaded = false; cadesplugin.then(function () { window.cadespluginLoaded = true; });
      
      





ハッシュサーバーに問い合わせます。 以前は、このために、ユーザーが署名する証明書、したがってアルゴリズムを知る必要があります。 ちょっとしたコメント:クライアント側で暗号化を操作するためのすべての関数と「変数」をCryptographyObjectに統合しました。



CryptographyObjectオブジェクトの証明書フィールドに入力する方法:



  fillCertificates: function (failCallback) { cadesplugin.async_spawn(function*() { try { let oStore = yield cadesplugin.CreateObjectAsync("CAPICOM.Store"); oStore.Open(cadesplugin.CAPICOM_CURRENT_USER_STORE, cadesplugin.CAPICOM_MY_STORE, cadesplugin.CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED); let certs = yield oStore.Certificates; certs = yield certs.Find(cadesplugin.CAPICOM_CERTIFICATE_FIND_TIME_VALID); let certsCount = yield certs.Count; for (let i = 1; i <= certsCount; i++) { let cert = yield certs.Item(i); CryptographyObject.certificates.push(cert); } oStore.Close(); } catch (exc) { failCallback(exc); } }); }
      
      





コメント:証明書ストアを開こうとしています。 この時点で、ユーザーのシステムは、サイトが証明書、暗号化、およびその他の魔法の、理解できないナンセンスで何かをしようとしていることを警告します。 ユーザーは「はい」ボタンをクリックする必要があります

次に、期限内に有効な証明書を取得し、証明書配列に追加します。 これは、cadespluginの非同期性のために行う必要があります(IEでは、すべてが異なります;))。



ハッシュ方法:



 getHash: function (certIndex, successCallback, failCallback, -  ) { try { cadesplugin.async_spawn(function*() { let cert = CryptographyObject.certificates[certIndex]; let certPublicKey = yield cert.PublicKey(); let certAlgorithm = yield certPublicKey.Algorithm; let algorithmValue = yield certAlgorithm.Value; let hashAlgorithm; //           if (algorithmValue === "1.2.643.7.1.1.1.1") { hashAlgorithm = "2012256"; } else if (algorithmValue === "1.2.643.7.1.1.1.2") { hashAlgorithm = "2012512"; } else if (algorithmValue === "1.2.643.2.2.19") { hashAlgorithm = "3411"; } else { failCallback("      ."); return; } $.ajax({ url: "/Services/SignService.asmx/GetHash", method: "POST", contentType: "application/json; charset=utf-8 ", dataType: "json", data: JSON.stringify({ //-     //          hashAlgorithm: hashAlgorithm, }), complete: function (response) { //   ,       if (response.status === 200) { CryptographyObject.signHash(response.responseJSON, function(data) { $.ajax({ url: CryptographyObject.signServiceUrl, method: "POST", contentType: "application/json; charset=utf-8", dataType: "json", data: JSON.stringify({ Signature: data.Signature, //-     //       }), complete: function(response) { if (response.status === 200) successCallback(); else failCallback(); } }); }, certIndex); } else { failCallback(); } } }); }); } catch (exc) { failCallback(exc); } }
      
      





コメント:cadesplugin.async_spawnに注意してください。ジェネレーター関数がcadesplugin.async_spawnに渡され、その上でnext()が順次呼び出され、yieldへの移行につながります。

したがって、async-awaitの特定のアナログをC#から取得します。 すべてが同期的に見えますが、非同期で動作します。



ハッシュが要求されたときにサーバー上で何が起こるか。



最初に、iTextSharp nugetパッケージをインストールする必要があります(執筆時点では、現在のバージョン5.5.13が必要です)



第二に、CryptoPro.Sharpeiが必要です。CryptoPRO .NET SDKの負荷になります



これでハッシュを取得できます



  // hash- HashAlgorithm hashAlgorithm; switch (hashAlgorithmName) { case "3411": hashAlgorithm = new Gost3411CryptoServiceProvider(); break; case "2012256": hashAlgorithm = new Gost3411_2012_256CryptoServiceProvider(); break; case "2012512": hashAlgorithm = new Gost3411_2012_512CryptoServiceProvider(); break; default: GetLogger().AddError("  ", $"hashAlgorithmName: {hashAlgorithmName}"); return HttpStatusCode.BadRequest; } // hash   ,  cadesplugin string hash; using (hashAlgorithm) //downloadResponse.RawBytes -     PDF  using (PdfReader reader = new PdfReader(downloadResponse.RawBytes)) { //    int existingSignaturesNumber = reader.AcroFields.GetSignatureNames().Count; using (MemoryStream stream = new MemoryStream()) { //      using (PdfStamper st = PdfStamper.CreateSignature(reader, stream, '\0', null, true)) { PdfSignatureAppearance appearance = st.SignatureAppearance; //        ,        appearance.SetVisibleSignature(new Rectangle(36, 100, 164, 150), reader.NumberOfPages, //  ,       $"{SignatureFieldNamePrefix}{existingSignaturesNumber + 1}"); //,     ExternalBlankSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED); //  -         //  , .. CAdES-X Long Type 1          MakeSignature.SignExternalContainer(appearance, external, 65536); // ,   ,     using (Stream contentStream = appearance.GetRangeStream()) { // hash     ,  cadesplugin hash = string.Join(string.Empty, hashAlgorithm.ComputeHash(contentStream).Select(x => x.ToString("X2"))); } } // stream  ,   ,      } }
      
      





クライアントで、サーバーからのハッシュを取得して、署名します



  //certIndex -    .           hash   signHash: function (data, callback, certIndex, failCallback) { try { cadesplugin.async_spawn(function*() { certIndex = certIndex | 0; let oSigner = yield cadesplugin.CreateObjectAsync("CAdESCOM.CPSigner"); let cert = CryptographyObject.certificates[certIndex]; oSigner.propset_Certificate(cert); oSigner.propset_Options(cadesplugin.CAPICOM_CERTIFICATE_INCLUDE_WHOLE_CHAIN); //     TSP .      oSigner.propset_TSAAddress("https://www.cryptopro.ru/tsp/"); let hashObject = yield cadesplugin.CreateObjectAsync("CAdESCOM.HashedData"); let certPublicKey = yield cert.PublicKey(); let certAlgorithm = yield certPublicKey.Algorithm; let algorithmValue = yield certAlgorithm.Value; if (algorithmValue === "1.2.643.7.1.1.1.1") { yield hashObject.propset_Algorithm(cadesplugin.CADESCOM_HASH_ALGORITHM_CP_GOST_3411_2012_256); oSigner.propset_TSAAddress(CryptographyObject.tsaAddress2012); } else if (algorithmValue === "1.2.643.7.1.1.1.2") { yield hashObject.propset_Algorithm(cadesplugin.CADESCOM_HASH_ALGORITHM_CP_GOST_3411_2012_512); oSigner.propset_TSAAddress(CryptographyObject.tsaAddress2012); } else if (algorithmValue === "1.2.643.2.2.19") { yield hashObject.propset_Algorithm(cadesplugin.CADESCOM_HASH_ALGORITHM_CP_GOST_3411); oSigner.propset_TSAAddress(CryptographyObject.tsaAddress2001); } else { alert("    "); return; } //   hash    hash   yield hashObject.SetHashValue(data.Hash); let oSignedData = yield cadesplugin.CreateObjectAsync("CAdESCOM.CadesSignedData"); oSignedData.propset_ContentEncoding(cadesplugin.CADESCOM_BASE64_TO_BINARY); //   base64 let signatureHex = yield oSignedData.SignHash(hashObject, oSigner, cadesplugin.CADESCOM_CADES_X_LONG_TYPE_1); data.Signature = signatureHex; callback(data); }); } catch (exc) { failCallback(exc); } }
      
      





コメント:受信した署名をサーバーに送信します(上記を参照)



最後に、サーバー側のドキュメントに署名を挿入します



 //   //downloadResponse.RawBytes -   PDF      using (PdfReader reader = new PdfReader(downloadResponse.RawBytes)) { using (MemoryStream stream = new MemoryStream()) { //requestData.Signature -     IExternalSignatureContainer external = new SimpleExternalSignatureContainer(Convert.FromBase64String(requestData.Signature)); //lastSignatureName -  ,      hash MakeSignature.SignDeferred(reader, lastSignatureName, stream, external); //   } }
      
      





コメント:SimpleExternalSignatureContainerは、IExternalSignatureContainerインターフェイスを実装する最も単純なクラスです。



  /// <summary> ///      /// </summary> private class SimpleExternalSignatureContainer : IExternalSignatureContainer { private readonly byte[] _signedBytes; public SimpleExternalSignatureContainer(byte[] signedBytes) { _signedBytes = signedBytes; } public byte[] Sign(Stream data) { return _signedBytes; } public void ModifySigningDictionary(PdfDictionary signDic) { } }
      
      





実際にPDFの署名があれば、それだけです。 検証については、記事の続きで説明します。 彼女が...



インスピレーションの源




Oid署名アルゴリズムの受信に関するコメントを修正しました。 ありがとう



All Articles