APK 署名スキーム v3

Android 9 では、APK アップデートの一環としてアプリで署名鍵を変更できる APK の鍵ローテーションがサポートされています。ローテーションを実施するには、APK で新旧の署名鍵間の信頼レベルを示す必要があります。鍵ローテーションをサポートするために、APK 署名スキームは v2 から v3 に更新されて新旧の鍵を使用できるようになりました。v3 では、サポートされる SDK バージョンと proof-of-rotation 構造に関する情報が APK 署名ブロックに追加されています。

APK 署名ブロック

v1 APK 形式との下位互換性を維持するため、v2 および v3 APK 署名は、ZIP セントラル ディレクトリの直前にある APK 署名ブロック内に格納されます。

v3 APK 署名ブロックの形式は、v2 と同じです。APK の v3 署名は、ID を 0xf05368c0 とする ID と値のペアとして格納されます。

APK 署名スキーム v3 ブロック

v3 スキームの設計は、v2 スキームによく似ています。おおよその形式は同じで、署名アルゴリズム ID、鍵サイズ、EC 曲線をサポートしています。

ただし、v3 スキームには、サポートされる SDK バージョンと proof-of-rotation 構造に関する情報が追加されています。

形式

APK 署名スキーム v3 ブロックは、0xf05368c0 という ID で APK 署名ブロック内に格納されます。

APK 署名スキーム v3 のブロックの形式は、次のように v2 の形式に準拠します。

  • 長さプレフィックス付き signer の長さプレフィックス付きシーケンス:
    • 長さプレフィックス付き signed data:
      • 長さプレフィックス付き digests の長さプレフィックス付きシーケンス:
        • signature algorithm ID(4 バイト)
        • digest(長さプレフィックス付き)
      • X.509 certificates の長さプレフィックス付きシーケンス:
        • 長さプレフィックス付き X.509 certificate(ASN.1 DER 形式)
      • minSDK(uint32)- プラットフォームのバージョンがこの数値を下回る場合、この署名者は無視されます。
      • maxSDK(uint32)- プラットフォームのバージョンがこの数値を上回る場合、この署名者は無視されます。
      • 長さプレフィックス付き additional attributes の長さプレフィックス付きシーケンス:
        • ID(uint32)
        • value(可変長: 追加属性の長さ - 4 バイト)
        • ID - 0x3ba06f8c
        • value - proof-of-rotation 構造
    • minSDK(uint32)- 署名付きデータ セクションの minSDK 値の複製 - 現在のプラットフォームが条件を満たさない場合、この署名の検証をスキップするために使用されます。署名付きデータの値と一致する必要があります。
    • maxSDK(uint32)- 署名付きデータ セクションの maxSDK 値の複製 - 現在のプラットフォームが条件を満たさない場合、この署名の検証をスキップするために使用されます。署名付きデータの値と一致する必要があります。
    • 長さプレフィックス付き signatures の長さプレフィックス付きシーケンス:
      • signature algorithm ID(uint32)
      • signed data に関する長さプレフィックス付き signature
    • 長さプレフィックス付き public key(SubjectPublicKeyInfo、ASN.1 DER 形式)

proof-of-rotation 構造と self-trusted-old-certs 構造

proof-of-rotation 構造によって、アプリは、他のアプリと通信を行ってもブロックされずに署名証明書をローテーションさせることができます。これを実現するために、アプリの署名には次の 2 つの新しいデータが含まれています。

  • アプリの古い署名証明書が信頼されている場所ならどこでも、新しい証明書を信頼できるという第三者へのアサーション
  • アプリ自体が信頼するアプリの古い署名証明書

署名付きデータ セクションの proof-of-rotation 属性は個々に紐付けられたリストで構成され、各ノードには前バージョンのアプリの署名で使用された署名証明書が含まれます。この属性には、概念的な proof-of-rotation と self-trusted-old-certs のデータ構造が含まれることになります。このリストはバージョン順に並んでいて、最も古い署名証明書がルートノードに対応します。proof-of-rotation データ構造は、リストの各ノードの証明書が次のノードの証明書を署名し、新しい鍵が古い鍵と同様に信頼されるべきであるという証拠を埋め込むことによって構築されます。

self-trusted-old-certs のデータ構造は、セット内のメンバーシップおよびプロパティを示すフラグを各ノードに追加することによって構築されます。たとえば、特定のノードの署名証明書が Android の署名権限を取得するための信頼できる署名書であることを示すフラグが存在する場合もあります。このフラグを使用すると、古い証明書で署名された他のアプリに、新たな署名証明書で署名されたアプリで定義された署名権限を付与できます。proof-of-rotation 属性はすべて v3 signer フィールドの署名付きデータ セクションに格納されるため、そこに含まれる APK の署名に使用される鍵によって保護されます。

この形式には複数の署名鍵が含まれません。また、上位の別の署名証明書からもう一方(単一の共通シンクへの複数の開始ノード)へのコンバージェンスが発生しないようになります。

形式

