スクリプト言語でPKCS#11暗号トークンメカニズムを使用する

「ロシアの認定x509証明書を表示するための英語のクロスプラットフォームユーティリティ」という記事に対する彼のコメントで、 Pasは「自分自身が数えることができる」PKCS#11トークンを非常に正確に指摘しました。 はい、トークンは実際には暗号化コンピューターです。 そして、Python、Perl、Rubyなど、スクリプト言語でこれらのコンピューターを使用したいのは自然なことです。 証明書リクエストを作成するために、ドキュメントの署名と暗号化のためにPythonでロシアの暗号化をサポートするPKCS#11トークンの使用を既に何らかの形で検討しています



画像



ここでは、Tcl言語に関する議論を続けます。 前の記事で 、PKCS#11トークン/スマートカードに保存された証明書を表示および検証する際、 TclPKCS11バージョン0.9.9パッケージを使用してそれらにアクセスしました(証明書)。 既に述べたように、残念ながら、このパッケージはRSA暗号化用に開発され、PKCS#11 v.2.20標準を考慮に入れています。 現在、PKCS#11 v.2.40規格がすでに使用されており、TK-26暗号化技術委員会がそれを指導し、ロシアの暗号化をサポートするトークン/スマートカードの国内メーカーに推奨事項を発行しています。 そして、これらすべてのことで、新しいTclPKCS11パッケージバージョン1.0.1が登場しました 。 RSAのすべての暗号化インターフェースがTclPKCS11 v.10.1パッケージの新しいバージョンに保存されるように、すぐに予約します。 パッケージライブラリはC言語で記述されています。



それでは、パッケージの新機能は何ですか? まず、接続されたトークンでサポートされている暗号化メカニズムのリストを取得できるコマンドが追加されました。



::pki::pkcs11::listmechs <handl> <slotid>
      
      





接続されたトークンを含むスロットのリストを取得する方法は、 ここに示されています (手順-proc :: slot_with_token):



 proc ::slots_with_token {handle} { set slots [pki::pkcs11::listslots $handle] # puts "Slots: $slots" array set listtok [] foreach slotinfo $slots { set slotid [lindex $slotinfo 0] set slotlabel [lindex $slotinfo 1] set slotflags [lindex $slotinfo 2] if {[lsearch -exact $slotflags TOKEN_PRESENT] != -1} { set listtok($slotid) $slotlabel } } #     parray listtok return [array get listtok] }
      
      





簡単なスクリプトを作成します。



 #!/usr/bin/tclsh lappend auto_path . package require pki::pkcs11 #      RuToken set lib "/usr/local/lib64/librtpkcs11ecp_2.0.so" <source lang="bash">set handle [pki::pkcs11::loadmodule $lib] #    #       set labslot [::slots_with_token $handle] if {[llength $labslot] == 0} { puts "     " exit } set slotid 0 set lmech [pki::pkcs11::listmechs $handle $slotid] set i 0 foreach mm $lmech { #   if {[string first "GOSTR3410" $mm] != -1} { puts -nonewline "[lindex $mm 0] " if {$i == 2} {puts "";set i 0} else { incr i} } } puts "\n" exit
      
      





このスクリプトを使用すると、RuTokenファミリトークンでサポートされているGOSTR3410暗号化メカニズムのリストを取得できます。 まず、 Pas記事で書いたように、「あらゆる種類のEDOに愛される灯明の光」を取り上げます。



 $ tclsh TEST_for_HABR.tcl listtok(0) = ruToken Lite 0 {ruToken Lite } $
      
      





そして当然、彼は証明されるべきGOSTメザニズムを支持していないことが判明します。 別のトークンRutoken EDSを取得します。



 $ tclsh TEST_for_HABR.tcl listtok(0) = ruToken ECP } 0 {ruToken ECP } CKM_GOSTR3410_KEY_PAIR_GEN CKM_GOSTR3410 CKM_GOSTR3410_DERIVE CKM_GOSTR3410_WITH_GOSTR3411 $
      
      





