Signature d'un PDF dans JS et collage d'une signature en C # à l'aide de Crypto PRO

Alors. La tâche est venue. À l'aide d'un navigateur, invitez l'utilisateur à signer le PDF avec une signature électronique (ci-après dénommé EP). L'utilisateur doit avoir un jeton contenant le certificat, les clés publiques et privées. Ensuite, sur le serveur, vous devez insérer la signature dans le document PDF. Après cela, vous devez vérifier la validité de la signature. Nous utilisons ASP.NET et, par conséquent, C # comme back-end.

Tout le sel est que vous devez utiliser une signature au format CAdES-X Long Type 1, et russe GOST R 34.10-2001, GOST R 34.10-2012, etc. De plus, il peut y avoir plusieurs signatures, c'est-à-dire que les utilisateurs peuvent signer à tour de rôle un fichier. Cependant, les signatures précédentes doivent rester valables.

Dans le processus de résolution du problème, ils ont décidé de nous compliquer la tâche et de réduire la quantité de données transmises au client. Ne transmettez que le hachage du document, mais pas le document lui-même.

Dans le code source j'oublierai les moments qui ont peu d'importance pour le sujet, je ne laisserai que cela comme pour la cryptographie. Je ne donnerai le code JS qu'aux navigateurs normaux dont les moteurs JS prennent en charge Promise et le générateur de fonctions. Je pense qui a besoin d'écrire pour IE eux-mêmes (je devais "ne pas vouloir").

Ce dont vous avez besoin:

  1. L'utilisateur doit recevoir une paire de clés et un certificat.
  2. L'utilisateur doit installer le plug-in depuis Crypto PRO. Sans cela, nous ne pourrons pas travailler avec un fournisseur de services cryptographiques à l'aide des outils JS.

Remarques:

  1. Pour les tests, j'avais un certificat émis par le test Crypto PRO CA et un jeton normal reçu par l'un de nos employés (au moment de la rédaction ~ 1500r avec une licence annuelle pour Crypto PRO et deux certificats: mais le "nouveau" et l '"ancien" GOST)
  2. Ils disent que le plug-in peut fonctionner avec ViPNet, mais je ne l'ai pas testé.

Nous supposons maintenant que sur notre serveur, un PDF est prêt à être signé.
Ajoutez un script de Crypto PRO à la page:

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

Ensuite, nous devons attendre que l'objet cadesplugin soit formé

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

Nous demandons au serveur de hachage. Auparavant, pour cela, nous devons encore savoir quel certificat, et donc l'algorithme, l'utilisateur signera. Une petite remarque: j'ai combiné toutes les fonctions et "variables" pour travailler avec la cryptographie côté client dans un CryptographyObject.

Méthode de remplissage du champ des certificats de l'objet 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); } }); } 

Commentaire: essayez d'ouvrir un magasin de certificats. À ce stade, le système de l'utilisateur vous avertira que le site essaie de faire quelque chose avec des certificats, de la cryptographie et d'autres non-sens magiques et incompréhensibles. L'utilisateur devra cliquer sur "Oui"
Ensuite, nous obtenons des certificats qui sont valides dans le temps (non expirés) et les mettons dans le tableau des certificats. Cela doit être fait en raison de la nature asynchrone du cadesplugin (pour IE, tout est différent;)).

Méthode de hachage:

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

Commentaire: faites attention à cadesplugin.async_spawn, la fonction générateur lui est passée, sur laquelle next () est appelée séquentiellement, ce qui conduit à la transition vers yield.
Ainsi, nous obtenons un certain analogue d'async-wait de C #. Tout semble synchrone, mais fonctionne de manière asynchrone.

Maintenant, que se passe-t-il sur le serveur quand le hachage a été demandé.

Tout d'abord, vous devez installer le package de nuget iTextSharp (au moment de la rédaction, la version actuelle 5.5.13 devrait être)

Deuxièmement, CryptoPro.Sharpei est nécessaire, il va à la charge du SDK Crypto PRO .NET

Maintenant, vous pouvez obtenir du hachage

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

Sur le client, ayant le hachage du serveur, nous le signons

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

Commentaire: envoyez la signature reçue au serveur (voir ci-dessus)

Enfin, insérez la signature dans le document côté serveur

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

Commentaire: SimpleExternalSignatureContainer est la classe la plus simple qui implémente l'interface 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) { } } 

En fait avec la signature du PDF, c'est tout. La vérification sera décrite dans la suite de l'article. J'espère qu'elle sera ...



Correction du commentaire sur la réception de l'algorithme de signature Oid. Je vous remercie

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


All Articles