Unityが提供しているSign in with Apple(SIWA)のコードを調整してまともにする
概要
まずい。Unityが提供しているSIWAのコードをそのまま使うと、脆弱性があるんで殴られまくり、最悪やらかしアドベントカレンダーにInすることになる。
問題があるUnity製のSIWAライブラリはこれですね
記事
https://blogs.unity3d.com/jp/2019/09/19/support-for-apple-sign-in/
記事で紹介されてるパッケージのURLはこれ
最終更新が2/14とかなんだけど、修正されてない。
ここで紹介する問題はすべてUnityが用意しているUnityクライアントSIWA用のObj-Cコードに由来する。
問題点は以下
・nonceを扱っていないのでリプレイアタックされ放題になる
・nonceを扱っていないので出元の確認ができない
・AuthorizationCodeを扱っていないので検証がしょぼい気がする(検証中)
nonceを扱ってないのの何が問題か
・おんなじデータをexprが切れる前に投げ放題
・サーバ側がJWTを検証する材料が不足する
ひとえに、せっかくSIWA自体の機構がnonceを使うことを推奨しているのに、それを使わないので脆弱になっている。
SIWAのnonce
iOS13以降に入っているASAuthorizationAppleIDProviderでは、リクエストの生成時にnonceパラメータをセットすることができる。
これは、任意の値をnonceパラメータにセットし、そのパラメータがリクエストの成果物に含まれることを意味する。
例えばサーバでnonce1を生成、クライアントがそれを受け取りnonce1をnonceパラメータにセットすると、Appleから返送されてくるデータにnonce=nonce1が入る。
これによって、クライアントがサーバへと認証データを送った際、サーバ側はnonceに何が含まれているかという情報で検証することが可能になる。
まーーーこれがないと、サーバ側は「jwtのフォーマット的には正しいが、それ以外に検証要素がない」という状態に陥る。
nonceの追加
で、Unityが提供しているSIWAライブラリにはnonceをセットする部分がゴッソリ抜け落ちているんで、足したものがこちら。
Autoya Thirdpary Authentication
request = [provider createRequest];
[request setRequestedScopes: @[ASAuthorizationScopeEmail, ASAuthorizationScopeFullName]];
// set nonce for verification.
[request setNonce:nonce];
これでnonceがセットできる。C#側のコードからのセットまで当然可能になってる。
で、これでsignup/signin時にnonceをセットすると、Appleが返してくるidToken(JWT)のpayloadにnonceが入るようになる。
例えば db862182-d25b-44f7-ae26-8c11437c1d80 という値をnonceに入れてsignup/signinを行うと、次のようなJWT payloadが帰ってくる。太字のところがnonce。
{
"iss": "https://appleid.apple.com",
"aud": "com.kissaki.app”,
"exp": 1584132002,
"iat": 1584131402,
"sub": “xxxxx.xxxxx”,
"nonce": "db862182-d25b-44f7-ae26-8c11437c1d80",
"c_hash": "VUbmqVL9jkYt5VpBCtWkhQ",
"email": “xxxxx@x.x”,
"email_verified": "true",
"auth_time": 1584131402,
"nonce_supported": true
}
んでーー、これを検証するnode.jsのコードはこんな感じになる。太文字のところがnonce関連。
事前にAUDIENCE_CLAIM(Appのidentifier)、
ID_TOKEN(検証したいidToken、クライアントでSIWAが成立したら出てくるやつ)、
NONCE(クライアントでSIWAのSignup/Signinをする際に入力したやつ) を入れておく。
verify.js
const AUDIENCE_CLAIM = "com.kissaki.app”;
const ID_TOKEN = “xxxxx.xxxxx.xxxxx”;
const NONCE = “xxxxx”;// nonce should be issed and recoded by the app-server and the client app should use it for SIWA signup/signin.
const jwt = require('jsonwebtoken')
const jwksClient = require('jwks-rsa');
var client = jwksClient({
jwksUri: 'https://appleid.apple.com/auth/keys'
});
function getApplePublicKey(header, callback) {
client.getSigningKey(header.kid, function (err, key) {
var signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
jwt.verify(ID_TOKEN, getApplePublicKey, null, function (err, decoded) {
if (err) {
console.error("validation err:" + err);
process.exit(1);
}
if (decoded.nonce !== NONCE) {
console.error("unexpected nonce (nonce claim): ", decoded.nonce);
process.exit(1);
}
if (decoded.iss !== "https://appleid.apple.com") {
console.error("unexpected issuer (iss claim): ", decoded.iss);
process.exit(1);
}
if (decoded.aud !== AUDIENCE_CLAIM) {
console.error("unexpected audience (aud claim): ", decoded.aud);
process.exit(1);
}
console.log("succeeded to validate Apple token: ", decoded);
});
検証に成功すると次のようなログが出る。
succeeded to validate Apple token: {
iss: 'https://appleid.apple.com',
aud: 'com.kissaki.app‘,
exp: 1584139641,
iat: 1584139041,
sub: 'xxxxx',
nonce: '8a12db08-1e5f-4bbf-83be-4220abb3c55d',
c_hash: ‘xxxxx’,
email: ‘x@x.x’,
email_verified: 'true',
auth_time: 1584139041,
nonce_supported: true
}
検証に失敗する場合はその理由が出る。
注意して欲しいのは、上のコードはあくまで手元のnode.jsを使って検証してみよう的なホビーコードだってこと。
実際にはJWTに含まれているnonceについて、サーバ側でそのnonce値が正しいものか、すでに1度使われたものではないか、というのを確認する必要がある。
なので~上のコードとはかなり違ってくる。
で、nonceパラメータをサーバサイドが発行するごとにユニークに変化 + 記録すれば、
・既に使われたnonceが来たら殴れる
・検証の材料にできる
という、まあ、はい。普通ですね。
これでやっと、Androidの課金検証と同レベルの検証ができるようになった、、はず。
nonceには何を入れても(なんか長さ制限とかはRFCとかにあると思うが)いいはずなんで、リプレイアタックの防止と、JWT検証の助けにしようね~。
AuthorizationCode関連
Unityが配布しているSIWAのコードに入ってなかったのは、nonceだけではなく。
AuthorizationCode と呼ばれるパラメータもObj-C側のコードで無視されていた。
んでーーこれーー、なんかrefresh tokenとかが取得できると思ってるんだけど、設定が膨大なんでまだ確認できてない。
AppleのサーバにAuthorizationCodeを使って問い合わせて、200が帰ってくればOK~という認識なんだけど、途中。
危機感が出てきたら続きを書く。
昨今のAndroidの課金周りとかが壊滅的な改竄を受けてる(ので、なんかチート防止ツールの価値がより強固になってる)のがかなり残念という認識を持ってるんで、
や~~っぱ通信しましょうよー~~みたいなことを言い出す気がする。自分は。
参考
調べるにあたって、このへんを参考にした。
Apple
JWTのverifyの話 nonceはここでも紹介されてる。
https://developer.apple.com/documentation/signinwithapplerestapi/verifying_a_user
Sign in with Apple を本番でやらかしをしないために考えてみた
最も参考にした記事。もっと読み込まないといけない。
https://nerocrux.hatenablog.com/entry/2019/12/11/163231
What the Heck is Sign In with Apple?
AuthorizationCodeをverifyするための設定の話を書いてくれてる。読んでる。
https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple