openCACAO / cocoa-issues

接触確認アプリ COCOA に関するIssues用レポジトリです
Creative Commons Zero v1.0 Universal
8 stars 0 forks source link

接触確認アプリの検証環境構築の提案 #22

Open moonmile opened 4 years ago

moonmile commented 4 years ago

日本版のCOCOAに限らずなんですが、EN APIの制限のおかげで各国のアプリの実機デバッグが非常にやりにく状態になっています(リリースした後にしかデータ不整合をチェックできない)。

本来ならば検証サーバーを立てて、疑似的にTEKの蓄積を行って検証パターンを決めていくところなので、その環境構築が作れるのかどうかをまずは考察しましょう。

という issue 出しです。

方法案

陽性登録はHER-SYS から割り振られるので、適当な番号を登録すれば間違って本番サーバーに通知されたとしても弾かれるはず。というか、弾かれないと困るし。

rocaz commented 4 years ago

ちょうど拙作のprobeCOCOATek使って、EN ServerのMock作れそうだな、現在の本物EN server(list.json + ZIP/TEK)の内容をキャッシュしてProxy的にも出来るしこれまで公開されてて溜め込んだ任意のlist.json + ZIP/TEKの状態を再現するとかも出来そうだなあと思っていたのですが、そういうことでしょうか。 その時々の状況で良いなら、単純にProxyでもいいかも知れませんね。 Validation Serverの方はFunctionsと同様のAPIパラメータを受け入れる(+Responseエミュレーション)だけですから、これも大した話では無さそうです。

ただし本物にProxy経由としてもアクセスし続けるのはあまり好ましくないやも知れないので、閉じた検証環境の方が良いのでしょうね。

moonmile commented 4 years ago

ちょうど拙作のprobeCOCOATek使って、EN ServerのMock作れそうだな、現在の本物EN server(list.json + ZIP/TEK)の内容をキャッシュしてProxy的にも出来るしこれまで公開されてて溜め込んだ任意のlist.json + ZIP/TEKの状態を再現するとかも出来そうだなあと思っていたのですが、そういうことでしょうか。

さいです。そういうことです。

実機でテストする場合に、スマホ内にある TEK がわからないので、

  1. 陽性者用スマホ、と検証スマホを2台用意する。近接させておく。
  2. 陽性者用スマホで陽性者登録をして、TEK を 疑似登録サーバーに送る
  3. 疑似登録サーバーで TEK を取得
  4. 疑似配信サーバー(EN Server)で、TEK が入った ZIP を作る
  5. 疑似 EN Server から検証スマホに配信
  6. 検証用スマホで、TEK が照合されて、陽性者数が出る。

というパターンを想像しています。 陽性者スマホのほうは、ビーコンを出せばよいので M5Stack でも可能かなと思ったりしてます。

1 と 4 のところでプロキシが必要なんですが、ここは Windows 10 やラズパイのホットスポット機能でできるかもしれません。

rocaz commented 4 years ago

流れは理解しました。疑似TEKも生成したいですよね。署名鍵も疑似になりますが、アプリ的には問題無いですよね。 1,4は当面は単純なProxyでも良いとして、疑似TEK/ZIP生成含めて、作ってみますね。期待せずにいて下さい。Docker化してもいいですよね? COCOAでのValidation ServerのI/F、手っ取り早く知りたいんですけど、どこ見るのが最適でしょうか。

rocaz commented 4 years ago

因みに、本物のTEKの署名って、Debug_Mockや実機テストで検証可能です?

本物TEKでも問題無いのか、あるいはどうしても疑似TEK必要になるのか、という意味です。

moonmile commented 4 years ago

COCOAでのValidation ServerのI/F、手っ取り早く知りたいんですけど、どこ見るのが最適でしょうか。

https://github.com/openCACAO/Covid19Radar/blob/master/src/Covid19Radar.Api/DiagnosisApi.cs#L30

ここの Azure Functions の DiagnosisApi です。

因みに、本物のTEKの署名って、Debug_Mockや実機テストで検証可能です?

Debug_Mock では zip を解凍していないので、ちょっと無理だと思います。 https://github.com/openCACAO/Covid19Radar/blob/master/Covid19Radar/Covid19Radar/Services/TestNativeImplementation.cs#L49 このあたりで、ExposureInfo を入れているだけです。

https://github.com/xamarin/XamarinComponents/blob/master/XPlat/ExposureNotification/source/Xamarin.ExposureNotification/ExposureNotification.ios.cs#L194

