GOST R 34.10 التوقيع الإلكتروني لمستندات PDF في مجموعة المكتب LibreOffice

على الرغم من كل الحرائق ، حان الوقت للوفاء بواجبنا المدني - لدفع الضرائب. سندفع الضرائب من خلال بوابة خدمات الدولة . سندخل الحساب الشخصي لبوابة خدمات الدولة باستخدام توقيع إلكتروني ( مصطلحات بوابة خدمات الدولة) ، أي الحصول على شهادة تم الحصول عليها في مركز الشهادات المعتمد (CA) ، ومفتاح خاص. كلاهما أقوم بتخزينه على رمز PKCS # 11 مع دعم التشفير الروسي:



وهكذا ، بعد أن أنجزت واجبي المدني ، قررت مرة أخرى التحقق من تشغيل التوقيع الإلكتروني في جناح المكتب libreoffice .

لماذا قررت القيام بذلك؟ للوصول إلى بوابة Gosuslug ، أستخدم نظام التشغيل Linux ومتصفح Redfox ، وهو متصفح Mozilla Firefox تم تعديله بدعم من التشفير الروسي. كما تعلم ، تستخدم مجموعة المكتب libreoffice أيضًا متجر NSS كمخزن شهادات.

تم تثبيت متصفح Redfox-52 في المجلد / usr / local / lib64 / firefox-52.

لتوصيل مكتبات حزمة NSS (Network Security Services) مع دعم خوارزميات GOST ، قم بتعيين قيمة متغير LD_LIBRARY_PATH على النحو التالي:

$export LD_LIBRARY_PATH=/usr/local/lib64/firefox-52:$LD_LIBRARY_PATH $ 

كمخزن شهادات ، يستخدم libreoffice عادةً متجر شهادات من Firefox أو عميل البريد الإلكتروني Thunderbird أو حزمة Seamonkey المدمجة. لا شيء يمنعك من استخدام متجر شهادات متصفح GoogleChrome / Cromium أو إنشاء متجر مستقل خاص بك (أدوات-> خيارات-> أمان-> شهادة):



بعد تحديد التخزين ، يتم توصيل المكتبات ، وتشغيل libreoffice ، وإنشاء ملف odt ومحاولة التوقيع عليه (File-> Digital Signatures-> Digital Signatures).

يتم عرض الشهادات في مستودع Firefox / NSS والتحقق منها بنجاح:



ومع ذلك ، لا يتم تشكيل التوقيع بعد تحديد الشهادة والضغط على زر موافق:



يبدو أن libreoffice لا تريد فهم خوارزميات التشفير الروسية ، على الرغم من حقيقة استخدام NSS من متصفح Redfox ، الذي يفهم خوارزميات GOST ، والتي تم تأكيدها من خلال التحقق من الشهادة بنجاح.

قمنا بالمحاولة الثانية: هذه المرة سنحاول توقيع ملف PDF. للقيام بذلك ، قم بتصدير الوثيقة المعدة بتنسيق PDF. لتوقيع ملف PDF ، تحتاج بطبيعة الحال إلى تنزيله (ملف-> تواقيع رقمية-> توقيع PDF). بعد تنزيله ، نحاول توقيعه (ملف-> تواقيع رقمية-> تواقيع رقمية) (انظر أعلاه ، حدد الشهادة ، حدد ، على سبيل المثال ، الغرض من توقيع الوثيقة):



وتم تشكيل التوقيع !!! نرى أن التوقيع ، يتكون على أساس شهادة "اختبار 12 512" مع مفتاح GOST R 34.10-2012 512 بت. التوقيع صحيح.

الخروج من libreoffice. قم بتشغيل libreoffice مرة أخرى ، قم بتحميل ملف pdf الموقع ، تحقق من التوقيعات. كل شيء على ما يرام. عرض شهادات الموقعين. كل شيء على ما يرام. معجزات! توقيع ملفات PDF يعمل. نضع التوقيع الثاني والثالث ... كل شيء يعمل. ولكن هناك شيء يطارد. نقوم بفحص إضافي.

