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はこれ

https://assetstore.unity.com/packages/tools/sign-in-with-apple-154202?_ga=2.99623134.1148277808.1584082182-434679709.1581905190


最終更新が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

https://github.com/sassembla/Autoya/blob/feature/signinwithapple/Assets/Autoya/ThirdPartyAuthentication/SignInWithApple/Plugins/iOS/SignInWithApple.m#L59


        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