Medley Developer Blog

株式会社メドレーのエンジニア・デザイナーによるブログです

新オフィスのデザインを任された話

先日、新卒エンジニアさんの内定式(メドレー初!)をおこないました。バキバキでキラキラのイケメン揃いなので中途イケメン枠で入社した私にとってこれまでの地位が危ぶまれ戦々恐々としています。開発本部デザイナーの小山です。
 
内定式もですが、大きな変化がメドレーにありデザイナーの役割が広がる機会が訪れたので、この場を借りてお話させていただきます。
 
近年デザイナーは分野を飛び越えた取りくみが求められる職種になりつつありますクラシカルデザインが中心だった頃、それができるのはスターデザイナーであり、ごく限られた人たちでした。いまではテクノロジーフレームワークの進歩により、デザイン思考やコンピュテーショーナルデザインなど、デザイナーが関われる分野はさらに広がりを見せています。いちデザイナーの私もそれに応えたいという想いはありつつも取りくむ難しさを感じています。
 
今回は分野を飛び越える難しさを日頃感じているUIデザイナーの私が、オフィスデザインという普段とは異なる分野に取りくんだお話をさせていただきます。
 

これからの受け皿を設計する

 
実は先月9月上旬に、メドレーは古巣の新六本木ビルを離れ六本木グランドタワーへ本社移転しました。私が入社した去年3月時点から社員数が3倍近く増えたので、「社員数拡大のためのキャパシティ確保設備の充実」「組織の一体感の強化が主に取りくむべき移転の課題でした。その解決のためオフィス専門の設計チームが初期のアーキテクトと施工管理を担当し、空間の方針とデザインと什器のディレクションマエダと私が担当しました。
 
先だって取りくんだのは空間の方針です。この先のメドレーの姿を踏まえ、そこに到達するための受け皿となるように設計しました。
これまでメドレーは社会の公器としての意識を持ち、様々なサービスで医療課題に取りくんできました。それが今年で創業10年を迎えています。規模も拡大し、さまざまな人がこのメドレーに参画するようになりベンチャー企業としてだけでなく社会の大きな責任を果たす存在になりつつあります。
そうしたときの空間の役割としてこれまでのベンチャーマインドとこれからの大きな責任を背負う姿勢の両方を意識できる空間づくりが良いのではと考え以下のような方針を組み立てました。
 

可変と不変の両極を横断する空間

 
急速に成長するためのベンチャーマインドと、拡大していく社会の公器として責任を負う姿勢を同居させるために、ただ新しくするだけではなく、今までのメドレーはしっかり持ち合わせる。そしてそれを日々の業務のなかで行き来できるように空間の方針をつくり、デザインに反映していきました。もちろん制約が多く結果論的な部分はありますが、新しくするために全てを壊すのではなく、かといって古いものを大事に取っておくのでもない、これまでのメドレーらしさとこれからのメドレーの2つをポジティブに意識し設計しました。
 
執務室は旧本社ビルの雰囲気をそのままスライドさせつつ、先に挙げた3つの課題を取りくむため機能を拡張しました。もっとも変えたのはエントランスから会議室にかけてです。コーポレートカラーである赤色を一切使わず真っ白な空間にしました。執務室が変えない場所であるなら、ここは変える場所であり、自分たちを一度否定し絶えず新しくしていく場所として位置付けました。これから様々な人と出会える場所としても日々新鮮な気持ちになれると考えたからです。

f:id:medley_inc:20181010113717p:plain

f:id:medley_inc:20181010113722j:plain

f:id:medley_inc:20181010113729p:plain

f:id:medley_inc:20181010113741p:plain

f:id:medley_inc:20181010113736p:plain

 

空間デザインの独特な難しさと向きあう

 
この空間をつくるために、この分野特有壁と向きあいました。UIデザインでは扱わない大きなサイズ感や専門知識を総動員して出来上がる空間を想像する力など、普段の仕事にはない技術や感覚を求められることが多く、独特の難しさを痛感しました。
 
たとえばUIデザインでは簡単にモックアップがつくれますが空間で「よし試しに壁たてるか!」なんてことはできません。仮にモックアップをつくれたとしてもUIデザインほどの情報量で仕上がることはなく、そこは培ってきた経験と知識で補わなければなりません。専門のチームがいるので、えーい丸投げ!あとヨロシク!という考えも頭をよぎりましたが、決めるのは私の役目でもあるので、円滑なコラボレーションにするために、その人たちの知識や感覚に追いつくことは急務でした。
 
ここまでの話を聞くと体力的にキツそうと思われるかもしれませんが、実際は現業と通じる部分やデザイナーの感覚を分野を超えて持ち込める部分もありましたので、楽しみながら取りくめました。例えばデザイナーは物の形や色の違いに敏感な種族なので、それが2Dでも3Dでもすぐさま察知できます(程度にもよりますが)。図面と比較してわずかな壁の色や照明の輝度や色、わずかな目地のズレなどなど。
 