Xamarin.ExposureNotifications のほうの PlatformDetectExposuresAsync 内でも、.bin と .sig を合わせて iOS EN api に渡すようになっているので、TEK の署名のところは実際やってみないとわからないという感じですね。解凍しているだけなので、本物でも疑似でも大丈夫だとは思うんですが。

moonmile commented 4 years ago

サーバーサイドは、接触確認アプリをホットスポットに繋げれば、Docker でも構わないですね。

陽性者の TEK は、Android などででビーコンを出すツールと作れば、それで十分かも。

「陽性者 TEK 登録アプリ」を作ると

rocaz commented 4 years ago

確認ですが、「陽性者 TEK 登録アプリ」からのValidation Serverへの登録は必ずしもDiagnosisApiに準じなくともよいのだろうと推察しますが、まあ擬似的にエミュレーションできたに越したことは無い、感じですね。 API側でのValidation処理はサボるとして、結局、 https://github.com/Covid-19Radar/Covid19Radar/blob/786a5f5c95bef96313ecb77be854ca6688791d1b/src/Covid19Radar.Api/Models/DiagnosisSubmissionParameter.cs で定義されているJSONがPOSTされてくるのでこれをTemporaryExposureKeyExport message化とZIP化してEN(配信)サーバーからDL出来れば良い(URLはあまり拘り無し)ということになりますね?

これはもし可能ならですが、上記のPOSTについて(1の処理)、curl形式などでRequest/Responseの実例が入手出来るようなら頂けませんか。要は陽性者 TEK 登録アプリとの結合テストとなると思いますが、こちらでも単体テストで試せた方が無難と思いますので。 DiagnosisSubmissionParameter.の既定値については実際のTEKを参考にしておきます。もし不明な点があればご質問させて下さい。

rocaz commented 4 years ago

Xamarin.ExposureNotifications のほうの PlatformDetectExposuresAsync 内でも、.bin と .sig を合わせて iOS EN api に渡すようになっているので、TEK の署名のところは実際やってみないとわからないという感じですね。解凍しているだけなので、本物でも疑似でも大丈夫だとは思うんですが。

こちら確実にマッチする陽性者TEKを渡さずとも、現在の本物TEKであっても.sigの署名検証は通るかどうかは分かるかと思うのですがいかがでしょうか。(検証失敗でもエラーが返らない可能性もありますが) ただし、と言うことであればEN APIが署名鍵に対応する検証鍵を「内部的に」有しており変更不可ということになりますか?だとすると疑似鍵では駄目かも知れないですね… (Registration時に登録するのでしたっけ…??)

rocaz commented 4 years ago

Androidなら実機テストでも署名検証は無視できそうですね。 https://developers.google.com/android/exposure-notifications/debug-mode iOSはちょっと分かりませんでした。

moonmile commented 4 years ago

https://github.com/xamarin/XamarinComponents/blob/master/XPlat/ExposureNotification/source/Xamarin.ExposureNotification/ExposureNotification.ios.cs#L194

ここの前後を見る限り、.bin と .sig を渡しているだけなので、DL 時に検証鍵が必要かどうかはわかりません。

ここの部分のテストって、rocaz さんの言われるように、「現在の本物のTEKを固めて、疑似的な配信サーバから iOS に落としてみる」で検証ができそうですね。何かプロキシを立てて。

もうひとつの「陽性登録」の json のほうは、最終的に、

https://github.com/Covid-19Radar/Covid19Radar/blob/786a5f5c95bef96313ecb77be854ca6688791d1b/Covid19Radar/Covid19Radar/Services/HttpDataService.cs#L71

を呼び出すですが、この手間がいくつかあって、結構かかりそう。

rocaz commented 4 years ago

DL時は何ら認証などは無いのですが、Googleのドキュメントを読む限りは、検証鍵(公開鍵)はGoogleへ提供されるとあります。これは恐らく、EN APIが呼び出されるとGoogle Playサービスが呼び出されて.sigの署名検証がされるの意味かなと思います。 Appleはやはり詳細不明なのですが恐らくはほぼ同様にアプリ公開時にiTunes Connectなど経由で預けているような気がします。 とすると

ここの部分のテストって、rocaz さんの言われるように、「現在の本物のTEKを固めて、疑似的な配信サーバから iOS に落としてみる」で検証ができそうですね。何かプロキシを立てて。

は本物なので問題無さそうですが、疑似TEKでは真性の署名は行えませんから、陽性者用スマホから疑似Validation Serverへの登録は行えても、疑似署名鍵で署名した疑似TEKではEN APIではねられてしまう気がします。

ただ上記の通りAndroidであればデバッグモードで検証を行われなくは出来そうですが、iOSの場合は難しいかも知れませんね。

