Menandatangani PDF dalam JS dan menempelkan tanda tangan di C # menggunakan Crypto PRO

Jadi Tugas telah tiba. Dengan menggunakan browser, undang pengguna untuk menandatangani PDF dengan tanda tangan elektronik (selanjutnya disebut EP). Pengguna harus memiliki token yang berisi sertifikat, kunci publik dan pribadi. Selanjutnya, pada server Anda harus memasukkan tanda tangan dalam dokumen PDF. Setelah itu, Anda perlu memeriksa tanda tangan untuk validitas. Kami menggunakan ASP.NET dan, karenanya, C # sebagai back-end.

Semua garam adalah bahwa Anda perlu menggunakan tanda tangan dalam format CAdES-X Long Type 1, dan Rusia GOST R 34.10-2001, GOST R 34.10-2012, dll. Selain itu, mungkin ada lebih dari satu tanda tangan, yaitu, pengguna dapat bergantian menandatangani file. Namun, tanda tangan sebelumnya harus tetap valid.

Dalam proses penyelesaian masalah, mereka memutuskan untuk mempersulit kami, dan mengurangi jumlah data yang dikirimkan ke klien. Hanya mengirimkan hash dokumen, tetapi bukan dokumen itu sendiri.

Dalam kode sumber saya akan menghilangkan momen-momen yang kurang penting untuk topik tersebut, saya hanya akan menyisakan kriptografi. Saya akan memberikan kode JS hanya untuk browser normal, yang mesin JS mendukung Promise dan generator fungsi. Saya pikir siapa yang perlu menulis untuk IE sendiri (saya harus "tidak mau").

Apa yang Anda butuhkan:

  1. Pengguna harus menerima pasangan kunci dan sertifikat.
  2. Pengguna harus menginstal plug-in dari Crypto PRO. Tanpa ini, kami tidak akan dapat bekerja dengan penyedia layanan kriptografi menggunakan alat JS.

Komentar:

  1. Untuk pengujian, saya memiliki sertifikat yang dikeluarkan oleh tes Crypto PRO CA dan token normal yang diterima oleh salah satu karyawan kami (pada saat penulisan ~ 1500r dengan lisensi tahunan untuk Crypto PRO dan dua sertifikat: tetapi GOST "baru" dan "lama")
  2. Mereka mengatakan plug-in dapat bekerja dengan ViPNet, tetapi saya belum mengujinya.

Sekarang kami menganggap bahwa di server kami ada PDF yang siap untuk ditandatangani.
Tambahkan skrip dari Crypto PRO ke halaman:

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

Maka kita perlu menunggu sampai objek cadesplugin terbentuk

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

Kami meminta server hash. Sebelumnya, untuk ini kita masih perlu tahu sertifikat mana, dan oleh karena itu algoritme, pengguna akan masuk. Sebuah komentar kecil: Saya menggabungkan semua fungsi dan "variabel" untuk bekerja dengan kriptografi di sisi klien menjadi CryptographyObject.

Metode untuk mengisi bidang sertifikat objek 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); } }); } 

Komentar: cobalah untuk membuka toko sertifikat. Pada titik ini, sistem pengguna akan memperingatkan Anda bahwa situs tersebut mencoba melakukan sesuatu dengan sertifikat, kriptografi, dan omong kosong ajaib lainnya yang tidak dapat dipahami. Pengguna harus mengklik "Ya"
Selanjutnya, kami memperoleh sertifikat yang valid dalam waktu (tidak kedaluwarsa) dan memasukkannya ke dalam array sertifikat. Ini harus dilakukan karena sifat asinkron dari cadesplugin (untuk IE, semuanya berbeda;)).

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

Komentar: perhatikan cadesplugin.async_spawn, fungsi generator diteruskan ke sana, yang selanjutnya () disebut berurutan, yang mengarah ke transisi untuk menghasilkan.
Jadi, kita mendapatkan analog-async-menunggu dari C #. Semuanya terlihat sinkron, tetapi berfungsi secara tidak sinkron.

Sekarang apa yang terjadi di server ketika hash telah diminta.

Pertama, Anda harus menginstal paket nuget iTextSharp (pada saat penulisan, versi terbaru 5.5.13 seharusnya)

Kedua, CryptoPro.Sharpei diperlukan, ia pergi ke beban untuk Crypto PRO .NET SDK

Sekarang Anda bisa mendapatkan 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  ,   ,      } } 

Pada klien, memiliki hash dari server, kami menandatanganinya

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

Komentar: kirim tanda tangan yang diterima ke server (lihat di atas)

Nah, akhirnya, masukkan tanda tangan di dokumen di sisi server

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

Komentar: SimpleExternalSignatureContainer adalah kelas paling sederhana yang mengimplementasikan antarmuka 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) { } } 

Sebenarnya dengan penandatanganan PDF, itu saja. Verifikasi akan dijelaskan dalam kelanjutan artikel. Saya berharap dia akan ...



Membuat koreksi dari komentar saat menerima algoritma tanda tangan Oid. Terima kasih

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


All Articles