办公套件LibreOffice中的GOST R 34.10电子签名的PDF文档

尽管发生大火 ,但现在是履行我们的公民义务的时候了-纳税。 我们将通过国家服务门户网站缴税。 我们将使用电子签名(State Services门户的术语 )输入State Services门户的个人帐户,即 拥有在认可的认证中心(CA)获得的证书和私钥。 我将它们都存储在PKCS#11令牌中,并支持俄罗斯密码:



因此,在履行了我的公民职责之后,我决定再次检查libreoffice办公室套件中电子签名的运行情况。

我为什么决定这样做? 要访问State Services门户,我使用Linux和Redfox浏览器,该浏览器是Mozilla Firefox浏览器,使用俄罗斯加密技术进行了修改。 如您所知,libreoffice办公套件还使用NSS存储作为证书存储。

Redfox-52浏览器安装在/ usr / local / lib64 / firefox-52文件夹中。

要连接支持GOST算法的NSS(网络安全服务)软件包的库,请按如下所示设置LD_LIBRARY_PATH变量的值:

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

作为证书存储,libreoffice通常使用Firefox,Thunderbird电子邮件客户端或集成的Seamonkey软件包中的证书存储。 没有什么可以阻止您使用GoogleChrome / Cromium浏览器证书存储或创建自己的独立存储(工具->选项->安全->证书):



选择存储后,将连接库,运行libreoffice,创建一个odt文件并尝试对其进行签名(文件->数字签名->数字签名)。

Firefox / NSS存储库中的证书已成功显示和验证:



但是,选择证书并按“确定”按钮后的签名未形成:



看来libreoffice不想理解俄罗斯的加密算法,尽管事实是Redfox浏览器使用了NSS,该浏览器了解GOST算法,并通过成功的证书验证得到了证实。

我们进行第二次尝试:这次我们将尝试对PDF文件签名。 为此,以PDF格式导出准备的文档。 要签署PDF文件,您自然需要下载它(文件->数字签名->签署PDF)。 下载后,我们尝试对其进行签名(文件->数字签名->数字签名)(请参见上文,选择证书,指定例如对文档进行签名的目的):



签名就形成了!!! 我们看到签名是在证书“ Test 12 512”的基础上使用密钥GOST R 34.10-2012 512位形成的。 签名正确。

退出libreoffice。 再次运行libreoffice,加载签名的pdf文件,检查签名。 一切都很好。 查看签名者证书。 一切都很好。 奇迹! 签名PDF即可。 我们放置了第二个,第三个签名...一切正常。 但是有些事情困扰着人们。 我们会进行额外的检查。

打开签名的PDF文件(我使用mc-Midnight Commander-Linux控制台文件管理器的内置编辑器)。 查找电子签名(/ Type / Sig /):



如您所见,签名以符号十六进制形式存储。 我们将其复制并保存到文件中。 要将文件转换为二进制(DER编码),我们使用xxd实用程序:

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

生成的文件包含断开的DER编码的PKCS#7格式签名。 现在,可以使用任何asn1-语句查看此签名,例如,使用openssl实用程序。 但是由于我们正在谈论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 $ 

然后很明显,并不是一切都那么好。 是的,摘要加密算法:GOST R 34.10-2012密钥512签名算法符合为签名选择的证书,但是签名是通过使用SHA-256算法计算出的哈希值生成的(摘要算法(1):SHA-256)。 这一点是错误的:应根据GOST R 34.11-2012-512算法考虑GOST R 34.10-2012密钥512的哈希。

让我们开始对libreoffice源代码的分析:魔鬼并没有被描绘得那么可怕。 在本文中,我们考虑使用NSS包来生成电子签名。 如果有人喜欢在MS Windows平台上使用CryptoAPI(以及相应的GOST-CSP),可以类似于此材料,进行相应的修订。

分析表明,只需在两个文件中进行更改:
-〜/ libreoffice-5.3.7.2 / vcl /来源/ gdi / pdfwriter_impl.cxx
-〜/ libreoffice-5.3.7.2 / xmlsecurity /源/ pdfio / pdfdocument.cxx

这些更改与正确选择GOST证书的哈希函数有关。 哈希函数的选择将取决于证书密钥的类型。 例如,在PDFWriter :: Sign(文件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 /程序):

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

在已安装的libreoffice发行版(/ usr / lib64 / libreoffice /程序)中替换了这三个库。

之后,PDF文件中GOST签名的签名和验证便毫不费力。 在其中一个站点上,这样的摘录引人注目:
联邦税收服务局(Federal Tax Service)可以为任何法人提供从统一法律实体联合州登记簿中摘录的优质服务 ,而且绝对免费。 可以通过由合格的电子签名签名的PDF文档的形式获得摘录。 这样的摘录可以发送到商业银行,政府机构,而不会以纸质形式要求您。 总而言之,非常舒适。
我们订购,接收和验证:



值得回顾的是,您不应忘记在存储库中安装签名证书的可信证书链。 但这是自然的。

仅此而已,现在可以在PDF文件中使用电子签名(一个或多个)。 协调文档和存储文档时都非常方便。

如果有人习惯使用已连接和已断开连接的PKCS#7格式的经典电子签名,则已经准备GUINSSPY图形包的更新版本(适用于Linux和Windows平台):



开发是使用Python3进行的,如果在Linux平台上没有问题,那么在MS Windows上,我不得不努力进行编码。 实际上,这是一个单独的开发,因此需要单独的文章。 所有这些细微差别都可以在源代码中看到。

使用此实用程序,您可以为libreoffice创建证书存储,管理证书,签名文件等:



该实用程序还允许您通过生成密钥对来创建证书请求,然后可以将其传输到认证中心,并将接收到的证书安装在存储库中:



而且,如果家用Linux分支的制造商修改了各种软件包(例如NSS,Firefox,Thunderbiird,GnuPG / SMIME,SSH,KMail,Kleopatra,LibreOffice,OpenSSL等)来使用俄罗斯加密技术,那么您可以这将与密码学领域中的进口替代有关。

Source: https://habr.com/ru/post/zh-CN428429/


All Articles