(本稿はKLab Advent Calendar 2016 の4日目の記事になります)
Travis CIはよく知られたCIサービスの一つです。読者の方々の中にも、個人的なプロジェクトのCIに利用している人は多いのではないでしょうか。一方で、設定ファイル .travis.yml
中に秘密情報を暗号化して記述できることはあまり知られていないかもしれません。
YAML中での暗号化のやり方はTravis CIのドキュメント「Encryption keys」にも書いてあるのですが、 travis encrypt
コマンドによりAPIトークンなどの秘密情報を暗号化して .travis.yml
中に記述するような仕組みになっています。この情報はTravis CI側で復号されてCIプロセス中で利用することができます。
今回指摘する内容は、この暗号の強度が多くのプロジェクトにおいて不足しているのではないかという点です。というのも、2015年4月以前に作られたTravis CIプロジェクトではRSA 1024bit鍵による暗号化が利用されているのです。
本稿ではTravis CIの暗号化の仕組みを簡単に説明した上で、どういうときに危険性があるかの詳細と鍵ペア再生成の方法を紹介します。
まずは travis
コマンドを用いた暗号化について簡単に紹介します。 travis
コマンドというのは gem install travis
でインストールされるRuby製のコマンドで、内部的にTravis CIのAPIを叩いて色々とよしなにやってくれる便利コマンドです。
そのサブコマンドの一つが travis encrypt
で、これを使えば任意の文字列を暗号化して .travis.yml
中に記述することができます。たとえば、Travis CI連携しているGitHubプロジェクト直下のディレクトリで下記コマンドを実行してみましょう。
$ travis encrypt 'FOO=secret_information'
Please add the following to your .travis.yml file:
secure: "BEC97APcjoBsKRRGS4DCcQoLCviHTzK88JxfEq0wDfJ4+kfuLktyXEbHbG6Ct9cP+KLnwxIDBamf0pgOS7iQGLLb5Irn00fn4JEBeHd6kyTXQbyuPSe/NffVceg5vq8RWPT8nlWzVHD3wtjJFWz/Ocm6q5RkqvOtLszwM1Nc0Ig="
上記コマンドで出力された行を .travis.yml
中に書けば FOO=secret_information
と書いたのと同じ意味になります。この仕組みにより、APIトークンやメールアドレスなどをYAML中に記述して外部サービスとの連携に使うことができます。
さて、この暗号化はどのような仕組みなのでしょうか? travis
コマンドの中身を見ると、その正体がRSAを利用した公開鍵暗号であるとわかります。具体的には、travis encrypt
コマンドが公開鍵で暗号化を行い、暗号文を受け取ったTravis CIが秘密鍵で復号するような仕組みになっています。
この暗号化に用いられるRSA鍵ペアの生成はTravis CI内部で暗黙に行われており、秘密鍵はTravis CIだけが持っています。つまり、公開鍵で暗号化した文字列は暗号化した本人でさえ復号できず、Travis CIだけが復号できるというわけです。
言い換えると、この暗号化を信用するためのポイントは2点だと言えます。
これさえ守られていれば、暗号化された情報は安全だと言えるでしょう。逆に上記のうちどちらかの問題が発生するようだと、秘密情報を .travis.yml
に平文で書いたのと同じということになりかねません。本当に大丈夫なのでしょうか?
実は2012年に少数のプロジェクトで1の問題が発生したことがあるようですが、今後は安全だと仮定するしかないでしょう。今回は2の問題について議論していきます。
今回指摘したいのは、RSA公開鍵への攻撃の可能性についてです。
まずはTravis CIの公開鍵にアクセスしてみましょう。リポジトリに紐付く公開鍵は下記のようにAPI経由で取得できます(参照:「Travis CI - API Reference - Repository Keys」)。URL中の hnw/php-build
というのがユーザー名/リポジトリ名になっています。
$ curl -s https://api.travis-ci.org/repos/hnw/php-build/key | jq -r '.key'
-----BEGIN RSA PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOQb++oR7aBL6TfjSZbo/ssNrE
sV9FJmOn5TZktfAgLFv7T5c93Iot1k6ha7OO0FaZyf67bR+5Nou4Vd4SaiFpvb38
NMj4Pz9Smdwi3pWisqcgZaQOOpe9IB0nTAGhzZp8+2EPC1syRUi30FXOD03xnL0q
X8rhgIkuD6415tGP3QIDAQAB
-----END RSA PUBLIC KEY-----
$
ところで、この公開鍵は短そうに見えますよね。実はこれが1024bit鍵です。RSA 1024bit鍵の素因数分解は現代のスパコンでもかなり手強い計算ではありますが、コンピュータの性能向上により5年後なり10年後なりには現実的に攻撃可能になると考えられています。「暗号の2010年問題」の頃に2048bit以上の鍵への移行が叫ばれたことを考えても、1024bit鍵の寿命が近いのは間違いないと言えるでしょう。
ちなみに、鍵ペアが1024bitになっているのは一定以上古いプロジェクトだけです。最近のプロジェクトでは4096bit鍵が利用されていますので、今後プロジェクトを作る分にはこの問題はありません。正確な時期はわかりませんが、2015年4月頃に4096bit鍵に切り替わったように推測しています。
ご自分のプロジェクトの鍵ペアの鍵長を確認したい場合、上のようにAPIから公開鍵を確認すればわかります。公開鍵がASCIIで210文字くらいだったら1024bit鍵、730文字くらいだったら4096bit鍵ということになります。わざわざ鍵を確認しなくても、 travis encrypt
の結果の長さも鍵長と同じサイズになるので、そこからも判断できます。
鍵の中身を正確に把握したい場合は openssl asn1parse
や dumpasn1
などを試してみてください。
もしかすると短い鍵ペアを使っているプロジェクトが見つかったかもしれませんね。もしそうだとしても、鍵ペアを再生成するAPIが提供されていますので安心してください。ちなみに、現時点ではAPIを直接叩く以外の方法は提供されていません。
鍵ペアの再生成APIを利用するにはAPIトークンが必要です。これは travis login
コマンドでログインした後で travis token
コマンドを実行することで取得できます。このAPIトークンを下記のように Authorization:
ヘッダから送信すればAPIの認証が成功します。ただし、下記の hnw/php-timecop
はユーザー名およびプロジェクト名です。
$ curl -d '' -H 'Authorization: token abcdefghijkl1234567890' -s https://api.travis-ci.org/repos/hnw/php-timecop/key
API呼び出しに成功すると新たな鍵ペアが生成されます。失敗した場合はおそらくトークンの指定が間違っています(ヘッダ中の token
は消してはいけません)。元の鍵ペアが1024bitだった場合でも再生成すれば4096bit鍵になりますので、あと20年くらいは安心だといえそうです。
もちろん、鍵ペアを再生成した場合は改めて秘密情報の暗号化を行う必要があります。また、古い暗号文を遠い将来第三者が解読する可能性まで考えれば、暗号化対象のパスワード変更やAPIトークン再発行なども合わせて行うのが良いでしょう。
travis encrypt
による暗号化にはRSAが使われている本題とはズレますが、RSAを署名ではなく暗号化に使っている事例は比較的珍しい気がするので、その観点でも面白い話題といえるかもしれません。
@hnw
KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。
合わせて読みたい
KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。