GOST R 34.10 elektronische Signatur von PDF-Dokumenten in der Office-Suite LibreOffice

Trotz aller Brände ist es an der Zeit, unsere Bürgerpflicht zu erfüllen - Steuern zu zahlen. Wir zahlen Steuern über das State Services- Portal. Wir werden das persönliche Konto des State Services-Portals mit einer elektronischen Signatur ( Terminologie des State Services-Portals) eingeben, d. H. mit einem Zertifikat, das bei einem akkreditierten Zertifizierungszentrum (CA) erworben wurde, und einem privaten Schlüssel. Beide speichere ich auf dem PKCS # 11-Token mit Unterstützung für die russische Kryptographie:



Nachdem ich meine Bürgerpflicht erfüllt hatte, beschloss ich, die Funktion der elektronischen Signatur in der libreoffice- Bürosuite erneut zu überprüfen.

Warum habe ich mich dazu entschieden? Für den Zugriff auf das Gosuslug-Portal verwende ich das Linux-Betriebssystem und den Redfox-Browser, einen Mozilla Firefox-Browser, der mit Unterstützung der russischen Kryptografie modifiziert wurde. Wie Sie wissen, verwendet die libreoffice Office Suite den NSS-Speicher auch als Zertifikatspeicher.

Der Redfox-52-Browser wurde im Ordner / usr / local / lib64 / Firefox-52 installiert.

Legen Sie den Wert der Variablen LD_LIBRARY_PATH wie folgt fest, um die Bibliotheken des NSS-Pakets (Network Security Services) mit Unterstützung für GOST-Algorithmen zu verbinden:

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

Als Zertifikatspeicher verwendet libreoffice normalerweise einen Zertifikatspeicher von Firefox, dem Thunderbird-E-Mail-Client oder dem integrierten Seamonkey-Paket. Nichts hindert Sie daran, den Zertifikatspeicher des GoogleChrome / Cromium-Browsers zu verwenden oder einen eigenen unabhängigen Speicher zu erstellen (Extras-> Optionen-> Sicherheit-> Zertifikat):



Nachdem der Speicher ausgewählt wurde, werden die Bibliotheken verbunden, führen libreoffice aus, erstellen eine Odt-Datei und versuchen, sie zu signieren (Datei-> Digitale Signaturen-> Digitale Signaturen).

Zertifikate im Firefox / NSS-Repository werden erfolgreich angezeigt und überprüft:



Die Signatur nach Auswahl des Zertifikats und Drücken der OK-Taste wird jedoch nicht gebildet:



Es scheint, dass libreoffice russische kryptografische Algorithmen nicht verstehen möchte, obwohl NSS vom Redfox-Browser verwendet wird, der GOST-Algorithmen versteht, was durch eine erfolgreiche Zertifikatsüberprüfung bestätigt wird.

Wir machen den zweiten Versuch: Dieses Mal werden wir versuchen, die PDF-Datei zu signieren. Exportieren Sie dazu das vorbereitete Dokument im PDF-Format. Um eine PDF-Datei zu signieren, müssen Sie sie natürlich herunterladen (Datei-> Digitale Signaturen-> PDF signieren). Nach dem Herunterladen versuchen wir, es zu signieren (Datei-> Digitale Signaturen-> Digitale Signaturen) (siehe oben, wählen Sie das Zertifikat aus und geben Sie beispielsweise den Zweck der Signatur des Dokuments an):



Und die Unterschrift wird gebildet !!! Wir sehen, dass die Signatur auf Basis des Zertifikats "Test 12 512" mit dem Schlüssel GOST R 34.10-2012 512 Bit bildet. Die Unterschrift ist korrekt.

Libreoffice verlassen. Führen Sie libreoffice erneut aus, laden Sie die signierte PDF-Datei und überprüfen Sie die Signaturen. Alles ok. Zertifikate von Unterzeichnern anzeigen. Alles ok. Wunder! Das Signieren von PDFs funktioniert. Wir setzen die zweite, dritte Unterschrift ... Alles funktioniert. Aber etwas verfolgt. Wir machen eine zusätzliche Prüfung.

Öffnen Sie die signierte PDF-Datei (ich habe den integrierten Editor von mc - Midnight Commander - Konsolendateimanager für Linux verwendet). Finden Sie die elektronische Signatur (/ Type / Sig /):



Wie Sie sehen können, wird die Signatur in symbolischer hexadezimaler Form gespeichert. Wir kopieren es und speichern es in einer Datei. Um die Datei in eine Binärdatei (DER-Codierung) zu konvertieren, verwenden wir das Dienstprogramm xxd:

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

Die resultierende Datei enthält eine nicht verbundene DER-codierte Signatur im PKCS # 7-Format. Jetzt kann diese Signatur mit jeder asn1-Prase angezeigt werden, beispielsweise mit dem Dienstprogramm openssl. Da es sich jedoch um das NSS-Paket handelt, verwenden wir das Dienstprogramm derdump oder das Dienstprogramm 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 $ 

