📜 ⬆️ ⬇️

Signing a PDF on JS and inserting a signature on C # using Krypto PRO

So. The task came. Using a browser, invite the user to sign a PDF with an electronic signature (hereinafter referred to as ES). The user must have a token containing a certificate, public and private key. Next on the server you need to insert a signature in the PDF document. After that, you need to check the signature for validity. As back-end we use ASP.NET and accordingly C #.

All salt in that it is necessary to use the signature in the CAdES-X Long Type 1 format, and the Russian GOST R 34.10-2001, GOST R 34.10-2012, etc. In addition, there can be more than one signatures, that is, users can sign a file in turn. At the same time, the previous signatures must remain valid.

In the process of solving the problem, we decided to make it more difficult for us to reduce the amount of transmitted data to the client. Pass only the hash of the document, but not the document itself.

In the source code I will omit moments of little significance for the topic, leaving only that with regard to cryptography. I will give the code on JS only for normal browsers whose JS engines support Promise and function generator. I think someone needs for IE will write themselves (I had to "through I do not want").
')
What do you need:

  1. The user must obtain a pair of keys and a certificate.
  2. The user must install the plug-in from Crypto PRO. Without this, using JS, we will not be able to work with a cryptographic provider.

Remarks:

  1. For tests, I had a certificate issued by a test CA of Crypto PRO and a normal token received by one of our employees (at the time of writing the article ~ 1500r with an annual license for Crypto PRO and two certificates: but “new” and “old” GOST)
  2. They say that the plug-in can work with ViPNet, but I have not tested it.

Now we will assume that we have a PDF ready for signing on the server.
Add a script from Crypto PRO to the page:

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

Next we need to wait until the cadesplugin object is formed.

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

We request from the server hash. For this, we still need to know what certificate, and therefore the user will sign the algorithm. A small remark: I combined all the functions and "variables" for working with client-side cryptography into a CryptographyObject object.

The method of filling in the certificates field of the CryptographyObject object:

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

Comment: try to open the certificate store. At this point, the user's system will issue a warning that the site is trying to do something with certificates, cryptography, and other magical incomprehensible nonsense. The user here will need to click "Yes"
Next, we get certificates that are valid in time (not expired) and add them to the array of certificates. This must be done because of the asynchronous nature of the cadesplugin (for IE, everything is different;)).

Method to get hash:

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

Comment: pay attention to cadesplugin.async_spawn, the generator function is passed to it, where next () is sequentially called, which leads to the transition to yield.
Thus it turns out a certain analogue of async-await from C #. Everything looks synchronous, but it works asynchronously.

Now what happens on the server when hash is requested.

Firstly, you need to install the nuext package iTextSharp (at the time of writing, the current version 5.5.13 should become)

Secondly, CryptoPro.Sharpei is needed, it goes to work with the Crypto PRO .NET SDK

Now you can get hash

  // 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  ,   ,      } } 

On the client, having received hash from the server we sign it

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

Comment: send the received signature to the server (see above)

Well, finally insert the signature into the document on the server side

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

Comment: SimpleExternalSignatureContainer is the simplest class that implements the IExternalSignatureContainer interface.

  /// <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) { } } 

Actually with the signing of the PDF on it all. The check will be described in the continuation of the article. I hope she will ...



Made corrections from the comment about getting the Oid signature algorithm. thank

Source: https://habr.com/ru/post/426087/


All Articles