افتح ملف PDF الموقع (لقد استخدمت المحرر المدمج من mc - Midnight Commander - مدير ملفات وحدة التحكم لنظام Linux). ابحث عن التوقيع الإلكتروني (/ النوع / Sig /):



كما ترى ، يتم تخزين التوقيع في شكل سداسي عشري رمزي. نقوم بنسخه وحفظه في ملف. لتحويل الملف إلى ملف ثنائي (ترميز DER) ، نستخدم الأداة المساعدة xxd:

 $xxd –p –r <    PDF> > <>.der $ 

يحتوي الملف الناتج على توقيع بتنسيق PKCS # 7 غير مشفر. الآن يمكن عرض هذا التوقيع مع أي asn1-prase ، على سبيل المثال ، مع أداة opensl. ولكن نظرًا لأننا نتحدث عن حزمة NSS ، فسنستخدم الأداة المساعدة derdump أو الأداة المساعدة pp :

 $pp –t p7 –u –i pkcs7_detach.p7 PKCS #7 Content Info: PKCS #7 Signed Data: Version: 1 (0x1) Digest Algorithm List: Digest Algorithm (1): SHA-256 Content Information: PKCS #7 Data: <no content> Certificate List: Certificate (1): Data: Version: 3 (0x2) Serial Number: 4107 (0x100b) Signature Algorithm: GOST R 34.10-2012 signature with GOST R 34.11-2012-512 Issuer: "E=ca_12_512@lissi.ru,OGRN=1234567890123,INN=1234 56789012,CN= 12_512,O= 12_512,L=GnuPG  -2012-512,ST= ,C=RU" Validity: Not Before: Sat Sep 08 07:17:56 2018 Not After : Tue Sep 12 07:17:56 2023 Subject: "C=RU,ST= ,CN=  ,SN=,givenName= ,E=xx@xx.ru,L= ,STREET=,INN=123456789012,SNILS=12345678901" Subject Public Key Info: Public Key Algorithm: GOST R 34.10-2012 512 Public Key: . . . Digest Encryption Algorithm: GOST R 34.10-2012 Key 512 Encrypted Digest: 34:9d:6f:37:e6:60:00:ed:fe:ef:f7:96:db:52:66:e1: 47:4c:5d:da:7f:9f:f3:20:50:ac:73:6c:97:db:f9:8d: 43:9b:8f:40:61:99:d3:4b:17:08:b8:34:e3:1e:92:76: b1:0c:dd:37:01:1e:2a:30:45:68:06:af:3d:33:5e:2f: 71:c8:17:b3:a9:8a:6b:2f:78:9e:e4:b2:00:59:6f:5a: a0:c5:9e:be:1e:4b:ca:d5:64:25:50:1a:6f:f9:55:b8: 3a:cf:37:a0:04:eb:89:b4:6c:39:77:27:92:de:61:c7: b1:d3:a5:2f:ef:66:9b:f5:71:42:77:0a:d2:10:7f:50 $ 

ثم اتضح أنه ليس كل شيء على ما يرام. نعم ، خوارزمية تشفير Digest: GOST R 34.10-2012 خوارزمية توقيع 512 الرئيسية وفقًا للشهادة المحددة للتوقيع ، ولكن يتم إنشاء التوقيع من تجزئة محسوبة باستخدام خوارزمية SHA-256 (خوارزمية Digest (1): SHA-256). وهذا خطأ من النقطة: يجب اعتبار تجزئة GOST R 34.10-2012 المفتاح 512 وفقًا لخوارزمية GOST R 34.11-2012-512.

دعونا ننتقل إلى تحليل شفرة مصدر libreoffice: الشيطان ليس فظيعًا للغاية كما هو مرسوم. في هذه المقالة ، نعتبر استخدام حزمة NSS لإنشاء توقيع إلكتروني. إذا كان أي شخص يفضل على منصة MS Windows ، استخدم CryptoAPI (وبالتالي ، GOST-CSP) ، يمكن ، عن طريق القياس مع هذه المادة ، إجراء المراجعة المقابلة.

