自堕落な技術者の日記

基本は喰ってるか飲んでるかですが、よく趣味でカラオケ・PKI・署名・認証・プログラミング・情報セキュリティをやっています。旅好き。テレビ好きで芸能通

JavaScript

Google Open Source Peer Bonus Award受賞しちゃいました

夏休みの宿題みたいな感じで、この2ヶ月くらい、jsrsasignの大改造をし、証明書、CRL、OCSP、CMS SignedData、タイムスタンプの読み取りと生成のコードを大幅に書き直し、後方互換性をバッサリ切ったので、なんだかんだでバージョン10.0.0となりました。これで読み取りも、全部一貫して、生成もJSONでちゃちゃっと簡単にできるようになったかなと思います。

jsrsasignの保守はこれくらいでやめにして、最近は既存の証明書チェーンや、CRL、OCSPレスポンス、タイムスタンプトークンから、検証環境を自動で作るようなツールを作っています。普通だとCAごとに環境作ったり、OCSPレスポンダつくったり、TSA作ったり手間もリソースもかかり面倒くさいですが、一つのVM、一つのIPで、楽ちんポンで、これができちゃうようになるというもので、概ねベータバージョン的なものができ、QCだの、eSeal証明書だの、TSAだのいろんなテスト環境作って遊んでいます。

syoukin_dollar_man さて、そんな中、自分のオープンソース活動について、Googleの中の方に推薦頂き、2020年Q3の、Google Open Source Peer Bonus Awardという賞をもらい、賞金ももらってしまいました。(社内表彰みたいなのを除いて)このところ賞をもらうことなんて殆どないのでとても嬉しいです。ありがとうございます。頂いた賞金で、Mac Book Airのバッテリがヘタってきたので交換したり、家族で小さいお祝い会をしたいと思います。

GoogleのOpen Source Peer Bonusは、4半期に一度、Googleの社員の方の推薦でオープンソース活動について表彰いただけるというプログラムなのだそうです。元気のある会社はすごいですねぇ。過去には、2020年1Qの受賞者のアナウンスがこのブログエントリでされていました。今回の表彰もまとめてアナウンスしてもらえるのではないかと思います。 ちなみに、頂いた賞状はこんな感じです。↓↓↓


award

また、前回あんなエントリを書いたあと、私のオープンソース活動に、いろんな方が気にかけてくれ、お声掛けしてくれたり、寄付してくださったりしました。本当にありがとうございます。これを励みに、のんびり自分のペースで活動を続けていこうと思います。

追記 (2020.10.06)

公式ブログに「Announcing the latest Google Open Source Peer Bonus winners! (Monday, October 5, 2020)」が掲載されました。あざます。あざます。

jsrsasignの寄付金を募ることにしてみました(やりがいって何だっけ?)

私はjsrsasignというJavaScriptのオープンソース暗号、PKIライブラリを個人的な趣味で開発し公開しています。ところが最近、npmパッケージのダウンロードが月間60〜70万件と、異常にユーザーも増え、製品でも使われ始め、ちょっと厄介なことになっており、いろいろ悩んだ挙げ句、これが正解なのかもわかりませんが、ライブラリの維持のために寄付金を募ることにした次第です。今日は、心の吐露をつらつら書いていくことにします。

jsrsasignとは

2010年ごろ、スタンフォードの学生さんであるTom Wooさんという人のJavaScriptでRSA暗号化できるコードを見つけ、自分はPKIや電子署名を専門にしていたので「JavaScriptでRSA署名できたら面白いな」と思い、2010年6月に、ほんのRSA署名単機能のライブラリとして公開したのが jsrsasign です。当時のはしゃぎっぷりはブログでこんな感じ。 小躍りしてツイートしてたのはこんな感じ。




その後、ASN.1ライブラリ、X.509証明書、CRL、CMS SignedData、タイムスタンプ、CAdES-BES/EPES/T、OCSP、CSR、共通鍵暗号、楕円曲線暗号、RSA-PSS署名、DSA署名、JSONのJWS、JWT、JWKの対応など、自分が欲しいと思う機能を少しずつ建て増しをして10年になります。多くの個人のオープンソースプロジェクトは1年くらいで終わってしまうそうですが、まぁ、なんとか自分のために追加開発やメンテナンスを続けています。

このライブラリが特徴的なのは、

なんでも入っている
一つライブラリに多くのPKI、暗号の機能がつめこんであります。 Swiss Army Knife(スイスの十徳ナイフ)スタイルというそうです。 自分がものぐさなので、これをロードしさえすれば何でも試せるように作りました。
とにかく使い方が簡単
どんな秘密鍵でも公開鍵でも自動判定してロードできるようになっていたり、ASN.1 DER構造の中身に簡単にアクセスできたり、面倒くさいことはなるべくライブラリに任せてできるようになっています。証明書やタイムスタンプなどはJSONのプロパティから簡単につくることもできます。共通鍵暗号も簡単です。APIの構成はJavaのBouncyCastleライブラリIAIKライブラリなどのJCEアーキテクチャを参考にしているので、それを知っていればさらに使いやすいでしょう。自分がものぐさだから(二回目)、極力短いコードでいろんなことができるようになっています。
ドキュメント、サンプルが非常に豊富
ライブラリはあってもAPIドキュメントがなくてどんなAPIがあるのわからなかったり、BoucyCastleみたいにメソッドはわかるけど呼び出し方がわからないなどのことが無いようにAPIドキュメントの記述が充実しており、APIドキュメント中に例も入れています。また、Wikiなどの入門解説ページや、多数のオンラインでも試せるサンプルコード、テストコードがあり、自学習でもかなり何とかなると思います。APIが前提知識最小でも行けるように工夫してますので。
環境依存がほぼ無い、他のパッケージの依存がない
JavaScriptが動く環境であればどこでも動作します。古いブラウザでもほとんどの機能は動作しますし、Node.jsでも動きます。npmパッケージでも公開されていますし、CloudFlareなどのCDNからも利用できます。必要なサードパーティの関数は含まれており、他のライブラリ、パッケージに全く依存しておらず単独で使えます。Nodeの中でOpenSSLに依存していることもありませんし、(ブラウザ互換性でいつも悩まされる、この先どうなるかもわからない)W3C Web Cryptography APIを使っているわけでもありません。
いろんなツールがすぐ使える
自分でプログラミングしなくても、オンラインで公開しているツールで証明書を発行したり、鍵ペアを生成したり、デジタル署名の生成や検証したり、JWTを生成したり、いろんなことができます。Nodeで実装されたいろんな便利なPKI関連ツールもついています。
速いとは言えない
自分がPKI関連のいろんなことを少ないコードで簡単にテストするために作っていますので、高速に動作することを目的にしていません。数百万のトランザクションを処理したいといった目的なら他のライブラリを使う方がいいでしょう。
一人でここまでよくやったなぁ、、、と。お節介か、、、www

jsrsasignの歴史を振り返る

10年だもの、まぁ、いろいろありますよ。自分のためにちょっと歴史を振り返ってみます。

  • 2010年6月:v1.0:jsrsasign初版公開。RSA署名だけ。
  • 2012年4月:v1.2:公開リポジトリをGitHubに移行。
  • 2013年4月:v2.0:PKCS#1 PEMなどの鍵のロード機能がつき始める。 JavaライクなSignature、MessageDigestクラス、ASN.1エンコーダー、X.509証明書エンコーダー、RSA-PSSサポートなど
  • 2013年5月:v3.1:暗号化されたPKCS#5鍵のロードのサポート
  • 2013年6月:v4.0:ECDSA署名サポート
  • 2013年9月:v4.1:PKCS8など柔軟な鍵ロード用クラス KEYUTIL を追加
  • 2013年10月:v4.2:DSA鍵、署名のサポート
  • 2014年4月:v4.5:CMS SignedDataサポート
  • 2014年5月:v4.6:RFC 3161 Timestampサポート
  • 2014年7月:v4.7:RFC 5126 CAdES-BES/EPES/Tサポート
  • 2015年6月:v4.8:NodeJS npm、JSON JWS、JWK、JWTサポート
  • 2015年10月:v5.0:CSRサポート、便利なNode.JSスクリプトの追加
  • 2016年9月:v6.0:OCSPサポート
  • 2016年11月:v6.2:識別名のmulti valued RDN、RFC 2253 LDAP識別名文字列表現のサポート
  • 2017年6月:v8.0:コードのスリム化、長期間非推奨(Deprecated)だった関数の削除
  • その後、細かい機能追加や脆弱性指摘対応などが続き現在に至る

jsrsasignはどれだけ使われているか

自分の趣味でやってる調査のために開発したライブラリですが、Node.JSのパッケージ npm で公開した2015年あたりから利用者ダウンロードが増えています。github.comからのダウンロードや、勝手に行われている公開CDNからの利用もあると思いますが、npmパッケージのダウンロード数だけでいうとnpm-stat.comさんのダウンロード数グラフを見てみるに、2018年あたりからブリブリ増えてしまっており、2019年あたりからサチりはじめ、それでも最近の月間ダウンロードは60万件超、先月は70万を超えてしまいました。npmでは他のパッケージの依存関係を書けるのですが、2020年7月現在で379ものnpmパッケージのプロジェクトがjsrsasignに依存していることがわかります。とほほ。
jsrsasign-monthly-dl-202006
jsrsasignは自由に使ってもらっていいですが、ライセンスは放棄はしていないので、ライセンス掲載の義務はあり、たまにエゴサーチでPDFで検索してみると、いろんな製品の中でも使われちゃってることがわかります。