proof-of-rotation は、0x3ba06f8c という ID で APK 署名スキーム v3 ブロック内に格納されます。形式は次のとおりです。

  • 長さプレフィックス付き levels の長さプレフィックス付きシーケンス:
    • 長さプレフィックス付き signed data(存在する場合は、前の証明書によるもの)
      • 長さプレフィックス付き X.509 certificate(ASN.1 DER 形式)
      • signature algorithm ID(uint32)- 前のレベルの証明書で使用されたアルゴリズム
    • flags(uint32)- この証明書を self-trusted-old-certs 構造に含める必要があるかどうか、およびその操作を示すフラグ。
    • signature algorithm ID(uint32)- 次のレベルの署名付きデータ セクションのものと一致する必要があります。
    • 上述の signed data に対する長さプレフィックス付き signature

複数の証明書

Android は現在、複数の証明書で署名された APK を、それらの証明書とは別の一意の署名 ID を持つものとして扱います。したがって、署名付きデータ セクションの proof-of-rotation 属性は有向非巡回グラフを形成します。これは、1 つのノードを表す特定のバージョンの署名者の各セットによって個々に関連付けられたリストとも言えるものです。これにより、次に挙げる複数の署名者バージョンのように、proof-of-rotation 構造の複雑性が増大し、特に順序が懸案になります。さらに、proof-of-rotation 構造では、古い署名証明書が新しい証明書に 1 つずつ署名するのではなく、新しい証明書のセットに署名しなければならないので、APK に個別に署名することができなくなりました。たとえば、2 つの新しい鍵 B と C での署名が必要となる鍵 A によって署名された APK は、A または B による署名が含まれただけの B 署名者を持つことはできません。この B 署名者の署名 ID が、B と C での署名とは異なるためです。このため、署名者がこうした構造を構築する前に事前に調整しておく必要があります。

複数の署名者の proof-of-rotation 属性

  • 長さプレフィックス付き sets の長さプレフィックス付きシーケンス:
    • signed data(存在する場合は、前のセットによるもの)
      • certificates の長さプレフィックス付きシーケンス:
        • 長さプレフィックス付き X.509 certificate(ASN.1 DER 形式)
      • signature algorithm IDs のシーケンス(uint32)- 前のセットの証明書ごとに 1 つずつ、同順で。
    • flags (uint32)- この証明書のセットを self-trusted-old-certs 構造に含めるかどうか、およびその操作を示すフラグ。
    • 長さプレフィックス付き signatures の長さプレフィックス付きシーケンス:
      • signature algorithm ID(uint32)- 署名されたデータ セクションからのものと一致する必要があります。
      • 上述の signed data に対する長さプレフィックス付き signature

proof-of-rotation 構造における複数の上位証明書

v3 スキームは、同じアプリで同一の署名鍵にローテーションする 2 つの異なる鍵についても処理しません。これは買収のケース(買収元の企業が、買収したアプリによる権限共有をそのアプリの署名鍵で行うようにしたい場合)とは異なります。このケースでは新しいアプリがパッケージ名で判別されて独自の proof-of-rotation 構造を持てるため、サポート対象のユースケースと考えることができます。サポート対象外のケース(同一のアプリが同一の証明書を 2 つの異なるパスで取得する)では、鍵ローテーション設計の多くの前提条件が崩れます。

検証

Android 9 以降では、APK 署名スキーム v3、v2 スキーム、または v1 スキームに準じて APK を検証できます。それ以前のプラットフォームでは v3 署名が無視され、v2、v1 の順に検証が試行されます。

APK 署名検証プロセス

図 1: APK 署名検証プロセス

APK 署名スキーム v3 の検証

  1. APK 署名ブロックを検出し、次のことを検証します。
    1. APK 署名ブロックの 2 つのサイズ フィールドに同じ値が含まれる。
    2. ZIP セントラル ディレクトリの直後に ZIP セントラル ディレクトリの末尾レコードがある。
    3. ZIP セントラル ディレクトリの末尾の後に追加のデータがない。
  2. APK 署名ブロック内の最初の APK 署名スキーム v3 ブロックを検索します。v3 ブロックがある場合は、ステップ 3 に進みます。v3 ブロックがない場合は、v2 スキームを使用した APK の検証に戻ります。
  3. 現在のプラットフォームの条件に含まれる、最小および最大バージョンの SDK を持つ APK 署名スキーム v3 ブロックの各 signer については、次のように取り扱われます。
    1. signatures の中からサポート対象となる最も強力な signature algorithm ID を選択する。強度の順序は、各実装 / プラットフォームのバージョンに応じて異なります。
    2. public key を使用して、signed data に対する signatures から、対応する signature を検証する(これで signed data の解析が安全に行えるようになります)。
    3. 署名付きデータの最小および最大 SDK バージョンが signer に指定されたものと一致することを検証します。
    4. digestssignatures の署名アルゴリズム ID の順序付きリストが同一であることを検証する(署名の削除 / 追加を防ぐため)。
    5. 署名アルゴリズムで使用されるアルゴリズムと同一のダイジェスト アルゴリズムを使用して、APK コンテンツのダイジェストを算出する。
    6. 算出されたダイジェストが digests に対応する digest と同一であることを検証する。
    7. certificates の最初の certificate の SubjectPublicKeyInfo が public key と同一であることを検証する。
    8. signer に対する proof-of-rotation 属性が存在する場合、構造が有効であり、この signer がリストの最後の証明書であることを検証します。
  4. 現在のプラットフォームの条件内で signer が 1 つだけ検出され、かつこの signer に対してステップ 3 が成功した場合、検証は正常に完了します。

検証

デバイスで v3 が適切にサポートされていることをテストするには、cts/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java CTS テストを実行します。