とりあえず別に無駄になってもよいので疑似EN ServerとValidation Serverは何かしら作ってみますが、JSONだけ何とか頂ければ助かります。

もしValidation Serverからの陽性者TEK取り込みは難しくとも、過去の本物TEKを用いた(特に障害を起こしてた奴とか)疑似EN Serverだけでも何かと便利かも知れません。

moonmile commented 4 years ago

なるほど。疑似TEKが通らないの件、わかりました。 これは、試しにプロキシを立てて list.json を実機に流し込めるかどうかで確認できそうです。これはどっちにせよ試さないといけないので、調べていきますね。

陽性登録のときに PUT する JSON データは、少しコードを変えて Debug_Mock でも拾えそうなので試してみます。

moonmile commented 4 years ago

陽性登録をしたときに PUT method で送る JSON データです。 戻り値はなしで、ステータスのみチェック。

{
    "userUuid":"dummy uuid",
    "keys":[
        {"keyData":"BVHX3Xp9tYtwFak2QtkoHw==","rollingStartNumber":2664652,"rollingPeriod":2,"transmissionRisk":3},
        {"keyData":"CGRNx3RfpSDqJBVN20JiBg==","rollingStartNumber":2664508,"rollingPeriod":2,"transmissionRisk":5},
        {"keyData":"IYJChwe9HQi6rL9jmPcf0w==","rollingStartNumber":2664364,"rollingPeriod":0,"transmissionRisk":6},
        {"keyData":"aj3deDhakpB2Ijln0zxUUQ==","rollingStartNumber":2664220,"rollingPeriod":7,"transmissionRisk":5},
        {"keyData":"zIFdj6pspPhgh4weLQjANA==","rollingStartNumber":2664076,"rollingPeriod":9,"transmissionRisk":5},
        {"keyData":"+ahJ6PR2R/rOU9iDDkb5yQ==","rollingStartNumber":2663932,"rollingPeriod":10,"transmissionRisk":3},
        {"keyData":"0NqScNRA+QsEWPQxpp6gDA==","rollingStartNumber":2663788,"rollingPeriod":3,"transmissionRisk":6},
        {"keyData":"DOSN5kiwnlAf3XhGJGQBvw==","rollingStartNumber":2663644,"rollingPeriod":0,"transmissionRisk":7},
        {"keyData":"tIjUCe7oce8FnKw54gLupA==","rollingStartNumber":2663500,"rollingPeriod":2,"transmissionRisk":4},
        {"keyData":"cuqR4tKDIByTvjOPMOc63w==","rollingStartNumber":2663356,"rollingPeriod":0,"transmissionRisk":4},
        {"keyData":"gAP1pHRZqgGj5vp2WH+o3g==","rollingStartNumber":2663212,"rollingPeriod":10,"transmissionRisk":6},
        {"keyData":"LtK1UOnSVbO7dmvF/wOqHg==","rollingStartNumber":2663068,"rollingPeriod":7,"transmissionRisk":6},
        {"keyData":"LM51ODjQ3zoiHWa051KawA==","rollingStartNumber":2662924,"rollingPeriod":6,"transmissionRisk":7}
    ],
    "regions":["440"],
    "platform":"android",
    "deviceVerificationPayload":"dummy DeviceVerificationPayload",
    "appPackageName":"net.moonmile.cocoa",
    "verificationPayload":"12345678",
    "padding":"4mMZg2sUvv9/YcmntCX5ZIsfx3dTTp9ZXKaVNTXAeFoss13On6jhLh8PFAdT1q1MnNtAdXWfPpPDwCfEfonKvuluegDIK8XiPplYQzqYBob5SZhZgALljHXJCt+BHK+WRVUe5Fusg3EVXTfcR/pZK7mhyzXwcD7mie6L/yHmYSHpwv3keiIqTMUmQyPLCEV6b98l31B5pdUXN1psIYRQL/sV84T9dbGXdn9wySwaGoCoEY9BwIHRbx0ozELHvipB1hQntRXAffktjmnRCduo/R3Zww/0Dm55RINn1Khv0LhULvMY8mRr55SVFDIYAGVCf88l7fobTMilrXGSWahRvr2wfNvokAq4btx7Xvo8YUQeJFTZSeK+OeEqckTz2Y7sRFV0aIOKWu/FbH8eoO3OaEwtczRTd3Tb8kL5vR3FxZVOjhqkx/jAk8plE91joJn0XAn3xWVZmMzgmXVAkZzRAKfqzxHTKqFUbeBq7WSXeFKpBWETzf8w9fZNwy6CfwItlBM1edCHIDJoo1eE7qtoSSEjl4w7vTOnudobhmZz0e7dPFlQYf+BIuQvqymnqwZL5HoNX+d28tfgjr/1bJrL9dJpp7JRGOchoXWnDBEJ/w7Uz6HsZnEGZ0qCcNNAjLVKY7ZWPgRT+cyS671yzuXF1o6wIJz5349gYz9oz6cU4RW/Xahbs8EKgdbUWXq0k5hkvJlEj2wYUYQn9dvE6ykkfgmos3E8aSPNpp/GJIUUrM+mAxIOz1KppwskNL3mRZWtr9twDOAmKFN2d+jMUiz/EkkLHecWFq63/wtmGDVm52EJGXSpo5/vgNu/mFhp3FTeJSFz2yeulpksuQ3UwA8dD5CGwzm4VW+pG50LV35WTiK2h/MU8gw6WoLbfeqSGr/LoYjH4hgbJs+xyr+Joj8b1WrqHTkcCR2ETGi23NiFFwHeQLX1jmsJm2AjYihMsLhDwKd/qPyGr5hqQ5WwZEr3LLO+q2KJJUf/4ILZ+af1N5uwcxwxBW4EV5Edi0vVWTYWLDV1jgVWuptgf8d6rLXwdQz6APEtxSZEuVPRa5r8KYZMw81xf6iWWrmB0YWniqvbLBU+21lE6GYPMdWxhE4BY1ZALenof/ZkY6Y4rPSgnC+o9T+EvU0tB9tnYU7S5dVrgG0vZlzC860f2XizCztxbpSlCY4OfKYIEyYOgB2nkX3QzSyHcDbT5gKSZeTVefuf9fO6LdxGXAPHJ1I1s3KPd0f64j4P6v1d0SKMfBfBMpoqb4oq7WShinEov0njnTKQAu2c2pa9jwv1XFu5ud/sBfS4C0TnZIrjWVjM5iq0eTTw/HkpJVgDbd9/NZAJ6NCRPLpVK5LtqJ5iuYE+8KjhQeMnyL1lwt+ctZ1laC/62nCHOqpvXk6hAlPbxmzWxHw2nfSt4h8TLHnauXX0iD9vaN8Qt7X+ceKUhcVpivxzxfEhLss6TPhRdA4wlaXZrHoUa16NxQzHOCsRGVxZqaF9Z/OD01l0NBvmvkOmzdw9u9hLVR6e5f/lWfafKTOb+Mpssy2yVdkhnzdCpgfIIGpO1F6DDlOD34Hznnli1QvnS8Z4ORmvi2X/KbbnWz/4HDiLUFTVGOp36dSsvrZfvsosWltAT+ZRn0n4g+SdnINB+4JA14S2ETLcAVBFAsyvoGvruwHkt8xSOrAL3ZQF2U1RasIzHudFdUgoZE2WC518SeN5seGCXUO27pzTqjSe1igD9JgKe4GDpkW4u1N+uy8TjpSxjChszgx+fS9LcsoUio0lkxcFcs1h9QubyaLvNzJtwByg"
}

