توقيع PDF في JS ولصق توقيع في C # باستخدام Crypto PRO

لذا لقد حان المهمة. باستخدام متصفح ، قم بدعوة المستخدم لتوقيع ملف PDF بتوقيع إلكتروني (يشار إليه فيما يلي باسم EP). يجب أن يكون لدى المستخدم رمز مميز يحتوي على الشهادة والمفاتيح العامة والخاصة. بعد ذلك على الخادم ، تحتاج إلى إدراج التوقيع في مستند PDF. بعد ذلك ، تحتاج إلى التحقق من صحة التوقيع. نستخدم ASP.NET ، وبالتالي ، C # كالخلفية.

كل الملح هو أنك تحتاج إلى استخدام توقيع بتنسيق CAdES-X Long Type 1 ، والروسية GOST R 34.10-2001 ، GOST R 34.10-2012 ، إلخ. بالإضافة إلى ذلك ، يمكن أن يكون هناك أكثر من توقيع واحد ، أي أنه يمكن للمستخدمين بالتناوب في التوقيع على ملف. ومع ذلك ، يجب أن تظل التوقيعات السابقة صالحة.

في عملية حل المشكلة ، قرروا تعقيدنا وتقليل كمية البيانات المرسلة إلى العميل. إرسال تجزئة المستند فقط ، ولكن ليس المستند نفسه.

في شفرة المصدر ، سأحذف اللحظات ذات الأهمية الضئيلة للموضوع ، سأترك ذلك فقط مثل التشفير. سأعطي كود JS فقط للمتصفحات العادية التي تدعم محركات JS الخاصة بها Promise and function generator. أعتقد من يحتاج إلى الكتابة لـ 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:

  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 ، يتم تمرير وظيفة المولد إليها ، حيث يتم استدعاء () التالي على التوالي ، مما يؤدي إلى الانتقال إلى العائد.
وبالتالي ، نحصل على تناظرية معينة من انتظار غير متزامن من C #. كل شيء يبدو متزامنًا ، ولكنه يعمل بشكل غير متزامن.

الآن ما يحدث على الخادم عند طلب التجزئة.

أولاً ، تحتاج إلى تثبيت حزمة 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/ar426087/


All Articles