質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.91%

JWTの署名検証をrubyではなくPHPで行いたい

解決済

回答 1

投稿

  • 評価
  • クリップ 1
  • VIEW 3,110

aiueoao

score 102

実現したいこと

Android向けのアプリを作成中で、セキュリティの為にGoogleのSafetyNetを利用したいと思っています。

こちらのサイトを参考にさせて頂き途中までは進められたのですが、rubyを使った署名検証のところでつまづいています。訳あってサーバー側はrubyではなくPHPで実装したい為、参考サイトの署名検証の処理をPHPに書き換えたいのですが上手く出来ません。

サイトを参考にして

  • SafetyNetのライブラリを使って端末の情報をGoogleサーバーに送り
  • GoogleサーバーからのJWT(JSON Web Token)を端末が受け取り
  • 端末が自前サーバー(Apache)にJWTを送る
    まで出来ました。

自前サーバーにて受け取ったJWTをLogに保存して、以下の事を試してみました。

  • JWTは"ヘッダ.ペイロード.署名" の順番にピリオド('.')で連結されているらしいので、ピリオドで分割
  • 分割したうちのペイロード部分をbase64_decodeした場合、正しいパラメータが表示されることを確認
  • ヘッダ部分をbase64_decode後にjsonDecodeすると

stdClass Object
(
[alg] => RS256
[x5c] => Array
(
[0] => 文字列
[1] => 文字列
)
}
となることを確認

  • 上記 x5cの各配列についてopenssl_x509_read ()など試してみましたがエラーが表示される [Warning: openssl_x509_read(): supplied parameter cannot be coerced into an X509 certificate!]
  • 署名部分をbase64_decodeした場合printでなにも表示されない

rubyのOpenSSL::X509::StoreのようなX509の証明書ストア(?)を作成して使う などといったことはPHPでは出来ないのでしょうか?
rubyは初めてなので調べながら進めていて、サンプルコードで理解出来ない個所も多々ありますがよろしくお願いします。

該当のソースコード

//参考にしたrubyコード
require 'jwt'
class InvalidLeafCertificate < StandardError; end
class InvalidCertificateChain < StandardError; end

X509_STORE = OpenSSL::X509::Store.new
X509_STORE.set_default_paths #X509_STOREにて、システムの証明書が格納されているパスを代入するメソッド

# 正しい署名の検証に成功すればtrue
# 不正なものがあればfalse
def improved_valid_jwt_signature?(jwt)

  jwt = jwt.gsub(/(\r\n|\r|\n|\f)/,"")
  ::JWT.decode(jwt, nil, true) do |header|


    # 証明書チェインのすべての証明書をインスタンス化
    certs = header['x5c'].map{|c| OpenSSL::X509::Certificate.new(Base64.decode64(c))}

    # リーフ証明書の取り出し
    leaf_cert = certs.first

    # 証明書チェインの取り出し
    cert_chain = certs.drop(1)

    # リーフ証明書と今検証しようとしているホスト名の整合性の確認
    unless OpenSSL::SSL.verify_certificate_identity(leaf_cert, 'attest.android.com')
      raise InvalidLeafCertificate, "Certificate isn't issued for the hostname attest.android.com"
    end

    # 証明書チェインを用いて、証明書の正当性を確認
    unless X509_STORE.verify(leaf_cert, cert_chain)
      raise InvalidCertificateChain, "Certificate chain verification is failed"
    end

    # 検証に使った証明書たち
    pp X509_STORE.chain

    # JWT検証用の公開鍵を取り出す
    leaf_cert.public_key
  end

  true
rescue JWT::VerificationError, JWT::DecodeError, InvalidLeafCertificate, InvalidCertificateChain
  false
end

################
# 実行サンプル用出力 #
################


MAL_JWT = "#{不正なJWT(省略)}"
SAFETYNET_JWT="#{SafetyNetAPIで取得した正規のJWT(省略)}"

puts "malformed JWT"
puts "result == #{improved_valid_jwt_signature?(MAL_JWT)}"
puts "safetynet JWT"
puts "improved_result == #{improved_valid_jwt_signature?(SAFETYNET_JWT)}"

補足情報(言語/FW/ツール等のバージョンなど)

XAMPP ver7.0.8
Apache1.2.4.18(win32)
PHP7.0.8

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

JWS ヘッダの x5c チェインに含まれる各証明書は、 DER (バイナリ) 形式の証明書を Base64 でエンコードしただけのもの かと思います。一方、 PHP の openssl_* 関数は 以前より PEM (テキスト) 形式しかサポートしておらず 、恐らく現在もそうなのだと思います。

というわけで、 openssl_x509_read() で開きたければ PEM 形式にきちんと直してやれば良いのですが、 PEM は殆ど DER の Base64 表現にヘッダ・フッタを付けただけのもの ですから、前掲の User Contributed Notes で示されているように、単純に以下ようなコードで変換してやれば良いのではないでしょうか。

$pemCert = (
    "-----BEGIN CERTIFICATE-----\n" .
    chunk_split($decodedHeader->x5c[0], 64, "\n") .
    "-----END CERTIFICATE-----\n"
);

$cert = openssl_x509_read($pemCert);

ちなみに、 JWT の基本的な処理は php-jwt というライブラリがありますので、参考にしたり、使ってみたりすると良いかもしれません。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/10 20:21

    回答ありがとうございます。教えて頂いたとおりにやったら出来ました!
    まだrubyのコードが理解しきれませんが、php-jwtライブラリも使ってみようと思います。
    本当に助かりました!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.91%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る