GOST R 34.10 assinatura eletrônica de documentos PDF no pacote do escritório LibreOffice

Apesar de todos os incêndios , chegou a hora de cumprir nosso dever cívico - pagar impostos. Pagaremos impostos através do portal de Serviços do Estado . Entraremos na conta pessoal do portal de Serviços de Estado usando uma assinatura eletrônica ( terminologia do portal de Serviços de Estado), ou seja, tendo em mãos um certificado obtido em um centro de certificação credenciado (CA) e uma chave privada. Os dois armazenam no token PKCS # 11 com suporte para criptografia russa:



E assim, tendo cumprido meu dever cívico, decidi mais uma vez verificar o funcionamento da assinatura eletrônica na suíte de escritório do libreoffice .

Por que eu decidi fazer isso? Para acessar o portal dos Serviços de Estado, eu uso o Linux e o navegador Redfox, que é um navegador Mozilla Firefox modificado com suporte à criptografia russa. Como você sabe, o pacote do libreoffice office também usa o repositório NSS como repositório de certificados.

O navegador Redfox-52 foi instalado na pasta / usr / local / lib64 / firefox-52.

Para conectar as bibliotecas do pacote NSS (Network Security Services) com suporte aos algoritmos GOST, defina o valor da variável LD_LIBRARY_PATH da seguinte maneira:

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

Como um repositório de certificados, o libreoffice normalmente usa um repositório de certificados do Firefox, o cliente de email Thunderbird ou o pacote Seamonkey integrado. Nada impede que você use o armazenamento de certificados dos navegadores GoogleChrome / Cromium ou crie seu próprio armazenamento independente (Ferramentas-> Opções-> Segurança-> Certificado):



Depois que o armazenamento é selecionado, as bibliotecas são conectadas, execute o libreoffice, crie um arquivo odt e tente assiná-lo (Arquivo-> Assinaturas digitais-> Assinaturas digitais).

Os certificados no repositório Firefox / NSS são exibidos e verificados com sucesso:



No entanto, a assinatura após selecionar o certificado e pressionar o botão OK não é formada:



Parece que o libreoffice não quer entender os algoritmos criptográficos russos, apesar do NSS ser usado no navegador Redfox, que entende os algoritmos GOST, o que é confirmado por uma verificação de certificado bem-sucedida.

Nós fazemos a segunda tentativa: desta vez, tentaremos assinar o arquivo PDF. Para fazer isso, exporte o documento preparado no formato PDF. Para assinar um arquivo PDF, é necessário fazer o download naturalmente (Arquivo-> Assinaturas digitais-> Assinar PDF). Após o download, tentamos assiná-lo (Arquivo-> Assinaturas digitais-> Assinaturas digitais) (veja acima, selecione o certificado, especifique, por exemplo, a finalidade de assinar o documento):



E a assinatura é formada !!! Vemos que a assinatura, formada com base no certificado "Teste 12 512" com a chave GOST R 34.10-2012 512 bits. A assinatura está correta.

Saindo do libreoffice. Execute o libreoffice novamente, carregue o arquivo pdf assinado, verifique as assinaturas. Está tudo bem. Ver certificados de assinantes. Está tudo bem. Milagres! A assinatura de PDFs funciona. Colocamos a segunda, terceira assinatura ... Tudo funciona. Mas algo está assombrando. Fazemos uma verificação adicional.

Abra o arquivo PDF assinado (usei o editor interno do mc - Midnight Commander - gerenciador de arquivos do console para Linux). Encontre a assinatura eletrônica (/ Type / Sig /):



Como você pode ver, a assinatura é armazenada na forma hexadecimal simbólica. Copiamos e salvamos em um arquivo. Para converter o arquivo em binário (codificação DER), usamos o utilitário xxd:

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

O arquivo resultante contém uma assinatura de formato PKCS # 7 codificada por DER desconectada. Agora, essa assinatura pode ser visualizada com qualquer asn1-prase, por exemplo, com o utilitário openssl. Mas, como estamos falando do pacote NSS, usaremos o utilitário derdump ou o utilitário 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 $ 