以下は、コードを参照するときのメモ書き1

+ NotifyOtherPageViewModel::OnClickRegister
    userData.AddDiagnosis(_diagnosisUid, new DateTimeOffset(DateTime.Now));
    await userDataService.SetAsync(userData);
    await Xamarin.ExposureNotifications.ExposureNotification.SubmitSelfDiagnosisAsync()
+ ExposureNotification::SubmitSelfDiagnosisAsync
    var selfKeys = await GetSelfTemporaryExposureKeysAsync();
    await Handler.UploadSelfExposureKeysToServerAsync(selfKeys);

        internal static Task<IEnumerable<TemporaryExposureKey>> GetSelfTemporaryExposureKeysAsync()
            => nativeImplementation != null ? nativeImplementation.GetSelfTemporaryExposureKeysAsync() : PlatformGetTemporaryExposureKeys();

+ ExposureNotificationHandler::UploadSelfExposureKeysToServerAsync
    var latestDiagnosis = userData.LatestDiagnosis;
    var selfDiag = await CreateSubmissionAsync(temporaryExposureKeys, latestDiagnosis);
    HttpStatusCode httpStatusCode = await httpDataService.PutSelfExposureKeysAsync(selfDiag);
    + ExposureNotificationHandler::CreateSubmissionAsync

+ HttpDataService::PutSelfExposureKeysAsync
    var url = $"{AppSettings.Instance.ApiUrlBase.TrimEnd('/')}/diagnosis";
    var content = new StringContent(Utils.SerializeToJson(request), Encoding.UTF8, "application/json");
    HttpStatusCode status = await PutAsync(url, content);
