GOST R 34.10オフィススイートLibreOfficeでのPDFドキュメントの電子署名

すべての火災にもかかわらず、市民の義務を果たす時でした-税金を支払うことです。 State Servicesポータルから税金を支払います。 電子署名(State Servicesポータルの用語)使用して 、State Servicesポータルの個人アカウントを入力します。 認定認証センター(CA)で取得した証明書と秘密キーを手元に持っている。 両方とも、ロシアの暗号化をサポートするPKCS#11トークンに保存します







そして、市民としての義務を果たしたので、私はlibreofficeオフィススイートで電子署名の動作をもう一度確認することにしました。



なぜこれを行うことにしたのですか? State Servicesポータルにアクセスするには、Linux OSとRedfoxブラウザーを使用します。Redfoxブラウザーは、ロシアの暗号化のサポートで変更されたMozilla Firefoxブラウザーです。 ご存じのように、libreofficeオフィススイートは証明書ストアとしてNSSストレージも使用します。



Redfox-52ブラウザーは、/ usr / local / lib64 / firefox-52フォルダーにインストールされました。



NOST(Network Security Services)パッケージのライブラリをGOSTアルゴリズムのサポートに接続するには、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リポジトリ内の証明書が正常に表示および検証されます。







ただし、証明書を選択して[OK]ボタンを押した後の署名は形成されません。







libreofficeは、NSSがRedfoxブラウザーから使用されるという事実にもかかわらず、ロシアの暗号化アルゴリズムを理解したくないようです。Redfoxブラウザーは、GOSTアルゴリズムを理解し、証明書の検証が成功することで確認されます。



2回目の試行を行います。今回はPDFファイルへの署名を試みます。 これを行うには、準備されたドキュメントをPDF形式でエクスポートします。 PDFファイルに署名するには、当然、ダウンロードする必要があります(ファイル->デジタル署名-> PDFに署名)。 ダウンロードした後、署名を試みます([ファイル]-> [デジタル署名]-> [デジタル署名])(上記を参照し、証明書を選択して、たとえばドキュメントに署名する目的を指定します):







そして、署名が形成されます!!! 署名は、GOST R 34.10-2012 512ビットのキーを持つ「Test 12 512」証明書に基づいて形成されていることがわかります。 署名が正しい。



libreofficeを終了します。 libreofficeを再度実行し、署名されたPDFファイルをロードして、署名を確認します。 すべてがOKです。 署名者証明書を表示します。 すべてがOKです。 奇跡! PDFへの署名は機能します。 2番目、3番目の署名を配置します...すべてが機能します。 しかし、何かが忘れられません。 追加のチェックを行います。



署名済みPDFファイルを開きます(mcの組み込みエディター-Midnight Commander-Linux用コンソールファイルマネージャーを使用しました)。 電子署名を検索します(/ Type / Sig /):







ご覧のとおり、署名は16進数のシンボリック形式で保存されています。 コピーしてファイルに保存します。 ファイルをバイナリ(DERエンコード)に変換するには、xxdユーティリティを使用します。



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





結果のファイルには、切断されたDERエンコードPKCS#7形式の署名が含まれます。 これで、この署名は、asn1-prase、たとえば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.10-2012キー512のハッシュは、GOST R 34.11-2012-512アルゴリズムに従って考慮されるべきです。



libreofficeのソースコードの分析に取りかかりましょう。悪魔は描かれているほどひどいものではありません。 この記事では、NSSパッケージを使用して電子署名を生成することを検討します。 MS Windowsプラットフォームを好む人がいる場合は、CryptoAPI(および、それに応じてGOST-CSP)を使用し、この資料と同様に、対応する改訂を行うことができます。



分析の結果、変更は2つのファイルでのみ行う必要があることが示されました。

-〜/ libreoffice-5.3.7.2 / vcl / source / gdi / pdfwriter_impl.cxx

-〜/ libreoffice-5.3.7.2 / xmlsecurity / source / 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パッケージをビルドします。 行われた変更は、3つのライブラリ(/ usr / lib64 / libreoffice / program)に影響を与えました。





これら3つのライブラリは、インストールされたlibreofficeディストリビューション(/ usr / lib64 / libreoffice / program)で置き換えられました。



その後、PDFファイル内のGOST署名の署名と検証は問題なく通過しました。 そして、ここでそのような抜粋で目を引くサイトの1つで:

連邦税務局は、法人の統一法人登記簿から抜粋を取得するための優れたサービス提供しており、完全に無料です。 抽出物は、資格のある電子署名で署名されたPDFドキュメントの形式で取得できます。 そして、そのような抜粋は、商業銀行、政府機関に送ることができ、あなたは紙の形でそれを求められません。 全体として、非常に快適です。
注文、受け取り、確認:







署名者証明書の信頼証明書のチェーンをリポジトリにインストールすることを忘れないでください。 しかし、これは自然なことです。



以上で、PDFファイルで電子署名(1つまたは複数)を使用できるようになりました。 ドキュメントを調整するときとドキュメントを保存するときの両方で非常に便利です。



また、誰かが接続および切断の両方でPKCS#7形式の古典的な電子署名の操作に慣れている場合、 GUINSSPYグラフィックパッケージの更新バージョン(LinuxおよびWindowsプラットフォーム用)が準備されています。







開発はPython3で行われました。Linuxプラットフォームで問題がなければ、MS Windowsではエンコードに悩まされました。 実際、これは別の開発であり、これには別の記事が必要です。 これらのニュアンスはすべてソースコードで見ることができます。



このユーティリティを使用すると、libreofficeの証明書ストアを作成したり、証明書を管理したり、ファイルに署名したりできます。







また、このユーティリティを使用すると、キーペアを生成して証明書リクエストを作成し、証明書センターに転送して、受信した証明書をリポジトリにインストールできます。







また、国内のLinuxフォークのメーカーがロシアの暗号化を使用するためにさまざまなパッケージ(NSS、Firefox、Thunderbiird、GnuPG / SMIME、SSH、KMail、Kleopatra、LibreOffice、OpenSSLなど)を変更した場合、暗号化の分野での輸入代替についてです。



All Articles