こんにちは、開発本部の宮内です。今回、HPKI カードについて調査を行いましたので、それについて書きます。 JAHIS HPKI 対応 IC カードガイドライン Ver.3.0 を参考にして、HPKI テストカードから実際に公開鍵証明書を取得しました。 今後も HPKI について調査を続行していきたいと思います。 HPKI とは? HPKI とは厚生労働省が認める医療福祉関係資格(医師・薬剤師・看護師など 26 種類の保健医療福祉分野の国家資格と、院長・管理薬剤師など 5 種類の管理者資格)を認証することができる PKI です。 配布された HPKI カードには、ルート CA、中間 CA、証明書が格納されています。 このカードは、電子署名などに使用することができ、今後普及していけば、医療文書(処方箋や病院への紹介状など)を印刷、押印、送付するなどの非効率な業務をすることなく、すべてデジタル化することができるようになります。 また、電子認証用の証明書も含まれているため、認証・認可処理にも使用することができます。 今回、HPKI テストカードを用いて調査を行いました。 調査環境 macOS v10.13.5 ACR39-NTTCom Ruby v2.5.1 smartcard v0.5.6 HPKI テスト用カード PC/SC HPKI カードのような IC カードとやり取りを行うには、 PC/SC という API 仕様を使う必要があります。 PC/SC はもともと Windows 環境のみで利用可能でしたが、pcsc-lite という OSS 実装があり、現在では様々な UNIX like OS でも利用できます。 macOS の場合、 /System/Library/Frameworks/PCSC.framework/PCSC にライブラリが用意されており、特に準備する必要なく利用可能です。(2018 年 07 月現在) ただし、IC カードリーダーのドライバーをインストールする必要があります。 今回利用した ACR39-NTTCom は ダウンロードページ に macOS v10.13 に対応したドライバーが配布されていなかったため、IC カードリーダーのチップメーカーである ACS 社の ダウンロードページ からドライバーを入手しました。 smartcard 検証する際に使用した gem は smartcard です。 普通の rubygem と同じく gem install して利用します。 gem install smartcard IC カードリーダーを PC に接続し、 ruby -rsmartcard -e 'pp Smartcard::PCSC::Context.new.readers' を実行し、IC カードリーダー名が表示されれば接続成功です。 アプリケーション識別子の取得 実際に HPKI テストカードから情報を取得していきます。 ガイドライン の「附属書 A(参考)PKI カードアプリケーション利用のシーケンス」にある「A.2.2 JIS X 6320-15 に従った PKI カードアプリケーションの検索と利用」を実装していきます。 引用 ガイドライン prog01.rb # prog01.rb require "smartcard" def puts_response ( response ) puts "status = %04X" % response[ :status ] puts "data = %s" % response[ :data ]. map { | i | "%02X" % i }. join ( " " ) end context = Smartcard::PCSC::Context . new begin card = context. card context. readers . first # SELECT コマンドで`E8 28 BD 08 0F`をパーシャル指定した DF を指定 apdu = [ 0x00 , 0xA4 , 0x04 , 0x00 , 0x05 , 0xE8 , 0x28 , 0xBD , 0x08 , 0x0F , 0x00 ] response = card. transmit apdu. pack ( "C*" ) response = Smartcard::Iso::IsoCardMixin . deserialize_response response. unpack ( "C*" ) puts_response response while response[ :status ] == 0x9000 # SELECT コマンドで次の DF を探す apdu = [ 0x00 , 0xA4 , 0x04 , 0x02 , 0x05 , 0xE8 , 0x28 , 0xBD , 0x08 , 0x0F , 0x00 ] response = card. transmit apdu. pack ( "C*" ) response = Smartcard::Iso::IsoCardMixin . deserialize_response response. unpack ( "C*" ) puts_response response end ensure context. release end 上記のプログラムを実行すると、次のような出力が得られます。 status = 9000 data = 6F 12 84 10 E8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 01 status = 9000 data = 6F 12 84 10 E8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 02 status = 6A82 data = SELECT コマンドを発行すると BER-TLV で符号化された FCI(ファイル制御情報)が取得できます。 1つ目のデータから見ていきます。 1バイト目は 6F なので、このデータはファイル制御パラメタ及びファイル管理データの集合を表します。 引用 JIS X 6320-4 表 8-ファイル制御情報用の産業感共通利用テンプレート 2バイト目は 12 なので、後続するデータの長さが 18 バイトあることを表します。 3バイト目は 84 なので、データ要素が DF 名であることを表します。 引用 JIS X 6320-4 表 10-ファイル制御パラメタデータオブジェクト 4バイト目は 10 なので、後続するデータの長さが 16 バイトあることを表します。 5バイト目以降は、DF 名(= アプリケーション識別子)です。 2つ目のデータもデータ構造は同じなため省略します。 これで HPKI テストカードには、 E8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 01 E8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 02 という2つのアプリケーション識別子が含まれていることが分かります。 公開鍵証明書を取得する 前段にて HPKI テストカードに含まれているアプリケーション識別子が分かりましたので、次は公開鍵証明書を取得していきます。 ガイドライン の「A.3.2 証明書の読み出し」にあるコマンドの通りに APDU を発行しても、正しいデータは返ってきません。 これは、HPKI テストカードの EF 識別子が、ガイドラインに記載されている EF 識別子とは異なるためです。 HPKI カードは JIS X 6320 に準拠しているため、各種暗号情報オブジェクトへのパス情報を含んだ EF.OD が存在しています。 この EF.OD を使い公開鍵証明書へのパスを取得してから、公開鍵証明書を取得していきます。 引用 ガイドライン EF.OD を読み込む prog02.rb # prog02.rb require "smartcard" require "openssl" def puts_response ( response ) puts "status = %04X" % response[ :status ] puts "data = %s" % response[ :data ]. map { | i | "%02X" % i }. join ( " " ) end def decode_asn1 ( response ) data = response[ :data ]. reverse_each . drop_while { | i | i == 0xFF }. reverse return if data. empty? OpenSSL::ASN1 . decode_all data. pack ( "C*" ) end context = Smartcard::PCSC::Context . new begin card = context. card context. readers . first [ [ 0xE8 , 0x28 , 0xBD , 0x08 , 0x0F , 0xA0 , 0x00 , 0x00 , 0x03 , 0x91 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x01 ], [ 0xE8 , 0x28 , 0xBD , 0x08 , 0x0F , 0xA0 , 0x00 , 0x00 , 0x03 , 0x91 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x02 ] ]. each do | aid | # SELECT コマンドでアプリケーションを選択する apdu = [ 0x00 , 0xA4 , 0x04 , 0x00 , 0x10 , *aid, 0x00 ] card. transmit apdu. pack ( "C*" ) # EF.OD の読み出し apdu = [ 0x00 , 0xB0 , 0x91 , 0x00 , 0x00 ] response = card. transmit apdu. pack ( "C*" ) response = Smartcard::Iso::IsoCardMixin . deserialize_response response. unpack ( "C*" ) pp decode_asn1 response end ensure context. release end EF.OD を読み込むと DER 符号化されたデータが返ってきます。 これを OpenSSL::ANS1 モジュールで復号化すると、次に取得するべき EF 識別子が分かります。 EF.OD の ASN.1 定義は以下のようになっているため、タグが 4 であるデータを読み込めば良さそうです。 CIOChoice ::= CHOICE { privateKeys [0] PrivateKeys, publicKeys [1] PublicKeys, trustedPublicKeys [2] PublicKeys, secretKeys [3] SecretKeys, certificates [4] Certificates, trustedCertificates [5] Certificates, usefulCertificates [6] Certificates, dataContainerObjects [7] DataContainerObjects, authObjects [8] AuthObjects, } prog02.rb を実行して実際に得られたデータ [ # 中略 #<OpenSSL::ASN1::ASN1Data:0x00007f8b8e0ef7b0 @indefinite_length= false , @tag=4, @tag_class=:CONTEXT_SPECIFIC, @value= [#<OpenSSL::ASN1::Sequence:0x00007f8b8e0ef7d8 @indefinite_length= false , @tag=16, @tag_class=:UNIVERSAL, @tagging=nil, @value= [#<OpenSSL::ASN1::OctetString:0x00007f8b8e0ef800 @indefinite_length= false , @tag=4, @tag_class=:UNIVERSAL, @tagging=nil, @value= "\x00\x04" >]>]> # 中略 ] [ # 中略 #<OpenSSL::ASN1::ASN1Data:0x00007f8b8d118df0 @indefinite_length= false , @tag=4, @tag_class=:CONTEXT_SPECIFIC, @value= [#<OpenSSL::ASN1::Sequence:0x00007f8b8d118e18 @indefinite_length= false , @tag=16, @tag_class=:UNIVERSAL, @tagging=nil, @value= [#<OpenSSL::ASN1::OctetString:0x00007f8b8d118e40 @indefinite_length= false , @tag=4, @tag_class=:UNIVERSAL, @tagging=nil, @value= "\x00\x04" >]>]>, # 中略 ] どちらのアプリケーションも 00 04 が EF.CD(証明書オブジェクト情報)の EF 識別子だということが分かります。 EF.CD を読み込む prog03.rb # prog03.rb require "smartcard" require "openssl" def puts_response ( response ) puts "status = %04X" % response[ :status ] puts "data = %s" % response[ :data ]. map { | i | "%02X" % i }. join ( " " ) end def decode_asn1 ( response ) data = response[ :data ]. reverse_each . drop_while { | i | i == 0xFF }. reverse return if data. empty? OpenSSL::ASN1 . decode_all data. pack ( "C*" ) end context = Smartcard::PCSC::Context . new begin card = context. card context. readers . first [ [ 0xE8 , 0x28 , 0xBD , 0x08 , 0x0F , 0xA0 , 0x00 , 0x00 , 0x03 , 0x91 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x01 ], [ 0xE8 , 0x28 , 0xBD , 0x08 , 0x0F , 0xA0 , 0x00 , 0x00 , 0x03 , 0x91 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x02 ] ]. each do | aid | # SELECT コマンドでアプリケーションを apdu 選択する = [ 0x00 , 0xA4 , 0x04 , 0x00 , 0x10 , *aid, 0x00 ] card. transmit apdu. pack ( "C*" ) # SELECT コマンドで EF 識別子`00 04`を選択する apdu = [ 0x00 , 0xA4 , 0x02 , 0x0C , 0x02 , 0x00 , 0x04 ] card. transmit apdu. pack ( "C*" ) # READ BINARY コマンドでファイルを読み込む data = [] offset = 0 loop do apdu = [ 0x00 , 0xB0 , (offset & 0x7FFF ) >> 8 , (offset & 0x00FF ), 0x00 ] response = card. transmit apdu. pack ( "C*" ) response = Smartcard::Iso::IsoCardMixin . deserialize_response response. unpack ( "C*" ) data. concat response[ :data ] break if response[ :data ]. all? { | e | e == 0xFF } break unless response[ :status ] == 0x9000 offset += response[ :data ]. size end pp decode_asn1 data: data end ensure context. release end prog03.rb を実行して実際に得られたデータ [ # 中略 #<OpenSSL::ASN1::Sequence:0x00007ffdf99aaf70 @indefinite_length= false , @tag=16, @tag_class=:UNIVERSAL, @tagging=nil, @value= [#<OpenSSL::ASN1::OctetString:0x00007ffdf99ab038 @indefinite_length= false , @tag=4, @tag_class=:UNIVERSAL, @tagging=nil, @value= "\x00\x16" >, #<OpenSSL::ASN1::Integer:0x00007ffdf99aafe8 @indefinite_length= false , @tag=2, @tag_class=:UNIVERSAL, @tagging=nil, @value=#<OpenSSL::BN 0>>, #<OpenSSL::ASN1::ASN1Data:0x00007ffdf99aaf98 @indefinite_length= false , @tag=0, @tag_class=:CONTEXT_SPECIFIC, @value= "\x05\x17" >]> # 中略 ] [ # 中略 #<OpenSSL::ASN1::Sequence:0x00007ffdfa072308 @indefinite_length= false , @tag=16, @tag_class=:UNIVERSAL, @tagging=nil, @value= [#<OpenSSL::ASN1::OctetString:0x00007ffdfa072448 @indefinite_length= false , @tag=4, @tag_class=:UNIVERSAL, @tagging=nil, @value= "\x00\x16" >, #<OpenSSL::ASN1::Integer:0x00007ffdfa0723d0 @indefinite_length= false , @tag=2, @tag_class=:UNIVERSAL, @tagging=nil, @value=#<OpenSSL::BN 0>>, #<OpenSSL::ASN1::ASN1Data:0x00007ffdfa072380 @indefinite_length= false , @tag=0, @tag_class=:CONTEXT_SPECIFIC, @value= "\x05%" >]> ] # 中略 これで公開鍵証明書ファイルの EF 識別子が 00 16 であることが判明しました。 公開鍵証明書を読み込む prog04.rb # prog04.rb require "smartcard" require "openssl" context = Smartcard::PCSC::Context . new begin card = context. card context. readers . first [ [ 0xE8 , 0x28 , 0xBD , 0x08 , 0x0F , 0xA0 , 0x00 , 0x00 , 0x03 , 0x91 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x01 ], [ 0xE8 , 0x28 , 0xBD , 0x08 , 0x0F , 0xA0 , 0x00 , 0x00 , 0x03 , 0x91 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x02 ] ]. each do | aid | # SELECT コマンドでアプリケーションを選択する apdu = [ 0x00 , 0xA4 , 0x04 , 0x00 , 0x10 , *aid, 0x00 ] card. transmit apdu. pack ( "C*" ) # SELECT コマンドで EF 識別子`00 16`を選択する apdu = [ 0x00 , 0xA4 , 0x02 , 0x0C , 0x02 , 0x00 , 0x16 ] card. transmit apdu. pack ( "C*" ) # READ BINARY コマンドでファイルを読み込む data = [] offset = 0 loop do apdu = [ 0x00 , 0xB0 , (offset & 0x7FFF ) >> 8 , (offset & 0x00FF ), 0x00 ] response = card. transmit apdu. pack ( "C*" ) response = Smartcard::Iso::IsoCardMixin . deserialize_response response. unpack ( "C*" ) data. concat response[ :data ] break if response[ :data ]. all? { | e | e == 0xFF } break unless response[ :status ] == 0x9000 offset += response[ :data ]. size end cert = OpenSSL::X509::Certificate . new (data. reverse_each . drop_while { | i | i == 0xFF }. reverse . pack ( "C*" )) puts cert. to_text end ensure context. release end HPKI テストカードから DER 符号化された公開鍵証明書データが取得できるので、 OpenSSL::X509::Certificate.new でインスタンス化できます。 上記の prog04.rb を実行すると下記のような出力が得られます。 Certificate: Data: Version: 3 (0x2) Serial Number: 13023 (0x32df) Signature Algorithm: sha256WithRSAEncryption Issuer: C=JP, O=Japan Medical Association, OU=Digital Certificate Center, CN=HPKI-01-HPKI_JV2-forNonRepudiation Validity Not Before: Aug 15 15:00:00 2017 GMT Not After : Aug 15 14:59:59 2018 GMT Subject: C=JP, CN=JMACombi20413/serialNumber=TESTC20413 Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit ) Modulus: 00:94:dd:09:40:f4:58:f9:0f:ec:3a:ea:e3:47:33: # 中略 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Authority Key Identifier: keyid:44:E9:20:05:4D:6D:C4:B7:FA:4B:F0:1B:C6:EA:C8:D6:5B:16:22:F4 DirName:/C =JP/O=Ministry of Health, Labour and Welfare/OU=Director-General for Policy Planning and Evaluation/OU=MHLW HPKI Root CA V2 serial:02 X509v3 Subject Key Identifier: 9E:E5:71:59:1E:A7:FC:1E:4A:31:F8:7B:30:0B:E3:7F:05:3D:9A:40 X509v3 Key Usage: critical Non Repudiation X509v3 CRL Distribution Points: Full Name: URI:https://crl.pki.med.or.jp/repository/crl/crl-sign2.crl X509v3 Subject Directory Attributes: 0402..(..B..1(1 & 0$. "1 ... *.............Medical Doctor X509v3 Certificate Policies: critical Policy: 1.2.392.100495.1.5.1.1.0.1 CPS: https://www.pki.med.or.jp/certpolicy/ Signature Algorithm: sha256WithRSAEncryption 84:ae:95:45:5e:e7:64:8b:0c:6e:20:5f:9f:1f:0d:5c:ae:4a: # 中略 Certificate: Data: Version: 3 (0x2) Serial Number: 12927 (0x327f) Signature Algorithm: sha256WithRSAEncryption Issuer: C=JP, O=Japan Medical Association, OU=Digital Certificate Center, CN=HPKI-01-HPKI_JV2-forAuthentication-forIndividual Validity Not Before: Aug 15 15:00:00 2017 GMT Not After : Aug 15 14:59:59 2018 GMT Subject: C=JP, CN=JMACombi20413/serialNumber=TESTC20413 Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:c6:f9:06:26:58:5e:11:b7:12:f2:8a:3e:97:0a: # 中略 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Authority Key Identifier: keyid:62:12:93:82:DE:3C:D7:FF:A8:D3:63:01:D3:01:6A:AE:6C:3B:C0:D4 DirName:/C=JP/O=Ministry of Health, Labour and Welfare/OU=Director-General for Policy Planning and Evaluation/OU=MHLW HPKI Root CA V2 serial:03 X509v3 Subject Key Identifier: 45:2B:7B:B4:47:89:3D:6C:05:6D:82:4D:4C:C8:80:B8:B4:B0:89:81 X509v3 Key Usage: critical Digital Signature X509v3 CRL Distribution Points: Full Name: URI:https://crl.pki.med.or.jp/repository/crl/crl-auth2.crl X509v3 Subject Directory Attributes: 0402..(..B..1(1&0$." 1 ... *.............Medical Doctor X509v3 Certificate Policies: critical Policy: 1.2.392.100495.1.5.1.2.0.1 CPS: https://www.pki.med.or.jp/certpolicy/ Signature Algorithm: sha256WithRSAEncryption # 中略 それぞれのアプリケーションから正しく公開鍵証明書が取得できました。 電子認証ガイドライン によると、電子認証に使用する証明書は Issuer の CN(Common Name)が HPKI-01-*-forAuthentication-forIndividual であることが定められているため、 使用した HPKI テストカードでは、電子認証に使用するアプリケーション識別子は E8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 02 であることが分かります。 また、電子署名に使用するアプリケーション識別子は E8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 01 であることが分かりました。 最後に 以上で ガイドライン の「A.2.2 JIS X 6320-15 に従った PKI カードアプリケーションの検索と利用」にある「PKI カードアプリケーションの検索」まで実装できました。 今後、次のステップである暗号計算を実装していきたいと思います。