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

TECH PLAY

株式会社メドレー

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

全1414件

こんにちは、開発本部の楊です。メドレーの社内勉強会「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 で実現できましたが、運用していく上での課題については、引き続き検討して行きたいと思います。
こんにちは、開発本部の楊です。メドレーの社内勉強会「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 カードアプリケーションの検索」まで実装できました。 今後、次のステップである暗号計算を実装していきたいと思います。