Ruby を使って HPKI カードのデータを読み取る
こんにちは、開発本部の宮内です。今回、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 カードアプリケーションの検索」まで実装できました。
今後、次のステップである暗号計算を実装していきたいと思います。