メタデータ暗号化

Android 7.0 以降では、ファイルベースの暗号化(FBE)がサポートされています。FBE を使用すると、さまざまなファイルを異なる鍵で暗号化して、個別にロック解除できます。これらの鍵は、ファイルの内容と名前の両方を暗号化するために使用されます。FBE を使用した場合、ディレクトリ レイアウト、ファイルサイズ、権限、作成日時や変更日時などの情報は暗号化されません。これは、総称してファイルシステム メタデータと呼ばれます。

Android 9 では、ハードウェア サポートがあるメタデータ暗号化のサポートが導入されました。メタデータ暗号化では、起動時に存在する 1 つの鍵で、FBE によって暗号化されないコンテンツが暗号化されます。この鍵は、確認済みの起動によって保護される Keymaster で保護されています。

実装

Android 9 を搭載した新しいデバイスでメタデータ暗号化を設定するには、メタデータ ファイルシステムを設定して init シーケンスを変更し、デバイスの fstab ファイルでメタデータ暗号化を有効にします。

ハードウェア要件

メタデータ暗号化は、データ パーティションの最初のフォーマット時にのみ設定できます。そのため、この機能は新しいデバイスのみを対象としており、OTA の変更は不要です。

現在、メタデータ暗号化をサポートするには、ファイルベースの暗号化に使用するインライン暗号エンジンにハードウェアが対応している必要があります。これは、fstab.hardware にあるユーザーデータ パーティションの fileencryption=ice ディレクティブで示されます。

さらに、dm-default-key モジュールが存在し、カーネルで有効になっている必要があります。

メタデータ ファイルシステムの設定

メタデータ暗号鍵がないとユーザーデータ パーティション内のデータを読み取れないため、この鍵を保護する keymaster blob を格納できるように、パーティション テーブルに「メタデータ パーティション」という独立したパーティションを確保する必要があります。メタデータ パーティションには 16 MB 必要です。

fstab.hardware には、起動時にフォーマットされていることを保証する formattable フラグを含む、/metadata でマウントされるパーティションに存在するメタデータ ファイルシステムのエントリを含める必要があります。f2fs ファイルシステムは小さいパーティションでは動作しないため、代わりに ext4 を使用することをおすすめします。次に例を示します。

/dev/block/bootdevice/by-name/metadata              /metadata          ext4        noatime,nosuid,nodev,discard                          wait,check,formattable

/metadata マウント ポイントが確実に存在するように、次の行を BoardConfig-common.mk に追加します。

BOARD_USES_METADATA_PARTITION := true

init シーケンスの変更

メタデータ暗号化を使用する場合、/data がマウントされるためには、まず vold が実行されている必要があります。早期に開始されるように、次のスタンザを init.hardware.rc に追加します。

# We need vold early for metadata encryption
    on early-fs
        start vold

init が /data のマウントを試行するには、Keymaster が動作して準備が整っている必要があります。

init.hardware.rcon late-fs スタンザには、/data 自体をマウントする mount_all 命令がすでに含まれています。この行の前に、wait_for_keymaster サービスを実行するディレクティブを追加します。

on late-fs
       …
        # Wait for keymaster
        exec_start wait_for_keymaster

        # Mount RW partitions which need run fsck
        mount_all /vendor/etc/fstab.${ro.boot.hardware.platform} --late

メタデータ暗号化の有効化

最後に、ユーザーデータの fstab.hardware エントリに keydirectory=/metadata/vold/metadata_encryption を追加します。

/dev/block/bootdevice/by-name/userdata              /data              f2fs        noatime,nosuid,nodev,discard latemount,wait,check,fileencryption=ice,keydirectory=/metadata/vold/metadata_encryption,quota,formattable

検証

メタデータ暗号化を実装する際は、次のよくある問題に注意して実装をテストしてください。

よくある問題

メタデータ暗号化済みの /data パーティションをマウントする mount_all の呼び出し中に、init が vdc ツールを実行します。vdc ツールは、binder を介して vold に接続し、メタデータ暗号化済みのデバイスを設定してパーティションをマウントします。この呼び出しの間は init がブロックされ、init プロパティの読み取りまたは設定の試行は、mount_all が終了するまでブロックされます。この段階で、プロパティの読み取りまたは設定に関する vold のいずれかの作業が直接的または間接的にブロックされると、デッドロックが発生します。voldinit の介入なしで、鍵の読み取り、Keymaster とのやり取り、データ ディレクトリのマウントを完了できることが重要です。

