avbtool: verify_image: Verify hash-, hashtree, and chain-descriptors. am: 5dfb4e9999 am: 4c3125a6ea am: 8aef401cdd
am: 9803f2739e
Change-Id: I97aaf8089a22a88e4afb06c86f044d89584442aa
diff --git a/README.md b/README.md
index 9b7fc5b..459308a 100644
--- a/README.md
+++ b/README.md
@@ -381,11 +381,6 @@
/path/to/my_signing_program SHA256_RSA2048 /path/to/publickey.pem
-The `verify_image` command verifies that the signature on the vbmeta
-struct is valid and that it was made with the embedded public
-key. This can be used to check that a custom signing helper works as
-intended.
-
The `append_vbmeta_image` command can be used to append an entire
vbmeta blob to the end of another image. This is useful for cases when
not using any vbmeta partitions, for example:
@@ -397,6 +392,60 @@
--vbmeta_image vbmeta.img
$ fastboot flash boot boot-with-vbmeta-appended.img
+The `verify_image` command can be used to verify the contents of
+several image files at the same time. When invoked on an image the
+following checks are performed:
+
+* If the image has a VBMeta struct the signature is checked against
+ the embedded public key. If the image doesn't look like `vbmeta.img`
+ then a footer is looked for and used if present.
+
+* If the option `--key` is passed then a `.pem` file is expected and
+ it's checked that the embedded public key in said VBMeta struct
+ matches the given key.
+
+* All descriptors in the VBMeta struct are checked in the following
+ way:
+ + For a hash descriptor the image file corresponding to the
+ partition name is loaded and its digest is checked against that
+ in the descriptor.
+ + For a hashtree descriptor the image file corresponding to the
+ partition name is loaded and the hashtree is calculated and its
+ root digest compared to that in the descriptor.
+ + For a chained partition descriptor its contents is compared
+ against content that needs to be passed in via the
+ `--expected_chain_partition` options. The format for this option
+ is similar to that of the `--chain_partition` option. If there
+ is no `--expected_chain_partition` descriptor for the chain
+ partition descriptor the check fails.
+
+Here's an example for a setup where the digests for `boot.img` and
+`system.img` are stored in `vbmeta.img` which is signed with
+`my_key.pem`. It also checks that the chain partition for partition
+`foobar` uses rollback index 8 and that the public key in AVB format
+matches that of the file `foobar_vendor_key.avbpubkey`:
+
+ $ avbtool verify_image \
+ --image /path/to/vbmeta.img \
+ --key my_key.pem \
+ --expect_chained_partition foobar:8:foobar_vendor_key.avbpubkey
+
+ Verifying image /path/to/vbmeta.img using key at my_key.pem
+ vbmeta: Successfully verified SHA256_RSA4096 vbmeta struct in /path_to/vbmeta.img
+ boot: Successfully verified sha256 hash of /path/to/boot.img for image of 10543104 bytes
+ system: Successfully verified sha1 hashtree of /path/to/system.img for image of 1065213952 bytes
+ foobar: Successfully verified chain partition descriptor matches expected data
+
+In this example the `verify_image` command verifies the files
+`vbmeta.img`, `boot.img`, and `system.img` in the directory
+`/path/to`. The directory and file extension of the given image
+(e.g. `/path/to/vbmeta.img`) is used together with the partition name
+in the descriptor to calculate the filenames of the images holding
+hash and hashtree images.
+
+The `verify_image` command can also be used to check that a custom
+signing helper works as intended.
+
## Build System Integration
In Android, AVB is enabled by the `BOARD_AVB_ENABLE` variable
diff --git a/avbtool b/avbtool
index 589e555..1a97ea4 100755
--- a/avbtool
+++ b/avbtool
@@ -1061,6 +1061,20 @@
ret = desc + self.data + padding
return bytearray(ret)
+ def verify(self, image_dir, image_ext, expected_chain_partitions_map):
+ """Verifies contents of the descriptor - used in verify_image sub-command.
+
+ Arguments:
+ image_dir: The directory of the file being verified.
+ image_ext: The extension of the file being verified (e.g. '.img').
+ expected_chain_partitions_map: A map from partition name to the
+ tuple (rollback_index_location, key_blob).
+
+ Returns:
+ True if the descriptor verifies, False otherwise.
+ """
+ # Nothing to do.
+ return True
class AvbPropertyDescriptor(AvbDescriptor):
"""A class for property descriptors.
@@ -1131,6 +1145,20 @@
ret = desc + self.key + '\0' + self.value + '\0' + padding
return bytearray(ret)
+ def verify(self, image_dir, image_ext, expected_chain_partitions_map):
+ """Verifies contents of the descriptor - used in verify_image sub-command.
+
+ Arguments:
+ image_dir: The directory of the file being verified.
+ image_ext: The extension of the file being verified (e.g. '.img').
+ expected_chain_partitions_map: A map from partition name to the
+ tuple (rollback_index_location, key_blob).
+
+ Returns:
+ True if the descriptor verifies, False otherwise.
+ """
+ # Nothing to do.
+ return True
class AvbHashtreeDescriptor(AvbDescriptor):
"""A class for hashtree descriptors.
@@ -1272,6 +1300,52 @@
ret = desc + encoded_name + self.salt + self.root_digest + padding
return bytearray(ret)
+ def verify(self, image_dir, image_ext, expected_chain_partitions_map):
+ """Verifies contents of the descriptor - used in verify_image sub-command.
+
+ Arguments:
+ image_dir: The directory of the file being verified.
+ image_ext: The extension of the file being verified (e.g. '.img').
+ expected_chain_partitions_map: A map from partition name to the
+ tuple (rollback_index_location, key_blob).
+
+ Returns:
+ True if the descriptor verifies, False otherwise.
+ """
+ image_filename = os.path.join(image_dir, self.partition_name + image_ext)
+ image = ImageHandler(image_filename)
+ # Generate the hashtree and checks that it matches what's in the file.
+ digest_size = len(hashlib.new(name=self.hash_algorithm).digest())
+ digest_padding = round_to_pow2(digest_size) - digest_size
+ (hash_level_offsets, tree_size) = calc_hash_level_offsets(
+ self.image_size, self.data_block_size, digest_size + digest_padding)
+ root_digest, hash_tree = generate_hash_tree(image, self.image_size,
+ self.data_block_size,
+ self.hash_algorithm, self.salt,
+ digest_padding,
+ hash_level_offsets,
+ tree_size)
+ # The root digest must match...
+ if root_digest != self.root_digest:
+ sys.stderr.write('hashtree of {} does not match descriptor\n'.
+ format(image_filename))
+ return False
+ # ... also check that the on-disk hashtree matches
+ image.seek(self.tree_offset)
+ hash_tree_ondisk = image.read(self.tree_size)
+ if hash_tree != hash_tree_ondisk:
+ sys.stderr.write('hashtree of {} contains invalid data\n'.
+ format(image_filename))
+ return False
+ # TODO: we could also verify that the FEC stored in the image is
+ # correct but this a) currently requires the 'fec' binary; and b)
+ # takes a long time; and c) is not strictly needed for
+ # verification purposes as we've already verified the root hash.
+ print ('{}: Successfully verified {} hashtree of {} for image of {} bytes'
+ .format(self.partition_name, self.hash_algorithm, image_filename,
+ self.image_size))
+ return True
+
class AvbHashDescriptor(AvbDescriptor):
"""A class for hash descriptors.
@@ -1371,6 +1445,34 @@
ret = desc + encoded_name + self.salt + self.digest + padding
return bytearray(ret)
+ def verify(self, image_dir, image_ext, expected_chain_partitions_map):
+ """Verifies contents of the descriptor - used in verify_image sub-command.
+
+ Arguments:
+ image_dir: The directory of the file being verified.
+ image_ext: The extension of the file being verified (e.g. '.img').
+ expected_chain_partitions_map: A map from partition name to the
+ tuple (rollback_index_location, key_blob).
+
+ Returns:
+ True if the descriptor verifies, False otherwise.
+ """
+ image_filename = os.path.join(image_dir, self.partition_name + image_ext)
+ image = ImageHandler(image_filename)
+ data = image.read(self.image_size)
+ ha = hashlib.new(self.hash_algorithm)
+ ha.update(self.salt)
+ ha.update(data)
+ digest = ha.digest()
+ if digest != self.digest:
+ sys.stderr.write('{} digest of {} does not match digest in descriptor\n'.
+ format(self.hash_algorithm, image_filename))
+ return False
+ print ('{}: Successfully verified {} hash of {} for image of {} bytes'
+ .format(self.partition_name, self.hash_algorithm, image_filename,
+ self.image_size))
+ return True
+
class AvbKernelCmdlineDescriptor(AvbDescriptor):
"""A class for kernel command-line descriptors.
@@ -1447,6 +1549,20 @@
ret = desc + encoded_str + padding
return bytearray(ret)
+ def verify(self, image_dir, image_ext, expected_chain_partitions_map):
+ """Verifies contents of the descriptor - used in verify_image sub-command.
+
+ Arguments:
+ image_dir: The directory of the file being verified.
+ image_ext: The extension of the file being verified (e.g. '.img').
+ expected_chain_partitions_map: A map from partition name to the
+ tuple (rollback_index_location, key_blob).
+
+ Returns:
+ True if the descriptor verifies, False otherwise.
+ """
+ # Nothing to verify.
+ return True
class AvbChainPartitionDescriptor(AvbDescriptor):
"""A class for chained partition descriptors.
@@ -1534,6 +1650,45 @@
ret = desc + encoded_name + self.public_key + padding
return bytearray(ret)
+ def verify(self, image_dir, image_ext, expected_chain_partitions_map):
+ """Verifies contents of the descriptor - used in verify_image sub-command.
+
+ Arguments:
+ image_dir: The directory of the file being verified.
+ image_ext: The extension of the file being verified (e.g. '.img').
+ expected_chain_partitions_map: A map from partition name to the
+ tuple (rollback_index_location, key_blob).
+
+ Returns:
+ True if the descriptor verifies, False otherwise.
+ """
+ value = expected_chain_partitions_map.get(self.partition_name)
+ if not value:
+ sys.stderr.write('No expected chain partition for partition {}. Use '
+ '--expected_chain_partition to specify expected '
+ 'contents.\n'.
+ format(self.partition_name))
+ return False
+ rollback_index_location, pk_blob = value
+
+ if self.rollback_index_location != rollback_index_location:
+ sys.stderr.write('Expected rollback_index_location {} does not '
+ 'match {} in descriptor for partition {}\n'.
+ format(rollback_index_location,
+ self.rollback_index_location,
+ self.partition_name))
+ return False
+
+ if self.public_key != pk_blob:
+ sys.stderr.write('Expected public key blob does not match public '
+ 'key blob in descriptor for partition {}\n'.
+ format(self.partition_name))
+ return False
+
+ print ('{}: Successfully verified chain partition descriptor matches '
+ 'expected data'.format(self.partition_name))
+
+ return True
DESCRIPTOR_CLASSES = [
AvbPropertyDescriptor, AvbHashtreeDescriptor, AvbHashDescriptor,
@@ -1948,13 +2103,38 @@
if num_printed == 0:
o.write(' (none)\n')
- def verify_image(self, image_filename):
+ def verify_image(self, image_filename, key_path, expected_chain_partitions):
"""Implements the 'verify_image' command.
Arguments:
image_filename: Image file to get information from (file object).
+ key_path: None or check that embedded public key matches key at given path.
+ expected_chain_partitions: List of chain partitions to check or None.
"""
+ expected_chain_partitions_map = {}
+ if expected_chain_partitions:
+ used_locations = {}
+ for cp in expected_chain_partitions:
+ cp_tokens = cp.split(':')
+ if len(cp_tokens) != 3:
+ raise AvbError('Malformed chained partition "{}".'.format(cp))
+ partition_name = cp_tokens[0]
+ rollback_index_location = int(cp_tokens[1])
+ file_path = cp_tokens[2]
+ pk_blob = open(file_path).read()
+ expected_chain_partitions_map[partition_name] = (rollback_index_location, pk_blob)
+
+ image_dir = os.path.dirname(image_filename)
+ image_ext = os.path.splitext(image_filename)[1]
+
+ key_blob = None
+ if key_path:
+ print 'Verifying image {} using key at {}'.format(image_filename, key_path)
+ key_blob = encode_rsa_key(key_path)
+ else:
+ print 'Verifying image {} using embedded public key'.format(image_filename)
+
image = ImageHandler(image_filename)
(footer, header, descriptors, image_size) = self._parse_image(image)
offset = 0
@@ -1964,8 +2144,32 @@
header.auxiliary_data_block_size)
image.seek(offset)
vbmeta_blob = image.read(size)
+ h = AvbVBMetaHeader(vbmeta_blob[0:AvbVBMetaHeader.SIZE])
+ alg_name, _ = lookup_algorithm_by_type(header.algorithm_type)
if not verify_vbmeta_signature(header, vbmeta_blob):
- raise AvbError('Signature check failed.')
+ raise AvbError('Signature check failed for {} vbmeta struct {}'
+ .format(alg_name, image_filename))
+
+ if key_blob:
+ # The embedded public key is in the auxiliary block at an offset.
+ key_offset = AvbVBMetaHeader.SIZE
+ key_offset += h.authentication_data_block_size
+ key_offset += h.public_key_offset
+ key_blob_in_vbmeta = vbmeta_blob[key_offset:key_offset + h.public_key_size]
+ if key_blob != key_blob_in_vbmeta:
+ raise AvbError('Embedded public key does not match given key.')
+
+ if footer:
+ print ('vbmeta: Successfully verified footer and {} vbmeta struct in {}'
+ .format(alg_name, image_filename))
+ else:
+ print ('vbmeta: Successfully verified {} vbmeta struct in {}'
+ .format(alg_name, image_filename))
+
+ for desc in descriptors:
+ if not desc.verify(image_dir, image_ext, expected_chain_partitions_map):
+ raise AvbError('Error verifying descriptor.')
+
def _parse_image(self, image):
"""Gets information about an image.
@@ -3407,6 +3611,14 @@
help='Image to verify',
type=argparse.FileType('rb'),
required=True)
+ sub_parser.add_argument('--key',
+ help='Check embedded public key matches KEY',
+ metavar='KEY',
+ required=False)
+ sub_parser.add_argument('--expected_chain_partition',
+ help='Expected chain partition',
+ metavar='PART_NAME:ROLLBACK_SLOT:KEY_PATH',
+ action='append')
sub_parser.set_defaults(func=self.verify_image)
sub_parser = subparsers.add_parser('set_ab_metadata',
@@ -3591,7 +3803,8 @@
def verify_image(self, args):
"""Implements the 'verify_image' sub-command."""
- self.avb.verify_image(args.image.name)
+ self.avb.verify_image(args.image.name, args.key,
+ args.expected_chain_partition)
def make_atx_certificate(self, args):
"""Implements the 'make_atx_certificate' sub-command."""
diff --git a/test/avbtool_unittest.cc b/test/avbtool_unittest.cc
index 9d53f6c..f1c1831 100644
--- a/test/avbtool_unittest.cc
+++ b/test/avbtool_unittest.cc
@@ -37,6 +37,7 @@
#include <libavb/libavb.h>
#include "avb_unittest_util.h"
+#include "fake_avb_ops.h"
namespace avb {
@@ -44,9 +45,20 @@
public:
AvbToolTest() {}
+ virtual void SetUp() override {
+ BaseAvbToolTest::SetUp();
+ ops_.set_partition_dir(testdir_);
+ ops_.set_stored_rollback_indexes({{0, 0}, {1, 0}, {2, 0}, {3, 0}});
+ ops_.set_stored_is_device_unlocked(false);
+ }
+
void AddHashFooterTest(bool sparse_image);
void AddHashtreeFooterTest(bool sparse_image);
void AddHashtreeFooterFECTest(bool sparse_image);
+
+ void GenerateImageWithHashAndHashtreeSetup();
+
+ FakeAvbOps ops_;
};
// This test ensure that the version is increased in both
@@ -1599,6 +1611,53 @@
vbmeta_image_path_.value().c_str());
}
+TEST_F(AvbToolTest, VerifyImageCorruptedVBMeta) {
+ GenerateVBMetaImage("vbmeta.img",
+ "SHA256_RSA2048",
+ 0,
+ base::FilePath("test/data/testkey_rsa2048.pem"));
+
+ // Corrupt four bytes of data in the end of the image. Since the aux
+ // data is at the end and this data is signed, this will change the
+ // value of the computed hash.
+ uint8_t corrupt_data[4] = {0xff, 0xff, 0xff, 0xff};
+ EXPECT_EQ(AVB_IO_RESULT_OK,
+ ops_.avb_ops()->write_to_partition(ops_.avb_ops(),
+ "vbmeta",
+ -4, // offset from end
+ sizeof corrupt_data,
+ corrupt_data));
+
+ EXPECT_COMMAND(1,
+ "./avbtool verify_image "
+ "--image %s ",
+ vbmeta_image_path_.value().c_str());
+}
+
+TEST_F(AvbToolTest, VerifyImageOtherKeyMatching) {
+ GenerateVBMetaImage("vbmeta.img",
+ "SHA256_RSA2048",
+ 0,
+ base::FilePath("test/data/testkey_rsa2048.pem"));
+
+ EXPECT_COMMAND(0,
+ "./avbtool verify_image "
+ "--image %s --key test/data/testkey_rsa2048.pem",
+ vbmeta_image_path_.value().c_str());
+}
+
+TEST_F(AvbToolTest, VerifyImageOtherKeyNotMatching) {
+ GenerateVBMetaImage("vbmeta.img",
+ "SHA256_RSA2048",
+ 0,
+ base::FilePath("test/data/testkey_rsa2048.pem"));
+
+ EXPECT_COMMAND(1,
+ "./avbtool verify_image "
+ "--image %s --key test/data/testkey_rsa4096.pem",
+ vbmeta_image_path_.value().c_str());
+}
+
TEST_F(AvbToolTest, VerifyImageBrokenSignature) {
base::FilePath vbmeta_path = testdir_.Append("vbmeta.bin");
base::FilePath signing_helper_test_path =
@@ -1620,6 +1679,176 @@
vbmeta_path.value().c_str());
}
+// Helper to generate boot.img, unsparse system.img, and vbmeta.img.
+void AvbToolTest::GenerateImageWithHashAndHashtreeSetup() {
+ const size_t boot_partition_size = 16 * 1024 * 1024;
+ const size_t boot_image_size = 5 * 1024 * 1024;
+ base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);
+ EXPECT_COMMAND(0,
+ "./avbtool add_hash_footer"
+ " --image %s"
+ " --rollback_index 0"
+ " --partition_name boot"
+ " --partition_size %zd"
+ " --salt deadbeef"
+ " --internal_release_string \"\"",
+ boot_path.value().c_str(),
+ boot_partition_size);
+
+ const size_t system_partition_size = 10 * 1024 * 1024;
+ const size_t system_image_size = 8 * 1024 * 1024;
+ base::FilePath system_path = GenerateImage("system.img", system_image_size);
+ EXPECT_COMMAND(0,
+ "./avbtool add_hashtree_footer --salt d00df00d --image %s "
+ "--partition_size %zd --partition_name system "
+ "--internal_release_string \"\" ",
+ system_path.value().c_str(),
+ system_partition_size);
+
+ GenerateVBMetaImage("vbmeta.img",
+ "SHA256_RSA2048",
+ 0,
+ base::FilePath("test/data/testkey_rsa2048.pem"),
+ base::StringPrintf("--include_descriptors_from_image %s "
+ "--include_descriptors_from_image %s",
+ boot_path.value().c_str(),
+ system_path.value().c_str()));
+}
+
+TEST_F(AvbToolTest, VerifyImageWithHashAndHashtree) {
+ GenerateImageWithHashAndHashtreeSetup();
+
+ // Do two checks - one for system.img not sparse, and one where it
+ // is sparse.
+ for (int n = 0; n < 2; n++) {
+ EXPECT_COMMAND(0,
+ "./avbtool verify_image "
+ "--image %s ",
+ vbmeta_image_path_.value().c_str());
+ if (n == 0) {
+ EXPECT_COMMAND(0,
+ "img2simg %s %s.sparse",
+ testdir_.Append("system.img").value().c_str(),
+ testdir_.Append("system.img").value().c_str());
+ EXPECT_COMMAND(0,
+ "mv %s.sparse %s",
+ testdir_.Append("system.img").value().c_str(),
+ testdir_.Append("system.img").value().c_str());
+ }
+ }
+}
+
+TEST_F(AvbToolTest, VerifyImageWithHashAndHashtreeCorruptHash) {
+ GenerateImageWithHashAndHashtreeSetup();
+
+ // Corrupt four bytes of data in the middle of boot.img.
+ uint8_t corrupt_data[4] = {0xff, 0xff, 0xff, 0xff};
+ EXPECT_EQ(AVB_IO_RESULT_OK,
+ ops_.avb_ops()->write_to_partition(ops_.avb_ops(),
+ "boot",
+ 105 * 1024, // offset from start
+ sizeof corrupt_data,
+ corrupt_data));
+
+ EXPECT_COMMAND(1,
+ "./avbtool verify_image "
+ "--image %s ",
+ vbmeta_image_path_.value().c_str());
+}
+
+TEST_F(AvbToolTest, VerifyImageWithHashAndHashtreeCorruptHashtree) {
+ GenerateImageWithHashAndHashtreeSetup();
+
+ // Corrupt four bytes of data in the middle of system.img.
+ uint8_t corrupt_data[4] = {0xff, 0xff, 0xff, 0xff};
+ EXPECT_EQ(AVB_IO_RESULT_OK,
+ ops_.avb_ops()->write_to_partition(ops_.avb_ops(),
+ "system",
+ 123 * 1024, // offset from start
+ sizeof corrupt_data,
+ corrupt_data));
+
+ // Do two checks - one for system.img not sparse, and one where it
+ // is sparse.
+ for (int n = 0; n < 2; n++) {
+ EXPECT_COMMAND(1,
+ "./avbtool verify_image "
+ "--image %s ",
+ vbmeta_image_path_.value().c_str());
+ if (n == 0) {
+ EXPECT_COMMAND(0,
+ "img2simg %s %s.sparse",
+ testdir_.Append("system.img").value().c_str(),
+ testdir_.Append("system.img").value().c_str());
+ EXPECT_COMMAND(0,
+ "mv %s.sparse %s",
+ testdir_.Append("system.img").value().c_str(),
+ testdir_.Append("system.img").value().c_str());
+ }
+ }
+}
+
+TEST_F(AvbToolTest, VerifyImageChainPartition) {
+ base::FilePath pk4096_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+ EXPECT_COMMAND(
+ 0,
+ "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+ " --output %s",
+ pk4096_path.value().c_str());
+
+ base::FilePath pk8192_path = testdir_.Append("testkey_rsa8192.avbpubkey");
+ EXPECT_COMMAND(
+ 0,
+ "./avbtool extract_public_key --key test/data/testkey_rsa8192.pem"
+ " --output %s",
+ pk8192_path.value().c_str());
+
+ GenerateVBMetaImage("vbmeta.img",
+ "SHA256_RSA2048",
+ 0,
+ base::FilePath("test/data/testkey_rsa2048.pem"),
+ base::StringPrintf("--chain_partition system:1:%s ",
+ pk4096_path.value().c_str()));
+
+ // Should not fail (name, rollback_index, contents all correct).
+ EXPECT_COMMAND(0,
+ "./avbtool verify_image "
+ "--image %s "
+ "--expected_chain_partition system:1:%s",
+ vbmeta_image_path_.value().c_str(),
+ pk4096_path.value().c_str());
+
+ // Should fail because we didn't use --expected_chain_partition.
+ EXPECT_COMMAND(1,
+ "./avbtool verify_image "
+ "--image %s ",
+ vbmeta_image_path_.value().c_str());
+
+ // Should fail because partition name is wrong.
+ EXPECT_COMMAND(1,
+ "./avbtool verify_image "
+ "--image %s "
+ "--expected_chain_partition xyz:1:%s",
+ vbmeta_image_path_.value().c_str(),
+ pk4096_path.value().c_str());
+
+ // Should fail because rollback index location is wrong.
+ EXPECT_COMMAND(1,
+ "./avbtool verify_image "
+ "--image %s "
+ "--expected_chain_partition system:2:%s",
+ vbmeta_image_path_.value().c_str(),
+ pk4096_path.value().c_str());
+
+ // Should fail because public key blob is wrong.
+ EXPECT_COMMAND(1,
+ "./avbtool verify_image "
+ "--image %s "
+ "--expected_chain_partition system:1:%s",
+ vbmeta_image_path_.value().c_str(),
+ pk8192_path.value().c_str());
+}
+
TEST_F(AvbToolTest, MakeAtxPikCertificate) {
base::FilePath subject_path = testdir_.Append("tmp_subject");
ASSERT_TRUE(base::WriteFile(subject_path, "fake PIK subject", 16));