こんにちは、開発本部の宮内です。今回、HPKIカードについて調査を行いましたので、それについて書きます。
tl;dr
JAHIS HPKI 対応 IC カードガイドライン Ver.3.0を参考にして、HPKIテストカードから実際に公開鍵証明書を取得しました。
今後もHPKIについて調査を続行していきたいと思います。
HPKIとは?
HPKIとは厚生労働省が認める医療福祉関係資格(医師・薬剤師・看護師など26種類の保健医療福祉分野の国家資格と、院長・管理薬剤師など5種類の管理者資格)を認証することができるPKIです。
配布されたHPKIカードには、ルートCA、中間CA、証明書が格納されています。
このカードは、電子署名などに使用することができ、今後普及していけば、医療文書(処方箋や病院への紹介状など)を印刷、押印、送付するなどの非効率な業務をすることなく、すべてデジタル化することができるようになります。
また、電子認証用の証明書も含まれているため、認証・認可処理にも使用することができます。
今回、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:http://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: http://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:http://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: http://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 カードアプリケーションの検索」まで実装できました。
今後、次のステップである暗号計算を実装していきたいと思います。