引き続きiOS14ネタとなります。
今回は、DeviceCheckフレームワークに新たに追加された「App Attest API」についてみていきます。
本記事は公開済みのドキュメントを元に作成しています。今後仕様変更などにより記載内容と異なる場合があります。予めご了承ください。
App Attest APIとはなにか
App Attest APIは雑にいうとアプリの不正使用をサーバー側で防ぐAPIです。
Appleデバイスが生成した証明書とサービスアカウントの紐づけを行い、サービス利用時にそのAppleデバイスだけが持つ秘密鍵で署名したデータをサーバー側で検証することで、サーバーへアクセスするデバイスを確実にできます。
また検証の際に署名やApp IDの確認が行われるため、サイドローディングされたアプリや改造アプリに対しても効果を発揮します。
サイドローディング... ストア(App Store)を経由せずにアプリを入手、インストールすること。iOSではデベロッパーアカウントを使って再署名することで可能となる。Androidでいう野良アプリのインストール。
さらにAppleが公開しているREST APIを呼ぶことで、不正行為を行っている可能性を評価したパラメータが取得できます(この評価はデバイス上の一意の証明書のおおよその数を元にしているようです)。
ただしJailbreakによってiOS自体に改変が加えられている場合、確実に不正使用を防げるわけではないため、不正対策の一環として考えておいた方が良いです。
どういったときに使えるのか
以下のようなサーバー側で制御ができるコンテンツに対して効果的です。
- 課金した人が使えるコンテンツへのアクセス
- オンラインゲーム
また実際に不正が確認された場合、DeivceCheckのAPIを使うことでより強力なハードウェアBANを行うことができます。
詳しくはこちら
どのような仕組みか
ほぼWebAuthnの仕組みに沿っており(といってもWebAuthnを知ったのがつい最近なので違っていたら教えて欲しいです‥🙇♂️)
流れとしてはこんな形です。
- 初回起動またはアカウント作成時にキーペアを生成し、サーバーサイドに送ります。
- サーバー側は送られてきたデータから検証を行い、問題なければ公開鍵とレシートをユーザー情報と紐付けます。
- クライアントは認可が必要なAPIにアクセスするタイミングで署名を行ったデータを一緒に送信します。
- サーバー側は送られてきたデータから検証を行い、問題なければ通信成功とします
実装方法(クライアント)
1. サポートされているかどうかの確認
まずはAPIが使えるデバイスかどうかを確認する必要があります。
サポートされているかどうかはDCAppAttestServiceのisSupportedを確認します
let service = DCAppAttestService.shared if service.isSupported { // サポートされている }
2. キーペアの生成と検証を行う(インストール・アカウント作成時のみ)
クライアント側でgenerateKey(completionHandler:)を呼びキーペアを生成します。 キーペアは生成と同時にSecure Enclaveに保存されており、アプリ側から読み取ったり変更はできません。
keyIdが引数に渡されますが、これは生成されたキーペアに紐づいており、今後署名するときに必ず使うのでキーチェーン、あるいはファイルに保存します。
service.generateKey { keyId, error in guard error == nil else { // Error return } // Save KeyId }
次に、サーバーからChallengeを受け取り、クライアント側でSHA256のハッシュ値を生成します。
let hash = Data(SHA256.hash(data: fromServerData))
keyId・ハッシュ値が揃ったら、attestKey(_:clientDataHash:completionHandler:)を呼びキーペアが有効であることをAppleに検証してもらいます。
成功するとDate型のAttestation Object
が渡ってくるのでこれをサーバーに送ります。
service.attestKey(keyId, clientDataHash: hash) { attestation, error in guard error == nil else { return } let attestationBase64 = attestation.base64EncodedString() // Base64に変換するかバイナリとしてそのままサーバーへ送る }
※ちなみに、iOS14 beta 3まではこのメソッドを呼ぶと必ずエラーが帰ってきていました。beta 4にアップデートしたところ改善したので古いバージョンをお使いの場合は注意してください
サーバー側は送られたAttestation Object
を検証し、問題がなければ公開鍵とレシートをアカウントに紐づけてキーペアの生成は完了です。
3. アプリの有効性を検証する
全ての通信、あるいはビジネス上不正が起こっては困る通信に対してクライアント側で署名を行ったオブジェクトを送信します。
署名にはgenerateAssertion(_:clientDataHash:completionHandler:)を呼びます。
service.generateAssertion(keyId, clientDataHash: hash) { assertion, error in guard error == nil else { return } let assertionBase64 = assertion.base64EncodedString() // Base64に変換するかバイナリとしてそのままサーバーへ送る }
ここで得られたAssertion Object
をサーバーに送ります。
サーバー側は送られたAssertion Object
を検証し、問題がなければ処理を進められます。
サーバー側の実装・仕様はAppleのドキュメントがあるのでこちらをどうぞ。
https://developer.apple.com/documentation/devicecheck/validating_apps_that_connect_to_your_server
参考
https://developer.apple.com/documentation/devicecheck
https://developer.apple.com/documentation/devicecheck/establishing_your_app_s_integrity