はい、このトークンはロシアの暗号化をサポートしていますが、GOST R 34.10-2001の署名のみを使用しています 。これはほとんど使用されていません 。 しかし、Rutoken EDS-2.0トークンを使用すると、すべてが正常になり、キー256および512ビット長のGOST R 34.10-2012がサポートされます。



 $ tclsh TEST_for_HABR.tcl listtok(0) = RuTokenECP20 0 {RuTokenECP20 } CKM_GOSTR3410_KEY_PAIR_GEN CKM_GOSTR3410 CKM_GOSTR3410_DERIVE CKM_GOSTR3410_512_KEY_PAIR_GEN CKM_GOSTR3410_512 CKM_GOSTR3410_12_DERIVE CKM_GOSTR3410_WITH_GOSTR3411 CKM_GOSTR3410_WITH_GOSTR3411_12_256 CKM_GOS TR3410_WITH_GOSTR3411_12_512 $
      
      





グラスホッパーやマグマの暗号化アルゴリズムを含むロシアの暗号化を1つまたは別のトークンでサポートすることについて話し合った場合、ソフトウェアとクラウドトークンで最も完全にサポートされます。



 $ tclsh TEST_for_HABR.tcl listtok(0) = LS11SW2016_LIN_64 0 {LS11SW2016_LIN_64 }
      
      





メカニズムのリスト
CKM_GOSTR3410_KEY_PAIR_GEN

CKM_GOSTR3410_512_KEY_PAIR_GEN

CKM_GOSTR3410

CKM_GOSTR3410_512

CKM_GOSTR3410_WITH_GOSTR3411

CKM_GOSTR3410_WITH_GOSTR3411_12_256

CKM_GOSTR3410_WITH_GOSTR3411_12_512

CKM_GOSTR3410_DERIVE

CKM_GOSTR3410_12_DERIVE

CKM_GOSR3410_2012_VKO_256

CKM_GOSR3410_2012_VKO_512

CKM_KDF_4357

CKM_KDF_GOSTR3411_2012_256

CKM_KDF_TREE_GOSTR3411_2012_256

CKM_GOSTR3410_KEY_WRAP

CKM_GOSTR3410_PUBLIC_KEY_DERIVE

CKM_LISSI_GOSTR3410_PUBLIC_KEY_DERIVE

CKM_GOST_GENERIC_SECRET_KEY_GEN

CKM_GOST_CIPHER_KEY_GEN

CKM_GOST_CIPHER_ECB

CKM_GOST_CIPHER_CBC

CKM_GOST_CIPHER_CTR

CKM_GOST_CIPHER_OFB

CKM_GOST_CIPHER_CFB

CKM_GOST_CIPHER_OMAC

CKM_GOST_CIPHER_KEY_WRAP

CKM_GOST_CIPHER_ACPKM_CTR

CKM_GOST_CIPHER_ACPKM_OMAC

CKM_GOST28147_KEY_GEN

CKM_GOST28147

CKM_GOST28147_KEY_WRAP

CKM_GOST28147_PKCS8_KEY_WRAP

CKM_GOST_CIPHER_PKCS8_KEY_WRAP

CKM_GOST28147_ECB

CKM_GOST28147_CNT

CKM_GOST28147_MAC

CKM_KUZNYECHIK_KEY_GEN

CKM_KUZNYECHIK_ECB

CKM_KUZNYECHIK_CBC

CKM_KUZNYECHIK_CTR

CKM_KUZNYECHIK_OFB

CKM_KUZNYECHIK_CFB

CKM_KUZNYECHIK_OMAC

CKM_KUZNYECHIK_KEY_WRAP

CKM_KUZNYECHIK_ACPKM_CTR

CKM_KUZNYECHIK_ACPKM_OMAC

CKM_MAGMA_KEY_GEN

CKM_MAGMA_ECB

CKM_MAGMA_CBC

CKM_MAGMA_CTR

CKM_MAGMA_OFB

CKM_MAGMA_CFB

CKM_MAGMA_OMAC

CKM_MAGMA_KEY_WRAP

