Electronic signature of the GOST R 34.10 PDF documents in the LibreOffice office suite

    It is time, despite all the fires , to fulfill its civic duty - to pay taxes. We will pay taxes through the Gosuslugi portal . We will enter the personal account of the portal of state services with the help of electronic signature (the terminology of the portal of public services), i.e. having a certificate obtained in an accredited certifying center (CA), and a private key. Both of these are stored on the PKCS # 11 token with the support of Russian cryptography:



    So, having fulfilled my civic duty, I decided to once again check the operation of the electronic signature in the office libreoffice package .

    Why did I decide to do this? To access the Government services portal, I use Linux and the Redfox browser, which is a Mozilla Firefox browser modified to support Russian cryptography. As you know, the libreoffice office suite also uses NSS storage as a certificate store.

    The Redfox-52 browser was installed in the / usr / local / lib64 / firefox-52 folder.

    To connect the libraries of the NSS (Network Security Services) package with GOST-algorithms support, set the value of the LD_LIBRARY_PATH variable as follows:

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

    As a certificate store, libreoffice typically uses a certificate store from a Firefox browser, Thunderbird email client, or the Seamonkey integrated package. Nothing prevents you from using the GoogleChrome / Cromium browser certificate store or creating your own independent store (Tools-> Options-> Security-> Certificate):



    Once the store is selected, libraries are connected, launch libreoffice, create an odt file and try to sign it (File -> Digital signatures-> Digital signatures).

    Certificates in the Firefox / NSS repository are successfully displayed and verified:



    However, the signature after selecting the certificate and clicking the “OK” button is not generated:



    It seems that libreoffice does not want to understand Russian cryptoalgorithms, despite the fact that NSS is used from the Redfox browser, which understands GOST-based algorithms, which is confirmed by the successful verification of certificates.

    We make a second attempt: this time we will try to sign a PDF file. To do this, export the prepared document to PDF-format. To sign a PDF file, of course, you need to download it (File-> Digital Signatures-> Sign PDF). After it is downloaded, we try to sign it (File-> Digital Signatures-> Digital Signatures) (see above, select a certificate, prescribe, for example, the purpose of signing a document):



    And the signature is formed !!! We see that the signature of the formation on the basis of the certificate "Test 12 512" with the key GOST R 34.10-2012 512 bits. The signature is valid.

    Exit libreoffice. Run libreoffice again, load the signed pdf-file, check the signatures. All OK. View signers certificates. All OK. Miracles! Signing PDFs works. We put the second, third signature ... Everything works. But something is haunted. We do an additional check.

    Open the signed PDF file (I used the built-in editor from mc - Midnight Commander - a console file manager for Linux). Find the electronic signature (/ Type / Sig /):



    As you can see, the signature is stored in a symbolic hexadecimal form. Copy it and save it in a file. To convert a file into a binary form (DER encoding), we use the xxd utility:

    $xxd –p –r <файл с подписью из PDF>  > <файл>.der 
    $

    The resulting file contains a detached PKCS # 7 format signature in DER-encoding. Now this signature can be viewed with any asn1-prase, for example, the openssl utility. But since we are talking about the NSS package, we will use the derdump utility or the pp utility :

    $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
    $

    And then it became clear that not everything is so good. Yes, the Digest Encryption Algorithm: GOST R 34.10-2012 Key 512 signature algorithm is in accordance with the certificate selected for signing, but the signature is generated from the hash calculated using the SHA-256 algorithm (Digest Algorithm (1): SHA-256). And this is wrong from the point: the hash for GOST R 34.10-2012 Key 512 should be considered according to the algorithm GOST R 34.11-2012-512.

    We proceed to the analysis of the source code libreoffice: the devil is not so terrible as he is painted. In this article, we consider the use of the NSS package for generating electronic signatures. If someone prefers on the MS Windows platform, use CryptoAPI (and, accordingly, GOST-CSP), can make a corresponding refinement by analogy with this material.

    Aniliz showed that the changes will have to make only two files:
    -~ / libreoffice-5.3.7.2 / vcl / source / gdi / pdfwriter_impl.cxx
    - ~ / libreoffice-5.3.7.2 / xmlsecurity / source / pdfio / pdfdocument.cxx

    These changes are related to the correct choice of hash function for GOST certificates. The choice of the hash function will be determined depending on the type of certificate key. Choosing a hash algorithm, for example, in PDFWriter :: Sign (pdfwriter_impl.cxx file) will look like this:

    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");
            returnfalse;
        }
    /*Получаем из сертификата открытый ключ*/
        pubk = CERT_ExtractPublicKey(cert);
        if (pubk == NULL)
    	returnNULL;
    /*Проверяем тип открытого ключа*/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));
    . .  .
    }

    The remaining changes in logic are similar. The patch for the file ~ / libreoffice-5.3.7.2 / vcl / source / gdi / pdfwriter_impl.cxx is located

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


    The patch for the file ~ / libreoffice-5.3.7.2 / xmlsecurity / source / pdfio / pdfdocument.cxx is located

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


    After making changes, we build the libreoffice package. The changes affected three libraries (/ usr / lib64 / libreoffice / program):

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

    These three libraries were replaced in the installed libreoffice distribution (/ usr / lib64 / libreoffice / program).

    After that, the signing and verification of GOST-signature in PDF-files passed without a hitch. And here on one of the sites comes this eye:
    The Federal Tax Service has an excellent service for obtaining an extract from the Unified State Register of Legal Entities for any legal entity, and is absolutely free. The statement can be obtained in the form of a PDF document signed by a qualified electronic signature. And such an extract can be sent to a commercial bank, a state institution, and you will not be asked for it in paper form. In general, very convenient.
    We order, receive and check:



    It is worth recalling that we should not forget to install in the store a chain of trusted certificates for the signatory's certificate. But it is natural.

    Everything, now there is a possibility of using an electronic signature (one or several) in PDF-files. This is very convenient both when coordinating documents and storing documents.

    And if someone is used to working with the classic electronic signature in the PKCS # 7 format, both attached and detached, then an updated GUINSSPY GUISSPY graphic package (for Linux and Windows platform) has been prepared for them :



    The development was carried out on Python3 and if there were no problems on the Linux platform, then on MS Windows it was necessary to sweat with encodings. In fact, it was a separate development and it requires a separate article. All these nuances can be seen in the source code.

    Using this utility, you can create a certificate store for libreoffice, manage certificates, sign files, etc.:



    The utility also allows you to create a certificate request with key pair generation, which you can then send to the certification authority, and install the received certificate in the store:



    And if manufacturers of domestic Linux forks have modified various packages (NSS, Firefox, Thunderbiird, GnuPG / SMIME, SSH, KMail, Kleopatra, LibreOffice, OpenSSL, etc., etc.) to work with Russian cryptography, then you can It would be talking about import substitution in the field of cryptography.

    Also popular now: