libavb_atx: Implement authenticated unlock
The ATX reference code implements two operations:
- Generation of an unlock challenge.
- Validation of an unlock credential provided as a response to the most
recent challenge.
Additionally, avbtool has been enhanced to produce unlock credentials.
Bug: 72234756
Test: Unit tests
Change-Id: Ic837fdae07461332311164ac7cccf2fe463dd1e3
diff --git a/avbtool b/avbtool
index 7be7027..e951775 100755
--- a/avbtool
+++ b/avbtool
@@ -3224,7 +3224,7 @@
def make_atx_certificate(self, output, authority_key_path, subject_key_path,
subject_key_version, subject,
- is_intermediate_authority, signing_helper,
+ is_intermediate_authority, usage, signing_helper,
signing_helper_with_files):
"""Implements the 'make_atx_certificate' command.
@@ -3246,6 +3246,7 @@
should be the same Product ID found in the permanent attributes.
is_intermediate_authority: True if the certificate is for an intermediate
authority.
+ usage: If not empty, overrides the cert usage with a hash of this value.
signing_helper: Program which signs a hash and returns the signature.
signing_helper_with_files: Same as signing_helper but uses files instead.
"""
@@ -3255,9 +3256,10 @@
hasher = hashlib.sha256()
hasher.update(subject)
signed_data.extend(hasher.digest())
- usage = 'com.google.android.things.vboot'
- if is_intermediate_authority:
- usage += '.ca'
+ if not usage:
+ usage = 'com.google.android.things.vboot'
+ if is_intermediate_authority:
+ usage += '.ca'
hasher = hashlib.sha256()
hasher.update(usage)
signed_data.extend(hasher.digest())
@@ -3333,6 +3335,67 @@
output.write(intermediate_key_certificate)
output.write(product_key_certificate)
+ def make_atx_unlock_credential(self, output, intermediate_key_certificate,
+ unlock_key_certificate, challenge_path,
+ unlock_key_path, signing_helper,
+ signing_helper_with_files):
+ """Implements the 'make_atx_unlock_credential' command.
+
+ Android Things unlock credentials can be used to authorize the unlock of AVB
+ on a device. These credentials are presented to an Android Things bootloader
+ via the fastboot interface in response to a 16-byte challenge. This method
+ creates all fields of the credential except the challenge signature field
+ (which is the last field) and can optionally create the challenge signature
+ field as well if a challenge and the unlock_key_path is provided.
+
+ Arguments:
+ output: The credential will be written to this file on success.
+ intermediate_key_certificate: A certificate file as output by
+ make_atx_certificate with
+ is_intermediate_authority set to true.
+ unlock_key_certificate: A certificate file as output by
+ make_atx_certificate with
+ is_intermediate_authority set to false and the
+ usage set to
+ 'com.google.android.things.vboot.unlock'.
+ challenge_path: [optional] A path to the challenge to sign.
+ unlock_key_path: [optional] A PEM file path with the unlock private key.
+ signing_helper: Program which signs a hash and returns the signature.
+ signing_helper_with_files: Same as signing_helper but uses files instead.
+
+ Raises:
+ AvbError: If an argument is incorrect.
+ """
+ EXPECTED_CERTIFICATE_SIZE = 1620
+ EXPECTED_CHALLENGE_SIZE = 16
+ if len(intermediate_key_certificate) != EXPECTED_CERTIFICATE_SIZE:
+ raise AvbError('Invalid intermediate key certificate length.')
+ if len(unlock_key_certificate) != EXPECTED_CERTIFICATE_SIZE:
+ raise AvbError('Invalid product key certificate length.')
+ challenge = bytearray()
+ if challenge_path:
+ with open(challenge_path, 'r') as f:
+ challenge = f.read()
+ if len(challenge) != EXPECTED_CHALLENGE_SIZE:
+ raise AvbError('Invalid unlock challenge length.')
+ output.write(struct.pack('<I', 1)) # Format Version
+ output.write(intermediate_key_certificate)
+ output.write(unlock_key_certificate)
+ if challenge_path and unlock_key_path:
+ signature = bytearray()
+ padding_and_hash = bytearray()
+ algorithm_name = 'SHA512_RSA4096'
+ alg = ALGORITHMS[algorithm_name]
+ hasher = hashlib.sha512()
+ padding_and_hash.extend(alg.padding)
+ hasher.update(challenge)
+ padding_and_hash.extend(hasher.digest())
+ signature.extend(raw_sign(signing_helper, signing_helper_with_files,
+ algorithm_name,
+ alg.signature_num_bytes, unlock_key_path,
+ padding_and_hash))
+ output.write(signature)
+
def calc_hash_level_offsets(image_size, block_size, digest_size):
"""Calculate the offsets of all the hash-levels in a Merkle-tree.
@@ -3846,6 +3909,10 @@
help=('Generate an intermediate authority '
'certificate'),
action='store_true')
+ sub_parser.add_argument('--usage',
+ help=('Override usage with a hash of the provided'
+ 'string'),
+ required=False)
sub_parser.add_argument('--authority_key',
help='Path to authority RSA private key file',
required=False)
@@ -3895,6 +3962,43 @@
required=True)
sub_parser.set_defaults(func=self.make_atx_metadata)
+ sub_parser = subparsers.add_parser(
+ 'make_atx_unlock_credential',
+ help='Create an Android Things eXtension (ATX) unlock credential.')
+ sub_parser.add_argument('--output',
+ help='Write credential to file',
+ type=argparse.FileType('wb'),
+ default=sys.stdout)
+ sub_parser.add_argument('--intermediate_key_certificate',
+ help='Path to intermediate key certificate file',
+ type=argparse.FileType('rb'),
+ required=True)
+ sub_parser.add_argument('--unlock_key_certificate',
+ help='Path to unlock key certificate file',
+ type=argparse.FileType('rb'),
+ required=True)
+ sub_parser.add_argument('--challenge',
+ help='Path to the challenge to sign (optional). If '
+ 'this is not provided the challenge signature '
+ 'field is omitted and can be concatenated '
+ 'later.',
+ required=False)
+ sub_parser.add_argument('--unlock_key',
+ help='Path to unlock key (optional). Must be '
+ 'provided if using --challenge.',
+ required=False)
+ sub_parser.add_argument('--signing_helper',
+ help='Path to helper used for signing',
+ metavar='APP',
+ default=None,
+ required=False)
+ sub_parser.add_argument('--signing_helper_with_files',
+ help='Path to helper used for signing using files',
+ metavar='APP',
+ default=None,
+ required=False)
+ sub_parser.set_defaults(func=self.make_atx_unlock_credential)
+
args = parser.parse_args(argv[1:])
try:
args.func(args)
@@ -4017,6 +4121,7 @@
args.subject_key_version,
args.subject.read(),
args.subject_is_intermediate_authority,
+ args.usage,
args.signing_helper,
args.signing_helper_with_files)
@@ -4032,6 +4137,17 @@
args.intermediate_key_certificate.read(),
args.product_key_certificate.read())
+ def make_atx_unlock_credential(self, args):
+ """Implements the 'make_atx_unlock_credential' sub-command."""
+ self.avb.make_atx_unlock_credential(
+ args.output,
+ args.intermediate_key_certificate.read(),
+ args.unlock_key_certificate.read(),
+ args.challenge,
+ args.unlock_key,
+ args.signing_helper,
+ args.signing_helper_with_files)
+
if __name__ == '__main__':
tool = AvbTool()
diff --git a/libavb_atx/avb_atx_ops.h b/libavb_atx/avb_atx_ops.h
index 182517f..53c898d 100644
--- a/libavb_atx/avb_atx_ops.h
+++ b/libavb_atx/avb_atx_ops.h
@@ -66,6 +66,15 @@
void (*set_key_version)(AvbAtxOps* atx_ops,
size_t rollback_index_location,
uint64_t key_version);
+
+ /* Generates |num_bytes| random bytes and stores them in |output|,
+ * which must point to a buffer large enough to store the bytes.
+ *
+ * Returns AVB_IO_RESULT_OK on success, otherwise an error code.
+ */
+ AvbIOResult (*get_random)(AvbAtxOps* atx_ops,
+ size_t num_bytes,
+ uint8_t* output);
};
#ifdef __cplusplus
diff --git a/libavb_atx/avb_atx_types.h b/libavb_atx/avb_atx_types.h
index 6d69859..e78bbfa 100644
--- a/libavb_atx/avb_atx_types.h
+++ b/libavb_atx/avb_atx_types.h
@@ -39,6 +39,9 @@
/* Size in bytes of an Android Things product ID. */
#define AVB_ATX_PRODUCT_ID_SIZE 16
+/* Size in bytes of an Android Things unlock challenge. */
+#define AVB_ATX_UNLOCK_CHALLENGE_SIZE 16
+
/* Size in bytes of a serialized public key with a 4096-bit modulus. */
#define AVB_ATX_PUBLIC_KEY_SIZE (sizeof(AvbRSAPublicKeyHeader) + 1024)
@@ -71,6 +74,21 @@
AvbAtxCertificate product_signing_key_certificate;
} AVB_ATTR_PACKED AvbAtxPublicKeyMetadata;
+/* Data structure of an Android Things unlock challenge. */
+typedef struct AvbAtxUnlockChallenge {
+ uint32_t version;
+ uint8_t product_id_hash[AVB_SHA256_DIGEST_SIZE];
+ uint8_t challenge[AVB_ATX_UNLOCK_CHALLENGE_SIZE];
+} AVB_ATTR_PACKED AvbAtxUnlockChallenge;
+
+/* Data structure of an Android Things unlock credential. */
+typedef struct AvbAtxUnlockCredential {
+ uint32_t version;
+ AvbAtxCertificate product_intermediate_key_certificate;
+ AvbAtxCertificate product_unlock_key_certificate;
+ uint8_t challenge_signature[AVB_RSA4096_NUM_BYTES];
+} AVB_ATTR_PACKED AvbAtxUnlockCredential;
+
#ifdef __cplusplus
}
#endif
diff --git a/libavb_atx/avb_atx_validate.c b/libavb_atx/avb_atx_validate.c
index 78cf774..f3c1d96 100644
--- a/libavb_atx/avb_atx_validate.c
+++ b/libavb_atx/avb_atx_validate.c
@@ -29,6 +29,9 @@
#include <libavb/avb_sysdeps.h>
#include <libavb/avb_util.h>
+/* The most recent unlock challenge generated. */
+static uint8_t last_unlock_challenge[AVB_ATX_UNLOCK_CHALLENGE_SIZE];
+
/* Computes the SHA256 |hash| of |length| bytes of |data|. */
static void sha256(const uint8_t* data,
uint32_t length,
@@ -59,7 +62,7 @@
/* Verifies structure and |expected_hash| of permanent |attributes|. */
static bool verify_permanent_attributes(
const AvbAtxPermanentAttributes* attributes,
- uint8_t expected_hash[AVB_SHA256_DIGEST_SIZE]) {
+ const uint8_t expected_hash[AVB_SHA256_DIGEST_SIZE]) {
uint8_t hash[AVB_SHA256_DIGEST_SIZE];
if (attributes->version != 1) {
@@ -75,10 +78,11 @@
}
/* Verifies the format, key version, usage, and signature of a certificate. */
-static bool verify_certificate(AvbAtxCertificate* certificate,
- uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE],
- uint64_t minimum_key_version,
- uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE]) {
+static bool verify_certificate(
+ const AvbAtxCertificate* certificate,
+ const uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE],
+ uint64_t minimum_key_version,
+ const uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE]) {
const AvbAlgorithmData* algorithm_data;
uint8_t certificate_hash[AVB_SHA512_DIGEST_SIZE];
@@ -115,9 +119,10 @@
}
/* Verifies signature and fields of a PIK certificate. */
-static bool verify_pik_certificate(AvbAtxCertificate* certificate,
- uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE],
- uint64_t minimum_version) {
+static bool verify_pik_certificate(
+ const AvbAtxCertificate* certificate,
+ const uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE],
+ uint64_t minimum_version) {
uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE];
sha256_str("com.google.android.things.vboot.ca", expected_usage);
@@ -131,10 +136,10 @@
/* Verifies signature and fields of a PSK certificate. */
static bool verify_psk_certificate(
- AvbAtxCertificate* certificate,
- uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE],
+ const AvbAtxCertificate* certificate,
+ const uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE],
uint64_t minimum_version,
- uint8_t product_id[AVB_ATX_PRODUCT_ID_SIZE]) {
+ const uint8_t product_id[AVB_ATX_PRODUCT_ID_SIZE]) {
uint8_t expected_subject[AVB_SHA256_DIGEST_SIZE];
uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE];
@@ -148,7 +153,32 @@
if (0 != avb_safe_memcmp(certificate->signed_data.subject,
expected_subject,
AVB_SHA256_DIGEST_SIZE)) {
- avb_error("Product ID mismatch.\n");
+ avb_error("PSK: Product ID mismatch.\n");
+ return false;
+ }
+ return true;
+}
+
+/* Verifies signature and fields of a PUK certificate. */
+static bool verify_puk_certificate(
+ const AvbAtxCertificate* certificate,
+ const uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE],
+ uint64_t minimum_version,
+ const uint8_t product_id[AVB_ATX_PRODUCT_ID_SIZE]) {
+ uint8_t expected_subject[AVB_SHA256_DIGEST_SIZE];
+ uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE];
+
+ sha256_str("com.google.android.things.vboot.unlock", expected_usage);
+ if (!verify_certificate(
+ certificate, authority, minimum_version, expected_usage)) {
+ avb_error("Invalid PUK certificate.\n");
+ return false;
+ }
+ sha256(product_id, AVB_ATX_PRODUCT_ID_SIZE, expected_subject);
+ if (0 != avb_safe_memcmp(certificate->signed_data.subject,
+ expected_subject,
+ AVB_SHA256_DIGEST_SIZE)) {
+ avb_error("PUK: Product ID mismatch.\n");
return false;
}
return true;
@@ -254,3 +284,118 @@
*out_is_trusted = true;
return AVB_IO_RESULT_OK;
}
+
+AvbIOResult avb_atx_generate_unlock_challenge(
+ AvbAtxOps* atx_ops, AvbAtxUnlockChallenge* out_unlock_challenge) {
+ AvbIOResult result = AVB_IO_RESULT_OK;
+ AvbAtxPermanentAttributes permanent_attributes;
+
+ /* We need the permanent attributes to compute the product_id_hash. */
+ result = atx_ops->read_permanent_attributes(atx_ops, &permanent_attributes);
+ if (result != AVB_IO_RESULT_OK) {
+ avb_error("Failed to read permanent attributes.\n");
+ return result;
+ }
+ result = atx_ops->get_random(
+ atx_ops, AVB_ATX_UNLOCK_CHALLENGE_SIZE, last_unlock_challenge);
+ if (result != AVB_IO_RESULT_OK) {
+ avb_error("Failed to generate random challenge.\n");
+ return result;
+ }
+ out_unlock_challenge->version = 1;
+ sha256(permanent_attributes.product_id,
+ AVB_ATX_PRODUCT_ID_SIZE,
+ out_unlock_challenge->product_id_hash);
+ avb_memcpy(out_unlock_challenge->challenge,
+ last_unlock_challenge,
+ AVB_ATX_UNLOCK_CHALLENGE_SIZE);
+ return result;
+}
+
+AvbIOResult avb_atx_validate_unlock_credential(
+ AvbAtxOps* atx_ops,
+ const AvbAtxUnlockCredential* unlock_credential,
+ bool* out_is_trusted) {
+ AvbIOResult result = AVB_IO_RESULT_OK;
+ AvbAtxPermanentAttributes permanent_attributes;
+ uint8_t permanent_attributes_hash[AVB_SHA256_DIGEST_SIZE];
+ uint64_t minimum_version;
+ const AvbAlgorithmData* algorithm_data;
+ uint8_t challenge_hash[AVB_SHA512_DIGEST_SIZE];
+
+ /* Be pessimistic so we can exit early without having to remember to clear.
+ */
+ *out_is_trusted = false;
+
+ /* Sanity check the credential. */
+ if (unlock_credential->version != 1) {
+ avb_error("Unsupported unlock credential format.\n");
+ return AVB_IO_RESULT_OK;
+ }
+
+ /* Read and verify permanent attributes. */
+ result = atx_ops->read_permanent_attributes(atx_ops, &permanent_attributes);
+ if (result != AVB_IO_RESULT_OK) {
+ avb_error("Failed to read permanent attributes.\n");
+ return result;
+ }
+ result = atx_ops->read_permanent_attributes_hash(atx_ops,
+ permanent_attributes_hash);
+ if (result != AVB_IO_RESULT_OK) {
+ avb_error("Failed to read permanent attributes hash.\n");
+ return result;
+ }
+ if (!verify_permanent_attributes(&permanent_attributes,
+ permanent_attributes_hash)) {
+ return AVB_IO_RESULT_OK;
+ }
+
+ /* Verify the PIK certificate. */
+ result = atx_ops->ops->read_rollback_index(
+ atx_ops->ops, AVB_ATX_PIK_VERSION_LOCATION, &minimum_version);
+ if (result != AVB_IO_RESULT_OK) {
+ avb_error("Failed to read PIK minimum version.\n");
+ return result;
+ }
+ if (!verify_pik_certificate(
+ &unlock_credential->product_intermediate_key_certificate,
+ permanent_attributes.product_root_public_key,
+ minimum_version)) {
+ return AVB_IO_RESULT_OK;
+ }
+
+ /* Verify the PUK certificate. The minimum version is shared with the PSK. */
+ result = atx_ops->ops->read_rollback_index(
+ atx_ops->ops, AVB_ATX_PSK_VERSION_LOCATION, &minimum_version);
+ if (result != AVB_IO_RESULT_OK) {
+ avb_error("Failed to read PSK minimum version.\n");
+ return result;
+ }
+ if (!verify_puk_certificate(
+ &unlock_credential->product_unlock_key_certificate,
+ unlock_credential->product_intermediate_key_certificate.signed_data
+ .public_key,
+ minimum_version,
+ permanent_attributes.product_id)) {
+ return AVB_IO_RESULT_OK;
+ }
+
+ /* Verify the challenge signature. */
+ algorithm_data = avb_get_algorithm_data(AVB_ALGORITHM_TYPE_SHA512_RSA4096);
+ sha512(last_unlock_challenge, AVB_ATX_UNLOCK_CHALLENGE_SIZE, challenge_hash);
+ if (!avb_rsa_verify(unlock_credential->product_unlock_key_certificate
+ .signed_data.public_key,
+ AVB_ATX_PUBLIC_KEY_SIZE,
+ unlock_credential->challenge_signature,
+ AVB_RSA4096_NUM_BYTES,
+ challenge_hash,
+ AVB_SHA512_DIGEST_SIZE,
+ algorithm_data->padding,
+ algorithm_data->padding_len)) {
+ avb_error("Invalid unlock challenge signature.\n");
+ return AVB_IO_RESULT_OK;
+ }
+
+ *out_is_trusted = true;
+ return AVB_IO_RESULT_OK;
+}
diff --git a/libavb_atx/avb_atx_validate.h b/libavb_atx/avb_atx_validate.h
index d75e5cf..1a0690d 100644
--- a/libavb_atx/avb_atx_validate.h
+++ b/libavb_atx/avb_atx_validate.h
@@ -70,6 +70,20 @@
size_t public_key_metadata_length,
bool* out_is_trusted);
+/* Generates a challenge which can be used to create an unlock credential. */
+AvbIOResult avb_atx_generate_unlock_challenge(
+ AvbAtxOps* atx_ops, AvbAtxUnlockChallenge* out_unlock_challenge);
+
+/* Validates an unlock credential. The certificate validation is very similar to
+ * the validation of public key metadata except in place of the PSK is a Product
+ * Unlock Key (PUK) and the certificate usage field identifies it as such. The
+ * challenge signature field is verified against this PUK.
+ */
+AvbIOResult avb_atx_validate_unlock_credential(
+ AvbAtxOps* atx_ops,
+ const AvbAtxUnlockCredential* unlock_credential,
+ bool* out_is_trusted);
+
#ifdef __cplusplus
}
#endif
diff --git a/test/avb_atx_generate_test_data b/test/avb_atx_generate_test_data
index 542f1b8..1b8bb2b 100755
--- a/test/avb_atx_generate_test_data
+++ b/test/avb_atx_generate_test_data
@@ -63,6 +63,10 @@
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -outform PEM \
-out testkey_atx_psk.pem
fi
+if [ ! -f testkey_atx_puk.pem ]; then
+ openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -outform PEM \
+ -out testkey_atx_puk.pem
+fi
# Construct permanent attributes.
${AVBTOOL} make_atx_permanent_attributes --output=atx_permanent_attributes.bin \
@@ -85,3 +89,17 @@
--intermediate_key_certificate=atx_pik_certificate.bin \
--product_key_certificate=atx_psk_certificate.bin
+# Generate a random unlock challenge.
+head -c 16 /dev/urandom > atx_unlock_challenge.bin
+
+# Construct a PUK certificate.
+${AVBTOOL} make_atx_certificate --output=atx_puk_certificate.bin \
+ --subject=atx_product_id.bin --subject_key=testkey_atx_puk.pem \
+ --usage=com.google.android.things.vboot.unlock --subject_key_version 42 \
+ --authority_key=testkey_atx_pik.pem
+
+# Construct an unlock credential.
+${AVBTOOL} make_atx_unlock_credential --output=atx_unlock_credential.bin \
+ --intermediate_key_certificate=atx_pik_certificate.bin \
+ --unlock_key_certificate=atx_puk_certificate.bin \
+ --challenge=atx_unlock_challenge.bin --unlock_key=testkey_atx_puk.pem
diff --git a/test/avb_atx_slot_verify_unittest.cc b/test/avb_atx_slot_verify_unittest.cc
index 8992c72..ff0abda 100644
--- a/test/avb_atx_slot_verify_unittest.cc
+++ b/test/avb_atx_slot_verify_unittest.cc
@@ -93,6 +93,10 @@
return ops_.set_key_version(rollback_index_location, key_version);
}
+ AvbIOResult get_random(size_t num_bytes, uint8_t* output) override {
+ return ops_.get_random(num_bytes, output);
+ }
+
void RunSlotVerify() {
ops_.set_stored_rollback_indexes(
{{0, initial_rollback_value_},
diff --git a/test/avb_atx_validate_unittest.cc b/test/avb_atx_validate_unittest.cc
index c32ecf8..296045c 100644
--- a/test/avb_atx_validate_unittest.cc
+++ b/test/avb_atx_validate_unittest.cc
@@ -44,6 +44,10 @@
"test/data/atx_permanent_attributes.bin";
const char kPRKPrivateKeyPath[] = "test/data/testkey_atx_prk.pem";
const char kPIKPrivateKeyPath[] = "test/data/testkey_atx_pik.pem";
+const char kPSKPrivateKeyPath[] = "test/data/testkey_atx_psk.pem";
+const char kPUKPrivateKeyPath[] = "test/data/testkey_atx_puk.pem";
+const char kUnlockChallengePath[] = "test/data/atx_unlock_challenge.bin";
+const char kUnlockCredentialPath[] = "test/data/atx_unlock_credential.bin";
class ScopedRSA {
public:
@@ -204,6 +208,22 @@
return ops_.read_permanent_attributes_hash(hash);
}
+ void set_key_version(size_t rollback_index_location,
+ uint64_t key_version) override {
+ ops_.set_key_version(rollback_index_location, key_version);
+ }
+
+ AvbIOResult get_random(size_t num_bytes, uint8_t* output) override {
+ if (fail_get_random_) {
+ return AVB_IO_RESULT_ERROR_IO;
+ }
+ if (fake_random_.size() >= num_bytes) {
+ memcpy(output, fake_random_.data(), num_bytes);
+ return AVB_IO_RESULT_OK;
+ }
+ return ops_.get_random(num_bytes, output);
+ }
+
protected:
virtual AvbIOResult Validate(bool* is_trusted) {
return avb_atx_validate_vbmeta_public_key(
@@ -215,6 +235,11 @@
is_trusted);
}
+ AvbIOResult ValidateUnlock(bool* is_trusted) {
+ return avb_atx_validate_unlock_credential(
+ ops_.avb_atx_ops(), &unlock_credential_, is_trusted);
+ }
+
void SignPIKCertificate() {
memset(metadata_.product_intermediate_key_certificate.signature,
0,
@@ -236,12 +261,55 @@
metadata_.product_signing_key_certificate.signature));
}
+ void SignUnlockCredentialPIKCertificate() {
+ memset(unlock_credential_.product_intermediate_key_certificate.signature,
+ 0,
+ AVB_RSA4096_NUM_BYTES);
+ ScopedRSA key(kPRKPrivateKeyPath);
+ ASSERT_TRUE(key.Sign(
+ &unlock_credential_.product_intermediate_key_certificate.signed_data,
+ sizeof(AvbAtxCertificateSignedData),
+ unlock_credential_.product_intermediate_key_certificate.signature));
+ }
+
+ void SignUnlockCredentialPUKCertificate() {
+ memset(unlock_credential_.product_unlock_key_certificate.signature,
+ 0,
+ AVB_RSA4096_NUM_BYTES);
+ ScopedRSA key(kPIKPrivateKeyPath);
+ ASSERT_TRUE(
+ key.Sign(&unlock_credential_.product_unlock_key_certificate.signed_data,
+ sizeof(AvbAtxCertificateSignedData),
+ unlock_credential_.product_unlock_key_certificate.signature));
+ }
+
+ void SignUnlockCredentialChallenge(const char* key_path) {
+ memset(unlock_credential_.challenge_signature, 0, AVB_RSA4096_NUM_BYTES);
+ ScopedRSA key(key_path);
+ ASSERT_TRUE(key.Sign(unlock_challenge_.data(),
+ unlock_challenge_.size(),
+ unlock_credential_.challenge_signature));
+ }
+
+ bool PrepareUnlockCredential() {
+ // Stage a challenge to be remembered as the 'most recent challenge'. Then
+ // the next call to unlock with |unlock_credential_| is expected to succeed.
+ fake_random_ = unlock_challenge_;
+ AvbAtxUnlockChallenge challenge;
+ return (AVB_IO_RESULT_OK ==
+ avb_atx_generate_unlock_challenge(ops_.avb_atx_ops(), &challenge));
+ }
+
AvbAtxPermanentAttributes attributes_;
AvbAtxPublicKeyMetadata metadata_;
bool fail_read_permanent_attributes_{false};
bool fail_read_permanent_attributes_hash_{false};
bool fail_read_pik_rollback_index_{false};
bool fail_read_psk_rollback_index_{false};
+ bool fail_get_random_{false};
+ std::string fake_random_;
+ AvbAtxUnlockCredential unlock_credential_;
+ std::string unlock_challenge_;
private:
void ReadDefaultData() {
@@ -253,6 +321,13 @@
base::ReadFileToString(base::FilePath(kPermanentAttributesPath), &tmp));
ASSERT_EQ(tmp.size(), sizeof(AvbAtxPermanentAttributes));
memcpy(&attributes_, tmp.data(), tmp.size());
+ ASSERT_TRUE(base::ReadFileToString(base::FilePath(kUnlockChallengePath),
+ &unlock_challenge_));
+ ASSERT_EQ(size_t(AVB_ATX_UNLOCK_CHALLENGE_SIZE), unlock_challenge_.size());
+ ASSERT_TRUE(
+ base::ReadFileToString(base::FilePath(kUnlockCredentialPath), &tmp));
+ ASSERT_EQ(tmp.size(), sizeof(AvbAtxUnlockCredential));
+ memcpy(&unlock_credential_, tmp.data(), tmp.size());
}
};
@@ -563,6 +638,310 @@
EXPECT_FALSE(is_trusted);
}
+TEST_F(AvbAtxValidateTest, GenerateUnlockChallenge) {
+ fake_random_ = std::string(AVB_ATX_UNLOCK_CHALLENGE_SIZE, 'C');
+ AvbAtxUnlockChallenge challenge;
+ EXPECT_EQ(AVB_IO_RESULT_OK,
+ avb_atx_generate_unlock_challenge(ops_.avb_atx_ops(), &challenge));
+ EXPECT_EQ(1UL, challenge.version);
+ EXPECT_EQ(0,
+ memcmp(fake_random_.data(),
+ challenge.challenge,
+ AVB_ATX_UNLOCK_CHALLENGE_SIZE));
+ uint8_t expected_pid_hash[AVB_SHA256_DIGEST_SIZE];
+ SHA256(attributes_.product_id, AVB_ATX_PRODUCT_ID_SIZE, expected_pid_hash);
+ EXPECT_EQ(0,
+ memcmp(expected_pid_hash,
+ challenge.product_id_hash,
+ AVB_SHA256_DIGEST_SIZE));
+}
+
+TEST_F(AvbAtxValidateTest, GenerateUnlockChallenge_NoAttributes) {
+ fail_read_permanent_attributes_ = true;
+ AvbAtxUnlockChallenge challenge;
+ EXPECT_NE(AVB_IO_RESULT_OK,
+ avb_atx_generate_unlock_challenge(ops_.avb_atx_ops(), &challenge));
+}
+
+TEST_F(AvbAtxValidateTest, GenerateUnlockChallenge_NoRNG) {
+ fail_get_random_ = true;
+ AvbAtxUnlockChallenge challenge;
+ EXPECT_NE(AVB_IO_RESULT_OK,
+ avb_atx_generate_unlock_challenge(ops_.avb_atx_ops(), &challenge));
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_TRUE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_UnsupportedVersion) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.version++;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_NoAttributes) {
+ PrepareUnlockCredential();
+ fail_read_permanent_attributes_ = true;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_NoAttributesHash) {
+ PrepareUnlockCredential();
+ fail_read_permanent_attributes_hash_ = true;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest,
+ ValidateUnlockCredential_UnsupportedAttributesVersion) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ attributes_.version = 25;
+ ops_.set_permanent_attributes(attributes_);
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_AttributesHashMismatch) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ ops_.set_permanent_attributes_hash("bad_hash");
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_FailReadPIKRollbackIndex) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ fail_read_pik_rollback_index_ = true;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest,
+ ValidateUnlockCredential_UnsupportedPIKCertificateVersion) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_intermediate_key_certificate.signed_data.version =
+ 25;
+ SignUnlockCredentialPIKCertificate();
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest,
+ ValidateUnlockCredential_BadPIKCert_ModifiedSubjectPublicKey) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_intermediate_key_certificate.signed_data
+ .public_key[0] ^= 1;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest,
+ ValidateUnlockCredential_BadPIKCert_ModifiedSubject) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_intermediate_key_certificate.signed_data
+ .subject[0] ^= 1;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_BadPIKCert_ModifiedUsage) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_intermediate_key_certificate.signed_data
+ .usage[0] ^= 1;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest,
+ ValidateUnlockCredential_BadPIKCert_ModifiedKeyVersion) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_intermediate_key_certificate.signed_data
+ .key_version ^= 1;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_BadPIKCert_BadSignature) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_intermediate_key_certificate.signature[0] ^= 1;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PIKCertSubjectIgnored) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_intermediate_key_certificate.signed_data
+ .subject[0] ^= 1;
+ SignUnlockCredentialPIKCertificate();
+ bool is_trusted = false;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_TRUE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PIKCertUnexpectedUsage) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_intermediate_key_certificate.signed_data
+ .usage[0] ^= 1;
+ SignUnlockCredentialPIKCertificate();
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PIKRollback) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ ops_.set_stored_rollback_indexes(
+ {{AVB_ATX_PIK_VERSION_LOCATION,
+ unlock_credential_.product_intermediate_key_certificate.signed_data
+ .key_version +
+ 1},
+ {AVB_ATX_PSK_VERSION_LOCATION, 0}});
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_FailReadPSKRollbackIndex) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ fail_read_psk_rollback_index_ = true;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest,
+ ValidateUnlockCredential_UnsupportedPUKCertificateVersion) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_unlock_key_certificate.signed_data.version = 25;
+ SignUnlockCredentialPUKCertificate();
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest,
+ ValidateUnlockCredential_BadPUKCert_ModifiedSubjectPublicKey) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_unlock_key_certificate.signed_data.public_key[0] ^=
+ 1;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest,
+ ValidateUnlockCredential_BadPUKCert_ModifiedSubject) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_unlock_key_certificate.signed_data.subject[0] ^= 1;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_BadPUKCert_ModifiedUsage) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_unlock_key_certificate.signed_data.usage[0] ^= 1;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest,
+ ValidateUnlockCredential_BadPUKCert_ModifiedKeyVersion) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_unlock_key_certificate.signed_data.key_version ^=
+ 1;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_BadPUKCert_BadSignature) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_unlock_key_certificate.signature[0] ^= 1;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PUKCertUnexpectedSubject) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_unlock_key_certificate.signed_data.subject[0] ^= 1;
+ SignUnlockCredentialPUKCertificate();
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PUKCertUnexpectedUsage) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.product_unlock_key_certificate.signed_data.usage[0] ^= 1;
+ SignUnlockCredentialPUKCertificate();
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PUKRollback) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ ops_.set_stored_rollback_indexes(
+ {{AVB_ATX_PIK_VERSION_LOCATION, 0},
+ {AVB_ATX_PSK_VERSION_LOCATION,
+ unlock_credential_.product_unlock_key_certificate.signed_data
+ .key_version +
+ 1}});
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_BadChallengeSignature) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_credential_.challenge_signature[10] ^= 1;
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_ChallengeMismatch) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ unlock_challenge_ = "bad";
+ SignUnlockCredentialChallenge(kPUKPrivateKeyPath);
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_UnlockWithPSK) {
+ ASSERT_TRUE(PrepareUnlockCredential());
+ // Copy the PSK cert as the PUK cert.
+ memcpy(&unlock_credential_.product_unlock_key_certificate,
+ &metadata_.product_signing_key_certificate,
+ sizeof(AvbAtxCertificate));
+ // Sign the challenge with the PSK instead of the PUK.
+ SignUnlockCredentialChallenge(kPSKPrivateKeyPath);
+ bool is_trusted = true;
+ EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted));
+ EXPECT_FALSE(is_trusted);
+}
+
// A fixture for testing avb_slot_verify() with ATX.
class AvbAtxSlotVerifyTest : public BaseAvbToolTest,
public FakeAvbOpsDelegateWithDefaults {
diff --git a/test/avbtool_unittest.cc b/test/avbtool_unittest.cc
index fe7d3ba..ba7f474 100644
--- a/test/avbtool_unittest.cc
+++ b/test/avbtool_unittest.cc
@@ -2396,6 +2396,30 @@
output_path.value().c_str());
}
+TEST_F(AvbToolTest, MakeAtxPukCertificate) {
+ base::FilePath pubkey_path = testdir_.Append("tmp_pubkey.pem");
+ EXPECT_COMMAND(
+ 0,
+ "openssl pkey -pubout -in test/data/testkey_atx_puk.pem -out %s",
+ pubkey_path.value().c_str());
+
+ base::FilePath output_path = testdir_.Append("tmp_certificate.bin");
+ EXPECT_COMMAND(0,
+ "./avbtool make_atx_certificate"
+ " --subject test/data/atx_product_id.bin"
+ " --subject_key %s"
+ " --subject_key_version 42"
+ " --usage com.google.android.things.vboot.unlock"
+ " --authority_key test/data/testkey_atx_pik.pem"
+ " --output %s",
+ pubkey_path.value().c_str(),
+ output_path.value().c_str());
+
+ EXPECT_COMMAND(0,
+ "diff test/data/atx_puk_certificate.bin %s",
+ output_path.value().c_str());
+}
+
TEST_F(AvbToolTest, MakeAtxPermanentAttributes) {
base::FilePath pubkey_path = testdir_.Append("tmp_pubkey.pem");
EXPECT_COMMAND(
@@ -2432,4 +2456,22 @@
0, "diff test/data/atx_metadata.bin %s", output_path.value().c_str());
}
+TEST_F(AvbToolTest, MakeAtxUnlockCredential) {
+ base::FilePath output_path = testdir_.Append("tmp_credential.bin");
+
+ EXPECT_COMMAND(
+ 0,
+ "./avbtool make_atx_unlock_credential"
+ " --intermediate_key_certificate test/data/atx_pik_certificate.bin"
+ " --unlock_key_certificate test/data/atx_puk_certificate.bin"
+ " --challenge test/data/atx_unlock_challenge.bin"
+ " --unlock_key test/data/testkey_atx_puk.pem"
+ " --output %s",
+ output_path.value().c_str());
+
+ EXPECT_COMMAND(0,
+ "diff test/data/atx_unlock_credential.bin %s",
+ output_path.value().c_str());
+}
+
} // namespace avb
diff --git a/test/data/atx_puk_certificate.bin b/test/data/atx_puk_certificate.bin
new file mode 100644
index 0000000..0e02cfe
--- /dev/null
+++ b/test/data/atx_puk_certificate.bin
Binary files differ
diff --git a/test/data/atx_unlock_challenge.bin b/test/data/atx_unlock_challenge.bin
new file mode 100644
index 0000000..23494f9
--- /dev/null
+++ b/test/data/atx_unlock_challenge.bin
@@ -0,0 +1 @@
+zgcveÐÏÑ5ìý
\ No newline at end of file
diff --git a/test/data/atx_unlock_credential.bin b/test/data/atx_unlock_credential.bin
new file mode 100644
index 0000000..b5b46d9
--- /dev/null
+++ b/test/data/atx_unlock_credential.bin
Binary files differ
diff --git a/test/data/testkey_atx_puk.pem b/test/data/testkey_atx_puk.pem
new file mode 100644
index 0000000..c8053be
--- /dev/null
+++ b/test/data/testkey_atx_puk.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+L3m6c6VepWqO0WWWlhzJarWyxDiRa2Q=
+-----END PRIVATE KEY-----
diff --git a/test/fake_avb_ops.cc b/test/fake_avb_ops.cc
index 6ee128a..c5fcdff 100644
--- a/test/fake_avb_ops.cc
+++ b/test/fake_avb_ops.cc
@@ -37,6 +37,7 @@
#include <base/files/file_util.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
+#include <openssl/rand.h>
#include <openssl/sha.h>
#include "fake_avb_ops.h"
@@ -362,6 +363,13 @@
verified_rollback_indexes_[rollback_index_location] = key_version;
}
+AvbIOResult FakeAvbOps::get_random(size_t num_bytes, uint8_t* output) {
+ if (!RAND_bytes(output, num_bytes)) {
+ return AVB_IO_RESULT_ERROR_IO;
+ }
+ return AVB_IO_RESULT_OK;
+}
+
static AvbIOResult my_ops_read_from_partition(AvbOps* ops,
const char* partition,
int64_t offset,
@@ -493,6 +501,14 @@
->set_key_version(rollback_index_location, key_version);
}
+static AvbIOResult my_ops_get_random(AvbAtxOps* atx_ops,
+ size_t num_bytes,
+ uint8_t* output) {
+ return FakeAvbOps::GetInstanceFromAvbOps(atx_ops->ops)
+ ->delegate()
+ ->get_random(num_bytes, output);
+}
+
FakeAvbOps::FakeAvbOps() {
memset(&avb_ops_, 0, sizeof(avb_ops_));
avb_ops_.ab_ops = &avb_ab_ops_;
@@ -519,6 +535,7 @@
avb_atx_ops_.read_permanent_attributes_hash =
my_ops_read_permanent_attributes_hash;
avb_atx_ops_.set_key_version = my_ops_set_key_version;
+ avb_atx_ops_.get_random = my_ops_get_random;
delegate_ = this;
}
diff --git a/test/fake_avb_ops.h b/test/fake_avb_ops.h
index 769f3cc..7beead6 100644
--- a/test/fake_avb_ops.h
+++ b/test/fake_avb_ops.h
@@ -103,6 +103,8 @@
virtual void set_key_version(size_t rollback_index_location,
uint64_t key_version) = 0;
+
+ virtual AvbIOResult get_random(size_t num_bytes, uint8_t* output) = 0;
};
// Provides fake implementations of AVB ops. All instances of this class must be
@@ -249,6 +251,8 @@
void set_key_version(size_t rollback_index_location,
uint64_t key_version) override;
+ AvbIOResult get_random(size_t num_bytes, uint8_t* output) override;
+
private:
AvbOps avb_ops_;
AvbABOps avb_ab_ops_;
@@ -378,6 +382,10 @@
ops_.set_key_version(rollback_index_location, key_version);
}
+ AvbIOResult get_random(size_t num_bytes, uint8_t* output) override {
+ return ops_.get_random(num_bytes, output);
+ }
+
protected:
FakeAvbOps ops_;
};