CKM_MAGMA_ACPKM_CTR

CKM_MAGMA_ACPKM_OMAC

CKM_GOSTR3411

CKM_GOSTR3411_12_256

CKM_GOSTR3411_12_512

CKM_GOSTR3411_HMAC

CKM_GOSTR3411_12_256_HMAC

CKM_GOSTR3411_12_512_HMAC

CKM_PKCS5_PBKD2

CKM_PBA_GOSTR3411_WITH_GOSTR3411_HMAC

CKM_TLS_GOST_KEY_AND_MAC_DERIVE

CKM_TLS_GOST_PRE_MASTER_KEY_GEN

CKM_TLS_GOST_MASTER_KEY_DERIVE

CKM_TLS_GOST_PRF

CKM_TLS_GOST_PRF_2012_256

CKM_TLS_GOST_PRF_2012_512

CKM_TLS12_MASTER_KEY_DERIVE

CKM_TLS12_KEY_AND_MAC_DERIVE

CKM_TLS_MAC

CKM_TLS_KDF

CKM_TLS_TREE_GOSTR3411_2012_256

CKM_EXTRACT_KEY_FROM_KEY

CKM_SHA_1

CKM_MD5



 $
      
      





パッケージに追加される次の新機能に進みます。



 set listcertsder [pki::pkcs11::listcertsder $handle $slotid]
      
      





この関数は、トークンなしで保存された証明書のリストを返します。 問題は自然に発生しますが、既存の関数pki :: pkcs11 :: listcertsとどう違うのですか?



まず、新しい関数は:: pkiパッケージを使用しません。 返される要素の1つは、完全な証明書を含むcert_der要素です。 これは、たとえば、証明書をエクスポートしたり、指紋を受け取ったりする場合に便利です。 以前は 、TBS証明書とその署名から完全な証明書を収集する必要がありました。 1つの証明書の内容を印刷すると、各証明書の返品アイテムの完全なリストが明確に表示されます。



 . . . array set derc [[pki::pkcs11::listcertsder $handle $slotid] 0] parray derc derc(cert_der) = 3082064a … derc(pkcs11_handle) = pkcsmod0 derc(pkcs11_id) = 5882d64386211cf3a8367d2f87659f9330e5605d derc(pkcs11_label) = Thenderbird-60   derc(pkcs11_slotid) = 0 derc(type) = pkcs11 . . .
      
      





pkcs11_id要素には、公開キーからのSHA-1ハッシュの値である属性CKA_IDが格納されます。 cert_derエレメントはCKA_VALUE証明書、pkcs11_labelはCKA_LABELです。



pkcs11_id要素(PKCS#11標準の用語ではCKA_ID)は、pkcs11_handleライブラリとともに、pkcs11_slotidトークンを持つスロット識別子は、トークンに格納されたキーおよび証明書にアクセスするためのキー要素です



そのため、証明書またはキーのラベル(pkcs11_label)を変更する場合は、次の形式のコマンドを実行します。



 pki::pkcs11::rname <cert|key|all> <  >
      
      





証明書またはキーをトークンから削除するには、次の形式のコマンドが実行されます。



 pki::pkcs11::delete <cert|key|all> <  >
      
      





キー要素のリストは、次のように形成できます。



 set listparam {} lappend listparam pkcs11_handle lappend listparam $handle lappend listparam pkcs11_slotid lappend listparam $pkcs11_slotid lappend listparam pkcs11_id lappend listparam $pkcs11_id
      
      





など

この場合の関数呼び出しは次のようになります(証明書とそれに関連するキーを削除します)



 pki::pkcs11::delete all $listparam
      
      





読者はおそらく、このリストを辞書辞書として配置できることをすでに推測しているでしょう。



 set listparam [dict create pkcs11_handle $pkcs11_handle] dict set listparam pkcs11_slotid $pkcs11_slotid) dict set listparam pkcs11_id $pkcs11_id
      
      





他の方法、たとえば配列(配列)があります。



