使用Crypto PRO在JS中签名PDF并在C#中粘贴签名

这样啊 任务来了。 使用浏览器,邀请用户使用电子签名(以下称为EP)对PDF进行签名。 用户必须具有包含证书,公钥和私钥的令牌。 接下来,在服务器上,您需要在PDF文档中插入签名。 之后,您需要检查签名的有效性。 我们使用ASP.NET,因此使用C#作为后端。

麻烦的是,您需要使用CAdES-X Long Type 1格式的签名,以及俄语的GOST R 34.10-2001,GOST R 34.10-2012等。 另外,可以有多个签名,即用户可以轮流签名文件。 但是,以前的签名必须保持有效。

在解决问题的过程中,他们决定为我们增加麻烦,并减少传输给客户的数据量。 仅传输文档的哈希,而不传输文档本身。

在源代码中,我将忽略对该主题意义不大的时刻,而仅将其留给密码学使用。 我将只为那些JS引擎支持Promise和函数生成器的普通浏览器提供JS代码。 我认为谁需要为IE自己写东西(我不得不“不想”)。

您需要什么:

  1. 用户必须收到密钥对和证书。
  2. 用户必须从Crypto PRO安装插件。 没有这个,我们将无法使用JS工具与加密服务提供商合作。

备注:

  1. 对于测试,我有测试Crypto PRO CA颁发的证书以及我们的一名员工收到的普通令牌(在撰写本文时,大约1500r并具有Crypto PRO的年度许可证和两个证书:但是“新”和“旧” 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对象的certificate字段的方法:

  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); } }); } 

注释:尝试打开证书存储。 此时,用户的系统将警告您该站点正在尝试使用证书,密码学和其他不可思议的不可理解的废话。 用户将需要单击“是”
接下来,我们获取及时有效的证书(未过期)并将其放入certificates数组中。 由于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,将生成器函数传递给该函数,在该函数上依次调用next(),从而导致转换为yield。
因此,我们从C#获得了async-await的某种模拟。 一切看起来都是同步的,但是异步地工作。

现在,当请求哈希时,在服务器上会发生什么。

首先,您需要安装iTextSharp nuget软件包(在撰写本文时,当前版本应为5.5.13)。

其次,需要CryptoPro.Sharpei,这将由Crypto PRO .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签名算法的评论进行了更正。 谢谢啦

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


All Articles