OAuth2.0のclient_secretって本当に秘密鍵ですか?

OAuthをしているサービスをAndroid + PhoneGap経由で使いたくて調べて見ました。

そして、色々調べたり考えたりした結果、

client_secret ってそもそも秘密鍵にする必要なくね? 

という天啓を得たので、つらつらと書いてみます。secretって名前なのに秘密である必要がないなんて、わけがわからないよ。

間違ってる気がしてならないので、誰か指摘をしてください。マジで。

 

とりあえず、前提として自分のサービスの認証替わりに使いたいわけじゃなくて、純粋にそのサービスを使うのが目的。OAuthなサービスはサーバサイドで使ったことは何度かあるんだけど、クライアントサイドで使うという事で、扱いが困るのがclient_secret。名前の通り、秘密鍵として扱う必要があると思ってたんだけど、正直安全にクライアント側で管理する方法が無い。

公開している通常のソースからは外してビルド時に組み込むのだとしても、逆アセされたら終わりだし。原理的にセキュアじゃない方法というのは大変微妙。

 

調べてみたところ、さすがにその辺は当然考慮済みらしく、Googleだと下記のプロファイルがある

  • Web Server Applications
  • Client-side Applications
  • Installed Application
  • Devices

 

Devicesはブラウザが使えない場合に、他の端末と連携してOAuthを使う方法。今回は関係無いのでパス。Web Server Applicatoinsがサーバサイドでの利用。残り2つがクライアントサイドでの利用となる。Client-side ApplicationsとInstalled Applicationのユースケースの本質的な違いが今ひとつ理解できてないのだけど、前者は主にJSで実装されることを想定しているみたい。今回の焦点はこの2つ。
 
まず、Client-side ApplicationsとInstalled Applicationsのフローを整理してみる。

Client-side Applications(implicit grant flow)

  1. client_idとscopeとredirect_urlを含むURLで認証ページにアクセス
  2. 各サービスの認証
  3. client_idに紐づいたサービスへのユーザの承認
  4. 承認が貰えれば、redirect_urlにリダイレクト。この時、URLにaccess_tokenを含む
  5. JSでURLからaccess_tokenを取得
  6. access_tokenを付与して各種APIを利用
  7. 寿命が切れたら最初からやり直し
 

Installed Applications

  1. client_idとscopeとredirect_urlを含むURLで認証ページにアクセス
  2. 各サービスの認証
  3. client_idに紐づいたサービスへのユーザの承認
  4. 承認が貰えれば、authorization codeを返す
  5. authorization codeとclient_idとclient_secretを使用して、access_tokenとrefresh_tokenを取得
  6. access_tokenを付与して各種APIを利用
  7. 寿命が切れたらrefresh_tokenとclient_secretでaccess_tokenを再取得

と、だいたいこんな感じ。主な違いはclient_secretを使うかどうか。というか、Installed ApplicationsはWebServerと同じな気がする。

 

いくつかトークンが出てくるので整理。

名前寿命機密性役割Client-SideInstalled
client_id なし 公開 OAuthクライアントの識別ID 使用 使用
client_secret なし 秘密? client_idの正当性確認用 未使用 使用
authorization code あり 秘密 access_tokenとrefresh_tokenの発行用 未使用 使用
access_token あり 秘密 認証内容を示すトークン 使用 使用
refresh_token なし 秘密 access_tokenの再発行用 未使用 使用
 
先程のフローとこの表から、client_secretの主な役目はautorization codeやrefresh_tokenといった秘密鍵がclient_idに紐づくものであることの検証だと理解。
Client-Sideの場合は、その検証が行えないから、refresh_tokenを発行できない、と。
access_tokenは寿命があるから、流出してしまっても、大きな問題にはならないので、検証なしでも発行しているのかな。
 
Googleのドキュメントにも「the client_secret is obviously not treated as a secret.」とあるわけだけど、そのくせclient_secretを使ってるし、ネットで調べても少なくない数の人がアプリに埋め込んでるので、client_secretを公開したときの問題を考えてみる。
 
基本的に各機能の認証をもらう時点では、client_idのみでOK. つまりclient_secretは戻ってきた値が自分と対になるclient_idで作られたことを保証する程度の機能しか無い。
おそらく、これで防げるのは認証のなりすまし問題。client_idとclient_secretが公開されている場合、悪意のある第三者は自由に自分向けのaccess_tokenを発行可能。でも、他人のauthorization codeとclient_secretを自分のそれに置き換えるのは結構難しいのでは? 通信経路はHTTPSだし、その辺がクラックされてるなら、もはやどうしようもないだろう...
 
次に考えられるのが、認証の横取り。さっきと違うのは、攻撃者の知ってる情報を被害者に使わせるのではなく、被害者の情報を掠めとる方法に関して。
これに関しては、client_idとclient_secretを公開しても問題ない。code, access_token, refresh_tokenというサーバから直接返される値を適切に管理しさえすれば、アプリが元々知っているclient_idとclient_secretをどう使ったところで、access_tokenを復元することはできない。むしろ管理として重要なのはrefresh_tokenかな。これは期限が無いので、こいつを適切に管理する必要がある。
 
最後に考えられるのがアプリケーションの信用の横取り。悪意のあるソフトに自アプリのclient_idを使って対象サービスへの承認を取られた場合、client_secretが分かっていればaccess_tokenの取得まで可能。ユーザは、自分の信頼しているアプリだと誤認して、承認をしてしまうことが考えられる。でも、これClient-Side Applictionsの場合も同じな気がするな。悪意があるソフトが自端末に入れられていること前提なので、リダイレクトされるサーバが固定とはいえ、HTTPをキャプチャすれば抜けるレベルの情報だし。かつ、OSSの場合は潜在的にコピーしてウィルスを埋め込まれたものが配布されるリスクがあるわけでで、それを踏まえると、リスクが無いわけじゃないけど、従来よりリスクが上がるわけじゃない、ってのは言えるかな。
 

まとめ

というわけで、自分が考えた結果、client_secretは秘密鍵じゃないので、ソースコードに埋め込んで公開して問題なし! というアリエナイ結論に至ってしまったので、誰か指摘してください...
ほとんどの入門記事で問題提起すらもせずに気にせず埋め込んでるので、考慮済みで業界常識ってことでしょうか...