GOST R 34.10 signature électronique de documents PDF dans la suite bureautique LibreOffice

Malgré tous les incendies , le moment est venu de remplir notre devoir civique: payer des impôts. Nous paierons des impôts via le portail des services d'État . Nous saisirons le compte personnel du portail des services d'État à l' aide d'une signature électronique ( terminologie du portail des services d'État), c'est-à-dire avoir en main un certificat obtenu dans un centre de certification accrédité (CA) et une clé privée. Je les stocke tous les deux sur le jeton PKCS # 11 avec le support de la cryptographie russe:



Et donc, ayant rempli mon devoir civique, j'ai décidé de vérifier à nouveau le fonctionnement de la signature électronique dans la suite bureautique libreoffice .

Pourquoi ai-je décidé de faire ça? Pour accéder au portail des services d'État, j'utilise Linux et le navigateur Redfox, qui est un navigateur Mozilla Firefox modifié avec le support de la cryptographie russe. Comme vous le savez, la suite bureautique libreoffice utilise également le magasin NSS comme magasin de certificats.

Le navigateur Redfox-52 a été installé dans le dossier / usr / local / lib64 / firefox-52.

Pour connecter les bibliothèques du package NSS (Network Security Services) avec la prise en charge des algorithmes GOST, définissez la valeur de la variable LD_LIBRARY_PATH comme suit:

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

En tant que magasin de certificats, libreoffice utilise généralement un magasin de certificats de Firefox, le client de messagerie Thunderbird ou le package Seamonkey intégré. Rien ne vous empêche d'utiliser le magasin de certificats des navigateurs GoogleChrome / Cromium ou de créer votre propre magasin indépendant (Outils-> Options-> Sécurité-> Certificat):



Une fois le stockage sélectionné, les bibliothèques sont connectées, exécutez libreoffice, créez un fichier odt et essayez de le signer (Fichier-> Signatures numériques-> Signatures numériques).

Les certificats dans le référentiel Firefox / NSS sont correctement affichés et vérifiés:



Cependant, la signature après avoir sélectionné le certificat et appuyé sur le bouton OK n'est pas formée:



Il semble que libreoffice ne veuille pas comprendre les algorithmes cryptographiques russes, malgré le fait que NSS soit utilisé à partir du navigateur Redfox, qui comprend les algorithmes GOST, ce qui est confirmé par une vérification réussie du certificat.

Nous faisons la deuxième tentative: cette fois, nous allons essayer de signer le fichier PDF. Pour ce faire, exportez le document préparé au format PDF. Pour signer un fichier PDF, vous devez naturellement le télécharger (Fichier-> Signatures numériques-> Signer PDF). Après l'avoir téléchargé, nous essayons de le signer (Fichier-> Signatures numériques-> Signatures numériques) (voir ci-dessus, sélectionnez le certificat, spécifiez, par exemple, le but de la signature du document):



Et la signature est formée !!! On voit que la signature, se forme sur la base du certificat "Test 12 512" avec la clé GOST R 34.10-2012 512 bits. La signature est correcte.

Quitter libreoffice. Réexécutez libreoffice, chargez le fichier pdf signé, vérifiez les signatures. Tout va bien. Afficher les certificats des signataires. Tout va bien. Des miracles! La signature de PDF fonctionne. Nous mettons la deuxième, la troisième signature ... Tout fonctionne. Mais quelque chose nous hante. Nous effectuons un contrôle supplémentaire.

Ouvrez le fichier PDF signé (j'ai utilisé l'éditeur intégré de mc - Midnight Commander - gestionnaire de fichiers de console pour Linux). Trouvez la signature électronique (/ Type / Sig /):



Comme vous pouvez le voir, la signature est stockée sous forme hexadécimale symbolique. Nous le copions et l'enregistrons dans un fichier. Pour convertir le fichier en binaire (encodage DER), nous utilisons l'utilitaire xxd:

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

Le fichier résultant contient une signature au format PKCS # 7 codée DER déconnectée. Maintenant, cette signature peut être visualisée avec n'importe quel asn1-prase, par exemple, avec l'utilitaire openssl. Mais puisque nous parlons du package NSS, nous utiliserons l' utilitaire derdump ou l' utilitaire 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 $ 

Et puis il est devenu clair que tout n'est pas si bon. Oui, l'algorithme de chiffrement Digest: GOST R 34.10-2012 Algorithme de signature de la clé 512 conformément au certificat sélectionné pour la signature, mais la signature est générée à partir d'un hachage calculé à l'aide de l'algorithme SHA-256 (algorithme Digest (1): SHA-256). Et c'est faux du point de vue: le hachage pour GOST R 34.10-2012 Key 512 doit être considéré selon l'algorithme GOST R 34.11-2012-512.

Passons à l'analyse du code source de libreoffice: le diable n'est pas aussi terrible qu'il est peint. Dans cet article, nous considérons l'utilisation du package NSS pour générer une signature électronique. Si quelqu'un préfère sur la plate-forme MS Windows, utiliser CryptoAPI (et, par conséquent, GOST-CSP), peut, par analogie avec ce matériel, faire la révision correspondante.

L'analyse a montré que les modifications devront être apportées dans seulement deux fichiers:
- ~ / libreoffice-5.3.7.2 / vcl / source / gdi / pdfwriter_impl.cxx
- ~ / libreoffice-5.3.7.2 / xmlsecurity / source / pdfio / pdfdocument.cxx

Ces modifications sont associées à la sélection correcte de la fonction de hachage pour les certificats GOST. Le choix d'une fonction de hachage sera déterminé en fonction du type de clé de certificat. Le choix d'un algorithme de hachage, par exemple, dans PDFWriter :: Sign (fichier pdfwriter_impl.cxx) ressemblera à ceci:

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

D'autres changements de logique sont similaires à ceux-ci. Le correctif du fichier ~ / libreoffice-5.3.7.2 / vcl / source / gdi / pdfwriter_impl.cxx est situé

ici:
 --- 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; 


Le correctif du fichier ~ / libreoffice-5.3.7.2 / xmlsecurity / source / pdfio / pdfdocument.cxx est situé

ici:
 --- 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; 


Après avoir apporté les modifications, nous construisons le package libreoffice. Les modifications apportées ont affecté trois bibliothèques (/ usr / lib64 / libreoffice / program):

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

Ces trois bibliothèques ont été remplacées dans la distribution libreoffice installée (/ usr / lib64 / libreoffice / program).

Après cela, la signature et la vérification de la signature GOST dans les fichiers PDF se sont déroulées sans accroc. Et ici sur l'un des sites on accroche le regard tel extrait:
Le Service fédéral des impôts dispose d'un excellent service pour obtenir un extrait du Registre d'État unifié des entités juridiques pour toute entité juridique, et absolument gratuitement. Un extrait peut être obtenu sous la forme d'un document PDF signé par une signature électronique qualifiée. Et un tel extrait peut être envoyé à une banque commerciale, une agence gouvernementale, et il ne vous sera pas demandé sous forme papier. Dans l'ensemble, très confortable.
Nous commandons, recevons et vérifions:



Il convient de rappeler que vous ne devez pas oublier d'installer dans le référentiel une chaîne de certificats de confiance pour le certificat signataire. Mais c'est naturel.

C'est tout, il y a maintenant la possibilité d'utiliser une signature électronique (une ou plusieurs) dans les fichiers PDF. Il est très pratique à la fois lors de la coordination de documents et du stockage de documents.

Et si quelqu'un est habitué à travailler avec la signature électronique classique au format PKCS # 7 à la fois connecté et déconnecté, une version mise à jour (pour les plates-formes Linux et Windows) du package graphique GUINSSPY a été préparée :



Le développement a été effectué en Python3 et s'il n'y avait aucun problème sur la plate-forme Linux, alors sur MS Windows, je devais transpirer avec les encodages. En fait, c'était une évolution distincte et cela nécessite un article séparé. Toutes ces nuances sont visibles dans le code source.

À l'aide de cet utilitaire, vous pouvez créer un magasin de certificats pour libreoffice, gérer des certificats, signer des fichiers, etc.:



L'utilitaire vous permet également de créer une demande de certificat avec la génération d'une paire de clés, qui peut ensuite être transférée au centre de certification, et d'installer le certificat reçu dans le référentiel:



Et si les fabricants de fourches Linux nationales ont modifié divers packages (NSS, Firefox, Thunderbiird, GnuPG / SMIME, SSH, KMail, Kleopatra, LibreOffice, OpenSSL, etc., etc.) pour travailler avec la cryptographie russe, alors vous pouvez Il s'agirait de la substitution des importations dans le domaine de la cryptographie.

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


All Articles