とはいえ異なる分野で直感1つで勝負できないのも理解しました。まだまだ学びは多そうですが、この取りくみを日頃の仕事にフィードバックしたいと思います。下記は取りくみを通して同じような機会を得たときに使える備忘録としてまとめてみたポイントです。
 
  • 考えかたの定型化 - 何かをデザインするのなら進め方に違いはないはず。違っても自分の考えかたの型をもとにカスタマイズする
  • 礼儀としての知識 - よほど未開拓でない限りその分野の専門家がいるはず。コラボレーションするために最低限の礼儀として知識は身につける
  • 違和感を無視しない - その分野の常識や知識のインプットが追いつかなくても、そこで感じる引っかかりを共有することで周りが立ち返れる
 

分野が違うだけで別世界ではない

 
今回は分野を変えることで発見したことや取りくみを、オフィスデザインを通してお話させていただきました。振りかえると備忘録の3つや思考の整理の方法など異なる分野だとしても、自分の専門分野での大事な考え方と共通する部分も多く感じました。もちろん全ての分野を渡り歩いたわけではないので当てはまらない場合はありますし、先述したように独特の難しさもあります。ただこの3つがどんな分野でも飛び越えれる最初の道具のうちの1つにすることで、良いスタートが切れるのかなと私は感じています。
 
異なる分野は今までの常識が全く通用しない別の世界ではないことを意識しながら、新しいことに挑戦していきたいと思います。
 
ここまで読んでいただき、ありがとうございました!
 

さいごに

 
メドレーが向き合う医療の課題は複雑です。課題を解決するために1つの分野からの一点突破もありますが、多角的な分野からの突破もおこなっています。デザイナーでありながら、エンジニアでありながら、様々な分野に横断しスイッチでき課題解決に向き合うことができます。もしご興味のある方はぜひご連絡ください。
 

松江で再びサテライトオフィス体験してきました

お疲れ様です。開発本部の宍戸です。

9/11〜9/14まで、昨年もお世話になった松江市でお試しサテライトオフィス勤務を行ってきました。

昨年度は総務省のサテライトオフィス事業を利用する形でしたが、今年は松江市独自のプロジェクトとして実施されるとのことで再度松江市さんからお声がけをいただき、開発本部から3名でサテライトオフィス勤務をしてきましたので、その様子をレポートします。

オフィスの様子など

今回のサテライトオフィス勤務は、全日程を松江テルサ別館で行いました。昨年もお世話になったこちらの施設ですが、開発に必要なものは基本的にすべて揃っているので、到着してすぐに作業することができました。(なぜ真ん中の卓に全員座らなかったのか、仲悪すぎだろと言われたりしますが決してそういうわけではありません(真顔))

f:id:Layzie:20180928172848j:plain

(写真は、今回アテンドしてくださった松江市の土江さんの許可をいただき使わせていただきました)

 

こちら松江テルサ別館には、松江オープンソースラボというOSSに関する作業や交流のために提供される施設があります。今回伺ったタイミングでは、このラボのエリアは、区画を広げるべく現在改装中でした。

話を伺うと、松江市サテライトオフィスを構えるWeb系企業も徐々に増えつつあるようで、現地での勉強会の開催などに利用しやすいよう今回の改装を行っているとのこと。(大部屋として使ったり、分割して使ったりなどなどできるようにしているそうです)

働く環境だけでなく、コミュニティ支援についても市が積極的にサポートしていく雰囲気を伺い知ることができ、あらためてRuby Cityとしての気概を感じました。

松江の雰囲気など

松江への到着は初日のお昼頃でしたので、まずは前回も伺った八雲庵さんでお蕎麦をいただきました。松江城のお堀のすぐ近くにあり、歴史ある雰囲気が印象的でした。割子そばという、何段かに分けられたそばに直接つゆを注いで食べるスタイルが、こちらのメジャーな食べ方とのこと。

f:id:Layzie:20180928172901j:plain

お昼を食べた後、再びサテライトオフィスで開発をした後は、前回も伺った現地の居酒屋、根っこやさんで夕食をいただきました。

今回もコーディネーターの方のオススメで地酒の「王祿の渓」をいただきました。期間中はいくつかのお店を回りましたが、日本海の魚から宍道湖しじみ、そば、地酒などなど、美味しいものに困らない土地だなーという印象が強く、ごはん(とお酒)大好き人間の自分には魅力的なものばかりでした。

前回お邪魔した古民家風オフィス「松江城下」なども、お昼休憩の際に簡単に案内していただきましたが、すでにこちらはサテライトオフィスとして企業が契約をし、稼働を開始しているとのこと。(個人的にはちょっと中を覗いてみたかったので残念...)

松江城を囲むお堀も観光スポットの一つとのことで、最終日には松江城と共にこちらも体験させていただきました。仕事の疲れを史跡巡りで癒やされに来るのも良さそうです。

f:id:Layzie:20180914101033j:plain

定番のお参り

最終日には、プロダクトの成功祈願も兼ねて出雲大社にお参りに行ってきました。(出雲大社は医療の神様とも言われる大国主命をお祀りしており、その出自は因幡の白うさぎという古事記の一節とのこと。不勉強にてこのタイミングで知りました・・・)

当日はあいにくのお天気でしたが、雨の中濡れる静かな出雲大社も厳かな雰囲気があり、パワーを沢山いただいてきました。