繰り返しますが、pkcs11_handleおよびpkcs11_slotid要素は、接続されたトークンを一意に識別するキー要素のリストに常に存在する必要があることに注意してください。 残りの構成は、特定の機能によって決まります。



次の関数を使用して、トークンに証明書をインストールします。



 set pkcs11_id_cert [::pki::pkcs11::importcert <cert_der_hex> <  >
      
      





この関数は、値CKA_IDを16進数で返します。 鍵パラメーターのリストは、証明書が配置されるトークンを決定します。



 {pkcs11_handle <handle> pkcs11_slotid <slotid>}
      
      





次はハッシュ計算です。 今日のロシアの暗号では、3種類のハッシュ関数が使用されています。

-GOST R 34.11-94

-GOST R 34.11-2012、ハッシュ値256ビット(stribog256)

-GOST R 34 .11-2012、ハッシュ値512ビット(stribog512)

どのハッシュがトークンをサポートするかを決定するために、関数pki :: pkcs11 :: listmechsがあります。



ハッシュ計算関数の形式は次のとおりです。



 set <> [pki::pkcs11::digest <gostr3411|stribog256|stribog512|sha1> <  > <  >]
      
      





計算の計算結果は16進形式で表示されることに注意してください。

 . . . set listparam [dict create pkcs11_handle $pkcs11_handle] dict set listparam pkcs11_slotid $pkcs11_slotid set res_hex [pki::pkcs11::digest stribog256 0123456789 $listparam] puts $res_hex 086f2776f33aae96b9a616416b9d1fe9a049951d766709dbe00888852c9cc021
      
      





検証のために、ロシアの暗号化をサポートするopensslを使用してみましょう。



 $ echo -n "0123456789"|/usr/local/lirssl_csp_64/bin/lirssl_s tatic dgst -md_gost12_256 (stdin)= 086f2776f33aae96b9a616416b9d1fe9a0499 51d766709dbe00888852c9 cc021 $
      
      





ご覧のとおり、結果は同じです。



証明書、失効した証明書のリスト、または形式の署名されたドキュメントのいずれであっても、電子署名を検証するには、署名検証機能のみが必要です。



 set result [pki::pkcs11::verify < > < > <  >]]
      
      





署名が検証に合格した場合は、1が返されます。それ以外の場合は0です。電子署名を検証するには、ドキュメントの署名自体、署名のタイプによって決定されるドキュメントハッシュ、および署名が作成された公開キーとすべてのパラメーター(値、タイプ、パラメーター)が必要です。 。 publickeyinfo asn1構造の形式のすべてのキー情報は、キー要素のリストに含める必要があります。

lpkar(pkcs11_handle)= pkcsmod0

lpkar(pkcs11_slotid)= 0

lpkar(pubkeyinfo)= 301f06082a85030701010101301306072a85030202240

006082a8503070101020203430004407d9306687af5a8e63af4b09443ed2e03794be

10eba6627bf5fb3da1bb474a3507d2ce2cd24b63c727a02521897d1dd6edbdc7084d

