株式会社メドレーのブログ - TECH PLAY

TECH PLAY

株式会社メドレー

株式会社メドレー の技術ブログ

1412

こんにちは、開発本部の楊です。メドレーの社内勉強会「TechLunch」で、前回は、 React の基本 を紹介しましたが、今回は HTTP Cache で、 医療介護求人サイト「ジョブメドレー」 のスピード改善ができないか検討した話について、共有しました。 なぜ HTTP Cache について話すことにしたのか ジョブメドレーには、医療機関や保育園、介護施設などさまざまな事業所の求人が掲載されています。現在、14 万を超える事業部の求人が掲載されており、かつ事業所側で求人原稿を修正することもできるため、求職者側が閲覧するスピードについては、いつも意識して改善に取り組んでいます。 その試行錯誤の中で、HTTP Cache を使って改善する方法について、実現性など含めて検証することにしました。 今回は、あるページをモデルケースに試してみました。このページは、PC は平均 250ms、モバイルは 180ms になっています。 ユーザが該当のページを見たときに「内容に更新がないときはブラウザ側のキャッシュを利用してスピードを最適化する」「ページが更新されていたら、最新の内容をすぐユーザへ反映する」という要件でスピード改善されることを目指すことにしました。 HTTP Cache とは? Google Developers では以下のように説明されています。 ネットワーク経由で情報を取得するには時間もコストもかかります。レスポンスが大きいと、クライアントとサーバ間のラウンドトリップを何度も繰り返す必要があるため、レスポンスが利用可能となってブラウザで処理できるようになるまで時間がかかります。さらに、ユーザ側ではデータの通信コストが発生します。そのため、前に取得したリソースをキャッシュに保存して再使用できることは、パフォーマンスを最適化する上で非常に重要です。 この機能はほぼ全てのブラウザに対応しており、HTTP ヘッダーで CacheControl 、 ETAG 、 Last-Modified などを利用して、リソース更新するタイミングなどを細かくコントロール可能です。 Rails での HTTP Cache expire_in でキャッシュ 1 時間キャッシュしたい場合は、コントローラにコードを一行書けば大丈夫です。 expires_in ( 1 . hour ) これで問題なく 1 時間キャッシュされるのですが、「ページが編集されたら即時にユーザへ反映する」という要件を満たしてはいません。 ETAG を利用 では「ページが更新されたら最新の内容をすぐユーザへ反映して、更新がない時はブラウザ側のキャッシュを利用してスピードを最適化する」を行いたい場合はどうすればよいでしょうか。 ここでは ETAG を利用しようと思います。 ETAG はページの内容によって、ユニークな文字列を作成して、ブラウザ側でページの更新あるかどうかを判定するものです。 ETAG はどう作成されているか Rails のデフォルトでは HTTP Cache を有効になっていて、 ETAG を自動的に作成しています。 header[ 'ETag' ] = Digest::MD5 . hexdigest (body) 上のコードのようなイメージで、レスポンス Body から ETAG を作成します。 つまり、サーバから返される HTML ソースの内容が毎回同じであれば、ブラウザは前回キャッシュした内容を読み込むようになります。 サーバレスポンスタイムの短縮 今回の対象ページの機能としてサーバ側では主に二つの部分で時間がかかります。 DB、Redis などで画面表示が必要なデータ取得、ロジック処理 クライアントに返す HTML のレンダリング 対象ページは「HTML のレンダリング」する時間が長かったので、それを改善できれば、サーバレスポンスタイムを一気に短くできます。 画面に表示する必要なデータに変換がなければ、HTML のレンダリングをせずにレスポンスを返す機能がないかをさらに調べました。 fresh_when を使う 名前の通り、いつ画面更新するかをコントロールするメソッドです。 データベース中の該当データが更新されたら、新しい ETAG を作成するコードは以下のようになります。 fresh_when ( etag: [ @job_offer , @job_offer . facility ]) model から ETAG をどう作成するか model の cache_key から ETAG を作ります。 該当ページで使用する job_offer モデルの cache_key は "job_offer/5-20071224150000" のような感じになります。 model の id と updated_at の組み合わせでユニークな cache_key を作成しています。 Rails の該当コード def cache_key ( * timestamp_names ) case when new_record? " #{ model_name. cache_key } /new" when timestamp_names. any? timestamp = max_updated_column_timestamp (timestamp_names) timestamp = timestamp. utc . to_s (cache_timestamp_format) " #{ model_name. cache_key } / #{ id } - #{ timestamp } " when timestamp = max_updated_column_timestamp timestamp = timestamp. utc . to_s (cache_timestamp_format) " #{ model_name. cache_key } / #{ id } - #{ timestamp } " else " #{ model_name. cache_key } / #{ id } " end end 自前の作成したクラスから ETAG をどう作成するか model だけではなく、自前で作成したクラスでデータ管理をしてるところもあります。 そちらでの実現方法も試してみました。 そこで ETAG を作るコードが Rails でどうなっているか追ってみました。 Rails の該当コード def retrieve_cache_key ( key ) case when key. respond_to? ( :cache_key ) then key. cache_key when key. is_a? ( Array ) then key. map { | element | retrieve_cache_key (element) }. to_param when key. respond_to? ( :to_a ) then retrieve_cache_key (key. to_a ) else key. to_param end . to_s end このように、自作クラスに cache_key のメソッドを定義したら、その結果から ETAG が作成されるようになっています。このメソッドを使って、ユニークな値を返せば大丈夫です。 ここまで調査したことを踏まえて、該当ページを HTTP Cache で実装をしてみました。以下が該当の疑似コードになります。 class JobOfferBrowsingHistory def cache_key # return job offer ids end end fresh_when に渡す @user_history = JobOfferBrowsingHistory . new fresh_when ( etag: [ @job_offer , @job_offer . facility , @user_history ]) テスト 開発環境で該当ページを 2 回ロードしました。 1 回目は 1200ms、キャッシュが効いた 2 回目は 130ms になりました。すごく早いです! もちろん、ページ内のデータが更新されたら、最新の内容がページに反映されるようにもなっています。 これで「ページのデータが更新されたら、最新の内容をすぐユーザへ反映する」「更新がなければ HTTP Cache を返す」という状態が実現しました。 課題 実装ミスで、更新が必要なのに、更新されない問題が起こりえる 例えば、画面に新しい model を追加したが、 fresh_when に渡すのを忘れたとか 問題なく更新されることを保証する仕組みの実装 まとめ HTTP Cache を利用して、ジョブメドレーのスピード改善を検討してみた調査過程を紹介しました。 expire_in 、 ETAG の作成方法など色々調べて、最終的に fresh_when で実現できましたが、運用していく上での課題については、引き続き検討して行きたいと思います。
こんにちは、開発本部の楊です。メドレーの社内勉強会「TechLunch」で、前回は、 React の基本 を紹介しましたが、今回は HTTP Cache で、 医療介護求人サイト「ジョブメドレー」 のスピード改善ができないか検討した話について、共有しました。 なぜ HTTP Cache について話すことにしたのか ジョブメドレーには、医療機関や保育園、介護施設などさまざまな事業所の求人が掲載されています。現在、14 万を超える事業部の求人が掲載されており、かつ事業所側で求人原稿を修正することもできるため、求職者側が閲覧するスピードについては、いつも意識して改善に取り組んでいます。 その試行錯誤の中で、HTTP Cache を使って改善する方法について、実現性など含めて検証することにしました。 今回は、あるページをモデルケースに試してみました。このページは、PC は平均 250ms、モバイルは 180ms になっています。 ユーザが該当のページを見たときに「内容に更新がないときはブラウザ側のキャッシュを利用してスピードを最適化する」「ページが更新されていたら、最新の内容をすぐユーザへ反映する」という要件でスピード改善されることを目指すことにしました。 HTTP Cache とは? Google Developers では以下のように説明されています。 ネットワーク経由で情報を取得するには時間もコストもかかります。レスポンスが大きいと、クライアントとサーバ間のラウンドトリップを何度も繰り返す必要があるため、レスポンスが利用可能となってブラウザで処理できるようになるまで時間がかかります。さらに、ユーザ側ではデータの通信コストが発生します。そのため、前に取得したリソースをキャッシュに保存して再使用できることは、パフォーマンスを最適化する上で非常に重要です。 この機能はほぼ全てのブラウザに対応しており、HTTP ヘッダーで CacheControl 、 ETAG 、 Last-Modified などを利用して、リソース更新するタイミングなどを細かくコントロール可能です。 Rails での HTTP Cache expire_in でキャッシュ 1 時間キャッシュしたい場合は、コントローラにコードを一行書けば大丈夫です。 expires_in ( 1 . hour ) これで問題なく 1 時間キャッシュされるのですが、「ページが編集されたら即時にユーザへ反映する」という要件を満たしてはいません。 ETAG を利用 では「ページが更新されたら最新の内容をすぐユーザへ反映して、更新がない時はブラウザ側のキャッシュを利用してスピードを最適化する」を行いたい場合はどうすればよいでしょうか。 ここでは ETAG を利用しようと思います。 ETAG はページの内容によって、ユニークな文字列を作成して、ブラウザ側でページの更新あるかどうかを判定するものです。 ETAG はどう作成されているか Rails のデフォルトでは HTTP Cache を有効になっていて、 ETAG を自動的に作成しています。 header[ 'ETag' ] = Digest::MD5 . hexdigest (body) 上のコードのようなイメージで、レスポンス Body から ETAG を作成します。 つまり、サーバから返される HTML ソースの内容が毎回同じであれば、ブラウザは前回キャッシュした内容を読み込むようになります。 サーバレスポンスタイムの短縮 今回の対象ページの機能としてサーバ側では主に二つの部分で時間がかかります。 DB、Redis などで画面表示が必要なデータ取得、ロジック処理 クライアントに返す HTML のレンダリング 対象ページは「HTML のレンダリング」する時間が長かったので、それを改善できれば、サーバレスポンスタイムを一気に短くできます。 画面に表示する必要なデータに変換がなければ、HTML のレンダリングをせずにレスポンスを返す機能がないかをさらに調べました。 fresh_when を使う 名前の通り、いつ画面更新するかをコントロールするメソッドです。 データベース中の該当データが更新されたら、新しい ETAG を作成するコードは以下のようになります。 fresh_when ( etag: [ @job_offer , @job_offer . facility ]) model から ETAG をどう作成するか model の cache_key から ETAG を作ります。 該当ページで使用する job_offer モデルの cache_key は "job_offer/5-20071224150000" のような感じになります。 model の id と updated_at の組み合わせでユニークな cache_key を作成しています。 Rails の該当コード def cache_key ( * timestamp_names ) case when new_record? " #{ model_name. cache_key } /new" when timestamp_names. any? timestamp = max_updated_column_timestamp (timestamp_names) timestamp = timestamp. utc . to_s (cache_timestamp_format) " #{ model_name. cache_key } / #{ id } - #{ timestamp } " when timestamp = max_updated_column_timestamp timestamp = timestamp. utc . to_s (cache_timestamp_format) " #{ model_name. cache_key } / #{ id } - #{ timestamp } " else " #{ model_name. cache_key } / #{ id } " end end 自前の作成したクラスから ETAG をどう作成するか model だけではなく、自前で作成したクラスでデータ管理をしてるところもあります。 そちらでの実現方法も試してみました。 そこで ETAG を作るコードが Rails でどうなっているか追ってみました。 Rails の該当コード def retrieve_cache_key ( key ) case when key. respond_to? ( :cache_key ) then key. cache_key when key. is_a? ( Array ) then key. map { | element | retrieve_cache_key (element) }. to_param when key. respond_to? ( :to_a ) then retrieve_cache_key (key. to_a ) else key. to_param end . to_s end このように、自作クラスに cache_key のメソッドを定義したら、その結果から ETAG が作成されるようになっています。このメソッドを使って、ユニークな値を返せば大丈夫です。 ここまで調査したことを踏まえて、該当ページを HTTP Cache で実装をしてみました。以下が該当の疑似コードになります。 class JobOfferBrowsingHistory def cache_key # return job offer ids end end fresh_when に渡す @user_history = JobOfferBrowsingHistory . new fresh_when ( etag: [ @job_offer , @job_offer . facility , @user_history ]) テスト 開発環境で該当ページを 2 回ロードしました。 1 回目は 1200ms、キャッシュが効いた 2 回目は 130ms になりました。すごく早いです! もちろん、ページ内のデータが更新されたら、最新の内容がページに反映されるようにもなっています。 これで「ページのデータが更新されたら、最新の内容をすぐユーザへ反映する」「更新がなければ HTTP Cache を返す」という状態が実現しました。 課題 実装ミスで、更新が必要なのに、更新されない問題が起こりえる 例えば、画面に新しい model を追加したが、 fresh_when に渡すのを忘れたとか 問題なく更新されることを保証する仕組みの実装 まとめ HTTP Cache を利用して、ジョブメドレーのスピード改善を検討してみた調査過程を紹介しました。 expire_in 、 ETAG の作成方法など色々調べて、最終的に fresh_when で実現できましたが、運用していく上での課題については、引き続き検討して行きたいと思います。
こんにちは、開発本部の平木です。 去る 7/14(土)に Rails Developers Meetup 2018 Day 3 Extreme というイベントが開催されまして、 弊社の宍戸 が 電子カルテとセキュリティガイドラインと AWS と私 というタイトルで発表させていただきましたので、レポートさせていただきます。 発表のきっかけ 昨年に引き続き RubyKaigi 2018 で弊社 CTO の平山が LT スポンサーとして発表させていただいたり、ブースに出展をしていた ことがきっかけになり、Rails Developers Meetup の主催者である平野さん( @yoshi_hirano )から平山へ出演のお声がけをしていただきました。イベントからイベントへ関係がつながるのはとても嬉しいですね。 発表テーマについて せっかく、お声がけしていただいたので、発表テーマも弊社の特色が出るものが良いであろうと決まったのが今回のテーマです。 4 月に発表した電子カルテシステム CLINICS カルテ ですが、何度かこちらのブログでもエントリが上がっていますとおり、電子カルテというシステムを構築する上で、セキュリティはかなり重要なウェイトを占める要素になっています。 このセキュリティを担保するための指針として 3 省 4 ガイドライン という総務省・経産省・厚労省の各省庁から出ている 4 種類のガイドラインが出ています。 これを実際に AWS をベースとしたシステムを構築する際の一例として CLINICS カルテでの構築例を発表しようということになりました。 発表スライド 当日の発表スライドはこちらになります。 電カルでガイドラインに沿うよう cloud trail とか vpc flow logs といった細かな AWS のサービス使ってるのか。 #rdm2018A #railsdm — yuyasat (@yuyasat) July 14, 2018 会場ではスライドの色見がすっごく色褪せしていたりしましたが、発表した中での AWS のサービスは会場のみなさんの中でも、あまり馴染みがないものが多いようでスライドの写真など撮られている方もいらっしゃって、知見共有という意味で参考になった模様です。 クライアント認証の話たのしいしめっちゃ知見だな。ALB, API Gateway は TLS クライアント認証に非対応だったので、Nginx で認証させてるとのこと。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 メドレーさんの発表、すげーよかった。 派手さはなくとも、こうやって一個ずつ地道に技術課題を解決している話はめちゃくちゃおもしろい。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 やはりクライアント認証に関しては普段サービス開発ではそこまでは使われないので、興味を持っていただいたようで良かったです! まとめ 弊社のような医療業界ではなくても、フルマネージドサービスでセキュリティを意識するような場面があればご参考になるかもしれないテーマをエンジニアの宍戸から発表させていただきました。 社内で貯まった知見で機会があれば、ぜひ公開していきたいと思います。 メドレーについて気になった方は、こちらからどうぞ。 About 株式会社メドレー - Wantedly You can read employee interviews and the latest company's initiative of 株式会社メドレー. 急速な高齢化や医療費の高騰、医療現場の疲弊が叫ばれる中で、このままでは家計を大きく圧迫して支えきれなくなり、日本の医療は崩壊してしまいます。この状態を解消するための鍵が「医療現場におけるクラウド活用を駆使した業務効率化」です。 しかし日本では、半数以上の医療機関がいまだに紙カルテを利用しているなど、クラウド化は疎かデジタル活用も進んでいないのが現状です。私たちはテクノロジーを活用した事業やプロジェクトを通じて、医療ヘルスケア分野のデジタル活用を推進し、日本の未来を作るための取り組みを行っていきます。 www.wantedly.com
こんにちは、開発本部の平木です。 去る 7/14(土)に Rails Developers Meetup 2018 Day 3 Extreme というイベントが開催されまして、 弊社の宍戸 が 電子カルテとセキュリティガイドラインと AWS と私 というタイトルで発表させていただきましたので、レポートさせていただきます。 発表のきっかけ 昨年に引き続き RubyKaigi 2018 で弊社 CTO の平山が LT スポンサーとして発表させていただいたり、ブースに出展をしていた ことがきっかけになり、Rails Developers Meetup の主催者である平野さん( @yoshi_hirano )から平山へ出演のお声がけをしていただきました。イベントからイベントへ関係がつながるのはとても嬉しいですね。 発表テーマについて せっかく、お声がけしていただいたので、発表テーマも弊社の特色が出るものが良いであろうと決まったのが今回のテーマです。 4 月に発表した電子カルテシステム CLINICS カルテ ですが、何度かこちらのブログでもエントリが上がっていますとおり、電子カルテというシステムを構築する上で、セキュリティはかなり重要なウェイトを占める要素になっています。 このセキュリティを担保するための指針として 3 省 4 ガイドライン という総務省・経産省・厚労省の各省庁から出ている 4 種類のガイドラインが出ています。 これを実際に AWS をベースとしたシステムを構築する際の一例として CLINICS カルテでの構築例を発表しようということになりました。 発表スライド 当日の発表スライドはこちらになります。 電カルでガイドラインに沿うよう cloud trail とか vpc flow logs といった細かな AWS のサービス使ってるのか。 #rdm2018A #railsdm — yuyasat (@yuyasat) July 14, 2018 会場ではスライドの色見がすっごく色褪せしていたりしましたが、発表した中での AWS のサービスは会場のみなさんの中でも、あまり馴染みがないものが多いようでスライドの写真など撮られている方もいらっしゃって、知見共有という意味で参考になった模様です。 クライアント認証の話たのしいしめっちゃ知見だな。ALB, API Gateway は TLS クライアント認証に非対応だったので、Nginx で認証させてるとのこと。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 メドレーさんの発表、すげーよかった。 派手さはなくとも、こうやって一個ずつ地道に技術課題を解決している話はめちゃくちゃおもしろい。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 やはりクライアント認証に関しては普段サービス開発ではそこまでは使われないので、興味を持っていただいたようで良かったです! まとめ 弊社のような医療業界ではなくても、フルマネージドサービスでセキュリティを意識するような場面があればご参考になるかもしれないテーマをエンジニアの宍戸から発表させていただきました。 社内で貯まった知見で機会があれば、ぜひ公開していきたいと思います。 メドレーについて気になった方は、こちらからどうぞ。 https://www.wantedly.com/companies/medley
こんにちは、開発本部の平木です。 去る 7/14(土)に Rails Developers Meetup 2018 Day 3 Extreme というイベントが開催されまして、 弊社の宍戸 が 電子カルテとセキュリティガイドラインと AWS と私 というタイトルで発表させていただきましたので、レポートさせていただきます。 発表のきっかけ 昨年に引き続き RubyKaigi 2018 で弊社 CTO の平山が LT スポンサーとして発表させていただいたり、ブースに出展をしていた ことがきっかけになり、Rails Developers Meetup の主催者である平野さん( @yoshi_hirano )から平山へ出演のお声がけをしていただきました。イベントからイベントへ関係がつながるのはとても嬉しいですね。 発表テーマについて せっかく、お声がけしていただいたので、発表テーマも弊社の特色が出るものが良いであろうと決まったのが今回のテーマです。 4 月に発表した電子カルテシステム CLINICS カルテ ですが、何度かこちらのブログでもエントリが上がっていますとおり、電子カルテというシステムを構築する上で、セキュリティはかなり重要なウェイトを占める要素になっています。 このセキュリティを担保するための指針として 3 省 4 ガイドライン という総務省・経産省・厚労省の各省庁から出ている 4 種類のガイドラインが出ています。 これを実際に AWS をベースとしたシステムを構築する際の一例として CLINICS カルテでの構築例を発表しようということになりました。 発表スライド 当日の発表スライドはこちらになります。 電カルでガイドラインに沿うよう cloud trail とか vpc flow logs といった細かな AWS のサービス使ってるのか。 #rdm2018A #railsdm — yuyasat (@yuyasat) July 14, 2018 会場ではスライドの色見がすっごく色褪せしていたりしましたが、発表した中での AWS のサービスは会場のみなさんの中でも、あまり馴染みがないものが多いようでスライドの写真など撮られている方もいらっしゃって、知見共有という意味で参考になった模様です。 クライアント認証の話たのしいしめっちゃ知見だな。ALB, API Gateway は TLS クライアント認証に非対応だったので、Nginx で認証させてるとのこと。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 メドレーさんの発表、すげーよかった。 派手さはなくとも、こうやって一個ずつ地道に技術課題を解決している話はめちゃくちゃおもしろい。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 やはりクライアント認証に関しては普段サービス開発ではそこまでは使われないので、興味を持っていただいたようで良かったです! まとめ 弊社のような医療業界ではなくても、フルマネージドサービスでセキュリティを意識するような場面があればご参考になるかもしれないテーマをエンジニアの宍戸から発表させていただきました。 社内で貯まった知見で機会があれば、ぜひ公開していきたいと思います。 メドレーについて気になった方は、こちらからどうぞ。 About 株式会社メドレー - Wantedly You can read employee interviews and the latest company's initiative of 株式会社メドレー. 急速な高齢化や医療費の高騰、医療現場の疲弊が叫ばれる中で、このままでは家計を大きく圧迫して支えきれなくなり、日本の医療は崩壊してしまいます。この状態を解消するための鍵が「医療現場におけるクラウド活用を駆使した業務効率化」です。 しかし日本では、半数以上の医療機関がいまだに紙カルテを利用しているなど、クラウド化は疎かデジタル活用も進んでいないのが現状です。私たちはテクノロジーを活用した事業やプロジェクトを通じて、医療ヘルスケア分野のデジタル活用を推進し、日本の未来を作るための取り組みを行っていきます。 www.wantedly.com
こんにちは、開発本部の平木です。 去る 7/14(土)に Rails Developers Meetup 2018 Day 3 Extreme というイベントが開催されまして、 弊社の宍戸 が 電子カルテとセキュリティガイドラインと AWS と私 というタイトルで発表させていただきましたので、レポートさせていただきます。 発表のきっかけ 昨年に引き続き RubyKaigi 2018 で弊社 CTO の平山が LT スポンサーとして発表させていただいたり、ブースに出展をしていた ことがきっかけになり、Rails Developers Meetup の主催者である平野さん( @yoshi_hirano )から平山へ出演のお声がけをしていただきました。イベントからイベントへ関係がつながるのはとても嬉しいですね。 発表テーマについて せっかく、お声がけしていただいたので、発表テーマも弊社の特色が出るものが良いであろうと決まったのが今回のテーマです。 4 月に発表した電子カルテシステム CLINICS カルテ ですが、何度かこちらのブログでもエントリが上がっていますとおり、電子カルテというシステムを構築する上で、セキュリティはかなり重要なウェイトを占める要素になっています。 このセキュリティを担保するための指針として 3 省 4 ガイドライン という総務省・経産省・厚労省の各省庁から出ている 4 種類のガイドラインが出ています。 これを実際に AWS をベースとしたシステムを構築する際の一例として CLINICS カルテでの構築例を発表しようということになりました。 発表スライド 当日の発表スライドはこちらになります。 電カルでガイドラインに沿うよう cloud trail とか vpc flow logs といった細かな AWS のサービス使ってるのか。 #rdm2018A #railsdm — yuyasat (@yuyasat) July 14, 2018 会場ではスライドの色見がすっごく色褪せしていたりしましたが、発表した中での AWS のサービスは会場のみなさんの中でも、あまり馴染みがないものが多いようでスライドの写真など撮られている方もいらっしゃって、知見共有という意味で参考になった模様です。 クライアント認証の話たのしいしめっちゃ知見だな。ALB, API Gateway は TLS クライアント認証に非対応だったので、Nginx で認証させてるとのこと。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 メドレーさんの発表、すげーよかった。 派手さはなくとも、こうやって一個ずつ地道に技術課題を解決している話はめちゃくちゃおもしろい。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 やはりクライアント認証に関しては普段サービス開発ではそこまでは使われないので、興味を持っていただいたようで良かったです! まとめ 弊社のような医療業界ではなくても、フルマネージドサービスでセキュリティを意識するような場面があればご参考になるかもしれないテーマをエンジニアの宍戸から発表させていただきました。 社内で貯まった知見で機会があれば、ぜひ公開していきたいと思います。 メドレーについて気になった方は、こちらからどうぞ。 About 株式会社メドレー - Wantedly You can read employee interviews and the latest company's initiative of 株式会社メドレー. 急速な高齢化や医療費の高騰、医療現場の疲弊が叫ばれる中で、このままでは家計を大きく圧迫して支えきれなくなり、日本の医療は崩壊してしまいます。この状態を解消するための鍵が「医療現場におけるクラウド活用を駆使した業務効率化」です。 しかし日本では、半数以上の医療機関がいまだに紙カルテを利用しているなど、クラウド化は疎かデジタル活用も進んでいないのが現状です。私たちはテクノロジーを活用した事業やプロジェクトを通じて、医療ヘルスケア分野のデジタル活用を推進し、日本の未来を作るための取り組みを行っていきます。 www.wantedly.com
こんにちは、開発本部の平木です。 去る 7/14(土)に Rails Developers Meetup 2018 Day 3 Extreme というイベントが開催されまして、 弊社の宍戸 が 電子カルテとセキュリティガイドラインと AWS と私 というタイトルで発表させていただきましたので、レポートさせていただきます。 発表のきっかけ 昨年に引き続き RubyKaigi 2018 で弊社 CTO の平山が LT スポンサーとして発表させていただいたり、ブースに出展をしていた ことがきっかけになり、Rails Developers Meetup の主催者である平野さん( @yoshi_hirano )から平山へ出演のお声がけをしていただきました。イベントからイベントへ関係がつながるのはとても嬉しいですね。 発表テーマについて せっかく、お声がけしていただいたので、発表テーマも弊社の特色が出るものが良いであろうと決まったのが今回のテーマです。 4 月に発表した電子カルテシステム CLINICS カルテ ですが、何度かこちらのブログでもエントリが上がっていますとおり、電子カルテというシステムを構築する上で、セキュリティはかなり重要なウェイトを占める要素になっています。 このセキュリティを担保するための指針として 3 省 4 ガイドライン という総務省・経産省・厚労省の各省庁から出ている 4 種類のガイドラインが出ています。 これを実際に AWS をベースとしたシステムを構築する際の一例として CLINICS カルテでの構築例を発表しようということになりました。 発表スライド 当日の発表スライドはこちらになります。 電カルでガイドラインに沿うよう cloud trail とか vpc flow logs といった細かな AWS のサービス使ってるのか。 #rdm2018A #railsdm — yuyasat (@yuyasat) July 14, 2018 会場ではスライドの色見がすっごく色褪せしていたりしましたが、発表した中での AWS のサービスは会場のみなさんの中でも、あまり馴染みがないものが多いようでスライドの写真など撮られている方もいらっしゃって、知見共有という意味で参考になった模様です。 クライアント認証の話たのしいしめっちゃ知見だな。ALB, API Gateway は TLS クライアント認証に非対応だったので、Nginx で認証させてるとのこと。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 メドレーさんの発表、すげーよかった。 派手さはなくとも、こうやって一個ずつ地道に技術課題を解決している話はめちゃくちゃおもしろい。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 やはりクライアント認証に関しては普段サービス開発ではそこまでは使われないので、興味を持っていただいたようで良かったです! まとめ 弊社のような医療業界ではなくても、フルマネージドサービスでセキュリティを意識するような場面があればご参考になるかもしれないテーマをエンジニアの宍戸から発表させていただきました。 社内で貯まった知見で機会があれば、ぜひ公開していきたいと思います。 メドレーについて気になった方は、こちらからどうぞ。 About 株式会社メドレー - Wantedly You can read employee interviews and the latest company's initiative of 株式会社メドレー. 急速な高齢化や医療費の高騰、医療現場の疲弊が叫ばれる中で、このままでは家計を大きく圧迫して支えきれなくなり、日本の医療は崩壊してしまいます。この状態を解消するための鍵が「医療現場におけるクラウド活用を駆使した業務効率化」です。 しかし日本では、半数以上の医療機関がいまだに紙カルテを利用しているなど、クラウド化は疎かデジタル活用も進んでいないのが現状です。私たちはテクノロジーを活用した事業やプロジェクトを通じて、医療ヘルスケア分野のデジタル活用を推進し、日本の未来を作るための取り組みを行っていきます。 www.wantedly.com
こんにちは、開発本部の平木です。 去る 7/14(土)に Rails Developers Meetup 2018 Day 3 Extreme というイベントが開催されまして、 弊社の宍戸 が 電子カルテとセキュリティガイドラインと AWS と私 というタイトルで発表させていただきましたので、レポートさせていただきます。 発表のきっかけ 昨年に引き続き RubyKaigi 2018 で弊社 CTO の平山が LT スポンサーとして発表させていただいたり、ブースに出展をしていた ことがきっかけになり、Rails Developers Meetup の主催者である平野さん( @yoshi_hirano )から平山へ出演のお声がけをしていただきました。イベントからイベントへ関係がつながるのはとても嬉しいですね。 発表テーマについて せっかく、お声がけしていただいたので、発表テーマも弊社の特色が出るものが良いであろうと決まったのが今回のテーマです。 4 月に発表した電子カルテシステム CLINICS カルテ ですが、何度かこちらのブログでもエントリが上がっていますとおり、電子カルテというシステムを構築する上で、セキュリティはかなり重要なウェイトを占める要素になっています。 このセキュリティを担保するための指針として 3 省 4 ガイドライン という総務省・経産省・厚労省の各省庁から出ている 4 種類のガイドラインが出ています。 これを実際に AWS をベースとしたシステムを構築する際の一例として CLINICS カルテでの構築例を発表しようということになりました。 発表スライド 当日の発表スライドはこちらになります。 電カルでガイドラインに沿うよう cloud trail とか vpc flow logs といった細かな AWS のサービス使ってるのか。 #rdm2018A #railsdm — yuyasat (@yuyasat) July 14, 2018 会場ではスライドの色見がすっごく色褪せしていたりしましたが、発表した中での AWS のサービスは会場のみなさんの中でも、あまり馴染みがないものが多いようでスライドの写真など撮られている方もいらっしゃって、知見共有という意味で参考になった模様です。 クライアント認証の話たのしいしめっちゃ知見だな。ALB, API Gateway は TLS クライアント認証に非対応だったので、Nginx で認証させてるとのこと。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 メドレーさんの発表、すげーよかった。 派手さはなくとも、こうやって一個ずつ地道に技術課題を解決している話はめちゃくちゃおもしろい。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 やはりクライアント認証に関しては普段サービス開発ではそこまでは使われないので、興味を持っていただいたようで良かったです! まとめ 弊社のような医療業界ではなくても、フルマネージドサービスでセキュリティを意識するような場面があればご参考になるかもしれないテーマをエンジニアの宍戸から発表させていただきました。 社内で貯まった知見で機会があれば、ぜひ公開していきたいと思います。 メドレーについて気になった方は、こちらからどうぞ。 About 株式会社メドレー - Wantedly You can read employee interviews and the latest company's initiative of 株式会社メドレー. 急速な高齢化や医療費の高騰、医療現場の疲弊が叫ばれる中で、このままでは家計を大きく圧迫して支えきれなくなり、日本の医療は崩壊してしまいます。この状態を解消するための鍵が「医療現場におけるクラウド活用を駆使した業務効率化」です。 しかし日本では、半数以上の医療機関がいまだに紙カルテを利用しているなど、クラウド化は疎かデジタル活用も進んでいないのが現状です。私たちはテクノロジーを活用した事業やプロジェクトを通じて、医療ヘルスケア分野のデジタル活用を推進し、日本の未来を作るための取り組みを行っていきます。 www.wantedly.com
こんにちは、開発本部の平木です。 去る 7/14(土)に Rails Developers Meetup 2018 Day 3 Extreme というイベントが開催されまして、 弊社の宍戸 が 電子カルテとセキュリティガイドラインと AWS と私 というタイトルで発表させていただきましたので、レポートさせていただきます。 発表のきっかけ 昨年に引き続き RubyKaigi 2018 で弊社 CTO の平山が LT スポンサーとして発表させていただいたり、ブースに出展をしていた ことがきっかけになり、Rails Developers Meetup の主催者である平野さん( @yoshi_hirano )から平山へ出演のお声がけをしていただきました。イベントからイベントへ関係がつながるのはとても嬉しいですね。 発表テーマについて せっかく、お声がけしていただいたので、発表テーマも弊社の特色が出るものが良いであろうと決まったのが今回のテーマです。 4 月に発表した電子カルテシステム CLINICS カルテ ですが、何度かこちらのブログでもエントリが上がっていますとおり、電子カルテというシステムを構築する上で、セキュリティはかなり重要なウェイトを占める要素になっています。 このセキュリティを担保するための指針として 3 省 4 ガイドライン という総務省・経産省・厚労省の各省庁から出ている 4 種類のガイドラインが出ています。 これを実際に AWS をベースとしたシステムを構築する際の一例として CLINICS カルテでの構築例を発表しようということになりました。 発表スライド 当日の発表スライドはこちらになります。 電カルでガイドラインに沿うよう cloud trail とか vpc flow logs といった細かな AWS のサービス使ってるのか。 #rdm2018A #railsdm — yuyasat (@yuyasat) July 14, 2018 会場ではスライドの色見がすっごく色褪せしていたりしましたが、発表した中での AWS のサービスは会場のみなさんの中でも、あまり馴染みがないものが多いようでスライドの写真など撮られている方もいらっしゃって、知見共有という意味で参考になった模様です。 クライアント認証の話たのしいしめっちゃ知見だな。ALB, API Gateway は TLS クライアント認証に非対応だったので、Nginx で認証させてるとのこと。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 メドレーさんの発表、すげーよかった。 派手さはなくとも、こうやって一個ずつ地道に技術課題を解決している話はめちゃくちゃおもしろい。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 やはりクライアント認証に関しては普段サービス開発ではそこまでは使われないので、興味を持っていただいたようで良かったです! まとめ 弊社のような医療業界ではなくても、フルマネージドサービスでセキュリティを意識するような場面があればご参考になるかもしれないテーマをエンジニアの宍戸から発表させていただきました。 社内で貯まった知見で機会があれば、ぜひ公開していきたいと思います。 メドレーについて気になった方は、こちらからどうぞ。 About 株式会社メドレー - Wantedly You can read employee interviews and the latest company's initiative of 株式会社メドレー. 急速な高齢化や医療費の高騰、医療現場の疲弊が叫ばれる中で、このままでは家計を大きく圧迫して支えきれなくなり、日本の医療は崩壊してしまいます。この状態を解消するための鍵が「医療現場におけるクラウド活用を駆使した業務効率化」です。 しかし日本では、半数以上の医療機関がいまだに紙カルテを利用しているなど、クラウド化は疎かデジタル活用も進んでいないのが現状です。私たちはテクノロジーを活用した事業やプロジェクトを通じて、医療ヘルスケア分野のデジタル活用を推進し、日本の未来を作るための取り組みを行っていきます。 www.wantedly.com
こんにちは、開発本部の平木です。 去る 7/14(土)に Rails Developers Meetup 2018 Day 3 Extreme というイベントが開催されまして、 弊社の宍戸 が 電子カルテとセキュリティガイドラインと AWS と私 というタイトルで発表させていただきましたので、レポートさせていただきます。 発表のきっかけ 昨年に引き続き RubyKaigi 2018 で弊社 CTO の平山が LT スポンサーとして発表させていただいたり、ブースに出展をしていた ことがきっかけになり、Rails Developers Meetup の主催者である平野さん( @yoshi_hirano )から平山へ出演のお声がけをしていただきました。イベントからイベントへ関係がつながるのはとても嬉しいですね。 発表テーマについて せっかく、お声がけしていただいたので、発表テーマも弊社の特色が出るものが良いであろうと決まったのが今回のテーマです。 4 月に発表した電子カルテシステム CLINICS カルテ ですが、何度かこちらのブログでもエントリが上がっていますとおり、電子カルテというシステムを構築する上で、セキュリティはかなり重要なウェイトを占める要素になっています。 このセキュリティを担保するための指針として 3 省 4 ガイドライン という総務省・経産省・厚労省の各省庁から出ている 4 種類のガイドラインが出ています。 これを実際に AWS をベースとしたシステムを構築する際の一例として CLINICS カルテでの構築例を発表しようということになりました。 発表スライド 当日の発表スライドはこちらになります。 電カルでガイドラインに沿うよう cloud trail とか vpc flow logs といった細かな AWS のサービス使ってるのか。 #rdm2018A #railsdm — yuyasat (@yuyasat) July 14, 2018 会場ではスライドの色見がすっごく色褪せしていたりしましたが、発表した中での AWS のサービスは会場のみなさんの中でも、あまり馴染みがないものが多いようでスライドの写真など撮られている方もいらっしゃって、知見共有という意味で参考になった模様です。 クライアント認証の話たのしいしめっちゃ知見だな。ALB, API Gateway は TLS クライアント認証に非対応だったので、Nginx で認証させてるとのこと。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 メドレーさんの発表、すげーよかった。 派手さはなくとも、こうやって一個ずつ地道に技術課題を解決している話はめちゃくちゃおもしろい。 #rdm2018A #railsdm — かつひささん (@katsuhisa__) July 14, 2018 やはりクライアント認証に関しては普段サービス開発ではそこまでは使われないので、興味を持っていただいたようで良かったです! まとめ 弊社のような医療業界ではなくても、フルマネージドサービスでセキュリティを意識するような場面があればご参考になるかもしれないテーマをエンジニアの宍戸から発表させていただきました。 社内で貯まった知見で機会があれば、ぜひ公開していきたいと思います。 メドレーについて気になった方は、こちらからどうぞ。 About 株式会社メドレー - Wantedly You can read employee interviews and the latest company's initiative of 株式会社メドレー. 急速な高齢化や医療費の高騰、医療現場の疲弊が叫ばれる中で、このままでは家計を大きく圧迫して支えきれなくなり、日本の医療は崩壊してしまいます。この状態を解消するための鍵が「医療現場におけるクラウド活用を駆使した業務効率化」です。 しかし日本では、半数以上の医療機関がいまだに紙カルテを利用しているなど、クラウド化は疎かデジタル活用も進んでいないのが現状です。私たちはテクノロジーを活用した事業やプロジェクトを通じて、医療ヘルスケア分野のデジタル活用を推進し、日本の未来を作るための取り組みを行っていきます。 www.wantedly.com
こんにちは、開発本部の宮内です。今回、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 カードアプリケーションの検索」まで実装できました。 今後、次のステップである暗号計算を実装していきたいと思います。
こんにちは、開発本部の宮内です。今回、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 カードアプリケーションの検索」まで実装できました。 今後、次のステップである暗号計算を実装していきたいと思います。
こんにちは、開発本部の宮内です。今回、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 カードアプリケーションの検索」まで実装できました。 今後、次のステップである暗号計算を実装していきたいと思います。
こんにちは、開発本部の宮内です。今回、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 カードアプリケーションの検索」まで実装できました。 今後、次のステップである暗号計算を実装していきたいと思います。
こんにちは、開発本部の宮内です。今回、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 カードアプリケーションの検索」まで実装できました。 今後、次のステップである暗号計算を実装していきたいと思います。
こんにちは、開発本部の宮内です。今回、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 カードアプリケーションの検索」まで実装できました。 今後、次のステップである暗号計算を実装していきたいと思います。
こんにちは、開発本部の宮内です。今回、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 カードアプリケーションの検索」まで実装できました。 今後、次のステップである暗号計算を実装していきたいと思います。
こんにちは、開発本部の宮内です。今回、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 カードアプリケーションの検索」まで実装できました。 今後、次のステップである暗号計算を実装していきたいと思います。
こんにちは、開発本部の竹内です。最近子どものプリンセスへの強い憧れに若干引いております。 さて先日、TechLunch という社内勉強会で「電子レセプト」について話しましたので、こちらでも簡単に紹介させていただきます。 レセプトとは ところで、みなさまは「レセプト」についてご存知でしょうか?私はメドレーに入社するまで知りませんでした。 レセプトとは医療機関が支払基金へ診療報酬を請求するための明細書情報のことです。 と言っても、初めて聞かれる方もいらっしゃると思いますので、医療機関におけるお金の流れとともに簡単に説明します。 (支払基金ってどんなところ?|社会保険診療報酬支払基金より) 医療機関は「診療」の対価として、被保険者等(≒ 患者)からお金を受け取るわけですが、被保険者の加入する保険や公費によってその額は変わります。負担割合が 3 割の場合、残りの 7 割を被保険者が加入する保険組合などへ請求する必要があります。 この保険組合などへの請求を取りまとめ、内容を審査しているのが支払基金と呼ばれる組織で、医療機関は月に一度、前月の患者ごとの診療点数を計算し「レセプト」として支払基金に提出することになります。 (支払基金ってどんなところ?|社会保険診療報酬支払基金より) レセプトには、請求する診療点数のほか、医療機関の情報、被保険者の情報(氏名などの基本情報、加入している保険者情報)、診療行為や傷病名に関する情報などが含まれています。 「レセプト」には紙と電子データとありますが、現在は原則として電子レセプトを提出することが求められているそうです( 電子レセプト請求に係る猶予措置及び免除措置について|社会保険診療報酬支払基金 )。 電子レセプトとレセ電ビューア 電子レセプトについての仕様は支払基金によって公開されています。今回は「 電子レセプト作成の手引き 」という資料を元に「医科」のレセプトについて調べて発表しました。 発表資料はこちら。 電子レセプトの実体は CSV 形式のシンプルなテキストファイルです(拡張子は UKE なので、UKE ファイルと呼ぶこともあるようです)。ただ、電子レセプトの仕様を把握したとしても、やはり CSV ファイルを見て内容を把握するのは至難の業です。ファイル上では診療行為や医薬品、傷病名はコードとして表現されているため、 マスタデータ を参照しなければその内容まで理解することはできないからです。 そこで登場するのが、レセ電ビューアというツールで、ORCA Project によって公開されているフリーの電子レセプトビューアです。「レセ電=電子レセプト」ですね。 ORCA Project: レセ電ビューワ 日本医師会開発・日医標準レセプトソフトウェアのサイトです www.orca.med.or.jp (※上記ページでは「レセ電ビューア」と「レセ電ビューワ」が混在していますが、本ブログでは「レセ電ビューア」で統一しています) レセ電ビューアは Windows と Ubuntu で動作し、上述した UKE ファイルを読み込み、見やすく表示してくれる便利ツールです。ここからはレセ電ビューアをインストールし、電子レセプトを読み込んで表示するところまでを紹介したいと思います。 レセ電ビューアのインストール レセ電ビューアは Windows と Ubuntu 上で動作しますので、まずは Ubuntu 環境を準備します。私は VirtualBox 上に Ubuntu 環境を用意しました。 公式のインストールマニュアル( ubuntu 環境へのレセ電ビューアインストール )に基づいて作業していきます。 # Keyring と apt-line の追加 $ sudo su $ wget -q https://ftp.orca.med.or.jp/pub/ubuntu/archive.key $ apt-key add archive.key $ wget -q -O \ /etc/apt/sources.list.d/jma-receipt-xenial50.list \ https://ftp.orca.med.or.jp/pub/ubuntu/jma-receipt-xenial50.list $ apt-get update $ apt-get dist-upgrade # レセ電ビューアパッケージインストール $ apt-get install jma-receview $ apt-get install jma-receview-server # レセ電ビューア起動 $ jma-receview レセ電ビューアの設定 電子レセプトに含まれる診療行為などのコードに対応するマスタデータを参照するため、日レセ(jma-receipt)の DB を利用することができます。今回は DBFile 形式で DB に接続します。レセ電ビューアに付属するスクリプトを実行し、jma-receipt の DB から必要なテーブルをダンプすることができます。このファイルをレセ電ビューアに設定することで、電子レセプトの表示がよりわかりやすくなります。 # レセ電ビューアで使う DBFile を作る # https://ftp.orca.med.or.jp/pub/receview/manual/jma-receview.pdf # 「2.7.4 DBFile の作成方法」 $ sudo su orca $ ls -la /usr/share/jma-receview/db/ $ mkdir /var/tmp/dbfile $ cp /usr/share/jma-receview/db/make_dbfile.sh /var/tmp/dbfile/ $ cd /var/tmp/dbfile/ $ sh ./make_dbfile.sh 20170101 $ ls -lh 合計 6.9M -rwxr-xr-x 1 orca orca 2.9K 5 月 18 14:55 make_dbfile.sh -rw-r--r-- 1 orca orca 1.2M 5 月 18 14:57 tbl_byomei.rdb -rw-r--r-- 1 orca orca 89 5 月 18 14:57 tbl_dbkanri.rdb -rw-r--r-- 1 orca orca 330K 5 月 18 14:57 tbl_hknjainf.rdb -rw-r--r-- 1 orca orca 9.6K 5 月 18 14:57 tbl_labor_sio.rdb -rw-r--r-- 1 orca orca 343K 5 月 18 14:57 tbl_syskanri.rdb -rw-r--r-- 1 orca orca 5.1M 5 月 18 14:57 tbl_tensu.rdb 接続設定で「DBFile」を選択し、先ほど作成した DBFile を選択します。 レセ電ビューア近影 「ファイル」から UKE ファイルを開くと、レセプトに基づいて患者基本情報、保険・公費情報、診療行為情報のほか、紙レセプトのプレビューや患者単位での電子レセプトを表示することができます。また、「編集モード」に切り替えることで患者情報や病名の編集が可能で、編集した内容でレセプトを再出力することもできるようです。 おまけ ここまで紹介してきたレセ電ビューアですが、調べてみるとどうやら Ruby で実装されているようです。これらのコードを読んでいくことで新たな地平を開くことができるかもしれません。 $ dpkg -L jma-receview | grep ruby /usr/lib/ruby /usr/lib/ruby/2.3.0 /usr/lib/ruby/2.3.0/jma /usr/lib/ruby/2.3.0/jma/receview /usr/lib/ruby/2.3.0/jma/receview/menu.rb /usr/lib/ruby/2.3.0/jma/receview/intconv.rb /usr/lib/ruby/2.3.0/jma/receview/base.rb /usr/lib/ruby/2.3.0/jma/receview/dbfile_lib.rb /usr/lib/ruby/2.3.0/jma/receview/gui.rb /usr/lib/ruby/2.3.0/jma/receview/yearconv.rb /usr/lib/ruby/2.3.0/jma/receview/exception.rb /usr/lib/ruby/2.3.0/jma/receview/api.rb /usr/lib/ruby/2.3.0/jma/receview/config.rb /usr/lib/ruby/2.3.0/jma/receview/image.rb /usr/lib/ruby/2.3.0/jma/receview/dialog.rb /usr/lib/ruby/2.3.0/jma/receview/upstart.rb /usr/lib/ruby/2.3.0/jma/receview/dbslib.rb /usr/lib/ruby/2.3.0/jma/receview/thread.rb /usr/lib/ruby/2.3.0/jma/receview/receview.rb /usr/lib/ruby/2.3.0/jma/receview/sickname_edit.rb /usr/lib/ruby/2.3.0/jma/receview/dayconv.rb /usr/lib/ruby/2.3.0/jma/receview/isoimage.rb /usr/lib/ruby/2.3.0/jma/receview/help.rb /usr/lib/ruby/2.3.0/jma/receview/other_csv.rb /usr/lib/ruby/2.3.0/jma/receview/print.rb /usr/lib/ruby/2.3.0/jma/receview/env.rb /usr/lib/ruby/2.3.0/jma/receview/generation.rb /usr/lib/ruby/2.3.0/jma/receview/red2cairo.rb /usr/lib/ruby/2.3.0/jma/receview/strconv.rb /usr/lib/ruby/2.3.0/jma/receview/log.rb /usr/lib/ruby/2.3.0/jma/receview/hokenconv.rb /usr/lib/ruby/2.3.0/jma/receview/keybind.rb /usr/lib/ruby/2.3.0/jma/receview/version.rb /usr/lib/ruby/2.3.0/jma/receview/gtk2_fix.rb /usr/lib/ruby/2.3.0/jma/receview/preview_widget.rb /usr/lib/ruby/2.3.0/jma/receview/command.rb # /usr/bin/jma-receview も ruby で書かれてました(12000 行以上ある…) まとめ 今回は電子レセプトとレセ電ビューアについて、簡単に紹介しました。 わたしたちの生活とは切り離せない「医療」に関するシステムや仕様は、意外と一般公開されているものもあり、誰でも触れることができます。ただ、動作環境が制限されていたり、インターネット界隈のエンジニアがよく目にする技術とは異なるスタックで構築されていたり、それなりにハードルがあるように感じています。 これらのハードルを下げ、より多くの人が「調べてみよう」「ちょっと触ってみよう」と思うようになれば、医療に関わるシステムや仕様もよりシンプルで使いやすいものになり、ひいては各医療問題の解決・患者体験の改善につながっていくのではないかな、メドレーがつなげていきたいなと思っています。 最後はいいことを言って締めたい性分なのですが、いかがだったでしょうか。 ここまでお読みいただき、ありがとうございました。 お知らせ メドレーの開発にご興味ある方は、こちらからご連絡ください。 メンバーのストーリー | 株式会社メドレー メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。... www.medley.jp 7/27 に開催されるデブサミ 2018Summer に協賛させていただきます。ぜひ遊びにいらしてください。 Developers Summit 2018 Summer ITエンジニアの祭典「Developers Summit 2018 Summer」(デブサミ2018夏)は、2018年7月27日に開催! event.shoeisha.jp
こんにちは、開発本部の竹内です。最近子どものプリンセスへの強い憧れに若干引いております。 さて先日、TechLunch という社内勉強会で「電子レセプト」について話しましたので、こちらでも簡単に紹介させていただきます。 レセプトとは ところで、みなさまは「レセプト」についてご存知でしょうか?私はメドレーに入社するまで知りませんでした。 レセプトとは医療機関が支払基金へ診療報酬を請求するための明細書情報のことです。 と言っても、初めて聞かれる方もいらっしゃると思いますので、医療機関におけるお金の流れとともに簡単に説明します。 (支払基金ってどんなところ?|社会保険診療報酬支払基金より) 医療機関は「診療」の対価として、被保険者等(≒ 患者)からお金を受け取るわけですが、被保険者の加入する保険や公費によってその額は変わります。負担割合が 3 割の場合、残りの 7 割を被保険者が加入する保険組合などへ請求する必要があります。 この保険組合などへの請求を取りまとめ、内容を審査しているのが支払基金と呼ばれる組織で、医療機関は月に一度、前月の患者ごとの診療点数を計算し「レセプト」として支払基金に提出することになります。 (支払基金ってどんなところ?|社会保険診療報酬支払基金より) レセプトには、請求する診療点数のほか、医療機関の情報、被保険者の情報(氏名などの基本情報、加入している保険者情報)、診療行為や傷病名に関する情報などが含まれています。 「レセプト」には紙と電子データとありますが、現在は原則として電子レセプトを提出することが求められているそうです( 電子レセプト請求に係る猶予措置及び免除措置について|社会保険診療報酬支払基金 )。 電子レセプトとレセ電ビューア 電子レセプトについての仕様は支払基金によって公開されています。今回は「 電子レセプト作成の手引き 」という資料を元に「医科」のレセプトについて調べて発表しました。 発表資料はこちら。 電子レセプトの実体は CSV 形式のシンプルなテキストファイルです(拡張子は UKE なので、UKE ファイルと呼ぶこともあるようです)。ただ、電子レセプトの仕様を把握したとしても、やはり CSV ファイルを見て内容を把握するのは至難の業です。ファイル上では診療行為や医薬品、傷病名はコードとして表現されているため、 マスタデータ を参照しなければその内容まで理解することはできないからです。 そこで登場するのが、レセ電ビューアというツールで、ORCA Project によって公開されているフリーの電子レセプトビューアです。「レセ電=電子レセプト」ですね。 https://www.orca.med.or.jp/receipt/use/jma-receview.html (※上記ページでは「レセ電ビューア」と「レセ電ビューワ」が混在していますが、本ブログでは「レセ電ビューア」で統一しています) レセ電ビューアは Windows と Ubuntu で動作し、上述した UKE ファイルを読み込み、見やすく表示してくれる便利ツールです。ここからはレセ電ビューアをインストールし、電子レセプトを読み込んで表示するところまでを紹介したいと思います。 レセ電ビューアのインストール レセ電ビューアは Windows と Ubuntu 上で動作しますので、まずは Ubuntu 環境を準備します。私は VirtualBox 上に Ubuntu 環境を用意しました。 公式のインストールマニュアル( ubuntu 環境へのレセ電ビューアインストール )に基づいて作業していきます。 # Keyring と apt-line の追加 $ sudo su $ wget -q https://ftp.orca.med.or.jp/pub/ubuntu/archive.key $ apt-key add archive.key $ wget -q -O \ /etc/apt/sources.list.d/jma-receipt-xenial50.list \ https://ftp.orca.med.or.jp/pub/ubuntu/jma-receipt-xenial50.list $ apt-get update $ apt-get dist-upgrade # レセ電ビューアパッケージインストール $ apt-get install jma-receview $ apt-get install jma-receview-server # レセ電ビューア起動 $ jma-receview レセ電ビューアの設定 電子レセプトに含まれる診療行為などのコードに対応するマスタデータを参照するため、日レセ(jma-receipt)の DB を利用することができます。今回は DBFile 形式で DB に接続します。レセ電ビューアに付属するスクリプトを実行し、jma-receipt の DB から必要なテーブルをダンプすることができます。このファイルをレセ電ビューアに設定することで、電子レセプトの表示がよりわかりやすくなります。 # レセ電ビューアで使う DBFile を作る # https://ftp.orca.med.or.jp/pub/receview/manual/jma-receview.pdf # 「2.7.4 DBFile の作成方法」 $ sudo su orca $ ls -la /usr/share/jma-receview/db/ $ mkdir /var/tmp/dbfile $ cp /usr/share/jma-receview/db/make_dbfile.sh /var/tmp/dbfile/ $ cd /var/tmp/dbfile/ $ sh ./make_dbfile.sh 20170101 $ ls -lh 合計 6.9M -rwxr-xr-x 1 orca orca 2.9K 5 月 18 14:55 make_dbfile.sh -rw-r--r-- 1 orca orca 1.2M 5 月 18 14:57 tbl_byomei.rdb -rw-r--r-- 1 orca orca 89 5 月 18 14:57 tbl_dbkanri.rdb -rw-r--r-- 1 orca orca 330K 5 月 18 14:57 tbl_hknjainf.rdb -rw-r--r-- 1 orca orca 9.6K 5 月 18 14:57 tbl_labor_sio.rdb -rw-r--r-- 1 orca orca 343K 5 月 18 14:57 tbl_syskanri.rdb -rw-r--r-- 1 orca orca 5.1M 5 月 18 14:57 tbl_tensu.rdb 接続設定で「DBFile」を選択し、先ほど作成した DBFile を選択します。 レセ電ビューア近影 「ファイル」から UKE ファイルを開くと、レセプトに基づいて患者基本情報、保険・公費情報、診療行為情報のほか、紙レセプトのプレビューや患者単位での電子レセプトを表示することができます。また、「編集モード」に切り替えることで患者情報や病名の編集が可能で、編集した内容でレセプトを再出力することもできるようです。 おまけ ここまで紹介してきたレセ電ビューアですが、調べてみるとどうやら Ruby で実装されているようです。これらのコードを読んでいくことで新たな地平を開くことができるかもしれません。 $ dpkg -L jma-receview | grep ruby /usr/lib/ruby /usr/lib/ruby/2.3.0 /usr/lib/ruby/2.3.0/jma /usr/lib/ruby/2.3.0/jma/receview /usr/lib/ruby/2.3.0/jma/receview/menu.rb /usr/lib/ruby/2.3.0/jma/receview/intconv.rb /usr/lib/ruby/2.3.0/jma/receview/base.rb /usr/lib/ruby/2.3.0/jma/receview/dbfile_lib.rb /usr/lib/ruby/2.3.0/jma/receview/gui.rb /usr/lib/ruby/2.3.0/jma/receview/yearconv.rb /usr/lib/ruby/2.3.0/jma/receview/exception.rb /usr/lib/ruby/2.3.0/jma/receview/api.rb /usr/lib/ruby/2.3.0/jma/receview/config.rb /usr/lib/ruby/2.3.0/jma/receview/image.rb /usr/lib/ruby/2.3.0/jma/receview/dialog.rb /usr/lib/ruby/2.3.0/jma/receview/upstart.rb /usr/lib/ruby/2.3.0/jma/receview/dbslib.rb /usr/lib/ruby/2.3.0/jma/receview/thread.rb /usr/lib/ruby/2.3.0/jma/receview/receview.rb /usr/lib/ruby/2.3.0/jma/receview/sickname_edit.rb /usr/lib/ruby/2.3.0/jma/receview/dayconv.rb /usr/lib/ruby/2.3.0/jma/receview/isoimage.rb /usr/lib/ruby/2.3.0/jma/receview/help.rb /usr/lib/ruby/2.3.0/jma/receview/other_csv.rb /usr/lib/ruby/2.3.0/jma/receview/print.rb /usr/lib/ruby/2.3.0/jma/receview/env.rb /usr/lib/ruby/2.3.0/jma/receview/generation.rb /usr/lib/ruby/2.3.0/jma/receview/red2cairo.rb /usr/lib/ruby/2.3.0/jma/receview/strconv.rb /usr/lib/ruby/2.3.0/jma/receview/log.rb /usr/lib/ruby/2.3.0/jma/receview/hokenconv.rb /usr/lib/ruby/2.3.0/jma/receview/keybind.rb /usr/lib/ruby/2.3.0/jma/receview/version.rb /usr/lib/ruby/2.3.0/jma/receview/gtk2_fix.rb /usr/lib/ruby/2.3.0/jma/receview/preview_widget.rb /usr/lib/ruby/2.3.0/jma/receview/command.rb # /usr/bin/jma-receview も ruby で書かれてました(12000 行以上ある…) まとめ 今回は電子レセプトとレセ電ビューアについて、簡単に紹介しました。 わたしたちの生活とは切り離せない「医療」に関するシステムや仕様は、意外と一般公開されているものもあり、誰でも触れることができます。ただ、動作環境が制限されていたり、インターネット界隈のエンジニアがよく目にする技術とは異なるスタックで構築されていたり、それなりにハードルがあるように感じています。 これらのハードルを下げ、より多くの人が「調べてみよう」「ちょっと触ってみよう」と思うようになれば、医療に関わるシステムや仕様もよりシンプルで使いやすいものになり、ひいては各医療問題の解決・患者体験の改善につながっていくのではないかな、メドレーがつなげていきたいなと思っています。 最後はいいことを言って締めたい性分なのですが、いかがだったでしょうか。 ここまでお読みいただき、ありがとうございました。 お知らせ メドレーの開発にご興味ある方は、こちらからご連絡ください。 https://www.medley.jp/recruit/creative.html 7/27 に開催されるデブサミ 2018Summer に協賛させていただきます。ぜひ遊びにいらしてください。 https://event.shoeisha.jp/devsumi/20180727