あとは、以前シャニマスのライセンス表示にjsrsasignが載っていたり、某社は使っているのにオープンにしていただいてなかったり、、、、(^^; こんな、個人が趣味で開発しているライブラリに、みなさん乗っかっちゃって大丈夫なんですかね。逆に心配になっちゃいますね。

ユーザが増えた悩みの始まり

最初は、「自分の調査のために趣味で維持しているライブラリがみなさんの役にたって、使ってもらえてるなら良いことだなぁ。これからも趣味で自分の必要そうな機能は追加していこうかなぁ、、、」で済んでたんですが、最近ユーザーも増えてそうも言ってられなくなってきたんです。

そもそも、jsrsasignは MITライセンスの下で無償、無保証で配布されています。もし、セキュリティ脆弱性やバグがあっても、私には修正する義務はないし(とはいえ、指摘されたら直すわけですが)、オープンソースなので問題があれば好きに修正して使っていただいていいわけです。もちろん修正コードをもらえれば、それは反映してすぐに公開します。

そんな中おきたThe RegisterやUsenixの指摘

2020年5月にITセキュリティのニュースサイトであるThe Registerで 「What happens when the maintainer of a JS library downloaded 26m times a week goes to prison for killing someone with a motorbike? Core-js just found out」という記事が掲載されました。JavaScriptのバージョンの違いを吸収するためのCore-jsという有名なライブラリがあって、その開発者がバイクで交通事故を起こして被害者の方が亡くなり収監されて、ソフトウェアのアップデートが滞っており問題だというものでした。

この記事の中で、私のjsrsasignも、2018年4月から2020年まで更新されてないと指摘をされてしまいました。

(記事引用)
Another JavaScript cryptographic library known as jsrsasign faces a similar challenge: its maintainer, Kenji Urushima, hasn't been active since April 2018. Programmers who use the software have expressed concern about the lack of communication and an unaddressed vulnerability, noting that 350 npm projects depend on the library, including some by Microsoft and Mozilla,
(抄訳)
jsrsasignとして知られている別のJavaScript暗号ライブラリも同様の課題に直面している。その管理者である Kenji Urushima は、2018年4月から活動していない。このソフトウェアを使用しているプログラマは、対話の欠如と対応されていないある脆弱性について関心を寄せている。注目すべきは、MicrosoftやMozillaを含む350のnpmプロジェクトがこのライブラリに依存しているという事である。
指摘された問題は、Minervaと呼ばれるECDSA署名のタイミング攻撃により処理時間を大量監視していれば秘密鍵が復元できる可能性があるというもので、元々、楕円曲線暗号の部分はBitcoin-JSを使っており、自分のコードではないので、調査するにしてもかなり時間がかかる根の深い問題でした。また、自分は本業の方も忙しい時期だったのでなかなか調査もできず、誰からも修正コードの支援をしてもらえないままでいました。jsrsasignは大量のトランザクションで使われることもあまりないし、攻撃の実現はかなり低いと考えていました。他の簡単に回答できるものには回答していましたが、根が深いのでこの問題は手付かずにしていたのです。

おそらくこの記事をうけて、USENIXという団体の機関紙のコラム「Who Will Pay the Piper for Open Source Software Maintenance? Can We Increase Reliability as We Increase Reliance?(誰がオープンソースソフトウェアの保守の費用を負担するのか?(オープンソースの)依存が増える中、信頼性を上げることできるのか?」の結論でも、jsrsasignを「(開発を)撤退したソフトウェア」として取り上げられてしまいました。

やっとMinerva脆弱性の修正

jsrsasignの根深い問題の調査まで手がまわらない状態が続いていたのですが、プライベートで慌ただしい時期がようやく終わりかかり落ち着きだした2020年4月、Node.JSのパッケージマネージャーを管理するnpmのセキュリティチームからもMinervaの脆弱性指摘が来てしまい、いよいよ直さざるをえない状況になりました。他の人が作ったコードを解析し、修正し、テストケースを作成して確認し、修正リリースを出しました。[勧告]

そしてまた別のMalleabilityに関する脆弱性指摘が3つも

2020年6月に Antonio de la Piedra さんから、3つのmalleability(展性)に関する脆弱性の指摘を受けてしまい、初めてUS-CERT、MITREの脆弱性データベースにCVE登録されてしまいました。3つのうちの1つは、npm のセキュリティチームからも対応依頼が来ました。「malleability(展性)」とは、金属を叩いて延ばしても破壊されずに広げることができる性質のことを言うのですが、ソフトウェアの世界では、同じ意味のデータを別の表現ができるという事をmalleabilityと呼んでます。例えば、10進数で数値の12345という値があったとして、先頭にゼロを幾つかつけて00012345としても値は同じであるといった事です。

暗号では、大きな数値を使って暗号文、署名値など表現することが多いですが、 指摘された3つの脆弱性は簡単には以下のようなものでした。

  • CVE-2020-14966 ECDSA署名の署名値が、前述のようなゼロを先頭に足した値を使ったものでも正しい署名として受け入れてしまう等。(CVSS3.X 7.5, CVSS2.0 5.0)
  • CVE-2020-14967 RSA暗号の暗号文が、前述のようなゼロを先頭に足した値を使ったものでも復号ができてしまう。(CVSS3.X 9.8, CVSS2.0 7.5)
  • CVE-2020-14968 RSA-PSS署名の署名値が、前述のようなゼロを先頭に足した値を使ったものでも正しい署名として受け入れてしまう。(CVSS3.X 9.8, CVSS2.0 7.5)
これが深刻な脆弱性にあたるかに関しては疑問があり、前述のようにゼロを足したとしても、別の署名対象文書を正しい署名として受け入れたり、暗号文の内容を別の内容に書き換えることができたりといった攻撃とは無関係です。また、大量にゼロを足した場合にメモリ破壊が起きると指摘されましたが、JavaScriptのBigIntegerでは、C言語のような桁あふれも発生しないので指摘にはあたらず、脆弱性を利用した攻撃は実現できないと考えています。vulndbのCVSSスコアはそれほどでもなかったですが、US-CERTのスコアはやたら高いです。

6月頃は仕事もプライベートも落ち着いていたので、週末や夜に対応の時間を取ることができ、問題の調査をし、コードを修正し、Antonioさんにもらったテストコードで問題なくなった確認し、2週間くらいで修正リリースし、セキュリティアドバイザリを公開しました。 [勧告1] [勧告2] [勧告3] かなり短期に対応できたかと思います。ちなみに、RSA-PSSはDaveさんが提供してくれたコードで、自分で解析して修正しなければならないので厄介でした。ECDSAについては、署名値が厳密なASN.1 DER構造であることをチェックしていなかったために起きた問題で、その厳密なチェックをする機能を追加するのにテストケースが多すぎて骨が折れました。

job_yarigai_sausyu_suit というわけで、オープンソースのライセンスで、現状有姿、無保証で提供するという条件で利用してもらっているにもかかわらず、結局、義務的に直さざるを得ず、問合せ対応や、報告者への対応、セキュリティアドバイザリの作成などしなければならない状況になっているのです。ソフトウェアの開発や保守でお金をもらっているわけでもないのに、とてもモヤモヤします。もちろん、本当に攻撃の影響を容易に受けてしまうような深刻な脆弱性については、何とか時間を作って急いで修正します。でも、「自分のために作ったライブラリを他の人が喜んで使ってくれるなら開発にかかったコストも要求せず、無料で、現状有姿で提供しましょう。」と言っただけなのに、多くの土日や夜まで潰して手厚いサポートまでタダで求めてくるのは「それは善意の搾取です!!!」とガッキーのように叫びたくなります。

同じオープンソースの暗号ライブラリでも、OpenSSLのように多くのメンバーが開発に関わっていて、修正パッチの提供も有志がやってくれるような状況なら、脆弱性指摘を対応する体制も作れますが、私一人で開発しているような状況なので、仕事やプライベートの都合でタイムリーに脆弱性対応できないケースがあります。そんな手厚いサポートを求められても困るのです。

私は、製品のセキュリティインシデントレスポンスチームであるPSIRTの仕事もしていたので、外部から脆弱性を指摘された場合に、しかるべき機関と調整しながら脆弱性対応する必要があることもわかっています。ただ、こうしたライブラリの場合は厄介で、よく使われる機能も、そうでない機能も様々取り揃えてライブラリを構成しているので、問題への影響が大きいのか小さいのかという判断をつけにくいです。そのため、リスクが高かろうが低かろうが「直せ」と要望が来てしまいます。なんか、段々、オープンソースを公開するのが面倒くさくなってしまいました。

そして寄付金を募ることにしました

「お金もらってるわけでもないのに文句言うな」とモヤモヤした気持ちを抱えたまま、jsrsasignを維持し続けるのもモチベーションが持ちそうにないので、ふと「寄付金」を受け付けてみてはどうかなぁ、、、と思いました。寄付金制度を導入すれば、どれだけの人がこの一人プロジェクトを応援してくれてるのかわかるし、「こんなに応援してくれてるんだから、ちょっと頑張らなきゃなぁ」という気持ちになるかもと思ったわけです。調べてみると、

  • (1) GitHubのスポンサー制度(毎月いくらとか設定して応援してもらえる。日本の口座もOK)
  • (2) Bitcoin、Bitcoin Cashなどの仮想通貨による寄付(自分の口座が放置されてた)
  • (3) Paypalによる寄付 (日本の金融機関ではできない)
  • (4) Open Collective (日本ではかなり敷居が高そう)
  • (5) Buy Me A Coffee (これも面倒くさそう。でも決済がGitHub Sponsorsと同じStripeだから可能性あるかな)
(1)は検索してみると日本の開発者の方も設定しているようで、いろんな方の設定内容を参考に設定することができました。(2)は、仮想通貨口座のアカウントの復旧、追加本人確認手続きが結構面倒だったんですが何とか復活させることができました。(3)はちょっと考えがあるので、そのうちチャレンジしてみようと思います。(1)の審査までの準備は、わからないことも多くて相当かかったのですが、審査申請して2週間と言われてたところを2日くらいで承認されました。(2)と合わせて、ようやく今日、READMEに寄付受付を書いてみました

今後、そのうちどうするか

と、まぁ、サポートや脆弱性対応のモチベーションは何とか寄付金で自分をだましだましやろうと思うんですが、自分もいい歳なので、自分が開発、サポートができなくなった時にどうするかですよね〜〜。急遽、長期入院などして何もできないことがあるかもしれませんし。GitHubでは、後継者を指定して何かあったとき、その人に管理を譲ることができる機能があるんですが、こんなわけのわからないソフトのメンテと問合せサポートを他人に押し付けるわけにもいかないので、多分自分が開発できなくなったら一代限りで終わりなんでしょうねぇ。ソースは全てリポジトリにあるので、引き継いでくれる人が世界のどこかから出てくるのかもしれませんが、完全にお任せで好きにしてもらうのが良いんじゃないかと思ってます。

ただ、自分に何かあったとき、「このプロジェクトは今後一切、メインの開発者がサポートできなくなりました。」と伝えてあげる必要はあるなぁと思っています。娘か知り合いに託すしかないのかなぁ。そうしないと、また The Registerの記事のような騒ぎになってしまうので、、、なんか、デジタル遺品とか終活ノートみたいな話ですね。

趣味でちょっと、好きなものだけ好きなように開発したかっただけなのになぁ、、、なんでこんなことになっちゃったのかなぁ、、、

こんなこと書いちゃってますが、開発はぼちぼち続けていきますよ。本人はいい加減な人間なので、そんなに気に病んでるわけでもないので、どうかお気になさらずに。 なんか、愚痴っぽい話につきあってもらってすみません。今日はこの辺で。

X.509証明書の識別名などで使われるMulti-valued RDNとjsrsasignのサポートについて

久々にちょっとPKI関連ネタです。いわゆるデジタル証明書(X.509証明書)には、主体者名(Subject Name)や発行者名(Issuer Name)に識別名(DN: Distinguished Name)を使います。例えば、

CN=yourname@example.com,O=example,C=JP
のようなものです。カンマで区切った一つ一つを相対識別名(RDN: Relative Distinguished Name)と呼んでいます。
O=example
一般的には相対識別名(RDN)は、「一つの」属性タイプと属性値のペア(AttributeTypeAndValue) より構成されます。
属性タイプ=属性値
O=example
ただ、「一般的には」と書いた通り、RDNについて複数のAttributeTypeAndValueを持つことも可能です。これをMulti-valued RDNと呼んでおり、プラス"+"記号でつないで以下のように表現します。
属性タイプ1=属性値1+属性タイプ2=属性値2...
CN=User1+serialNumber=123
Googleとかで「Multi-valued RDN」で検索するとわかると思うんですが、英語では結構あるのに、日本語で触れている記事って、自分のブログ以外みつからないみたいなんですよね。 今日は、拙作の暗号ライブラリ jsrsasign や OpenSSL を使いながら、証明書識別名のMulti-valued RDNや、識別名について掘り下げてみたいます。

エントリと識別名

LDAPや、その元となっているX.500ディレクトリサービスでは「エントリ」のツリー構造により情報を管理し、例えば会社、部門、社員は以下のように管理することができます。
図1
LDAPでは、あるエントリを特定するために「○×商事」の「総務部」の「佐藤二朗」さんという特定の仕方をします。エントリの名前、「総務部」や「佐藤二朗」という値は、属性タイプという型をつけることができ、組織名(O: Organization Name)、部署名(OU: Organizational Unit Name)、一般名(CN: Common Name)などのタイプがあります。
図2
例えば、営業の鈴木さんを特定するときに一番上までのエントリを辿って、以下のように表現します。これを「識別名(DN: Distinguished Name)」と呼びます。これにより他の部署のSuzukiさんとも区別できます。

CN=Suzuki,OU=Sales,O=MaruBatsu
識別名のうち、「OU=Sales」のようにエントリの丸の中を相対識別名(RDN: Relative Distinguished Name)と呼びます。

また、このエントリのツリー構造をDIT(Directory Information Tree)と呼びます。

Muti-valued RDNとは?なぜ必要か?

上記で説明した識別名(DN)で、同じ営業部に鈴木花子さんが二人いたらどうしましょう。一般名に区別するための数字を追加したり、追加の値として、社員番号やメールアドレスで区別することもでき、エントリを追加しても良いのですが、どれもイマイチ。
図3
そこで、一つのエントリに複数の値をつけて識別することもできます。これを Multi-valued RDNと呼んでいます。
図4
同性同名の人は多分いるでしょうから、社員番号やメールアドレスなど他の一意なものと組み合わせて管理するのはスマートな管理方法だと思いますし、一部の商用のディレクトリサーバー製品では、利用者数ベースでライセンス課金するために、エントリ数を使うものもありますので、Multi-valued RDNを使うことによってコスト削減を狙うこともできます。ただ、Multi-valued RDNは、すべての製品で使えるというものでもないので(例えば、とある製品のスマートカードとか802.1X認証とかで後になって問題になったことがありましたよね、、、)本当に使ってしまってよいかどうかは、アプリケーションと相談して決める必要があるでしょう。

識別名の文字列表現

識別名の文字列表現にはざっくり2つの表現があります。

CN=Matsuda Kenji,OU=Sales,O=MaruBatsu
/O=MaruBatsu/OU=Sales/CN=Matsuda Kenji
DITのツリー構造の下から順にカンマ","でつないだ方法と、上から順にスラッシュ"/"でつなぐ方法です。

カンマで逆順につなぐ方法はRFC 2253 Lightweight Directory Access Protocol (v3): UTF-8 String Representation of Distinguished Namesや後継の4514で規定されています。LDAPのアプリケーションソフトウェアでは一般的に使われている方法です。

もう一方の、先頭にスラッシュを付け、スラッシュで正順でつなぐ方法はOpenSSL compatフォーマットと呼ばれ、OpenSSLで標準的に使われるとともに、OpenSSL系のウェブサーバーであるApache HTTP Server、nginx、lighttpdなどの設定などで使われる方法です。

Multi-valued RDNの場合には、どちらの形式でも値をプラス"+"記号でつないで表現します。

CN=Matsuda Kenji+emailAddress=matsu@mb.com,OU=Sales,O=MaruBatsu
/O=MaruBatsu/OU=Sales/CN=Matsuda Kenji+emailAddress=matsu@mb.com
プラスで繋がれた値の表示順序については、特に決まりは無いと認識しており、以下のMulti-valued RDNでCNとemailAddressのどちらを先にしても良いはずです。これがどのようにASN.1でエンコードされるかは後で述べます。
CN=Matsuda Kenji+emailAddress=matsu@mb.com
emailAddress=matsu@mb.com+CN=Matsuda Kenji

次にCNやOUなどの属性タイプの文字列表現ですが、どのように表記しなければならいといった厳格な標準はなく、実装もバラバラであることがわかっています。8年前にXAdES長期署名に関連して、識別名の中の属性タイプの表記の実装状況について調査しており、その時にまとめた表を再掲します。
RFC2253テスト1属性タイプ名のテスト
X.509証明書プロファイルを定めたRFC 5280の4.1.2.4節 発行者名(Issuer)では、識別名の属性タイプとして対応しなければならない(MUST)リストと、対応すべき(SHOULD)属性タイプのリストが掲載されており、表中ではMUSTを黄緑、SHOULDを黄色、その他、証明書で実際に使われることのある属性タイプのリストを白とし、.NETや各種Javaベースの暗号ライブラリでどのように属性タイプが表記されるかをテストしました。表を見ればわかるとおり、結果はかなりバラバラです。また、S/MIMEのために使用される事があり、実際の証明書でもかなり含まれているemailAddressの属性タイプも、標準では実装を求めていないために対応にばらつきが出ているように思います。

今、見直してみると当時はなかったEV証明書用の以下の属性タイプも、今ならテストすべきだったかなぁと思います。

  • jurisdictionOfIncorporationL - 法人登録管轄地(市町村)
  • jurisdictionOfIncorporationSP - 法人登録管轄地(都道府県)
  • jurisdictionOfIncorporationC - 法人登録管轄地(国)

また、 カンマつなぎの識別名表記であるRFC 2253とその後継のRFC 4584の違いについて8年前の記事 でまとめており、仕様の改定によって、より識別名表記が一意になる方向に修正されていますが、 仕様の中で「RFC 4514は識別名文字列は一意にならない(=正規化しない)」という 事が明記されており、識別名文字列は、様々な表現が許されており、 単純な文字列比較では同じであるかどうかを判断できない事に注意しなければなりません。

識別名のASN.1定義と構造

次に、識別名が、ASN.1 DERエンコーディングにより、どのようにバイト列にエンコードされるのかを、 説明したいと思います。まず最初に、識別名のASN.1定義を紹介しましょう。 RFC 5280 4.1.2.4 Issuerより

// X.500名、識別名(DN)はRDNの並び(SEQUENCE) Name ::= CHOICE { rdnSequence RDNSequence } RDNSequence ::= SEQUENCE OF RelativeDistinguishedName // RDNは、AttributeTypeAndValue 1つ以上のSET // つまり、複数AttributeTypeAndValueがあってもよい。 // これが複数あれば Multi-valued RDN RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue // 属性タイプと属性値のペア AttributeTypeAndValue ::= SEQUENCE { type AttributeType, value AttributeValue } AttributeType ::= OBJECT IDENTIFIER AttributeValue ::= ANY // 属性値はANYと定義していながらも、DirectoryStringで // 定義されたいずれかの文字タイプを使用する DirectoryString ::= CHOICE { teletexString TeletexString (SIZE (1..MAX)), printableString PrintableString (SIZE (1..MAX)), universalString UniversalString (SIZE (1..MAX)), utf8String UTF8String (SIZE (1..MAX)), bmpString BMPString (SIZE (1..MAX)) }
つまり、
  • 識別名(DN)は、相対識別名(RDN)の並び(SEQUENCE OF)であり
  • 相対識別名(RDN)は、属性タイプと値(AttributeTypeAndValue)の集合(SET OF)であり
  • 属性タイプと値(AttributeTypeAndValue)は、属性タイプと値の並び(SEQUENCE)である
という事です。SEQUENCEとSETは構造型と呼ばれるASN.1プリミティブですが、
  • SEQUENCEは配列のようなもので、順序関係のある並びを表す際に使います。
  • SETは集合のようなもので、構成要素の中には特に順序関係はない場合に使います。
ついでに、SEQUENCEやSETと、SEQUENCE OF 〜、SET OF 〜の違いですが、
  • 単にSEQUENCEやSETとなっている場合には、構成要素のASN.1クラスが異なる場合に 使います。上の例ではAttributeTypeAndValueがそれに当たります。
  • SEQUENCE OF、SET OFとした場合、構成要素のASN.1クラスが同じ型の場合に 使います。上の例では、NameやRDNがそれに当たります。

それでは、例として以下の識別名をASN.1 DERエンコーディングしてみましょう。

CN=aaa,O=TEST,C=JP
RFC 2253の場合には、逆順でRDNが並ぶので、以下のようにエンコードされます。
302A SEQUENCE(30) OF -- DN 310B SET(31) OF -- RDN[1] 3009 SEQUENCE(30) -- AttributeTypeAndValue 0603550406 ObjectIdentifier(06) countryName 13024A50 PrintableString(13) "JP" 310D SET(31) OF -- RDN[2] 300B SEQUENCE(30) -- AttributeTypeAndValue 060355040A ObjectIdentifier(06) organizationName 0C0454455354 UTF8String(0C) "TEST" 310C SET(31) OF -- RDN[3] 300A SEQUENCE(30) -- AttributeTypeAndValue 0603550403 ObjectIdentifier(06) commonName 0C03616161 UTF8String(0C) "aaa"
ASN.1データはデータ型を表すタグ、バイト長、値データより構成され、上の例の最後の行では、0CがUTF8String型、03がバイト長(=3)、616161(=aaa)が値を表しています。

さて、次にMulti-valued RDNの場合にはどのようにエンコードされるのか、下の例を元に見てみましょう。ここでは、CN=aaaとCN=aの2つのAttributeTypeAndValueが使用されています。

CN=aaa+CN=a,O=TEST,C=JP
これをASN.1 DERエンコーディングすると以下のようになります。最後のRDNに注目してください。CN=aとCN=aaaと二つのAttributeTypeAndValuesがあることが確認できます。また、また、CN=aとCN=aaaでは、必ずCN=aが先に来ることにも注目です。
3034 DN 310B RDN[1] C=JP 3009 0603550406 13024A50 310D RDN[2] O=TEST 300B 060355040A 0C0454455354 3116 RDN[3] CN=aaa+CN=a SEQUENCE(30)が2つある 3008 ATV[1] CN=a CN=aの方が先に来ている 0603550403 0C0161 300A ATV[2] CN=aaa 0603550403 0C03616161
このRDN中のCN=a、CN=aaaの順序関係にはASN.1 DERとBERのちょっとした違いが関係があります。DERはBERのサブセットでなんですが、BERでは複数の表現が許されるのに対し、DERでは必ず一意な表現になります。その違いを表にまとめました。
ASN.1 DERASN.1 BER
概要ASN.1の一意なエンコード規則ASN.1のエンコード規則。DERのスーパーセットでDERであればBER
共通の特徴通信の世界では長い歴史のある、CPUや整数型のサイズに制限されない、巨大なデータも扱える、任意の構造化データを扱えるデータ表現。XML, JSONに比べコンパクト。
用途証明書、CRL、OCSP、RFC3161タイムスタンプS/MIMEデータ、CMS署名・暗号化データ、PKCS#12
比較必ず表現は一意。超巨大なデータでも長さが予めわかっていないといけないので、ストリーム処理など不向き複数の表現がある。超大きなデータでも取り扱い可能
SET要素のバイト列で昇順ソートするソートしなくて良い
BOOLEANTRUEのみ使え、FALSEは省略するようクラス定義TRUE、FALSEが使える
不定長表現長さ表現は一意で、予めデータサイズがわかっていないといけない。長さ表現で不定長表現が使え、長さを8000とした場合それは開始記号で0000が続くまで一つの要素であり、大きなデータも扱いやすい。
以上のような違いがあり、SETの違いによりMulti-valued RDNのSET OFの順序が決まっているわけです。

SETの要素は、各要素をASN.1エンコードしたときの昇順の辞書順でソートされ、ざっくり言えば、

  • 要素の短い物程先
  • 同じ長さなら属性タイプの長さが短い方が先
ということになります。例でみてみましょう。
3008 0603550403 0C0161 CN=a 300A 0603550403 0C03616161 CN=aaa ^^ 全体の長さLが08, 0Aの順になるので同じ属性タイプ長なら属性値の短い方が先 C,O,OU,CNなど主要な属性タイプはOIDの値が2.5.4.xになるので同一属性タイプ長
全体の長さが同じ時、
^^ 全体の長さは同じなら 3011 0603550403 0C0A6162636465666768696A CN=abcdefghij 3011 060B2B0601040182373C020103 0C024A50 jurisdictionOfIncorporateC=JP ^^ 属性タイプの値の短い方が先

OpenSSLのMulti-valued RDN対応

OpenSSLはMULTI-valued RDNに対応しており、"-multivalue-rdn"をつけるだけです。 例えば、既存の秘密鍵でワンライナーでMulti-valued RDNの自己署名証明書を作りたい時

openssl genrsa 2048 > a.prv
openssl req -new -key a.prv -x509 -subj /C=JP/O=Test/OU=b+CN=a -out c.cer -multivalue-rdn
Multi-valued RDNの証明書発行要求を作りたいとき
openssl req -new -key a.prv -subj /C=JP/O=Test/OU=b+CN=a -out c.csr -multivalue-rdn
となります。

jsrsasignのMulti-valued RDN対応

jsrsasignは、私が趣味で作ったPure JavaScriptによる暗号ライブラリでして、2010年ぐらいからボチボチ暇を見つけては昨日を追加しており、最初はRSA署名だけだったものが、ASN.1や証明書やタイムスタンプやJOSEなんか、自分が「欲しいな」と思った時に増築を繰り返しており、PKIやASN.1やJOSE(JWS,JWT,JWK)関係でちょっと試したいなと思った時に重宝しています。

ウェブブラウザ上でも、Nodeでも使え、APIドキュメントやサンプルも充実させているので、結構ユーザは世界中にいたり、最近はSONYや横河(や勝手にうちの会社(^^;)のハードウェア商品でも使われていることが発覚したり、Nodeのnpmパッケージは月間10万弱のダウンロードがあるようで、ホントありがたい話です。

JavaScriptの暗号ライブラリのAPIとしては、W3C Web Crypto APIなどあるんですが、モバイルブラウザでサポートしていないケースがあったり、古い暗号が使えなかったり、ちょっと書こうと思っても何行も書かなければいけなかったり、面倒くさいんですよね。そこで、jsrsasignでは、「なるべく少ない行数でやりたい事ができる」っていうのを目標にしていて、例えば鍵なんかは秘密鍵でも公開鍵でもPKCS#5でもPKCS#8でもJSON Web KeyでもなんでもKEYUTIL.getKeyに渡してしまえば適当に処理します。また、PCでもスマホでもNodeでも、多少古い環境でもJavaScriptさえ動けば使えるようになっています。また、APIドキュメントやチュートリアルの資料もできる限り潤沢に用意したつもりです。

割と最新の話まで入っている英語の入門スライドがあったり、
slidee
またちょっと古いですが、2013年にJNSAのWGでお話したjsrsasignとjsjwsが別の開発ラインだった時の入門スライド があるのでよかったら参考にしてください。
slidej

ドキュメント類は拙い英語のものしかなくて申し訳ないですが、問題とかあれば、Issueには日本語で入れて頂いて構わないので入れて頂ければと思います。

で、jsrsasignをMulti-valued RDN対応させたり、カンマ繋ぎDN対応したいなと思っていて、ようやく6.2.2をリリースした最近になってから対応させました。 例えば、Multi-valued RDNの識別名がどのようにASN.1 DERエンコードされるのかなんて話は、次のように確認できます。

% node > var X509Name = require("jsrsasign").KJUR.asn1.x509.X500Name; > new X509Name({str: "/C=JP/O=T1+CN=kjur"}).getEncodedHex(); '3027310b3009060355040613024a5031183009060355040a0c025431300b06035504030c046b6a7572'
あとは、証明書発行要求(CSR)を作ったり、
var rs = require("jsrsasign"); var kp = rs.KEYUTIL.generateKeypair("RSA", 2048); pem = rs.KJUR.asn1.csr.CSRUtil.newCSRPEM({ subject: {ldapstr: 'OU=T1+CN=example.com,O=Test,C=US'}, ext: [ {subjectAltName: {array: [{dns: 'example.net'}]} ], sbjpubkey: pubKeyPEM, sigalg: "SHA256withRSA", sbjprvkey: prvKeyPEM });
証明書を発行したりする時にもMulti-valued RDNが使えます。
var pem = KJUR.asn1.x509.X509Util.newCertPEM({ serial: {int: 4}, sigalg: {name: 'SHA1withRSA', paramempty: true}, issuer: {str: '/C=US/O=a'}, notbefore: {str: '130504235959Z'}, notafter: {str: '140504235959Z'}, subject: {ldapstr: 'OU=kjur+CN=kjur,O=b,C=US'}, sbjpubkey: kp.pubKeyObj, ext: [ {basicConstraints: {cA: true, critical: true}}, {keyUsage: {bin: '11'}}, ], cakey: kp.pubKeyObj });
割と融通が利くので、よかったら使ってやってください。

おわりに

というわけで長々、Multi-valued RDNや識別名(DN)のことでダラダラ書いてしまいました。ごめんなさい。誰かの参考になれば良いかな、と思います。

追記(2016.12.19)

あっ、誤解されないように書いておきますと、私としては、Multi-valued RDNを広めたいとか、使うべきだとか言うつもりは毛頭ありません。相互運用性が高い方向でインフラ設計するのが原則であり、使わなくて済むなら使わない方がいいでしょう。ただ、受け取ったとしても、びっくりしないでね、と、、、、w

関連記事

go.jpドメインのHTTPSサイトの状況について私もみてみました(2015年3月4日時点)

慶応義塾大学とレピダムさんで共同調査された「日本政府機関Webサイト(.go.jp)のTLS対応状況について(2015.03.04)」を大変興味深く拝見し、もうちょっと知りたいことも多々あったので、私も調べてみるかなぁと思い、今日はそのご報告を、と。

調査対象

.go.jpドメインのサイトには省庁、外局、独立行政法人、政府系のイベントで作られたサイトなどがあり、そのうちパブリックなサイトのSSLサーバー証明書の枚数は2015年3月4日時点で累計1,819枚のようでした。そのうち、ユニークなコモンネーム(FQDNもしくはワイルドーカード証明書のドメイン)の数は877ありました。
gojp-01
省庁、それらの外局、それらが所管する独立行政法人の数で分類すると以下のような構成になっています。(実はこの表を作るのが一番大変だった。証明書はあるからFQDNはすぐに集まるんだけども、FQDNの独法や局や委員会なんかがどこの省庁が所管しているのかとか、イベントサイトはどこで管理されているかなどをGoogle先生やブラウザで開いたりなんかして、ちまちま調べるわけです。いや〜、独法っていっぱいあるんすねorz)
gojp-02
独立行政法人を除いたものの比率は以下のようになっていました。これには最高裁判所、内閣官房、会計検査院、国会図書館なども含まれています。
gojp-03

ワイルドカード証明書を使っている場合には、そのワイルドカード証明書を使っている任意の1つのサイトが見つかれば、そのサイトを調査対象としました。

イベントで一時的に立ち上がってたサイトや停止したサーバーなどがあり、インターネットからアクセス可能なgo.jpドメインのHTTPSサイトは882中、722であり、 今回は722のgo.jpドメインサイトを調査対象としました。

SSL/TLSプロトコル

まず最初に、go.jpドメインのサイトのSSL/TLSプロトコルのサポート状況を見てみたいと思います。ダウングレード攻撃やPOODLE攻撃で問題となるSSLv2、SSLv3をサポートしているサイトがかなりあることがわかります。まず最初に、全ての*.go.jpドメイン、つまり省庁と独法を合わせた接続可能な722サイトに対して、対応プロトコルをグラフにしました。
gojpa-1
独法を除いた場合、333サイトが接続可能で、同様に対応プロトコルをグラフにしました。
gojpa-2
ダウングレード攻撃で脆弱なSSLv2はかなり少ないですが、POODLE攻撃で脆弱とされるSSLv3が使えるサイトは未だに3、4割のサイトで利用可能になっていることがわかります。

暗号スイート(共通鍵暗号)

次に、暗号スイート(CipherSuite)のうち、使用可能な共通鍵暗号アルゴリズムについて見ていきましょう。最初に全*.go.jpドメインに対してです。
gojpa-3
独法を除いた場合のグラフは以下の通りです。
gojpa-4
どちらもAESはほとんどのサイトで利用できるようになっており、弱い暗号である3DESやRC4も8、9割のサイトで利用できるようになっています。また、国産の暗号であるCamelliaも2割程度のサイトで使えますが、SEEDやIDEAも同程度しかないというのは少し残念ですね。

ブロック暗号モードについては、GCMとCBCしかありませんが、全*.go.jpでのサポート状況は以下の通りになります。
gojpa-5
また、独法を抜いた場合以下のようになります。
gojpa-6
ブロック暗号を一切サポートせず、ストリーム暗号の暗号スイート、つまりRC4しかサポートしないサイトは無かったため、全数は722、333で同じになっています。

暗号スイート(鍵交換)

次に、暗号スイートで使われている鍵交換についてみてみましょう。

まず最初に気になるのが、スノーデンの暴露したNSAの盗聴問題をきっかけに、PFS(Perfect Forward Secrecy)をサポートする暗号スイートを使うことを推奨されるようになりました。 具体的にはDH、DHE、ECDH、ECHDEのいずれかを鍵交換に使う暗号スイートが推奨されています。 PFS、DH、DHE、ECDH、ECDHEの*.go.jpドメインでのサポート状況は以下の通りです。
gojpa-7
独法を除いた場合、以下のようになります。
gojpa-8
DHEやDHは処理パフォーマンスが悪かったり、長い鍵長をサポートする実装が少ないので、ECDHやECDHEを使えるようにして欲しいですね。

DH、DHEの暗号スイートで不十分な鍵長の脆弱なサイトが無いか確認するために、DHの鍵長別にサポート状況をみてみましょう。まずは全*go.jpドメインで見てみます。
gojpb-01
独法を除いた場合、以下のようになります。
gojpb-02
RSA 1024bit以下の証明書が使えなくなったのと同様にDiffie-Hellman(DH) 1024bit以下による鍵交換をするべきではないそうです。ただ、DH 1024bit以下しかサポートしない実装が多かったり、デフォルトでDH 1024bitであったりすることから、リスク回避のためにDHやDHEを使わないようにするのが良いと思います。

ECDH、ECDHEでは鍵パラメータ(=曲線名)はどうなっているでしょうか。まずは、全*.go.jpドメインで見てみましょう。
gojpb-03
独法を除いた場合、以下のようになります。
gojpb-04
ECDH、ECDHEをサポートする場合に、鍵交換で使われる名前付き曲線がNIST P-256(=secp256r1)なのは一般的だし、ブラウザでサポートされている事が多いのでいいですが、NIST B-571(=sect571r1)って世の中で使ってるところを見た事がなかったのでビックリしました。省庁の1サイトと独法の9サイトは先進的というか、デフォルトでは設定されず、意図的にやってるに決まってるので凄いなぁと思いました。そこにブラウザで繋いでみたんですが、TLS_RSA_WITH_RC4_128_SHAで接続してしまい残念orz。

暗号スイート(メッセージ認証(MAC))

世の中では、メッセージ認証に関してはMD5はダメ、SHA1からSHA2に移行している過程にあるのかなと思います。まず、全*.go.jpドメインについて見てみましょう。
gojpb-05
独法を除いた場合、以下のようになります。
gojpb-06
脆弱なHmacMD5を使った暗号スイートが利用できるサイトが4割近く残っているのは問題かなと思います。

SSL/TLS攻撃に脆弱なサイト

BEAST攻撃、POODLE攻撃、FREAK攻撃など、SSL/TLSプロトコルや暗号スイートの設定による脆弱性の影響をみてみましょう。全*.go.jpドメインでのこれらの攻撃の影響は以下のようになっています。 (少しグラフにゴミが入ったけど、まぁ、いっか)
gojpb-07
独法を除いた場合、以下のようになります。
gojpb-08

その他のSSL/TLSサーバーの設定

SSL/TLSでは接続する際、デフォルトではクライアントが提示する暗号スイートのリストの優先順位に基づいて、サーバーと使用する暗号スイートがされますが、これだと酷いクライアントの場合、脆弱な暗号スイートが使用されてしまう可能性があります。これを防止するために、設定によりサーバー側の持つリストを優先して使うようにすることができます。これが「サーバー側暗号スイート優先」です。ApacheであればSSLHonorCipherOrder "on"で設定できます。

OCSP Staplingとは、TLSの拡張でプライバシー保護と証明書失効検証の誤りへの対策です。 まずは全*go.jpドメインで見てみましょう。
gojpb-09
独法を除いた場合、以下のようになります。
gojpb-10
OCSP Staplingの導入はなかなか進んでいない現状がよくわかります。ただ、先進的な設定をしている政府系サイトもあることがわかります。

SSLサーバー証明書

全*.go.jpドメインのアクティブなサイトのサーバー証明書の発行元の 証明書発行サービスで分類したのが以下です。
gojpb-11
独法を除いた場合、以下のようになります。
gojpb-12
やはりVeriSignの比率がかなり高く、GPKI Application CA、GlobalSign、Cybertrustなども頑張っています。特に独法を除いたには、GPKI Application CA 2の比率もかなり高いです。 珍しい所ではServision、KAGOYA、Firstserver、AlphaSSLなどを使っているところもありました。 822枚のうちワイルドカード証明書は19枚、EV SSL証明書は24枚でした。

次に接続可能な全*.go.jp 723ドメインにおける、証明書の公開鍵アルゴリズムと鍵長についてみてみましょう。全てがRSA鍵であって、ECC鍵やDSA鍵はありませんでした。また、鍵長は2048bitがほとんどで、ほんの少し1024bitが残っており、4098bitなどの長いものはありませんでした。
gojpc-01
また、723のRSA鍵について公開指数は全て65537(0x10001)でした。公開指数に3などが使われているために秘密鍵が入手できるといった問題があるサイトは0でした。
gojpc-02

署名アルゴリズムについてはSHA256withRSA、SHA1withRSA、MD5withRSAのいずれかしかなく、SHA256withRSAの移行が30%以上とChromeやWindowsのSHA1の無効化が2017年1月に迫っていることから、SHA1からSHA2への移行はかなり進んでいます。
gojpc-03

証明書の有効期間については、1年物、3年物、2年物の順に多く、10年物は1サイトのみでした。
gojpc-04

Google Chromeが2017年1月までにSHA1証明書に対して段階的に警告を出していき、SHA2証明書への移行を促すというマイルストーンがアナウンスされていますが、有効期限がどの時期であるかを調べてみました。
gojpc-05
有効期限が2017年1月を超える証明書が144サイトであり、Chromeでの警告表示を避けるためにSHA2証明書へのリプレースが必要になります。

また、証明書のOCSPによる失効検証、EV SSLサーバー証明書、Certificate Transparency(CT)のための組込みのSigned Certificate Timestamp(SCT)拡張のサポート状況について調べてみました。CTサポートが11サイトもあったのには、少し驚きました。
gojpc-06

おわりに

以上、*.go.jp ドメインのHTTPSサイトについて自分なりに調査してみました。SSL Pulseとの状況と比較して、以下のポイントで若干コンサバティブというか古い設定になっているなという感じはします。

  • GCMがあまり使われていない
  • ECDHEよりもDHEが使われる
  • EXP、DESなどの古い暗号スイートがかなり残っている
  • SSLv2などもかなり残っている感じがする
サーバーがきちんと対応してくれないなら、クライアント側で最新のブラウザを使うなどで自衛するしかないのかなと思います。

今日はこの辺で。

追記

  • 2015.03.11 08:34 - 3/4から3/11 0:00頃の更新を確認したらgo.jpドメインのHTTPSサイトは3つしか増えていないようです。そんなに頻繁に更新確認しなくてよさそうなので、ちょっと安心しました。現時点では証明書の調査項目が十分でないので、そのうち追加しておきたいと思います。
  • 2015.03.12 00:11 - *.go.jpドメインのSSLサーバー証明書について調べた事を追記しました。
  • 2015.03.12 07:55 - 証明書の調査でなぜ722から723に増えたかというと、HTTPS通信はできて証明書は取れるんだけど、対応暗号スイートを調べようとするとタイムアウトしちゃうサイトが一つあったため、こんなことになってます。

(小ネタ)4月10日(金)のJNSA PKI Day 2015でSSL/TLSの話をさせてもらいます

毎年恒例、日本ネットワークセキュリティ協会(JNSA)の主催セミナー PKI Day 2015「サイバーセキュリティの要となるPKIを見直す」に出させて頂きます。

http://www.jnsa.org/seminar/pki-day/2015/index.html

私は第2部の「SSL/TLS実装の今とこれから」で

  • 「SSL/TLS生誕20年、脆弱性と対策を振返る」
  • パネル:「SSL/TLSの実装が進むべき道を語ろう」
でお話をさせて頂きます。会場は「ヒューリックカンファレンス秋葉原」とありますが、浅草橋駅なのだそうです。 無料セミナーですので、よかったらお気軽に参加ください。

個人的には米丸先生の電子署名法の話と、小谷さんの自動車のPKIの話がとても気になります。JNSAさんから転送自由と書いてあったパートを載せておきます。


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     <JNSA PKI相互運用技術WG・電子署名WG主催セミナー>
   PKI Day 2015「サイバーセキュリティの要となるPKIを見直す」
       http://www.jnsa.org/seminar/pki-day/2015/
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

■ 日 時: 2015年4月10日(金)9時30分〜17時40分(受付開始9時10分)

■ 場 所: ヒューリックカンファレンス秋葉原 ROOM 1
      (東京都台東区浅草橋1丁目22-16 ヒューリック浅草橋ビル3F)
      http://www.hulic-hall.com/access/

■ 主 催: NPO日本ネットワークセキュリティ協会(JNSA)
      PKI相互運用技術WG・電子署名WG

■ 定 員: 132名

■ 料 金: 無料

■ 申 込: 下記URLページ文末の申込フォームよりお申し込みください。
       http://www.jnsa.org/seminar/pki-day/2015/

■ 講演資料:
 講演資料は上記URLよりPDFにて事前公開いたします。
 当日は講演資料の配布はいたしませんので、参加される方はご自身で
 ダウンロードいただき、当日お持ち下さいますようお願いいたします。

■ 開催趣旨:
 今日、サイバー空間におけるセキュリティの確保や信頼関係の構築にPKIは
 欠かせない技術になっています。また、サイバー空間の広がりとともに、
 IoT/M2M等の新しいPKIの応用領域も期待されています。
 その一方、社会基盤としてのPKIは、制度的な課題や、更には実装や展開等
 において様々な課題も浮上しています。
 PKI Day 2015では、以上のことを踏まえ、「サイバーセキュリティの要となる
 PKIを見直す」をテーマに、今後の社会におけるPKIの在り方を議論します。

                 ■■■ プログラム ■■■

9:30-9:40
【ご挨拶】「PKI day 2015のオーバビュー」
  セコム株式会社 IS研究所/PKI相互運用技術WGリーダー 松本 泰 氏

9:40-10:25
【基調講演】「サイバーセキュリティの状況とPKIの取組み」
       講師:東京工科大学 教授 手塚 悟 氏

◆第1部 新しい時代の電子署名◆

10:30-10:35
【第1部のプログラム紹介】
  三菱電機株式会社 情報技術総合研究所 宮崎 一哉 氏

10:35-11:15
【講 演】「欧州の動向-電子署名指令からeIDAS規則へ」
      講師:株式会社コスモス・コーポレイション 濱口 総志 氏

11:15-11:50
【講 演】「トラストリストと信頼のグローバル化」
      講師:セイコーソリューションズ株式会社 村尾 進一 氏

11:50-12:30
【講 演】「電子署名法改正のポイント」
      講師:神戸大学大学院法学研究科・法学部 教授 米丸 恒治 氏

              === 昼休み (12:30-13:30) ===

◆第2部 SSL/TLS実装の今とこれから◆

13:30-13:40
【第2部のプログラム紹介】
  セコム株式会社 IS研究所 島岡 政基 氏

13:40-14:15
【講 演】「SSL/TLS生誕20年、脆弱性と対策を振返る」
      講師:富士ゼロックス株式会社 漆嶌 賢二 氏, CISSP

14:20-14:55
【講 演】「Windows, Internet Explorerのセキュリティのいま」
      講師:日本マイクロソフト セキュリティプログラムマネージャー 村木 由梨香 氏

15:00-15:30
【パネルディスカション】 「SSL/TLSの実装が進むべき道を語ろう」
 モデレータ: セコム株式会社 IS研究所 島岡 政基 氏
 パネリスト: 富士ゼロックス株式会社 漆嶌 賢二 氏, CISSP
        日本マイクロソフト セキュリティプログラムマネージャー 村木 由梨香 氏

◆第3部 広がるサイバー空間に対応するPKIの新しい応用領域◆

15:50-16:00
【第3部のプログラム紹介】
  セコム株式会社 IS研究所/PKI相互運用技術WGリーダー 松本 泰 氏

16:00-16:45
【講 演】「RPKIの技術課題と信頼構造」
      講師:一般社団法人日本ネットワークインフォメーションセンター(JPNIC)技術部
          インターネット基盤企画部 セキュリティ事業担当 木村 泰司 氏

16:50-17:40
【講 演】「PKIの新しい活躍の場=繋がる自動車。そこで生まれる恩恵と脅威、それらへの方策」
      − PC/サーバでの10年のセキュリティ経験を踏まえた提案、標準化活動 −
      講師:富士通研究所 R&D戦略本部IPR戦略室シニアエキスパート、博士(工学)、
         TCG理事 小谷 誠剛 氏


(※)予告無く講演内容が変更される場合がございます。予めご了承下さい

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

SSL Pulseの統計情報で見るSSL/TLS (2015年1月版)

前にもお話した通り、 SSL Pulse (https://www.trustworthyinternet.org/ssl-pulse/)サイトは、 ssllabsでも有名なQualys社が運営しているサイトで、 Webサイト調査のAlexa社による 世界のアクセストップ20万サイトを対象にSSL関係の統計情報を毎月公開しています。 以前、2014年11月のSSL PulseでのSSL/TLSの状況推移をグラフ化しましたが、 2ヶ月たってどうなったのか、また今月もグラフ化してみました。

脆弱性対応の推移


pulse201501-vuln2
BEAST対応(CBC対応?)を除いて、全体的に順調に良くなる方向にあります。 悪い状態が上、良い状態が下になるようにグラフを統一したので、見やすくなったかと思いますが、どうですかね。

SSL/TLSプロトコルの推移


pulse201501-proto

SSLサーバー証明書の鍵長、署名アルゴリズムの推移


pulse201501-cert

新しい技術のサポートの推移


pulse201501-adv
このあたりは、どれも普及が10%未満のままだと、、、

関連記事

W3C Web Cryptography APIとの果てしなき戦い(第3回 動くサンプル1)

前回はW3C Web Cryptography APIの鍵生成、署名などについて説明しましたが、実際に動く実例がないとAPIの有り難みもわかないのかなと思いまして、とりあえず3つのサンプルを作ってみました。サンプルはChromeやFirefoxでは動くようになっています。画像をクリックするとサンプルページが開くようになっています。

ハッシュ関数


wc-hash
ハッシュアルゴリズムを選んで、ハッシュ計算したい文字列(UTF-8だから日本語も可能)を入力して、「ハッシュ計算」のボタンを押すだけで、16進数でハッシュ計算結果が表示されます。

公開鍵ペアの生成


wc-genkey
RSA公開鍵ペアを生成します。W3C Web Cryptography APIでは、鍵生成やインポートの際、どのような用途で、どのようなハッシュ関数と一緒に使用するかを指定する必要があるようで、生成時に選択するようにしていますが、PKCS#8 で秘密鍵や公開鍵をエクスポートする際には、まぁ、関係のない話になってしまいます。PKCS#8のバイナリデータ(ArrayBufferView)をPEM形式に変換するのにjsrsasignライブラリを使っています。

RSA鍵による署名と検証


wc-sigver
RSA鍵でUTF-8の文字列に対してSHA256withRSA署名アルゴリズムで署名と署名検証ができます。鍵はW3C Web Cryptography APIで内部生成した鍵ペアで署名と署名検証することもできますし、PKCS#8 PEM形式やJSON Web Key(JWK)フォーマットの秘密鍵や公開鍵をインポートして署名や署名検証することもできます。

サンプルの動作環境

これらのサンプルページは新しいChromeのWindows、Android、Mac OS X、Linux版、新しいFirefoxのWindows、Mac OS X、Android版で動作します。Safariでは一部がMac OS X版、iOS8+などで動作します。Internet ExplorerではPromise APIをサポートしていないので動作しません。IEでも動作するようにコード書くのはちょっと面倒で、それらの相互運用性を持たせるような実装の仕方については別途紹介しようかと思っています。

おわりに

とりあえず、いろんなブラウザでW3C Cryptography APIの動作を試してみることができるので、遊んでやってください。ソースコードはgithubに置いてあるのでご覧ください。FirefoxとChromeだけでも相互運用性持たせるのに結構壁にぶちあたったりして、その上、SafariやIEなども一緒にサポートするコードを書くのはかなり骨が折れるんですよね。でも、AndroidのFirefoxでも動くのを見ると、ちょっと目がうるうるしてきたりしますw。今日はこのへんで。

関連記事

W3C Web Cryptography APIとの果てしなき戦い(第2回 RSA署名生成と検証)

前回は、 W3C Web Cryptography APIについて、一番簡単そうなハッシュの生成を紹介しながら、 幾つかの課題について紹介しました。

今回は、W3C Web Cryptography APIによる、 RSA公開鍵暗号によるデジタル署名の生成と検証を紹介していこうと思います。

署名と検証の例題って、よく一人の人というかプログラムが、 鍵ペアを生成して、署名して、検証するサンプルを出しますよね。( sjclの楕円暗号のテストコードとか・・・) 一人の人が同時に署名と検証を行うってユースケースとしてありえないと思うんですよね。 実際、私が見た当時、sjclでは鍵オブジェクトの秘密鍵と公開鍵を 分けてエクスポートができないようで、実際問題使えないことがありました。

そんなわけで、今回はまず、ちゃんとOpenSSLで生成した秘密鍵と公開鍵をインポートして、 署名の生成と検証をできるようにしたいと思います。 その後で、RSAの鍵ペアの生成をします。

データの準備

W3C Web Cryptoで鍵生成してもよいのですが、今回はOpenSSLで作ったRSA 2048bitの 秘密鍵で署名することにしたいと思います。PKCS#5 PEM形式の2048bit RSA秘密鍵を生成し、 それを平文のPKCS#8 DER形式に変換し、 bin2hexスクリプト で16進数文字列表現に変換します。

% openssl genrsa 2048 > k2048.p5p.pem % openssl pkcs8 -topk8 -nocrypt -in k2048.p5p.pem -outform DER -out k2048.p8p.der % bin2hex < k2048.p8p.der > k2048.p8p.hex #16進数形式のRSA秘密鍵
鍵ペアとなる公開鍵について、同様に16進数文字列形式のものを取得します。
% openssl rsa -in k2048.p5p.pem -pubout -out k2048.pub.pem # PEM形式の公開鍵 % openssl rsa -in k2048.p5p.pem -pubout -outform DER -out k2048.pub.der #DER形式の公開鍵 % bin2hex < k2048.pub.der > k2048.pub.hex # 16進数形式のRSA公開鍵
次に"aaa"という文字列に対して署名するとして、この文字列が含まれる署名対象 データファイルを作っておきます。
% echo -n aaa > aaa.txt
OpenSSLで前述の秘密鍵と署名対象データを使ってSHA1で署名すると結果のデータは以下のように 作られます。同様に16進数データを作っておきます。
% openssl dgst -sha -sign k2048.p5p.pem aaa.txt > k2048oaaa.sig.bin % bin2hex < k2048oaaa.sig.bin > k2048oaaa.sig.hex
OpenSSLで前述の公開鍵と署名対象データと生成された署名値データを使って検証するには 以下のように行います。
% openssl dgst -sha1 -verify k2048.pub.pem -signature k2048oaaa.sig.bin aaa.txt Verified OK
署名値は正しいことがわかります。

秘密鍵のインポートと署名の生成

秘密鍵の16進数値を使って文字列"aaa"に対してSHA1withRSAで署名する場合の、 W3C Web Cryptoのコードは以下のようになります。

var prvHex = "308204bd02010030..."; // 前節の16進数秘密鍵 var prvUint8a = hextouint8a(prvHex); // 秘密鍵のUint8Array var aaaUint8a = asciitouint8a("aaa"); // 署名対象aaaのUint8Array // PKCS8形式の秘密鍵を署名用にインポート window.crypto.subtle.importKey( "pkcs8", prvUint8a, { name: "RSASSA-PKCS1-v1_5", hash: {name: "SHA-1"} }, true, ["sign"] ).then( // 秘密鍵インポートに成功したら署名する function(prvKey) { console.log("**importKey** 成功"); console.log("prvkey=" + prvKey); return window.crypto.subtle.sign("RSASSA-PKCS1-v1_5", prvKey, aaaUint8a); }, function(e) { console.log("**importKey** エラー: " + e); } ).then( // 署名に成功したら署名値(ArrayBuffer)を16進数表示 function(sigVal) { console.log("**sign** 成功"); console.log("sigVal=" + abtohex(sigVal)); }, function(e) { console.log("**sign** エラー: " + e); } );
実装で注意しなければいけないポイントは以下の通りです。
  • OpenSSLの(平文の)PKCS#8秘密鍵を使うにはimportKeyで"pkcs8"を指定する。
  • 鍵使用目的で["sign"]を指定する。
コンソールに"sigVal="で表示された16進数の署名値は、 k2048oaaa.sig.hexファイルの値と同じになっていると思います。 hex2binスクリプト で署名値をバイナリデータに変換して、以下のようにOpenSSLで検証することが できます。うまく検証できたでしょうか。
署名値16進数をk2048waaa.sig.hexとして保存 % hex2bin < k2048waaa.sig.hex > k2048waaa.sig.bin openssl dgst -sha1 -verify k2048.pub.pem -signature k2048waaa.sig.bin aaa.txt Verified OK
hextouint8aについてはこちらをご覧ください。

公開鍵のインポートと署名の検証

前々節のOpenSSLで作った署名値でもよいですし、前節で生成された署名値でも よいですが、これをインポートした公開鍵で検証してみましょう。 以下のようなコードで検証することができます。

var pubHex = "30820122300d0609..."; // 前々節の16進公開鍵 var sigHex = "afd36b6f3f2af788..."; // 前節or前々節の16進署名値 var pubUint8a = hextouint8a(pubHex); // var sigUint8a = hextouint8a(sigHex); var aaaUint8a = asciitouint8a("aaa"); window.crypto.subtle.importKey( "spki", pubUint8a, { name: "RSASSA-PKCS1-v1_5", hash: {name: "SHA-1"} }, true, ["verify"] ).then( function(pubKey) { console.log("**importKey** 成功"); console.log("pubKey=" + pubKey); return window.crypto.subtle.verify("RSASSA-PKCS1-v1_5", pubKey, sigUint8a, aaaUint8a); }, function(e) { console.log("**importKey** エラー: " + e); } ).then( function(isValid) { console.log("**verify** 処理成功"); if (isValid == true) { console.log("**verify** 署名検証成功(一致)"); } else { console.log("**verify** 署名検証失敗(不一致)"); } }, function(e) { console.log("**verify** エラー: " + e); } );
実装で注意するポイントは以下の通りです。
  • OpenSSLの公開鍵をインポートする際には"spki"を指定する。
  • インポートする際の鍵使用目的に"verify"を指定する。
  • 署名検証の結果はブール値(isValid)で返されるのでこれに従う。 関数が呼ばれただけで安心して終わりにしない。

RSA鍵ペアの生成

上の例ではインポートした秘密鍵、公開鍵を使っていますが、 鍵ペアの生成だってできます。(現状RSAだけのようですが・・・) RSA 2048bitの鍵ペアは以下のように生成します。

var paramKeyGen = { name: "RSASSA-PKCS1-v1_5", // 現状ではRSA-PSS, RSA-OAEPは指定できなそう modulusLength: 2048, // 鍵長 publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 公開指数 65537 hash: { name: "SHA-256" } // 署名のハッシュアルゴリズム?別に利用外のを指定しても良い? }; window.crypto.subtle.generateKey( paramKeyGen, // 生成する鍵ペアのための鍵長等の各種パラメータ true, // エクスポート可能にするかどうかのフラグ ["sign", "verify"] // 鍵使用目的 ).then( function(key) { console.log("**generateKey** 成功"); console.log("秘密鍵:" + key.privateKey); console.log("公開鍵:" + key.publicKey); }, function(e) { console.log("**generateKey** エラー: " + e); } );

Web Crypto APIの困った所5:importKeyの融通の利かなさ

こうしてこのブログをすんなり見ていただくと、「W3C Web Cryptoって簡単じゃん」と 思われるかもしれませんが、このような動く例を見つけるまで、かなりの紆余曲折があり 時間がかかっています。importKeyの正しく動く例というのが、仕様に記載されておらず、 他の方のサンプルも動くものあり、動かないものあり、何が正しいのかよくわかりません。 importKeyの引数には、

  • format - 鍵データの形式 (pkcs8, spki, jwk, raw)
  • keyData - 鍵データ (ArrayBufferView(Uint8Array)かJSONデータ)
  • algorithm - アルゴリズムのJSONデータ
  • exportable - エクスポート可能かのブール値
  • keyUsage - 鍵使用目的の配列 ["sign", "verify"] 等
を指定しますが、仕様がイケてないと思うのは
  • 鍵のデータ形式など指定させる必要があるのか。公開鍵か秘密鍵のバイナリかはASN.1構造を見ればわかる話。JWKだって区別はできる。
  • アルゴリズムの指定も必要性がよくわからない。pkcs8、spkiであれば、鍵アルゴリズムが何であるかわかるし、鍵のインポートの際に署名アルゴリズムやハッシュアルゴリズムを指定させる 意味がわからない。"RSASSA-PKCS1-v1_5"にするか"RSA-PSS"にするかは、鍵インポート時に決める必要がない。署名のハッシュアルゴリズムについても同様に決める必要がないのに、"MD-5"などサポート外のアルゴリズムを指定するとエラーとなる。JWKデータも同様に鍵アルゴリズムの指定の必要がない。
  • keyUsageの指定の必要性もよくわからない。例えば、RSASSA-PKCS1-v1_5で秘密鍵をインポートしたらkeyUsageはsignに決まっており、省略できることの方が多い。
  • JSON等指定の自由度が非常に高い割に、値の指定を間違えるとすぐにエラーとなり融通が利かない。省略可能やデフォルト値を持つ引数、パラメータがあっても良さそうだが、そのようにはなっていない。
  • algorithmのhashのパラメータ値でMozillaのテストコードでは"SHA-1"となっているが、仕様上は{name: "SHA-1"}となるのが正しいようでこれならChromeでもFirefoxでも動作する。前述のように必要の無いパラメータの指定方法が原因で実装により動作するもの、しないものがあり、相互運用性の問題が生じている。
そのような意味では、jsrsasignのKEYUTIL.getKey()メソッドでは入力は極力自動解析し最小のパラメータで動作するようになっていて、秘密鍵であろうが、公開鍵であろうが、JSON形式であろうが、鍵が暗号化されていようが、鍵アルゴリズムが何であろうが、許容範囲の広い実装となっています。

Web Crypto APIの困った所6:アルゴリズムサポート状況の不明瞭

前回紹介したCan I Use CryptographyのページでChrome等いろんなブラウザが フルサポートかのような記述になっていますが、W3Cの勧告候補でも特にどのアルゴリズムは 実装必須となっているわけではないようで、ECDSA、RSA-PSSなど サポートしていないアルゴリズムはかなり多いです。(IE11+ではSHA-1未サポートのよう) この辺りについてスペックがどうなっているのかブラウザベンダーから正式な開示が 無いようですし、仕様上も何をサポートしているかを知る術が特になく、 動かしてみて動かなかったら未サポートのような状況になっています。 そもそも、Java JCEやOpenSSLなどに比べたら、 アルゴリズムの数が圧倒的に少ないのですから、全アルゴリズムを実装必須(MUST) としても良いぐらいではないかと思います。 また、Java JCEではどのようなアルゴリズムをサポートしているかを知るためのAPIがあります。 ブラウザ毎のアルゴリズムや機能のサポート状況の比較は、 そのうち表などで比較できればと思っています。

Web Crypto APIの困った所7:サポートアルゴリズムの狭さと変なバランス感

W3C Web Crypto APIでは、 MD5、RSA暗号(RSAES-PKCS1-v1_5)など現時点で脆弱とされているアルゴリズムは除外されており、 サポートされているアルゴリズムはかなり限定的で狭いものです。 後方互換性や相互運用性のために使いたいというケースもあるでしょうから、暗号ライブラリとしては、 サポートしても良いのかなと思ったりもします。 その割には、例えばFirefoxではRSA鍵の鍵長が256bit〜7168bitをサポートしており、むしろそちらの方を制限すべきなのでは?とも思ってしまいます。 また、FirefoxではECDSAをサポートしていないのに、ECDHはサポートしているなど、同じECCの鍵生成なのに、このあたりのバランス感や優先順位も奇妙に思います。

Web Crypto APIの困った所8:鍵生成のパラメータ

例えばRSA鍵生成のパラメータですが、RSA署名やRSA暗号化のアルゴリズムを 指定しなければならないのかわからず、公開指数の指定の仕方も面倒で、 何故ハッシュを指定しなければならないのかわかりません。 また、これらのパラメータは省略は一切許されていません。 間違えるとエラーとなり、どこが間違っているのかに関するエラーメッセージは どの実装も非常に不親切です。

var paramKeyGen = { name: "RSASSA-PKCS1-v1_5", // RSA-PSS, RSA-OAEPなど指定する必要なくRSAで十分では? modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 整数65537でいいのでは?Uint8Array面倒 hash: { name: "SHA-256" } // hashが必須な意味がわからない };

高い相互運用性のために

一連のシリーズでは、最も汎用性が高くなるように、 window.crypto、window.crypto.subtleを使っており、 FirefoxやChromeでそのまま動く動作コードになっています。 これらに加え、IEやSafariなどいろんなブラウザで動かそうとする場合には、 以下のようなコード先頭に入れ、

var WC = null; var WCS = null; if (window.msCrypto) WC = window.msCrypto; // IE11+ if (window.crypto) WC = window.crypto; // FF34+,CH37+ and others if (WC.subtle) WCS = WC.subtle; // IE11+,FF34+,CH37+ and others if (WC.webkitSubtle) WCS = WC.webkitSubtle; // Safari 7.1+
window.cryptoの代わりにWCを、 window.crypto.subtleの代わりにWCSを使えば、 どこでも動く可能性が高くなるかと思います。

おわりに

今回は、RSA鍵ペアの生成、署名生成、署名検証についてみてみました。 今日はこんなとこで。

関連記事

W3C Web Cryptography APIとの果てしなき戦い(第1回)

あけましておめでとうござます。 ウェブ関連技術の標準化を進めているW3Cから W3C Web Cryptography API という勧告候補が 出てまして、このAPIを使えば公開鍵暗号、共通鍵暗号、鍵交換、鍵生成、 暗号化、署名、ハッシュ関数、擬似乱数なんかが使えちゃうのだそう。 Twitterの私のリプライに「ほとんどのブラウザがサポートしてるから (jsrsasignでも) 使いなさいよ」と海外から何名かの方がコメントしてくださるので、 重い腰を上げて勉強してみたんですが、「ムキ〜〜っ!!わけわからん! 標準化って何なの?相互運用性著しく低いしっ。そもそも、 このAPIってJavaScriptを書くプログラマにちっとも優しくないよね!」 と怒り心頭なんですが、 年も明けたことですし、そのあたりのことを愚痴っぽく、 ぼちぼち書いてみたいと思います。

結論から言うと「今すぐ、これを使うとヤケドするので、 実装がこなれてきたり、様々なラッパーやユーティリティが整備される まで使うのは1、2年はお待ちなさい」という事かなと思います。

APIはどれくらい使えるの?

いろいろなウェブブラウザの機能のサポート状況を 公開してくれているサイトにcaniuse.comという サイトがあるのですが、そこで W3C Web Cryptography API の主要なウェブブラウザ毎のサポート状況 が公開されています。


fig1
(出典: Can I Use Web Cryptography)
グレーで囲まれているのが2015年1月時点で現行バージョンのブラウザで、 緑はフルサポート、黄緑は一部サポートを示しているんですが、 IE、Firefox、Chrome、Safari、Operaやスマホなどのほとんどブラウザで APIをサポートしていると言っています。 世界のブラウザシェアでは51.39%、日本では58.17%が APIをサポートしているのだそうです。 この図だけを見たら、問題なく簡単に使えそうな気がしちゃいますよね。

ハッシュ関数の例

それでは早速、ハッシュ関数の例を見ていきましょう。 文字列"aaa"のSHA-1ハッシュ値を計算するとします。 細かい関数は後で紹介していくとして、ハッシュ計算の流れは以下の例の ようになります。

var textBuf = asciitouint8a("aaa"); // ハッシュ対象文字列aaaをUint8Arrayに変換 window.crypto.subtle.digest({name: 'SHA-1'}, textBuf).then( // SHA1ハッシュを計算 function(hashValue) { // 成功した場合に呼び出される関数 console.log("sha1(aaa)=" + abtohex(hashValue)); // 結果ArrayBufferを16進数に }, function(error) { // 失敗した場合に呼び出される関数 console.log("sha1(aaa)エラー: " + error); } ); #文末にasciitouint8a, abtohexのコードはつけておきます。
APIのハッシュ関数、暗号化、鍵生成などの関数は、乱数生成を除いて 全て "window.crypto.subtle" の名前空間に入っており、 ハッシュ関数は "digest" となっています。

ハッシュの入力はHTML5で導入されたバイナリデータを扱うクラスUint8Arrayなど、 ハッシュの結果はArrayBufferクラスで帰ってきます。例では文字列から Uint8Array配列にしたり、ArrayBufferを16進数で表示できるようにユーティリティー関数を 使っています。

APIの殆ど全ての暗号機能は非同期実行APIになっており、 関数やメソッドが実行されても、すぐには結果が帰ってきません。 ウェブブラウザで、ある処理を行っている間、表示が固まったりしないように 非同期実行にしてあるのだそうです。(後日、突っ込みますw)

JavaScriptで非同期実行するには2通りの方法があるらしく、 windows.crypto.subtle.digest関数は非同期実行オブジェクトを返します。

  • Promiseパターンを使う方法(thenを使う。WebCryptでは主流?)
  • イベント(oncomplete,onerror)を使う方法(IE WebCryptで主流?)

Promiseパターンによる非同期実行

Promiseパターン、もしくはDeferredパターンと呼ばるデザインパターンは、 非同期処理は遅延実行を扱うパターンなのだそうです。 上の例ではdigest()関数により、非同期実行のためのPromiseオブジェクトが 生成され、処理が終わった際にどんな関数を実行するか、エラーが起きた際に どんな関数を実行するかをthen()メソッドで定義しておきます。

var promiseObj = new Promise(); promiseObj.then( 処理が終わった時に実行される関数, エラー発生時に実行される関数 );
Promiseパターンでは、非同期処理するオブジェクトをthenでチェーンすることにより、 処理1、処理2・・・のようにシーケンシャルに実行させることも可能です。
var promiseObj1 = new Promise(); promiseObj1 .then( function(result) { // 処理1が成功したら実行される関数 ... return new Promise(); // 新しい処理2の非同期処理オブジェクト } function(err) {...} ).then( function(result) { // 処理2が成功したら実行される関数 ... return new Promise(); // 新しい処理3の非同期処理オブジェクト } function(err) {...} ).then( // 処理3 );
Promiseパターンの方が、後述のイベントによる非同期実行よりも、 thenチェーンなど使えて綺麗に書けるので Web Crypto APIでは、 こちらの方が主流の使い方となるのだと思います。 Promiseパターンについて詳しく知りたい場合には、以下を参考に されると良いと思います。

イベントによる非同期実行

IEのWeb Crypto APIのサンプルでは、digest()で非同期実行オブジェクトを 生成するところまでは一緒ですが、これに処理が完了した場合に実行される 関数を参照するイベントプロパティ oncomplete と、エラーが発生した際に 実行される関数を参照するイベントプロパティ onerror を定義することにより ハッシュ計算を非同期実行しています。

var data = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); var digestValue; var crypto = window.msCrypto; if (crypto.subtle) { var op = crypto.subtle.digest({ name: "SHA-256" }, data); op.onerror = function (evt) { console.log("op.onerror event handler fired."); } op.oncomplete = function (evt) { digestValue = evt.target.result; console.log("op.oncomplete event handler fired, digest computation complete."); }; } else { console.log("Unable to create window.crypto object."); } (出典:MSDN: Web Cryptography: digest method)
IE 11+のW3C Web Cryptography APIでは、Promiseパターンではなく、 イベントによる方法しかサポートされていないのだと思います。

Web Crypto APIの困った所1:非同期実行

ここから、なんだか愚痴や文句っぽくなってきますorz W3C Web Cryptography APIの困ったなぁと思うのは、 (処理が大して重くないのに)非同期実行しかサポートされていないところです。 非同期実行は、それを使い始めてしまうと以降の全ての処理を非同期実行 しなければならず、途中の一部だけを非同期実行することができません。
fig2

非同期実行は鍵生成して、鍵を保管して、署名するといった一連の処理を まとめて非同期実行させて、表示が固まらないようにしたいのであり、 個々の暗号プリミティブレベルで非同期実行したいわけではないと思います。 暗号プリミティブを非同期実行するかはプログラマに任せてほしく、 暗号プリミティブは同期実行の方がよかったのではないかと思います。

IEと他のブラウザとで非同期実行の記述に一貫性が無いのも面倒だと思います。

Web Crypto APIの困った所2:名前空間が統一されてない

Can I useサイトやMSDNの例を見て「あれ?」と思った人もいると 思いますが、Web Crypto APIの名前空間って実装によって統一されている というわけではないんです。

  • window.crypto.subtle - Firefox、Chromeなど
  • window.msCrypt.subtle - IE11+
  • window.crypto.webkitSubtle - Safari, iOS Safari
実装の中身がブラウザによっていろいろ違うので、 名前で分かれていてもいいのかなとも思いますが、 そもそもW3Cで「標準化」してるんですよねぇ?と。 酷い話だなぁと思います。

Web Crypto APIの困った所3:ArrayBufferによるインタフェース

ハッシュや署名の入力はUint8ArrayといったArrayBufferViewのサブクラスでなければ いけなかったり、出力はArrayBufferになっていたりと、 文末のasciitouint8aやabtohexなど見てもらえれば判る通り、 処理するのが結構面倒くさいです。 ファイルなど大きなサイズのデータを扱う際にはわかりますが、 ハッシュの結果となるハッシュ値なんか大した大きさでないのに ArrayBufferで戻されます。

Web Crypto APIの困った所4:多態性のなさ

ハッシュ関数の入力に関して、文字列でも、整数は配列でも、Uint8Arrayでも 「ひとつよしなに」ハッシュを計算してくれてもよさそうなもんですが、 そうした 多態性(polymorphism)は無くて、 入力はUint8ArrayのようなArrayBufferViewでなければなりません。 多態性はJavaScriptの大きなメリットの一つだと思うんですが、 W3C Web Cryptography APIを使っていると、JavaScriptから Cで関数を呼び出ししているような気分になります。 (入力の融通のきかなさは次回他にも紹介します。)

Web Crypto APIの困った所5:ドキュメントやサンプルの少なさ

解説記事やサンプルを紹介しているサイトもあるのですが、そもそも、例が少ないですし、 それが希望のブラウザで動作するかわからず、コードの相互運用性に本当に苦労させられます。 また、ブラウザがどの機能をちゃんとサポートしているのか、よくわからず、公開ドキュメントも ないので、それが自分のコードのバグなのか仕様なのかよくわからない事も問題です。

おわりに

というわけで、今回は導入として、W3C Web Cryptography APIの雰囲気とハッシュ関数の例に ついて見てみました。 不勉強なため、認識違いをしている所もあると思いますが、その際にはコメント等でご指摘いただければ と思います。 次回以降、鍵生成、署名、共通鍵暗号等を紹介したり、 勉強になるサイトを紹介していこうと思います。 ではでは。

関連記事

(おまけ)例で使ったabtohex、asciitouint8aの定義

// ASCII文字列をUint8Arrayの配列に変換 function asciitouint8a(s) { var buf = new Uint8Array(s.length); for (var i = 0; i < s.length; i++) { buf[i] = s.charCodeAt(i); } return buf; } // ArrayBufferから16進数文字列に変換 function abtohex(arrayBuffer) { var s = ""; var dataView = new DataView(arrayBuffer); for (var i = 0; i < arrayBuffer.byteLength; i++) { var hex = dataView.getUint8(i).toString(16); hex = "00".substr(0, 2 - hex.length) + hex; s += hex; } return s; }

ロシアの方が開発したJavaScript PKIライブラリPKI.jsをさわってみたぞ

私はJavaScript実装の暗号ライブラリ jsrsasign(注:RSAだけじゃないよ!) を開発などして遊んでいるわけですが、 そんな矢先、先週ぐらいですかねぇ、PKI.js(http://pkijs.org/) というJavaScript暗号ライブラリが公開されました。 ぶったまげたのはpure JavaScriptなのにOCSPやCMS SignedDataやTimeStampなんか に対応しているのだそうです。さらに凄いのはNIST PKITSのテストも通すせるような認証パス検証(証明書のチェーンの検証)機能も実装されているのだそうです。 先を越されて悔し〜〜〜って感じ。 ようやく時間ができたので、ちょっと動かしたりしてみたのでブログに書いておこうと思います。

WebCryptoを使っているのね

早速ダウンロードしてみて、サンプルには証明書の生成と、CMS SignedDataの生成があるんですが ブラウザで動かしてみようとするも動かない。ちょっと調べてみると W3C Web Cryptography API を使っているんですよね。 ええ?これってまともな実装あるんだっけ?と思ってTwitterでちょっと書いていたら 作者のYury Strozhevskyさんから連絡が来てメールでいろいろ質問をしてしまいました。 GlobalSignのgithubリポジトリなのでGlobalSignの人かとおもったら フリーランスのプログラマなのだそうです。 YuryさんはCAdESに興味があって ブログ記事書かれているみたいなのでそのうちPKI.jsがCAdESに 対応する日も近いのではと、、、

さて、WebCryptoの実装なんですが、普通のほとんどのブラウザではだめで Chromeのnightly buildを使えって言われました。 確認したところWebCryptoを今使うには方法は3つあるようで

  1. Chromiumを使って環境設定する
  2. Chrome nightly buildを使って環境設定する
  3. (Safari)WebKit Nightly Buildを使う
Chrome nightly/Chromiumの環境設定は以下のようにやればいいようです。(Yuryさんあざます。)
  1. アドレスバーでchrome://flagsを開く
  2. 次の4つを有効にする
    1. #extension-apis
    2. #enable-javascript-harmony
    3. #enable-experimental-web-platform-features
    4. #enable-devtools-experiments
  3. フラグ設定画面の一番したのボタンで再起動する
ChromiumのWebCrypto実装に関するIssue Trackerはここを参照すればよさそう。

早速動かしてみるぞ

githubで落としただけでは簡単に動かず、まぁ以下のようにやります。

  1. pkijs.orgからソースを落とす
  2. asn1js.orgからソースを落とす
  3. 解凍しasn1jsのorg/pkijs/asn1.jsをpkijsのorg/pkijs/にコピーする
  4. サンプルHTMLを解凍したpkijsの直下にコピーする(cd pkijs; cp examples/*/*.html .)
サンプルは以下があります。
  • CreateNewX509Certificate.html - RSA鍵ペア生成と証明書の生成
  • CreateCMSSignedData.html - RSA鍵ペア生成と証明書の生成とCMS SignedDataの生成
コピーしたHTMLをChromium等で開けば証明書のサンプルは動くんですが、 CMS SignedDataは生成されませんでした。コード追ってみると 以下のようにコメントアウトしてやればとりあえず動きました。
// in CreateCMSSignedData.html (line 341) //result_string = result_string + formatPEM(hex_coded);

48
ほら、ちゃんとPKCS#7 (まぁだいたいCMS SignedDataと同じ)署名データが できていますよね。 中身のぞいてみるとちゃんと内包形式のCMS SignedDataができてます。 証明書DNはBMPStringなんすよね〜〜。なんか、ロシアの人はBMPString好きですよね。

タイムスタンプを発行しようとするも

ちょっと格闘してタイムスタンプを発行しようとするも、 APIドキュメントがなかったり、タイムスタンプを発行するにも MessageImprintを作るのですら結構手間だったり、 TimeStampResponseに署名をしようとするようなちょっと メソッドがわかりにくかったりで一筋縄では発行できそうになく 心が折れてしまいましたorz

おわりに

W3C WebCrypto APIが一般のブラウザで使われるようになれば、 とても可能性が広がりそうなJavaScript PKIライブラリ PKI.js を さわってみました。とても悔しいのでjsrsasignでもCMS SignedData 対応をしようと思っています。

追記

PKI.jsのコードを眺めてみたりして気になった特徴をここにまとめておきます。

  • 現時点ではSHA1,SHA2(256,384,512)の証明書しか対応していない
  • 現時点ではRSA鍵しか扱えないようだ

(2014.05.11 23:14) 私がブログやTwitterで書いたことで、日本のプログラマの多くの方がYuryさんのPKI.js githubにLikeをしてくれたそうで、Yuryさんはとても喜んでくれました。ロシアからは何故かこのライブドアのブログが見えないそうで、どんな事が書かれているのか気になるので是非送ってほしいと言われてしまい、Google翻訳したページをメールで送ってあげました。YuryさんがCAdESの記事を書いていたのでETSIやECOMのCAdESの話を紹介してあげたら [1] [2] 喜んでくれました。

追記2(2014.05.12 23:49、2014.10.04更新)

簡単にPKI.jsとjsrsasignと比較をしてみましょう。

PKI.jsjsrsasign
暗号計算 W3C WebCryptoAPIを使ったネイティブ処理なので速いはず 各暗号ライブラリに依存するがpure JavaScript実装のため遅い
対応暗号アルゴリズム W3C WebCryptoAPIに依存しているが無いものも多い。現時点ではRSA, SHA1, SHA2のみ。 比較的豊富でRSA, DSA, ECDSA, SHA1, SHA2, MD5等幅広くサポート
APIドキュメント 現時点では無い 公開済
サンプル 公開されているが非常に少ない 公開済
テストコード 現時点では無い公開済
対応ブラウザ・動作環境 WebCryptoAPIを有効にしたChromium, Chrome, WebKit(Safari)等 開発用ブラウザのみ。スマートフォンは設定不能のため非サポート ブラウザ、動作環境をあまり選ばない。スマートフォンブラウザでも動作
Node.js対応 基本的にWebCryptoAPIはブラウザ必須なのでNodeは対応していないはず。 鍵生成/乱数生成に一部問題があるもののNode.jsに対応
証明書チェーンの検証機能 ありなし
RFC 3161デジタルタイムスタンプ ありあり(2014.05より)
CMS署名/暗号フォーマット あり署名のみ(2014.05より)
RFC5126CAdES長期署名 なしBES/EPES/T(2014.06より)
2014.10.04: Node.jsについて追記

最新記事
Categories
Archives
Twitter
記事Google検索

本ブログ内をGoogle検索
Yahoo!アクセス解析
Travel Advisor
記事検索
QRコード
QRコード
  • ライブドアブログ