f:id:Layzie:20180914134135j:plain

まとめ

以上、簡単ではありますが、松江市でのサテライトオフィスお試し勤務の様子を振り返ってみました。

昨年のブログでも書かれていますが、思ったよりも都内からのアクセスが良いなと思いましたし、街自体もおちついて静かなので(テルサ別館が駅前にあるのに、日中は電車の音くらいしか気にならない)、非常に集中して作業することができました。

また普段一緒に机を並べて作業しているチームから一時的とはいえ離れて作業をしてみて、隔離された環境での集中しやすさを得た一方、やはり面と向かってメンバーと話したほうが円滑なコミュニケーション取れるなということも改めて実感しました。(実際にこの期間中、東京のメンバーに色々動いてもらっていたのに、自分はリモートだったので、もどかしく感じる部分もありました・・・💦)

短期間ですが、個人ではなくスモールチームでリモートワークをしてみて良いところ、対面のほうが良いところを実際に把握できたのも収穫でした。

さいごに

今回のお試し勤務は、松江市役所の土江さんにコーディネートいただき、実現することができました。

スケジュール等々きっちり準備頂いたおかげもあり、滞りなく業務を行うことができましたし、ただただオフィスに籠もって開発をしていただけではきっとわからなかった、松江という場所の雰囲気なども知ることができました。土江さんをはじめ、今回お世話になりました皆様、改めて本当にありがとうございました!

ということで、簡単ではありますが、第二回松江市でのサテライトオフィスお試し勤務のレポートでした。

メドレーでは、エンジニア・デザイナーを絶賛募集中です。ご興味ある方は、こちらからぜひご連絡ください。

www.wantedly.com

iOSDC Japan 2018にメドレーが協賛しました

こんにちは、開発本部の高井です。

メドレーは、去年に引き続き8/30〜9/2に早稲田大学で開催された iOSDC Japan 2018(以下iOSDC)に協賛しました。 みなさんご存知かと思いますが、iOSDCは国内のiOSイベントの中ではtry! Swiftと並ぶ最大級のイベントです。 f:id:medley_inc:20180913175434p:plain

(オンライン診療アプリ「CLINICS」初期開発時からSwiftで実装しています)

CLINICS (クリニクス)

CLINICS (クリニクス)

  • Medley, Inc.
  • メディカル
  • 無料

メドレーは今回、シルバースポンサーとして協賛させていただきました。ブース出展はしていないのですが、スポンサー枠で私が参加してきました。

オープニングのスポンサー紹介は去年に引き続き今年も三石琴乃さんのナレーションでした!豪華です。

イベントの様子

ランチのお弁当はもちろん、朝はドーナツ、夕方のLTが始まるとビールが提供されるなど至れり尽くせりでした。セッションは最大4つが同時に進み、それに加えてアンカンファレンスと特定のテーマを設けたディスカッション企画などもあったのですが、進行も非常に円滑で非常に参加者の満足度の高いカンファレンスではないかと思います。 f:id:medley_inc:20180913175617p:plain

セッション

今年はSwiftのコンパイラのソースからSwiftの機能を解説するようなセッションがいくつかありました。去年はそのようなセッションはなかったのではないかと思います。Swiftがオープンソース化されて3年弱ぐらいになり、日本でもSwiftのコミュニティがより成熟しているように感じました。 また、機械学習などのキャッチーなトピックの話題が少し減って、業務上得た知見や設計についてなどの実用的なテーマが多かった印象でした。 特にViewの設計や実装についてのセッションが面白かったです。やはり、iOSアプリ開発ではView周りの実装が悩みの多い領域ですよね。

ここからは特に気になったセッションをいくつかご紹介します。

MicroViewControllerで無限にスケールするiOS開発

www.icloud.com

UIパーツごとにViewControllerを持たせて、一つの画面の異なる機能を複数のエンジニアで開発しやすいように実装しているというお話でした。一般的な画面単位でViewControllerを持つ実装だと、開発の人数を増やしてもコンフリクトやオーバーヘッドが発生しやすくなり、効率的に開発することが難しくなってしまいます。そのため、開発チームの規模をスケールさせることができないということからそのような方法を取り入れたそうです。

MicroViewControllerを採用したことによって、一つのアプリに20人のエンジニアを充てることができるようになったということでしたが、他にそんなところあるのでしょうか、、、という気がしないでもなかったです。ただ、数人でやっていてもコンフリクトはよく起こりますし(project.pbxproj、、、!)、機能を追加したり、削除したりということを素早く試行錯誤するのには良さそうだなと思いました。

また、ビルドの効率化のためにサンドボックスアプリを用意したり、ViewControllerのテンプレートを作って実装の効率化を図るなど開発効率向上の参考になる取り組みも多かったです。

バイス・OSバージョンの依存が少なく、メンテナンスしやすいビューを作る

ビューの実装について起こりがちな問題とその対処法について紹介したセッションでした。特にレイアウト崩れを防ぐために意識すべきポイントについて、具体的な失敗例を示しながら紹介されていました。