أظهر التحليل أنه يجب إجراء التغييرات في ملفين فقط:
- ~ / libreoffice-5.3.7.2 / vcl / source / gdi / pdfwriter_impl.cxx
- ~ / libreoffice-5.3.7.2 / xmlsecurity / source / pdfio / pdfdocument.cxx

ترتبط هذه التغييرات بالاختيار الصحيح لوظيفة التجزئة لشهادات GOST. سيُحدد اختيار وظيفة التجزئة حسب نوع مفتاح الشهادة. اختيار خوارزمية التجزئة ، على سبيل المثال ، في PDFWriter :: Sign (file pdfwriter_impl.cxx) سيبدو كما يلي:

 bool PDFWriter::Sign(PDFSignContext& rContext) { #ifndef _WIN32 /* */ SECKEYPublicKey *pubk = NULL; SECOidTag hashAlgTag; HASH_HashType hashType; int hashLen; CERTCertificate *cert = CERT_DecodeCertFromPackage(reinterpret_cast<char *>(rContext.m_pDerEncoded), rContext.m_nDerEncoded); if (!cert) { SAL_WARN("vcl.pdfwriter", "CERT_DecodeCertFromPackage failed"); return false; } /*    */ pubk = CERT_ExtractPublicKey(cert); if (pubk == NULL) return NULL; /*   */ switch(pubk->keyType){ case gost3410Key: hashAlgTag = SEC_OID_GOSTHASH; hashType = HASH_AlgGOSTHASH; hashLen = SHA256_LENGTH; break; case gost3410Key_256: hashAlgTag = SEC_OID_GOST3411_2012_256; hashType = HASH_AlgGOSTHASH_12_256; hashLen = SHA256_LENGTH; break; case gost3410Key_512: hashAlgTag = SEC_OID_GOST3411_2012_512; hashLen = SHA256_LENGTH * 2; hashType = HASH_AlgGOSTHASH_12_512; break; default: hashAlgTag = SEC_OID_SHA256; hashType = HASH_AlgSHA256; hashLen = SHA256_LENGTH; break; } /* */ HashContextScope hc(HASH_Create(hashType)); . . . } 

التغييرات الأخرى في المنطق مماثلة لتلك. يقع تصحيح الملف ~ / libreoffice-5.3.7.2 / vcl / source / gdi / pdfwriter_impl.cxx

هنا:
 --- pdfwriter_impl_ORIG.cxx 2017-10-25 17:25:39.000000000 +0300 +++ pdfwriter_impl.cxx 2018-10-31 19:48:32.078482227 +0300 @@ -6698,6 +6698,9 @@ CERTCertificate *cert, SECItem *digest) { + SECKEYPublicKey *pubk = NULL; + SECOidTag hashAlgTag; + NSSCMSMessage *result = NSS_CMSMessage_Create(nullptr); if (!result) { @@ -6732,8 +6735,31 @@ NSS_CMSMessage_Destroy(result); return nullptr; } - + pubk = CERT_ExtractPublicKey(cert); + if (pubk == NULL) + return NULL; + switch(pubk->keyType){ + case gost3410Key: + hashAlgTag = SEC_OID_GOSTHASH; +fprintf(stderr, "CreateCMSMessage: gost3410Key Use HASH_AlgGOSTHASH_=%d\n", hashAlgTag); + break; + case gost3410Key_256: + hashAlgTag = SEC_OID_GOST3411_2012_256; +fprintf(stderr, "CreateCMSMessage: gost3410Key_256 Use HASH_AlgGOSTHASH_=%d\n", hashAlgTag); + break; + case gost3410Key_512: + hashAlgTag = SEC_OID_GOST3411_2012_512; +fprintf(stderr, "CreateCMSMessage: gost3410Key_512 Use HASH_AlgGOSTHASH_=%d\n", hashAlgTag); + break; + default: + hashAlgTag = SEC_OID_SHA256; + break; + } +/* *cms_signer = NSS_CMSSignerInfo_Create(result, cert, SEC_OID_SHA256); +*/ + *cms_signer = NSS_CMSSignerInfo_Create(result, cert, hashAlgTag); + if (!*cms_signer) { SAL_WARN("vcl.pdfwriter", "NSS_CMSSignerInfo_Create failed"); @@ -6773,8 +6799,8 @@ NSS_CMSMessage_Destroy(result); return nullptr; } + if (NSS_CMSSignedData_SetDigestValue(*cms_sd, hashAlgTag, digest) != SECSuccess) - if (NSS_CMSSignedData_SetDigestValue(*cms_sd, SEC_OID_SHA256, digest) != SECSuccess) { SAL_WARN("vcl.pdfwriter", "NSS_CMSSignedData_SetDigestValue failed"); NSS_CMSSignedData_Destroy(*cms_sd); @@ -6982,6 +7008,10 @@ bool PDFWriter::Sign(PDFSignContext& rContext) { #ifndef _WIN32 + SECKEYPublicKey *pubk = NULL; + SECOidTag hashAlgTag; + HASH_HashType hashType; + int hashLen; CERTCertificate *cert = CERT_DecodeCertFromPackage(reinterpret_cast<char *>(rContext.m_pDerEncoded), rContext.m_nDerEncoded); @@ -6990,8 +7020,33 @@ SAL_WARN("vcl.pdfwriter", "CERT_DecodeCertFromPackage failed"); return false; } + pubk = CERT_ExtractPublicKey(cert); + if (pubk == NULL) + return NULL; + switch(pubk->keyType){ + case gost3410Key: + hashAlgTag = SEC_OID_GOSTHASH; + hashType = HASH_AlgGOSTHASH; + hashLen = SHA256_LENGTH; + break; + case gost3410Key_256: + hashAlgTag = SEC_OID_GOST3411_2012_256; + hashType = HASH_AlgGOSTHASH_12_256; + hashLen = SHA256_LENGTH; + break; + case gost3410Key_512: + hashAlgTag = SEC_OID_GOST3411_2012_512; + hashLen = SHA256_LENGTH * 2; + hashType = HASH_AlgGOSTHASH_12_512; + break; + default: + hashAlgTag = SEC_OID_SHA256; + hashType = HASH_AlgSHA256; + hashLen = SHA256_LENGTH; + break; + } + HashContextScope hc(HASH_Create(hashType)); - HashContextScope hc(HASH_Create(HASH_AlgSHA256)); if (!hc.get()) { SAL_WARN("vcl.pdfwriter", "HASH_Create failed"); @@ -7005,15 +7060,18 @@ HASH_Update(hc.get(), static_cast<const unsigned char*>(rContext.m_pByteRange2), rContext.m_nByteRange2); SECItem digest; - unsigned char hash[SHA256_LENGTH]; + unsigned char hash[SHA256_LENGTH * 2]; + digest.data = hash; - HASH_End(hc.get(), digest.data, &digest.len, SHA256_LENGTH); + HASH_End(hc.get(), digest.data, &digest.len, hashLen); + hc.clear(); #ifdef DBG_UTIL { FILE *out = fopen("PDFWRITER.hash.data", "wb"); - fwrite(hash, SHA256_LENGTH, 1, out); + fwrite(hash, hashLen, 1, out); + fclose(out); } #endif @@ -7078,8 +7136,8 @@ fclose(out); } #endif + HashContextScope ts_hc(HASH_Create(hashType)); - HashContextScope ts_hc(HASH_Create(HASH_AlgSHA256)); if (!ts_hc.get()) { SAL_WARN("vcl.pdfwriter", "HASH_Create failed"); @@ -7090,16 +7148,19 @@ HASH_Begin(ts_hc.get()); HASH_Update(ts_hc.get(), ts_cms_signer->encDigest.data, ts_cms_signer->encDigest.len); SECItem ts_digest; - unsigned char ts_hash[SHA256_LENGTH]; + unsigned char ts_hash[SHA256_LENGTH * 2]; + ts_digest.type = siBuffer; ts_digest.data = ts_hash; - HASH_End(ts_hc.get(), ts_digest.data, &ts_digest.len, SHA256_LENGTH); + HASH_End(ts_hc.get(), ts_digest.data, &ts_digest.len, hashLen); + ts_hc.clear(); #ifdef DBG_UTIL { FILE *out = fopen("PDFWRITER.ts_hash.data", "wb"); - fwrite(ts_hash, SHA256_LENGTH, 1, out); + fwrite(ts_hash, hashLen, 1, out); + fclose(out); } #endif @@ -7111,7 +7172,8 @@ src.messageImprint.hashAlgorithm.algorithm.data = nullptr; src.messageImprint.hashAlgorithm.parameters.data = nullptr; - SECOID_SetAlgorithmID(nullptr, &src.messageImprint.hashAlgorithm, SEC_OID_SHA256, nullptr); + SECOID_SetAlgorithmID(nullptr, &src.messageImprint.hashAlgorithm, hashAlgTag, nullptr); + src.messageImprint.hashedMessage = ts_digest; src.reqPolicy.type = siBuffer; @@ -7340,11 +7402,13 @@ // Write ESSCertIDv2.hashAlgorithm. aCertID.hashAlgorithm.algorithm.data = nullptr; aCertID.hashAlgorithm.parameters.data = nullptr; - SECOID_SetAlgorithmID(nullptr, &aCertID.hashAlgorithm, SEC_OID_SHA256, nullptr); + SECOID_SetAlgorithmID(nullptr, &aCertID.hashAlgorithm, hashAlgTag, nullptr); + // Write ESSCertIDv2.certHash. SECItem aCertHashItem; - unsigned char aCertHash[SHA256_LENGTH]; - HashContextScope aCertHashContext(HASH_Create(HASH_AlgSHA256)); + unsigned char aCertHash[SHA256_LENGTH*2]; + HashContextScope aCertHashContext(HASH_Create(hashType)); + if (!aCertHashContext.get()) { SAL_WARN("vcl.pdfwriter", "HASH_Create() failed"); @@ -7354,7 +7418,8 @@ HASH_Update(aCertHashContext.get(), reinterpret_cast<const unsigned char *>(rContext.m_pDerEncoded), rContext.m_nDerEncoded); aCertHashItem.type = siBuffer; aCertHashItem.data = aCertHash; - HASH_End(aCertHashContext.get(), aCertHashItem.data, &aCertHashItem.len, SHA256_LENGTH); + HASH_End(aCertHashContext.get(), aCertHashItem.data, &aCertHashItem.len, hashLen); + aCertID.certHash = aCertHashItem; // Write ESSCertIDv2.issuerSerial. IssuerSerial aSerial; 


يقع تصحيح الملف ~ / libreoffice-5.3.7.2 / xmlsecurity / source / pdfio / pdfdocument.cxx

هنا:
 --- pdfdocument_ORIG.cxx 2017-10-25 17:25:39.000000000 +0300 +++ pdfdocument.cxx 2018-10-31 19:49:34.174485641 +0300 @@ -2400,6 +2400,19 @@ case SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION: eOidTag = SEC_OID_SHA512; break; + case SEC_OID_GOST3410_SIGN_256: + case SEC_OID_GOST3411_2012_256: + eOidTag = SEC_OID_GOST3411_2012_256; + break; + case SEC_OID_GOST3410_SIGN_512: + case SEC_OID_GOST3411_2012_512: + eOidTag = SEC_OID_GOST3411_2012_512; + break; + case SEC_OID_GOST3410_SIGNATURE: + case SEC_OID_GOSTHASH: + eOidTag = SEC_OID_GOSTHASH; + break; + default: break; } @@ -2453,6 +2466,16 @@ case SEC_OID_SHA512: nMaxResultLen = msfilter::SHA512_HASH_LENGTH; break; + case SEC_OID_GOST3411_2012_256: + nMaxResultLen = msfilter::SHA256_HASH_LENGTH; + break; + case SEC_OID_GOST3411_2012_512: + nMaxResultLen = msfilter::SHA512_HASH_LENGTH; + break; + case SEC_OID_GOSTHASH: + nMaxResultLen = msfilter::SHA256_HASH_LENGTH; + break; + default: SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: unrecognized algorithm"); return false; 


بعد إجراء التغييرات ، نقوم ببناء حزمة libreoffice. أثرت التغييرات التي أجريت على ثلاث مكتبات (/ usr / lib64 / libreoffice / program):

  • libvcllo.so ؛
  • libxmlsecurity.so ؛
  • libxsec-xmlsec.so

تم استبدال هذه المكتبات الثلاث في توزيع libreoffice المثبت (/ usr / lib64 / libreoffice / program).

بعد ذلك ، ذهب التوقيع والتحقق من توقيع GOST في ملفات PDF دون وجود عوائق. وهنا في أحد المواقع يلفت انتباه المرء بمثل هذا المقتطف:
خدمة الضرائب الفيدرالية لديها خدمة ممتازة للحصول على مقتطف من سجل الكيانات القانونية الموحدة لأي كيان قانوني ، ومجاني تمامًا. يمكن الحصول على مقتطف في شكل وثيقة PDF موقعة بتوقيع إلكتروني مؤهل. ويمكن إرسال هذا المقتطف إلى بنك تجاري أو وكالة حكومية ، ولن يُطلب منك ذلك في شكل ورقي. الكل في الكل ، مريح جدا.
نطلب ونستقبل ونتحقق من:



تجدر الإشارة إلى أنه لا يجب أن تنسى أن تقوم بتثبيت سلسلة من الشهادات الموثوق بها للشهادة الموقعة في المستودع. لكن هذا طبيعي.

هذا كل شيء ، الآن هناك إمكانية استخدام توقيع إلكتروني (واحد أو أكثر) في ملفات PDF. إنها مريحة للغاية عند تنسيق المستندات وتخزينها.

وإذا اعتاد شخص ما على العمل باستخدام التوقيع الإلكتروني الكلاسيكي بتنسيق PKCS # 7 سواء كان متصلاً أو غير متصل ، فقد تم إعداد إصدار محدث (لمنصات Linux و Windows) لحزمة رسومات GUINSSPY :



تم إجراء التطوير في Python3 ، وإذا لم تكن هناك مشاكل في نظام Linux الأساسي ، فيجب عليّ التعرق على الترميزات على نظام التشغيل Windows. في الواقع ، كان تطورًا منفصلًا وهذا يتطلب مقالة منفصلة. يمكن رؤية كل هذه الفروق الدقيقة في شفرة المصدر.

باستخدام هذه الأداة ، يمكنك إنشاء مخزن شهادات للمكتبة ، وإدارة الشهادات ، وتوقيع الملفات ، وما إلى ذلك:



تتيح لك الأداة المساعدة أيضًا إنشاء طلب شهادة بإنشاء زوج مفاتيح ، والذي يمكن بعد ذلك نقله إلى مركز الشهادة ، وتثبيت الشهادة المستلمة في المستودع:



وإذا قام مصنعو شوك لينكس المحليون بتعديل الحزم المختلفة (NSS و Firefox و Thunderbiird و GnuPG / SMIME و SSH و KMail و Kleopatra و LibreOffice و OpenSSL وما إلى ذلك) للعمل مع التشفير الروسي ، فيمكنك سيكون حول استبدال الواردات في مجال التشفير.

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


All Articles