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_;
 };