Keymaster は mount_all の実行時に完全に起動していないと、init から特定のプロパティを読み込むまで vold に応答しません。その結果、上述のデッドロックが発生します。前述したように、関連する mount_all 呼び出しの上に exec_start wait_for_keymaster を追加すると、事前に Keymaster が完全に動作して、このデッドロックを回避できます。

メタデータ暗号化のテスト

これらのテストは間もなく完成しますが、それまでの間は Android.bp に数行を追加し、platform/system/voldcheck_encryption.cpp を追加して実装をテストしてください。

Android.bp の変更

以下に示すように Android.bp を変更します。

...
    }

    cc_binary {
        name: "vold",
        defaults: [
            "vold_default_flags",
            "vold_default_libs",
        ],

        srcs: ["main.cpp"],
        static_libs: ["libvold"],
        product_variables: {
            arc: {
                static_libs: [
                    "arc_services_aidl",
                    "libarcobbvolume",
                ],
            },
        },
        init_rc: [
            "vold.rc",
            "wait_for_keymaster.rc",
        ],

        required: [
            "check_encryption",
            "mke2fs",
            "vold_prepare_subdirs",
            "wait_for_keymaster",
        ],
    }
    ...
    
    }

    cc_binary {
        name: "check_encryption",
        defaults: ["vold_default_flags"],
        srcs: [
            "FileDeviceUtils.cpp",
            "check_encryption.cpp",
        ],
        shared_libs: [
            "libbase",
        ],
    }

check_encryption.cpp の追加

check_encryption.cppplatform/system/vold に追加します。

/*
     * Copyright (C) 2017 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    #include "FileDeviceUtils.h"

    #include <cmath>
    #include <string>

    #include <assert.h>
    #include <stdio.h>

    #include <android-base/file.h>
    #include <android-base/logging.h>
    #include <android-base/unique_fd.h>
    using android::base::unique_fd;
    using android::base::ReadFileToString;
    using android::base::WriteStringToFile;

    namespace android {
    namespace vold {
    const size_t sectorsize = 1 << 12;
    const int sectorcount = 1024;
    static double randomness_score(const std::string& checkme) {
        unsigned int freq[256] = {0};
        unsigned int sum = 256;
        double loglaplace = 0;
        for (auto b : checkme) {
            loglaplace -= 8 + log2(static_cast<double>(++freq[static_cast<uint8_t>(b)]) / (sum++));
        }
        return loglaplace;
        LOG(INFO) << "Score: " << loglaplace;  // if negative, not random
        return loglaplace < 0;
    }
    static bool run_test(const std::string& device) {
        unique_fd device_fd(open(device.c_str(), O_RDONLY | O_CLOEXEC));
        if (device_fd.get() == -1) {
            PLOG(ERROR) << "Failed to open " << device;
            return false;
        }
        int randompassed = 0;
        auto buf = std::string(sectorsize, '\0');
        for (int i = 0; i < sectorcount; i++) {
            auto l = read(device_fd.get(), &buf[0], buf.size());
            if (l < 1) {
                PLOG(ERROR) << "Failed read on sector " << i;
                return false;
            }
            if (((size_t)l) != buf.size()) {
                LOG(ERROR) << "Short read on sector " << i;
                return false;
            }
            auto score = randomness_score(buf);
            if (score >= 0) {
                randompassed++;
                LOG(INFO) << "Passed randomness check on sector " << i << " with score " << score;
            } else {
                LOG(ERROR) << "Failed randomness check on sector " << i << " with score " << score;
            }
        }
        LOG(INFO) << "Passed randomness check on " << randompassed << "/" << sectorcount << " sectors";
        return randompassed == sectorcount;
    }
    }  // namespace vold
    }  // namespace android
    int main(int argc, const char* const argv[]) {
        setenv("ANDROID_LOG_TAGS", "*:v", 1);
        android::base::InitLogging(const_cast<char**>(argv), android::base::StderrLogger);
        if (argc != 2) {
            LOG(ERROR) << "Usage: " << argv[0] << " <device>";
            LOG(ERROR) << "example: " << argv[0] << " /dev/block/bootdevice/by-name/userdata";
            return -1;
        }
        android::vold::run_test(std::string(argv[1]));
        return 0;
    }