E então ficou claro que nem tudo é tão bom. Sim, o algoritmo de assinatura Digest Encryption: GOST R 34.10-2012 algoritmo de assinatura Key 512 de acordo com o certificado selecionado para assinatura, mas a assinatura é gerada a partir de um hash calculado usando o algoritmo SHA-256 (Algoritmo Digest (1): SHA-256). E isso está errado desde o ponto: o hash da chave 512 do GOST R 34.10-2012 deve ser considerado de acordo com o algoritmo GOST R 34.11-2012-512.

Vamos começar com a análise do código fonte do libreoffice: o diabo não é tão terrível quanto é pintado. Neste artigo, consideramos o uso do pacote NSS para gerar uma assinatura eletrônica. Se alguém preferir na plataforma MS Windows, use CryptoAPI (e, consequentemente, GOST-CSP), pode, por analogia com este material, fazer a revisão correspondente.

A análise mostrou que as alterações deverão ser feitas em apenas dois arquivos:
- ~ / libreoffice-5.3.7.2 / vcl / source / gdi / pdfwriter_impl.cxx
- ~ / libreoffice-5.3.7.2 / xmlsecurity / source / pdfio / pdfdocument.cxx

Essas alterações estão associadas à seleção correta da função de hash para certificados GOST. A escolha de uma função de hash será determinada dependendo do tipo de chave do certificado. A escolha de um algoritmo de hash, por exemplo, em PDFWriter :: Sign (arquivo pdfwriter_impl.cxx) terá a seguinte aparência:

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

Outras mudanças na lógica são semelhantes a estas. O patch para o arquivo ~ / libreoffice-5.3.7.2 / vcl / source / gdi / pdfwriter_impl.cxx está localizado

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


O patch para o arquivo ~ / libreoffice-5.3.7.2 / xmlsecurity / source / pdfio / pdfdocument.cxx está localizado

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


Após fazer as alterações, criamos o pacote libreoffice. As alterações feitas afetaram três bibliotecas (/ usr / lib64 / libreoffice / program):

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

Essas três bibliotecas foram substituídas na distribuição libreoffice instalada (/ usr / lib64 / libreoffice / program).

Depois disso, a assinatura e a verificação da assinatura GOST nos arquivos PDF foram sem problemas. E aqui, em um dos sites, chama-se atenção com esse trecho:
O Serviço Tributário Federal oferece um excelente serviço para obter um extrato do Registro Estadual Unificado de Pessoas Jurídicas de qualquer entidade jurídica e totalmente gratuito. Um extrato pode ser obtido na forma de um documento PDF assinado por uma assinatura eletrônica qualificada. E esse extrato pode ser enviado a um banco comercial, agência governamental, e você não será solicitado em papel. Ao todo, muito confortável.
Solicitamos, recebemos e verificamos:



Vale lembrar que você não deve esquecer de instalar no repositório uma cadeia de certificados confiáveis ​​para o certificado de signatário. Mas isso é natural.

Isso é tudo, agora existe a possibilidade de usar uma assinatura eletrônica (uma ou mais) em arquivos PDF. É muito conveniente ao coordenar documentos e armazenar documentos.

E se alguém está acostumado a trabalhar com a assinatura eletrônica clássica no formato PKCS # 7, conectada e desconectada, uma versão atualizada (para as plataformas Linux e Windows) do pacote gráfico GUINSSPY foi preparada :



O desenvolvimento foi conduzido em Python3 e, se não houvesse problemas na plataforma Linux, no MS Windows eu precisava suar com codificações. Na verdade, era um desenvolvimento separado e isso requer um artigo separado. Todas essas nuances podem ser vistas no código fonte.

Usando este utilitário, você pode criar um armazenamento de certificados para o libreoffice, gerenciar certificados, assinar arquivos etc.:



O utilitário também permite criar uma solicitação de certificado com a geração de um par de chaves, que pode ser transferido para o centro de certificação e instalar o certificado recebido no repositório:



E se os fabricantes de forks domésticos do Linux modificaram vários pacotes (NSS, Firefox, Thunderbiird, GnuPG / SMIME, SSH, KMail, Kleopatra, LibreOffice, OpenSSL, etc., etc.) para trabalhar com criptografia russa, você pode Seria sobre substituição de importações no campo da criptografia.

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


All Articles