UIコンポーネントのサンプル実装(https://github.com/folio-sec/Folio-UI-Collection/tree/master/Folio-UI-Collection)も紹介されていましたが、CLINICSでも共通UIをコンポーネント化して使っているので、カスタムViewの実装方法やコンポーネントの粒度などが再確認でき、非常に有益でした。また、UIのユニットテストもあり、CLINICSではあまりUI関連のテストは書けていないので参考にしたいと思います。

宣言的UICollectionView

UICollectionViewに複数の種類がある場合(よくあります!)に、宣言的な実装をすることでコードの見通しをよくする方法を提案されているセッションでした。

自分もあのswitch文をまとめようとして、うまくいかなかった経験があるのでとても興味深かったです。すぐに自分たちのコードにも適用できる実用的なセッションでした。

あとはReactorKitのライブコーディングや差分検出アルゴリズムに関連する話がいくつかあり、ちょうど業務でReactorKitとRxDataSourcesを使っているので参考になりました。

差分アルゴリズムの原理について

UITableViewなどで変更があった箇所だけを更新するのに利用される差分検出のアルゴリズムについて解説したセッションでした。IGListKitやRxDataSourcesなどで使われているアルゴリズムAndroidのDiffUtilで利用されているアルゴリズムの原理について紹介されていました。ライブラリを使っていると、利用方法は分かるけど内部でどう動いてるかはあまり理解していないということも割とあると思いますが、その辺りを理解できると用途に応じて最適なライブラリを選択できたり、より効果的な使い方ができそうだと思いました。

5000行のUITableViewを差分更新する

差分更新のライブラリを使って多数行を更新した際に発生した問題の紹介とその原因を特定して、改善した内容についてのお話でした。採用していたライブラリはDifferでしたが、ライブラリごとの特徴やInstrumentsによるボトルネックの特定などが参考になりました。

まとめ

iOSDCは去年に引き続きの参加だったのですが、今年のセッションもどれも興味深く、勉強になることが多かったです。またセッションの裏で特定の技術についてディスカッションするコーナーができるなど年々充実してきているように感じました。また、LTのときの会場の一体感はとても良いなあと思いました。

f:id:medley_inc:20180913175405p:plain 弊社のアプリ開発でもそういった知見などを活かして開発していける仲間を引き続き募集しています! 興味がある方は、こちらからご連絡ください。

www.wantedly.com

Kotlin Fest 2018にメドレーが"ひよこスポンサー"として協賛しました

こんにちは、開発本部平木です。去る8/25に行われた、日本初(!!)のKotlinの言語カンファレンスであるKotlin Fest 2018に弊社は"ひよこスポンサー"として協賛させていただきました。

公式Twitterで紹介された様子

今回スポンサーチケットで参加させていただいたので、つれづれとレポートを書いてまいります。

メドレーとKotlinの関わり

なぜ、メドレーがKotlin Festに協賛したかというとKotlinを使ってAndroidアプリを作っているからになります。

オンライン診療アプリ CLINICSのAndroid版で使っています。 play.google.com

Androidで正式にKotlinサポートすることがアナウンスされてからできるところをJavaからKotlinに書きかえていっています。

方針としては、ムリに全部のソースをKotlinにするという形ではなく改修などで触ったソースで余力があれば書きかえるというスタンスでやっていますが、それでも現在50%弱のソースがKotlinになってい ます。

Kotlinで書いた場合にJavaよりも可読性や堅牢性が上がるというメリットを実感していたところ、今回のKotlin Festの開催を知り、協賛させていただいたという次第です。

イベントの様子

会場は東京コンファレンスセンター品川でした。自分は初訪問だったのですが、設備も充実しており良い会場だと思いました。

無限に出てくるかのようなコーヒー・飲み物とお菓子が大変ホスピタリティを感じさせます。

スポンサーブースも盛況で、なかでもYahoo! Japanさんのモブプロ実演や、CyberAgentさんのKotlinクイズなどが人気を集めていました。

f:id:medley_inc:20180903111847j:plain

M3さんのブースでいただいたロゴ入りじゃがりこ

f:id:medley_inc:20180903111902j:plain

セッション

セッションは2セッションが同時に行われるという形式でした。

自分が参加したセッションのみですが簡単な感想でご紹介します。

Kotlinで改善するAndroidアプリの品質

speakerdeck.com

JavaからKotlinへの書きかえを考えた場合のアプリの品質を主軸にしてメリットを紹介する…というものでした。

Effective Javaの中で紹介されている項目について、Kotlinではどうなるかという視点での紹介は大変興味深かったです。

KotlinはJavaよりもNull安全を始め、堅牢だというイメージがありましたが、こうしてJavaするべきであるという項目がKotlinでは言語仕様レベルで対応されていることが多いというのを目の当たりにすると、さらに頼もしく思えるというようなセッションでした。

Kotlinアプリのリファクタリングポイント

www.slideshare.net

既に存在するKotlinのコードをどのような指針でリファクタリングしていくかというセッションです。

「こういうときに書き方複数あるけどどうしよう…」という例ばかりだったので、自分達のアプリでもすぐに使えるような実践的なセッションでした。

中でもいかにMutableなプロパティを避けるかというフローチャートは、理路整然としていてこれからKotlinを書いていく上でかなり参考になりました。

Kotlin linter

speakerdeck.com

自分の場合、JavaScriptなどでもわりとLinterは興味がある分野だったのですが、KotlinのLintについて はandroid-lintしか使ったことがなかったので、紹介されているLinterの情報が参考になるセッションでした。

KotlinのLinterもやはりASTを触らないとオリジナルルールの設定ができないのかーなど普段触れていなかった知識が得られて有意義でした。

が、KotlinのASTはPsiViewerというIntliJ プラグインくらいしか対応してなさそうで、自分で設定するとなると若干つらそうだなという印象でした。

AST Exploreあたりで気軽に試せると良いですね。

Kotlinで愛でるMicroservices

speakerdeck.com

サーバサイドKotlinをMicroserviceでガンガン使用するために必要なエッセンスがまとまったセッションでした。

元々使っていたGoとの使いわけや、実際どのようなアーキテクチャで作って、デプロイや監視など運用をどのようにしているのかなどがコンパクトにまとまっていて大変分かりやすかったです。

サーバサイドKotlinは弊社で使う予定は現状まだ無いのですが、このセッションを見た限りミニマムに始めることが可能な感じに思えたのが収穫でした。

まとめ

Kotlinの日本初のカンファレンスでしたが、来場者もかなり多くKotlinエンジニアの裾野が広いなという印象でした(セッションによっては立ち見も出ていました)。

またセッションも初級から上級まで幅広く取り揃えられていたので、飽きることなく楽しめましたし、来年以降も続いていってほしいと思ったカンファレンスでした。

メドレーでは今後も色々なTechカンファレンスをスポンサードして参ります。色々な場所で、お会いできたらと思います!

HTTP Cacheで求人サイトのスピード改善を試してみた話

こんにちは、開発本部の楊です。メドレーの社内勉強会「TechLunch」で、前回は、Reactの基本を紹介しましたが、今回はHTTP Cacheで、医療介護求人サイト「ジョブメドレー」のスピード改善ができないか検討した話について、共有しました。

なぜHTTP Cacheについて話すことにしたのか

ジョブメドレーには、医療機関や保育園、介護施設などさまざまな事業所の求人が掲載されています。現在、14万を超える事業部の求人が掲載されており、かつ事業所側で求人原稿を修正することもできるため、求職者側が閲覧するスピードについては、いつも意識して改善に取り組んでいます。

その試行錯誤の中で、HTTP Cacheを使って改善する方法について、実現性など含めて検証することにしました。

今回は、あるページをモデルケースに試してみました。このページは、PCは平均250ms、モバイルは180msになっています。

f:id:medley_inc:20180824171410p:plain

ユーザが該当のページを見たときに「内容に更新がないときはブラウザ側のキャッシュを利用してスピードを最適化する」「ページが更新されていたら、最新の内容をすぐユーザへ反映する」という要件でスピード改善されることを目指すことにしました。

HTTP Cacheとは?

Google Developersでは以下のように説明されています。

ネットワーク経由で情報を取得するには時間もコストもかかります。レスポンスが大きいと、クライアントとサーバ間のラウンドトリップを何度も繰り返す必要があるため、レスポンスが利用可能となってブラウザで処理できるようになるまで時間がかかります。さらに、ユーザ側ではデータの通信コストが発生します。そのため、前に取得したリソースをキャッシュに保存して再使用できることは、パフォーマンスを最適化する上で非常に重要です。

この機能はほぼ全てのブラウザに対応しており、HTTPヘッダーでCacheControlETAGLast-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ソースの内容が毎回同じであれば、ブラウザは前回キャッシュした内容を読み込むようになります。

サーバレスポンスタイムの短縮

今回の対象ページの機能としてサーバ側では主に二つの部分で時間がかかります。

  1. DB、Redisなどで画面表示が必要なデータ取得、ロジック処理
  2. クライアントに返す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_inETAGの作成方法など色々調べて、最終的にfresh_whenで実現できましたが、運用していく上での課題については、引き続き検討して行きたいと思います。

Rails Developers Meetup 2018 Day 3 Extremeで弊社の宍戸が発表させていただきました

こんにちは、開発本部の平木です。

去る7/14(土)にRails Developers Meetup 2018 Day 3 Extremeというイベントが開催されまして、弊社の宍戸電子カルテとセキュリティガイドラインAWSと私というタイトルで発表させていただきましたので、レポートさせていただきます。 f:id:medley_inc:20180719114338j:plain

発表のきっかけ

昨年に引き続きRubyKaigi 2018で弊社CTOの平山がLTスポンサーとして発表させていただいたり、ブースに出展をしていたことがきっかけになり、Rails Developers Meetupの主催者である平野さん(@yoshi_hirano)から平山へ出演のお声がけをしていただきました。イベントからイベントへ関係がつながるのはとても嬉しいですね。

発表テーマについて

せっかく、お声がけしていただいたので、発表テーマも弊社の特色が出るものが良いであろうと決まったのが今回のテーマです。

4月に発表した電子カルテシステムCLINICSカルテですが、何度かこちらのブログでもエントリが上がっていますとおり、電子カルテというシステムを構築する上で、セキュリティはかなり重要なウェイトを占める要素になっています。

このセキュリティを担保するための指針として3省4ガイドラインという総務省経産省厚労省の各省庁から出ている4種類のガイドラインが出ています。

これを実際にAWSをベースとしたシステムを構築する際の一例としてCLINICSカルテでの構築例を発表しようということになりました。

発表スライド

当日の発表スライドはこちらになります。

speakerdeck.com

会場ではスライドの色見がすっごく色褪せしていたりしましたが、発表した中でのAWSのサービスは会場のみなさんの中でも、あまり馴染みがないものが多いようでスライドの写真など撮られている方もいらっしゃって、知見共有という意味で参考になった模様です。

やはりクライアント認証に関しては普段サービス開発ではそこまでは使われないので、興味を持っていただいたようで良かったです!

まとめ

弊社のような医療業界ではなくても、フルマネージドサービスでセキュリティを意識するような場面があればご参考になるかもしれないテーマをエンジニアの宍戸から発表させていただきました。

社内で貯まった知見で機会があれば、ぜひ公開していきたいと思います。

メドレーについて気になった方は、こちらからどうぞ。

www.wantedly.com

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

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

tl;dr

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

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

HPKIとは?

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

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

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

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

今回、HPKIテストカードを用いて調査を行いました。 f:id:medley_inc:20180712151907j:plain

調査環境

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

f:id:medley_inc:20180712152004p:plain 引用 ガイドライン

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

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

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

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

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

f:id:medley_inc:20180712152231p:plain f:id:medley_inc:20180712152245p:plain 引用 ガイドライン

EF.ODを読み込む

prog02.rb

# prog02.rb
require "smartcard"
require "openssl"

def puts_response(response)
  puts "status = %04X" % response[:status]
  puts "data = %s" % response[:data].map { |i| "%02X" % i }.join(" ")
end

def decode_asn1(response)
  data = response[:data].reverse_each.drop_while { |i| i == 0xFF }.reverse
  return if data.empty?
  OpenSSL::ASN1.decode_all data.pack("C*")
end

context = Smartcard::PCSC::Context.new
begin
  card = context.card context.readers.first

  [
    [0xE8, 0x28, 0xBD, 0x08, 0x0F, 0xA0, 0x00, 0x00, 0x03, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
    [0xE8, 0x28, 0xBD, 0x08, 0x0F, 0xA0, 0x00, 0x00, 0x03, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]
  ].each do |aid|
    # SELECTコマンドでアプリケーションを選択する
    apdu = [0x00,
            0xA4,
            0x04,
            0x00,
            0x10,
            *aid,
            0x00]
    card.transmit apdu.pack("C*")

    # EF.ODの読み出し
    apdu = [0x00,
            0xB0,
            0x91,
            0x00,
            0x00]
    response = card.transmit apdu.pack("C*")
    response = Smartcard::Iso::IsoCardMixin.deserialize_response response.unpack("C*")
    pp decode_asn1 response
  end
ensure
  context.release
end

EF.ODを読み込むとDER符号化されたデータが返ってきます。 これを OpenSSL::ANS1 モジュールで復号化すると、次に取得するべきEF識別子が分かります。

EF.ODのASN.1定義は以下のようになっているため、タグが4であるデータを読み込めば良さそうです。

CIOChoice ::= CHOICE {
  privateKeys          [0] PrivateKeys,
  publicKeys           [1] PublicKeys,
  trustedPublicKeys    [2] PublicKeys,
  secretKeys           [3] SecretKeys,
  certificates         [4] Certificates,
  trustedCertificates  [5] Certificates,
  usefulCertificates   [6] Certificates,
  dataContainerObjects [7] DataContainerObjects,
  authObjects          [8] AuthObjects,
}

prog02.rbを実行して実際に得られたデータ

[
 # 中略
 #<OpenSSL::ASN1::ASN1Data:0x00007f8b8e0ef7b0
  @indefinite_length=false,
  @tag=4,
  @tag_class=:CONTEXT_SPECIFIC,
  @value=
   [#<OpenSSL::ASN1::Sequence:0x00007f8b8e0ef7d8
     @indefinite_length=false,
     @tag=16,
     @tag_class=:UNIVERSAL,
     @tagging=nil,
     @value=
      [#<OpenSSL::ASN1::OctetString:0x00007f8b8e0ef800
        @indefinite_length=false,
        @tag=4,
        @tag_class=:UNIVERSAL,
        @tagging=nil,
        @value="\x00\x04">]>]>
 # 中略
]
[
 # 中略
 #<OpenSSL::ASN1::ASN1Data:0x00007f8b8d118df0
  @indefinite_length=false,
  @tag=4,
  @tag_class=:CONTEXT_SPECIFIC,
  @value=
   [#<OpenSSL::ASN1::Sequence:0x00007f8b8d118e18
     @indefinite_length=false,
     @tag=16,
     @tag_class=:UNIVERSAL,
     @tagging=nil,
     @value=
      [#<OpenSSL::ASN1::OctetString:0x00007f8b8d118e40
        @indefinite_length=false,
        @tag=4,
        @tag_class=:UNIVERSAL,
        @tagging=nil,
        @value="\x00\x04">]>]>,
 # 中略
]

どちらのアプリケーションも00 04がEF.CD(証明書オブジェクト情報)のEF識別子だということが分かります。

EF.CDを読み込む

prog03.rb

# prog03.rb
require "smartcard"
require "openssl"

def puts_response(response)
  puts "status = %04X" % response[:status]
  puts "data = %s" % response[:data].map { |i| "%02X" % i }.join(" ")
end

def decode_asn1(response)
  data = response[:data].reverse_each.drop_while { |i| i == 0xFF }.reverse
  return if data.empty?
  OpenSSL::ASN1.decode_all data.pack("C*")
end

context = Smartcard::PCSC::Context.new
begin
  card = context.card context.readers.first

  [
    [0xE8, 0x28, 0xBD, 0x08, 0x0F, 0xA0, 0x00, 0x00, 0x03, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
    [0xE8, 0x28, 0xBD, 0x08, 0x0F, 0xA0, 0x00, 0x00, 0x03, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]
  ].each do |aid|
    # SELECTコマンドでアプリケーションをapdu
    選択する = [0x00,
            0xA4,
            0x04,
            0x00,
            0x10,
            *aid,
            0x00]
    card.transmit apdu.pack("C*")

    # SELECTコマンドでEF識別子`00 04`を選択する
    apdu = [0x00,
            0xA4,
            0x02,
            0x0C,
            0x02,
            0x00, 0x04]
    card.transmit apdu.pack("C*")

    # READ BINARYコマンドでファイルを読み込む
    data = []
    offset = 0
    loop do
      apdu = [0x00,
              0xB0,
              (offset & 0x7FFF) >> 8,
              (offset & 0x00FF),
              0x00]
      response = card.transmit apdu.pack("C*")
      response = Smartcard::Iso::IsoCardMixin.deserialize_response response.unpack("C*")
      data.concat response[:data]
      break if response[:data].all? { |e| e == 0xFF }
      break unless response[:status] == 0x9000
      offset += response[:data].size
    end
    pp decode_asn1 data: data
  end
ensure
  context.release
end

prog03.rbを実行して実際に得られたデータ

[
 # 中略
 #<OpenSSL::ASN1::Sequence:0x00007ffdf99aaf70
  @indefinite_length=false,
  @tag=16,
  @tag_class=:UNIVERSAL,
  @tagging=nil,
  @value=
   [#<OpenSSL::ASN1::OctetString:0x00007ffdf99ab038
     @indefinite_length=false,
     @tag=4,
     @tag_class=:UNIVERSAL,
     @tagging=nil,
     @value="\x00\x16">,
    #<OpenSSL::ASN1::Integer:0x00007ffdf99aafe8
     @indefinite_length=false,
     @tag=2,
     @tag_class=:UNIVERSAL,
     @tagging=nil,
     @value=#<OpenSSL::BN 0>>,
    #<OpenSSL::ASN1::ASN1Data:0x00007ffdf99aaf98
     @indefinite_length=false,
     @tag=0,
     @tag_class=:CONTEXT_SPECIFIC,
     @value="\x05\x17">]>
 # 中略
]
[
 # 中略
 #<OpenSSL::ASN1::Sequence:0x00007ffdfa072308
  @indefinite_length=false,
  @tag=16,
  @tag_class=:UNIVERSAL,
  @tagging=nil,
  @value=
   [#<OpenSSL::ASN1::OctetString:0x00007ffdfa072448
     @indefinite_length=false,
     @tag=4,
     @tag_class=:UNIVERSAL,
     @tagging=nil,
     @value="\x00\x16">,
    #<OpenSSL::ASN1::Integer:0x00007ffdfa0723d0
     @indefinite_length=false,
     @tag=2,
     @tag_class=:UNIVERSAL,
     @tagging=nil,
     @value=#<OpenSSL::BN 0>>,
    #<OpenSSL::ASN1::ASN1Data:0x00007ffdfa072380
     @indefinite_length=false,
     @tag=0,
     @tag_class=:CONTEXT_SPECIFIC,
     @value="\x05%">]>
]
 # 中略

これで公開鍵証明書ファイルのEF識別子が00 16であることが判明しました。

公開鍵証明書を読み込む

prog04.rb

# prog04.rb
require "smartcard"
require "openssl"

context = Smartcard::PCSC::Context.new
begin
  card = context.card context.readers.first

  [
    [0xE8, 0x28, 0xBD, 0x08, 0x0F, 0xA0, 0x00, 0x00, 0x03, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
    [0xE8, 0x28, 0xBD, 0x08, 0x0F, 0xA0, 0x00, 0x00, 0x03, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02]
  ].each do |aid|
    # SELECTコマンドでアプリケーションを選択する
    apdu = [0x00,
            0xA4,
            0x04,
            0x00,
            0x10,
            *aid,
            0x00]
    card.transmit apdu.pack("C*")

    # SELECTコマンドでEF識別子`00 16`を選択する
    apdu = [0x00,
            0xA4,
            0x02,
            0x0C,
            0x02,
            0x00, 0x16]
    card.transmit apdu.pack("C*")

    # READ BINARYコマンドでファイルを読み込む
    data = []
    offset = 0
    loop do
      apdu = [0x00,
              0xB0,
              (offset & 0x7FFF) >> 8,
              (offset & 0x00FF),
              0x00]
      response = card.transmit apdu.pack("C*")
      response = Smartcard::Iso::IsoCardMixin.deserialize_response response.unpack("C*")
      data.concat response[:data]
      break if response[:data].all? { |e| e == 0xFF }
      break unless response[:status] == 0x9000
      offset += response[:data].size
    end
    cert = OpenSSL::X509::Certificate.new(data.reverse_each.drop_while { |i| i == 0xFF }.reverse.pack("C*"))
    puts cert.to_text
  end
ensure
  context.release
end

HPKIテストカードからDER符号化された公開鍵証明書データが取得できるので、OpenSSL::X509::Certificate.newインスタンス化できます。

上記のprog04.rbを実行すると下記のような出力が得られます。

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 13023 (0x32df)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=JP, O=Japan Medical Association, OU=Digital Certificate Center, CN=HPKI-01-HPKI_JV2-forNonRepudiation
        Validity
            Not Before: Aug 15 15:00:00 2017 GMT
            Not After : Aug 15 14:59:59 2018 GMT
        Subject: C=JP, CN=JMACombi20413/serialNumber=TESTC20413
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:94:dd:09:40:f4:58:f9:0f:ec:3a:ea:e3:47:33:
 # 中略
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Authority Key Identifier:
                keyid:44:E9:20:05:4D:6D:C4:B7:FA:4B:F0:1B:C6:EA:C8:D6:5B:16:22:F4
                DirName:/C=JP/O=Ministry of Health, Labour and Welfare/OU=Director-General for Policy Planning and Evaluation/OU=MHLW HPKI Root CA V2
                serial:02

            X509v3 Subject Key Identifier:
                9E:E5:71:59:1E:A7:FC:1E:4A:31:F8:7B:30:0B:E3:7F:05:3D:9A:40
            X509v3 Key Usage: critical
                Non Repudiation
            X509v3 CRL Distribution Points:

                Full Name:
                  URI:http://crl.pki.med.or.jp/repository/crl/crl-sign2.crl

            X509v3 Subject Directory Attributes:
                0402..(..B..1(1&0$."1 ...
*.............Medical Doctor
            X509v3 Certificate Policies: critical
                Policy: 1.2.392.100495.1.5.1.1.0.1
                  CPS: http://www.pki.med.or.jp/certpolicy/

    Signature Algorithm: sha256WithRSAEncryption
         84:ae:95:45:5e:e7:64:8b:0c:6e:20:5f:9f:1f:0d:5c:ae:4a:
 # 中略

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 12927 (0x327f)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=JP, O=Japan Medical Association, OU=Digital Certificate Center, CN=HPKI-01-HPKI_JV2-forAuthentication-forIndividual
        Validity
            Not Before: Aug 15 15:00:00 2017 GMT
            Not After : Aug 15 14:59:59 2018 GMT
        Subject: C=JP, CN=JMACombi20413/serialNumber=TESTC20413
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:c6:f9:06:26:58:5e:11:b7:12:f2:8a:3e:97:0a:
 # 中略
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Authority Key Identifier:
                keyid:62:12:93:82:DE:3C:D7:FF:A8:D3:63:01:D3:01:6A:AE:6C:3B:C0:D4
                DirName:/C=JP/O=Ministry of Health, Labour and Welfare/OU=Director-General for Policy Planning and Evaluation/OU=MHLW HPKI Root CA V2
                serial:03

            X509v3 Subject Key Identifier:
                45:2B:7B:B4:47:89:3D:6C:05:6D:82:4D:4C:C8:80:B8:B4:B0:89:81
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 CRL Distribution Points:

                Full Name:
                  URI:http://crl.pki.med.or.jp/repository/crl/crl-auth2.crl

            X509v3 Subject Directory Attributes:
                0402..(..B..1(1&0$."1 ...
*.............Medical Doctor
            X509v3 Certificate Policies: critical
                Policy: 1.2.392.100495.1.5.1.2.0.1
                  CPS: http://www.pki.med.or.jp/certpolicy/

    Signature Algorithm: sha256WithRSAEncryption
 # 中略

それぞれのアプリケーションから正しく公開鍵証明書が取得できました。

電子認証ガイドラインによると、電子認証に使用する証明書はIssuerのCN(Common Name)がHPKI-01-*-forAuthentication-forIndividualであることが定められているため、 使用したHPKIテストカードでは、電子認証に使用するアプリケーション識別子はE8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 02であることが分かります。 また、電子署名に使用するアプリケーション識別子はE8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 01であることが分かりました。

最後に

以上でガイドラインの「A.2.2 JIS X 6320-15 に従った PKI カードアプリケーションの検索と利用」にある「PKI カードアプリケーションの検索」まで実装できました。

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