株式会社メドレーDeveloper Portal

2018-07-12

Ruby を使って HPKI カードのデータを読み取る

こんにちは、開発本部の宮内です。今回、HPKI カードについて調査を行いましたので、それについて書きます。

JAHIS HPKI 対応 IC カードガイドライン Ver.3.0を参考にして、HPKI テストカードから実際に公開鍵証明書を取得しました。

今後も HPKI について調査を続行していきたいと思います。

HPKI とは?

HPKIとは厚生労働省が認める医療福祉関係資格(医師・薬剤師・看護師など 26 種類の保健医療福祉分野の国家資格と、院長・管理薬剤師など 5 種類の管理者資格)を認証することができる PKI です。

配布された HPKI カードには、ルート CA、中間 CA、証明書が格納されています。

このカードは、電子署名などに使用することができ、今後普及していけば、医療文書(処方箋や病院への紹介状など)を印刷、押印、送付するなどの非効率な業務をすることなく、すべてデジタル化することができるようになります。

また、電子認証用の証明書も含まれているため、認証・認可処理にも使用することができます。

今回、HPKI テストカードを用いて調査を行いました。

20180712151907.jpg

調査環境

  • 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 カードアプリケーションの検索と利用」を実装していきます。

20180712152004.png

引用 ガイドライン

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なので、このデータはファイル制御パラメタ及びファイル管理データの集合を表します。

20180712152144.png

引用 JIS X 6320-4 表 8-ファイル制御情報用の産業感共通利用テンプレート

2バイト目は12なので、後続するデータの長さが 18 バイトあることを表します。

3バイト目は84なので、データ要素が DF 名であることを表します。

20180712152208.png

引用 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 を使い公開鍵証明書へのパスを取得してから、公開鍵証明書を取得していきます。

20180712152245.png

引用 ガイドライン

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 カードアプリケーションの検索」まで実装できました。

今後、次のステップである暗号計算を実装していきたいと思います。

株式会社メドレーDeveloper Portal

© Medley Developer Portal