8886a39289c3f81bdf2e179
ASN1公開鍵構造は、署名者証明書から取得されます。



 proc ::pki::x509::parse_cert_pubkeyinfo {cert_hex} { array set ret [list] set wholething [binary format H* $cert_hex] ::asn::asnGetSequence wholething cert ::asn::asnPeekByte cert peek_tag if {$peek_tag != 0x02} { # Version number is optional, if missing assumed to be value of 0 ::asn::asnGetContext cert - asn_version ::asn::asnGetInteger asn_version ret(version) } ::asn::asnGetBigInteger cert ret(serial_number) ::asn::asnGetSequence cert data_signature_algo_seq ::asn::asnGetObjectIdentifier data_signature_algo_seq ret(data_signature_algo) ::asn::asnGetSequence cert issuer ::asn::asnGetSequence cert validity ::asn::asnGetUTCTime validity ret(notBefore) ::asn::asnGetUTCTime validity ret(notAfter) ::asn::asnGetSequence cert subject ::asn::asnGetSequence cert pubkeyinfo binary scan $pubkeyinfo H* ret(pubkeyinfo) return $ret(pubkeyinfo) }
      
      





ファイルからの証明書の電子署名を検証するためのスクリプトテキストがあります
こっち
 #! /usr/bin/env tclsh package require pki lappend auto_path . package require pki::pkcs11 #     PKCS#11 #set pkcs11_module "/usr/local/lib/libcackey.so" #set pkcs11_module "/usr/local/lib64/librtpkcs11ecp_2.0.so" set pkcs11_module "/usr/local/lib64/libls11sw2016.so" puts "Connect the Token and press Enter" gets stdin yes set handle [pki::pkcs11::loadmodule $pkcs11_module] set slots [pki::pkcs11::listslots $handle] foreach slotinfo $slots { set slotid [lindex $slotinfo 0] set slotlabel [lindex $slotinfo 1] set slotflags [lindex $slotinfo 2] if {[lsearch -exact $slotflags TOKEN_PRESENT] != -1} { set token_slotlabel $slotlabel set token_slotid $slotid #    break } } # PEM  DER proc ::cert_to_der {data} { if {[string first "-----BEGIN CERTIFICATE-----" $data] != -1} { set data [string map {"\r\n" "\n"} $data] } array set parsed_cert [::pki::_parse_pem $data "-----BEGIN CERTIFICATE-----" "-----END CERTIFICATE-----"] if {[string range $parsed_cert(data) 0 0 ] == "0" } { #   DER- "0" == 0x30 set asnblock $parsed_cert(data) } else { set asnblock "" } return $asnblock } proc usage {use error} { puts "Copyright(C) Orlov Vladimir (http://soft.lissi.ru) 2019" if {$use == 1} { puts $error puts "Usage:\nverify_cert_with_pkcs11 <file with certificate> \[<file with CA certificate>\]\n" } } set countcert [llength $argv] if { $countcert < 1 || $countcert > 2 } { usage 1 "Bad usage!" exit } set file [lindex $argv 0] if {![file exists $file]} { usage 1 "File $file not exist" exit } #  cert_user puts "Loading user certificate: $file" set fd [open $file] chan configure $fd -translation binary set cert_user [read $fd] close $fd if {$cert_user == "" } { usage 1 "Bad file with certificate user: $file" exit } set cert_user [cert_to_der $cert_user] if {$cert_user == ""} { puts "User certificate bad" exit } catch {array set cert_parse [::pki::x509::parse_cert $cert_user]} if {![info exists cert_parse]} { puts "User certificate bad" exit } #parray cert_parse if {$countcert == 1} { if {$cert_parse(issuer) != $cert_parse(subject)} { puts "Bad usage: not self signed certificate" } else { set cert_CA $cert_user } } else { set fileca [lindex $argv 1] if {![file exists $fileca]} { usage 1 "File $fileca not exist" exit } #  cert_CA puts "Loading CA certificate: $fileca" set fd [open $fileca] chan configure $fd -translation binary set cert_CA [read $fd] close $fd if {$cert_CA == "" } { usage 1 "Bad file with certificate CA=$fileca" exit } set cert_CA [cert_to_der $cert_CA] if {$cert_CA == ""} { puts "CA certificate bad" exit } } foreach slotinfo $slots { set slotid [lindex $slotinfo 0] set slotlabel [lindex $slotinfo 1] set slotflags [lindex $slotinfo 2] if {[lsearch -exact $slotflags TOKEN_PRESENT] != -1} { set token_slotlabel $slotlabel set token_slotid $slotid } } #    #array set cert_parse_CA [::pki::x509::parse_cert $cert_CA] catch {array set cert_parse_CA [::pki::x509::parse_cert $cert_CA]} #array set cert_parse_CA [::pki::x509::parse_cert $cert_CA_256] #array set cert_parse_CA [::pki::x509::parse_cert $CA_12_512] if {![info exists cert_parse_CA]} { puts "CA certificate bad" exit } ############################### set aa [dict create pkcs11_handle $handle pkcs11_slotid $token_slotid] set tbs_cert [binary format H* $cert_parse(cert)] #puts "SIGN_ALGO1=$cert_parse(signature_algo)" catch {set signature_algo_number [::pki::_oid_name_to_number $cert_parse(signature_algo)]} if {![info exists signature_algo_number]} { set signature_algo_number $cert_parse(signature_algo) } #puts "SIGN_ALGO=$signature_algo_number" switch -- $signature_algo_number { "1.2.643.2.2.3" - "1 2 643 2 2 3" { # "GOST R 34.10-2001 with GOST R 34.11-94" set digest_algo "gostr3411" } "1.2.643.7.1.1.3.2" - "1 2 643 7 1 1 3 2" { # "GOST R 34.10-2012-256 with GOSTR 34.11-2012-256" set digest_algo "stribog256" } "1.2.643.7.1.1.3.3" - "1 2 643 7 1 1 3 3" { # "GOST R 34.10-2012-512 with GOSTR 34.11-2012-512" set digest_algo "stribog512" } default { puts "  :$signature_algo_number" exit } } #   tbs-!!!! set digest_hex [pki::pkcs11::digest $digest_algo $tbs_cert $aa] puts "digest_hex=$digest_hex" puts [string length $digest_hex] # asn-   #    binary scan $cert_CA H* cert_CA_hex array set infopk [pki::pkcs11::pubkeyinfo $cert_CA_hex [list pkcs11_handle $handle pkcs11_slotid $token_slotid]] parray infopk set lpk [dict create pkcs11_handle $handle pkcs11_slotid $token_slotid] # pybkeyinfo     lappend lpk "pubkeyinfo" #lappend lpk $pubinfo lappend lpk $infopk(pubkeyinfo) array set lpkar $lpk parray lpkar puts "Enter PIN user for you token \"$token_slotlabel\":" #set password "01234567" gets stdin password if { [pki::pkcs11::login $handle $token_slotid $password] == 0 } { puts "Bad password" exit } if {[catch {set verify [pki::pkcs11::verify $digest_hex $cert_parse(signature) $lpk]} res] } { puts $res exit } if {$verify != 1} { puts "BAD SIGNATURE=$verify" } else { puts "SIGNATURE OK=$verify" } puts "!" exit
      
      







スクリプトをファイルに保存し、実行してみてください。



 $./verify_cert_with_pkcs11.tcl Copyright(C) Orlov Vladimir (http://museum.lissi-crypto.ru/) Usage: verify_cert_with_pkcs11 <file with certificate> <file with CA certificate> $
      
      





トークン上の証明書についてはどうでしょうか? まず、PKCS#11暗号化マシンを使用する問題を解決しました。 それらを使用しました。 また、トークン付きの証明書を放送するために、pki :: pkcs11 :: listcertsderパッケージの機能があります。これにより、目的の証明書を選択して検証することができます。 これは宿題と考えることができます。



TclPKCS11v.1.0.1パッケージの新しいバージョンの登場により、トークンの証明書をインポートする機能、トークンから証明書と関連キーを削除する機能、証明書とキーのラベルを変更する機能などを追加することにより 、証明書表示ユーティリティを改良することが可能になりました:







追加された最も重要な機能は、証明書のデジタル署名検証です。







気配りのある読者は、キーペアの生成については何も言われていないことを正しく指摘しました。 この機能は、TclPKCS11パッケージにも追加されます。



 array set genkey [pki::pkcs11::keypair < > <> <  >]
      
      





もちろん、TclPKCS11パッケージの機能がどのように使用されるかは、ユーティリティのソースコードに記載されています。



キーペアを生成する機能については、次の記事で詳しく説明します。PKCS#11トークンでキーペアを生成することにより、資格証明書のリクエストを作成するユーティリティ、認証センター( CA )で証明書を取得してトークンにインポートするメカニズムについて説明します:







同じ記事では、文書に署名する機能が考慮されます。 これは、このシリーズの最後の記事になります。 次に、現在流行しているRubyスクリプト言語でロシアの暗号化をサポートする一連の記事が計画されています。 じゃあね!



All Articles