Und dann wurde klar, dass nicht alles so gut ist. Ja, der Digest-Verschlüsselungsalgorithmus: GOST R 34.10-2012 Schlüssel 512-Signaturalgorithmus gemäß dem zum Signieren ausgewählten Zertifikat, aber die Signatur wird aus einem Hash generiert, der mit dem SHA-256-Algorithmus berechnet wurde (Digest-Algorithmus (1): SHA-256). Und das ist von dem Punkt aus falsch: Der Hash für GOST R 34.10-2012 Schlüssel 512 sollte gemäß dem GOST R 34.11-2012-512-Algorithmus berücksichtigt werden.

Kommen wir zur Analyse des libreoffice-Quellcodes: Der Teufel ist nicht so schrecklich, wie er gemalt ist. In diesem Artikel betrachten wir die Verwendung des NSS-Pakets zum Generieren einer elektronischen Signatur. Wenn jemand die MS Windows-Plattform bevorzugt, verwenden Sie CryptoAPI (und dementsprechend GOST-CSP) und kann in Analogie zu diesem Material die entsprechende Überarbeitung vornehmen.

Die Analyse ergab, dass die Änderungen nur in zwei Dateien vorgenommen werden müssen:
- ~ / libreoffice-5.3.7.2 / vcl / source / gdi / pdfwriter_impl.cxx
- ~ / libreoffice-5.3.7.2 / xmlsecurity / source / pdfio / pdfdocument.cxx

Diese Änderungen hängen mit der korrekten Auswahl der Hash-Funktion für GOST-Zertifikate zusammen. Die Auswahl einer Hash-Funktion hängt vom Typ des Zertifikatschlüssels ab. Die Auswahl eines Hash-Algorithmus, beispielsweise in PDFWriter :: Sign (Datei pdfwriter_impl.cxx), sieht folgendermaßen aus:

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

Andere Änderungen in der Logik sind diesen ähnlich. Der Patch für die Datei ~ / libreoffice-5.3.7.2 / vcl / source / gdi / pdfwriter_impl.cxx befindet sich

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


Der Patch für die Datei ~ / libreoffice-5.3.7.2 / xmlsecurity / source / pdfio / pdfdocument.cxx befindet sich

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


Nachdem wir die Änderungen vorgenommen haben, erstellen wir das libreoffice-Paket. Die vorgenommenen Änderungen betrafen drei Bibliotheken (/ usr / lib64 / libreoffice / program):

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

Diese drei Bibliotheken wurden in der installierten libreoffice-Distribution (/ usr / lib64 / libreoffice / program) ersetzt.

Danach verlief das Signieren und Überprüfen der GOST-Signatur in den PDF-Dateien reibungslos. Und hier an einer der Stellen fällt ein solcher Auszug auf:
Der Federal Tax Service bietet einen hervorragenden Service für die Erlangung eines Auszugs aus dem Unified State Register of Legal Entities für jede juristische Person und ist absolut kostenlos. Ein Auszug kann in Form eines PDF-Dokuments erhalten werden, das mit einer qualifizierten elektronischen Signatur signiert ist. Ein solcher Auszug kann an eine Geschäftsbank oder eine Regierungsbehörde gesendet werden, und Sie werden nicht in Papierform danach gefragt. Alles in allem sehr bequem.
Wir bestellen, erhalten und verifizieren:



Es sei daran erinnert, dass Sie nicht vergessen sollten, eine Kette vertrauenswürdiger Zertifikate für das Unterzeichnerzertifikat im Repository zu installieren. Das ist aber natürlich.

Das ist alles, jetzt besteht die Möglichkeit, eine elektronische Signatur (eine oder mehrere) in PDF-Dateien zu verwenden. Dies ist sowohl beim Koordinieren von Dokumenten als auch beim Speichern von Dokumenten sehr praktisch.

Und wenn jemand daran gewöhnt ist, mit der klassischen elektronischen Signatur im PKCS # 7-Format sowohl verbunden als auch getrennt zu arbeiten, wurde eine aktualisierte Version (für die Linux- und Windows-Plattformen) des GUINSSPY -Grafikpakets erstellt :



Die Entwicklung wurde in Python3 durchgeführt und wenn es auf der Linux-Plattform keine Probleme gab, musste ich unter MS Windows mit Codierungen schwitzen. In der Tat war es eine separate Entwicklung und dies erfordert einen separaten Artikel. All diese Nuancen sind im Quellcode zu sehen.

Mit diesem Dienstprogramm können Sie einen Zertifikatspeicher für libreoffice erstellen, Zertifikate verwalten, Dateien signieren usw.:



Mit dem Dienstprogramm können Sie auch eine Zertifikatanforderung mit der Generierung eines Schlüsselpaars erstellen, das dann an das Zertifizierungscenter übertragen werden kann, und das empfangene Zertifikat im Repository installieren:



Und wenn Hersteller von inländischen Linux-Gabeln verschiedene Pakete (NSS, Firefox, Thunderbiird, GnuPG / SMIME, SSH, KMail, Kleopatra, LibreOffice, OpenSSL usw. usw.) für die Arbeit mit russischer Kryptographie modifiziert haben, können Sie dies Es würde um Importsubstitution im Bereich der Kryptographie gehen.

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


All Articles