moonmile commented 4 years ago

ネットワークシーケンスの考察

TEK受信

TEK送信(HER-SYS登録)

TEK送信のフック

この場合、実機cocoaのTEKs(陽性者のTEKs)を取得できるが、結構面倒。

の手順でもよいかもしれない。

TEK受信のフック

PROXY 自体は、Windows 10 かラズパイのホットスポット機能を使えばよい。

懸念点

SafetyNet Attestation API  |  Android デベロッパー  |  Android Developers

rocaz commented 4 years ago

JSONありがとうございました。承知です。

rocaz commented 4 years ago

あ、そうそう、SafetyNetのことを言いたいのでした。また、”Attestation”ですね。 APK詐称防止以外にもFIDO2のPlatform Authenticatorとかいろいろ広く応用されてます。

rocaz commented 4 years ago

なんとなく動くような感じのが出来たので、置いておきました。非常に簡易版です。よろしければ試してみて下さい。 ただし疑似署名なので、Androidなどで署名チェックを無視できないとEN APIでは無視されるだろうなあとも思います。 現在は1TEK=1ZIPにバラしています。本当は、モバイルアプリ側から固める単位を指示させたり、rolling_start_numberあたりから同一判明日?単位でまとめたりはすべきなのでしょうね

https://github.com/rocaz/COCOAmockservice

rocaz commented 4 years ago

export.binのFixed Header付けるのとSHA256取り忘れるという大失敗があったので、version2に更新しました

moonmile commented 4 years ago

COCOAmockservice ありがとうございます。 環境構築をして、まずは「Androidなどで署名チェック」が突破できるか/できないかを確認しないと。

陽性登録のほうは、改めてシーケンスを見ると突破できそうになさそうですね。 スマホ内の TEKs が抜けてしまうと、zip と直接照合できるから駄目なのかな。

moonmile commented 4 years ago

検証環境では、スマホのプロキシを使って HTTP Proxy + Hosts を使えばいけるんじゃないか、というアドバイスを貰いました。 確かに、PC にプロキシサーバーを立てて、スマホにプロキシを設定したほうがよさそう。あとで検証。

と、Android のブラウザでプロキシ設定をしたが、どうやらブラウザの方にしか効かないので、アプリのほうはうまくいかず。まじめに、ラズパイあたりでルーターを作って「ポートフォーワード」するのが良いっぽい。

moonmile commented 4 years ago

プロキシで list.json と zip を切り替える方式なのですが、Android で https を呼び出すときにプロキシが正式な CA を返せないので無理そうです。

この場合、途中で挟まるプロキシの CA を Android 側に入れてやれば通ると思うのですが、もとの COCOA は改変しない前提なので駄目。

正式な環境であれば、

ことで可能なのですが。この環境ができているかどうかが不明だし。 ということで、いったんこの issue は保留にさせてください。あとで、ドイツとフランスではどうやっているか見ておきます。

参考サイト

moonmile commented 4 years ago

image

はじめに戻って要件のまとめ

これで、list.json や zip のすり替えが出来きることによって、

などが実機でできるのではいないか、という想定。

ただし、rocaz さんの

ただし疑似署名なので、Androidなどで署名チェックを無視できないとEN APIでは無視されるだろうなあとも思います。

のような懸念があるので、想定通りには動かない可能性があり。

なので、あまり検証環境に労力を掛けても無駄かもしれないという不安があるので、そもそもこの環境が構築可能かどうかを考えておきたい。

アクセスポイントを通した後に、透過プロキシ、 SOCKS Proxy を通したらいけるのではないかという話もあるのだけど、ちょっとよくわからず。

b-wind commented 4 years ago

書き殴ったので読みにくいかもですが、どうぞ。

結論としては rooted 端末か Android 6 であれば単体で可能。出来ればアプリで network-security-config 設定して。と言う事になります。

https://gist.github.com/b-wind/d69ec95604329a3af6e3f618e4cf93c9

moonmile commented 4 years ago

ありがとうございます。なかなか、ネットワーク素人には難しそうですね。

App Store に公開されている COCOA を使ってのテストは厚生労働省環境以外では難しそうなので、 EN API をすり替えて、通知と Risk 計算のところをテストできないかと模索中です。

エミュレータ上で、key server から zip をダウンロードするところまではいけたので、接触データの TEK を入れ替えながらというのはできそうです。まあ、このあたりは単体テスト部分を切り出して、xUnit を使うのが筋なんですが。