Implement AVB Android Things eXtension (ATX) am: 147b08db62
am: 46bf7f29ff

Change-Id: Ib533722403e33df3f465b27469a554ebb465a2d5
diff --git a/Android.mk b/Android.mk
index b474fa1..9ec6d4d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -118,6 +118,20 @@
     libavb_ab/avb_ab_flow.c
 include $(BUILD_HOST_STATIC_LIBRARY)
 
+# Build libavb_atx for the host (for unit tests).
+include $(CLEAR_VARS)
+LOCAL_MODULE := libavb_atx_host
+LOCAL_REQUIRED_MODULES := libavb_host
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(avb_common_cflags) -fno-stack-protector -DAVB_ENABLE_DEBUG -DAVB_COMPILATION
+LOCAL_LDFLAGS := $(avb_common_ldflags)
+LOCAL_SRC_FILES := \
+    libavb_atx/avb_atx_validate.c
+include $(BUILD_HOST_STATIC_LIBRARY)
+
 include $(CLEAR_VARS)
 LOCAL_MODULE := libavb_host_sysdeps
 LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
@@ -132,7 +146,7 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := libavb_host_unittest
-LOCAL_REQUIRED_MODULES := simg2img img2simg
+LOCAL_REQUIRED_MODULES := simg2img img2simg avbtool
 LOCAL_MODULE_HOST_OS := linux
 LOCAL_CPP_EXTENSION := .cc
 LOCAL_CLANG := true
@@ -143,12 +157,15 @@
     libavb_host \
     libavb_host_sysdeps \
     libavb_ab_host \
+    libavb_atx_host \
     libgmock_host \
     libgtest_host
 LOCAL_SHARED_LIBRARIES := \
-    libchrome
+    libchrome \
+    libcrypto-host
 LOCAL_SRC_FILES := \
     test/avb_ab_flow_unittest.cc \
+    test/avb_atx_validate_unittest.cc \
     test/avb_slot_verify_unittest.cc \
     test/avb_unittest_util.cc \
     test/avb_util_unittest.cc \
@@ -159,6 +176,18 @@
 include $(BUILD_HOST_NATIVE_TEST)
 
 include $(CLEAR_VARS)
+LOCAL_MODULE := libavb_host_user_code_test
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(avb_common_cflags)
+LOCAL_CPPFLAGS := $(avb_common_cppflags)
+LOCAL_LDFLAGS := $(avb_common_ldflags)
+LOCAL_SRC_FILES := test/user_code_test.cc
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
 LOCAL_MODULE := bootctrl.avb
 LOCAL_MODULE_RELATIVE_PATH := hw
 LOCAL_REQUIRED_MODULES := libavb
diff --git a/README b/README
index 2cd32d7..b5156b5 100644
--- a/README
+++ b/README
@@ -64,6 +64,11 @@
 An implementation of a boot_control HAL using AVB-specific A/B
 metadata is also provided.
 
+Android Things has specific requirements and validation logic for the
+vbmeta public key. An extension is provided in libavb_atx which performs
+this validation as an implementatio of libavb's public key validation
+operation (see avb_validate_vbmeta_public_key in avb_ops.h).
+
 -- FILES AND DIRECTORIES
 
  libavb/
@@ -85,6 +90,9 @@
    An A/B implementation for use in boot loaders. This is built on top
    of libavb.
 
+ libavb_atx/
+
+   An Android Things Extension for validating public key metadata.
 
  boot_control/
 
@@ -103,7 +111,7 @@
 
  test/
 
-   Contains unit tests for abvtool, libavb, and libavb_ab.
+   Contains unit tests for abvtool, libavb, libavb_ab, and libavb_atx.
 
 -- AUDIENCE AND PORTABILITY NOTES
 
@@ -269,7 +277,7 @@
 e.g. derive AVB_pk. Both AVB_pk and AVB_pkmd is passed to the
 bootloader for validation as part of verifying a slot.
 
-To prevent rollback attakcs, the rollback index should be increased on
+To prevent rollback attacks, the rollback index should be increased on
 a regular basis. The rollback index can be set with the
 BOARD_AVB_ROLLBACK_INDEX variable:
 
diff --git a/avbtool b/avbtool
index 328f927..4b1ed45 100755
--- a/avbtool
+++ b/avbtool
@@ -33,6 +33,7 @@
 import subprocess
 import sys
 import tempfile
+import time
 
 import Crypto.PublicKey.RSA
 
@@ -373,6 +374,41 @@
   raise AvbError('Unknown algorithm type {}'.format(alg_type))
 
 
+def raw_sign(signing_helper, algorithm_name, key_path, raw_data_to_sign):
+  """Computes a raw RSA signature using |signing_helper| or openssl.
+
+  Arguments:
+    signing_helper: Program which signs a hash and returns the signature.
+    algorithm_name: The algorithm name as per the ALGORITHMS dict.
+    key_path: Path to the private key file. Must be PEM format.
+    raw_data_to_sign: Data to sign (bytearray or str expected).
+
+  Returns:
+    A bytearray containing the signature.
+
+  Raises:
+    Exception: If an error occurs.
+  """
+  p = None
+  if signing_helper is not None:
+      p = subprocess.Popen(
+          [signing_helper, algorithm_name, key_path],
+          stdin=subprocess.PIPE,
+          stdout=subprocess.PIPE,
+          stderr=subprocess.PIPE)
+  else:
+      p = subprocess.Popen(
+          ['openssl', 'rsautl', '-sign', '-inkey', key_path, '-raw'],
+          stdin=subprocess.PIPE,
+          stdout=subprocess.PIPE,
+          stderr=subprocess.PIPE)
+  (pout, perr) = p.communicate(str(raw_data_to_sign))
+  retcode = p.wait()
+  if retcode != 0:
+    raise AvbError('Error signing: {}'.format(perr))
+  return bytearray(pout)
+
+
 class ImageChunk(object):
   """Data structure used for representing chunks in Android sparse files.
 
@@ -2030,25 +2066,9 @@
       binary_hash.extend(ha.digest())
 
       # Calculate the signature.
-      p = None
-      if signing_helper is not None:
-          p = subprocess.Popen(
-              [signing_helper, algorithm_name, key_path],
-              stdin=subprocess.PIPE,
-              stdout=subprocess.PIPE,
-              stderr=subprocess.PIPE)
-      else:
-          p = subprocess.Popen(
-              ['openssl', 'rsautl', '-sign', '-inkey', key_path, '-raw'],
-              stdin=subprocess.PIPE,
-              stdout=subprocess.PIPE,
-              stderr=subprocess.PIPE)
       padding_and_hash = str(bytearray(alg.padding)) + binary_hash
-      (pout, perr) = p.communicate(padding_and_hash)
-      retcode = p.wait()
-      if retcode != 0:
-        raise AvbError('Error signing: {}'.format(perr))
-      binary_signature.extend(pout)
+      binary_signature.extend(raw_sign(signing_helper, algorithm_name, key_path,
+                                       padding_and_hash))
 
     # Generate Authentication data block.
     auth_data_blob = bytearray()
@@ -2412,6 +2432,119 @@
       image.truncate(original_image_size)
       raise
 
+  def make_atx_certificate(self, output, authority_key_path, subject_key,
+                           subject_key_version, subject,
+                           is_intermediate_authority, signing_helper):
+    """Implements the 'make_atx_certificate' command.
+
+    Android Things certificates are required for Android Things public key
+    metadata. They chain the vbmeta signing key for a particular product back to
+    a fused, permanent root key. These certificates are fixed-length and fixed-
+    format with the explicit goal of not parsing ASN.1 in bootloader code.
+
+    Arguments:
+      output: Certificate will be written to this file on success.
+      authority_key_path: A PEM file path with the authority private key.
+                          If None, then a certificate will be created without a
+                          signature. The signature can be created out-of-band
+                          and appended.
+      subject_key: A PEM or DER subject public key.
+      subject_key_version: A 64-bit version value. If this is None, the number
+                           of seconds since the epoch is used.
+      subject: A subject identifier. For Product Signing Key certificates this
+               should be the same Product ID found in the permanent attributes.
+      is_intermediate_authority: True if the certificate is for an intermediate
+                                 authority.
+      signing_helper: Program which signs a hash and returns the signature.
+    """
+    signed_data = bytearray()
+    signed_data.extend(struct.pack('<I', 1))  # Format Version
+    signed_data.extend(
+        encode_rsa_key(Crypto.PublicKey.RSA.importKey(subject_key)))
+    hasher = hashlib.sha256()
+    hasher.update(subject)
+    signed_data.extend(hasher.digest())
+    usage = 'com.google.android.things.vboot'
+    if is_intermediate_authority:
+      usage += '.ca'
+    hasher = hashlib.sha256()
+    hasher.update(usage)
+    signed_data.extend(hasher.digest())
+    if not subject_key_version:
+      subject_key_version = int(time.time())
+    signed_data.extend(struct.pack('<Q', subject_key_version))
+    signature = bytearray()
+    if authority_key_path:
+      padding_and_hash = bytearray()
+      algorithm_name = None
+      hasher = None
+      if is_intermediate_authority:
+        hasher = hashlib.sha512()
+        algorithm_name = 'SHA512_RSA4096'
+      else:
+        hasher = hashlib.sha256()
+        algorithm_name = 'SHA256_RSA2048'
+      padding_and_hash.extend(ALGORITHMS[algorithm_name].padding)
+      hasher.update(signed_data)
+      padding_and_hash.extend(hasher.digest())
+      signature.extend(raw_sign(signing_helper, algorithm_name,
+                                authority_key_path, padding_and_hash))
+    output.write(signed_data)
+    output.write(signature)
+
+  def make_atx_permanent_attributes(self, output, root_authority_key,
+                                    product_id):
+    """Implements the 'make_atx_permanent_attributes' command.
+
+    Android Things permanent attributes are designed to be permanent for a
+    particular product and a hash of these attributes should be fused into
+    hardware to enforce this.
+
+    Arguments:
+      output: Attributes will be written to this file on success.
+      root_authority_key: A PEM or DER public key for the root authority.
+      product_id: A 16-byte Product ID.
+
+    Raises:
+      AvbError: If an argument is incorrect.
+    """
+    if len(product_id) != 16:
+      raise AvbError('Invalid Product ID length.')
+    output.write(struct.pack('<I', 1))  # Format Version
+    write_rsa_key(output, Crypto.PublicKey.RSA.importKey(root_authority_key))
+    output.write(product_id)
+
+  def make_atx_metadata(self, output, intermediate_key_certificate,
+                        product_key_certificate, google_key_version):
+    """Implements the 'make_atx_metadata' command.
+
+    Android Things metadata are included in vbmeta images to facilitate
+    verification. The output of this command can be used as the
+    public_key_metadata argument to other commands.
+
+    Arguments:
+      output: Metadata 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.
+      product_key_certificate: A certificate file as output by
+                               make_atx_certificate with
+                               is_intermediate_authority set to false.
+      google_key_version: The version of the Google Signing Key used in the
+                          associated vbmeta image.
+
+    Raises:
+      AvbError: If an argument is incorrect.
+    """
+    if len(intermediate_key_certificate) != 1108:
+      raise AvbError('Invalid intermediate key certificate length.')
+    if len(product_key_certificate) != 852:
+      raise AvbError('Invalid product key certificate length.')
+    output.write(struct.pack('<I', 1))  # Format Version
+    output.write(intermediate_key_certificate)
+    output.write(product_key_certificate)
+    output.write(struct.pack('<Q', google_key_version))
+
 
 def calc_hash_level_offsets(image_size, block_size, digest_size):
   """Calculate the offsets of all the hash-levels in a Merkle-tree.
@@ -2772,6 +2905,77 @@
                             default='15:7:0:14:7:0')
     sub_parser.set_defaults(func=self.set_ab_metadata)
 
+    sub_parser = subparsers.add_parser(
+        'make_atx_certificate',
+        help='Create an Android Things eXtension (ATX) certificate.')
+    sub_parser.add_argument('--output',
+                            help='Write certificate to file',
+                            type=argparse.FileType('wb'),
+                            default=sys.stdout)
+    sub_parser.add_argument('--subject',
+                            help=('Path to subject file'),
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--subject_key',
+                            help=('Path to subject RSA public key file'),
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--subject_key_version',
+                            help=('Version of the subject key'),
+                            type=parse_number,
+                            required=False)
+    sub_parser.add_argument('--subject_is_intermediate_authority',
+                            help=('Generate an intermediate authority '
+                                  'certificate'),
+                            action='store_true')
+    sub_parser.add_argument('--authority_key',
+                            help='Path to authority RSA private key file',
+                            required=False)
+    sub_parser.add_argument('--signing_helper',
+                            help='Path to helper used for signing',
+                            metavar='APP',
+                            default=None,
+                            required=False)
+    sub_parser.set_defaults(func=self.make_atx_certificate)
+
+    sub_parser = subparsers.add_parser(
+        'make_atx_permanent_attributes',
+        help='Create Android Things eXtension (ATX) permanent attributes.')
+    sub_parser.add_argument('--output',
+                            help='Write attributes to file',
+                            type=argparse.FileType('wb'),
+                            default=sys.stdout)
+    sub_parser.add_argument('--root_authority_key',
+                            help='Path to authority RSA public key file',
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--product_id',
+                            help=('Path to Product ID file'),
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.set_defaults(func=self.make_atx_permanent_attributes)
+
+    sub_parser = subparsers.add_parser(
+        'make_atx_metadata',
+        help='Create Android Things eXtension (ATX) metadata.')
+    sub_parser.add_argument('--output',
+                            help='Write metadata 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('--product_key_certificate',
+                            help='Path to product key certificate file',
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--google_key_version',
+                            help=('Version of the Google signing key'),
+                            type=parse_number,
+                            default=0)
+    sub_parser.set_defaults(func=self.make_atx_metadata)
+
     args = parser.parse_args(argv[1:])
     try:
       args.func(args)
@@ -2838,6 +3042,28 @@
     """Implements the 'info_image' sub-command."""
     self.avb.info_image(args.image.name, args.output)
 
+  def make_atx_certificate(self, args):
+    """Implements the 'make_atx_certificate' sub-command."""
+    self.avb.make_atx_certificate(args.output, args.authority_key,
+                                  args.subject_key.read(),
+                                  args.subject_key_version,
+                                  args.subject.read(),
+                                  args.subject_is_intermediate_authority,
+                                  args.signing_helper)
+
+  def make_atx_permanent_attributes(self, args):
+    """Implements the 'make_atx_permanent_attributes' sub-command."""
+    self.avb.make_atx_permanent_attributes(args.output,
+                                           args.root_authority_key.read(),
+                                           args.product_id.read())
+
+  def make_atx_metadata(self, args):
+    """Implements the 'make_atx_metadata' sub-command."""
+    self.avb.make_atx_metadata(args.output,
+                               args.intermediate_key_certificate.read(),
+                               args.product_key_certificate.read(),
+                               args.google_key_version)
+
 
 if __name__ == '__main__':
   tool = AvbTool()
diff --git a/libavb/avb_crypto.h b/libavb/avb_crypto.h
index d241520..7e8d7e2 100644
--- a/libavb/avb_crypto.h
+++ b/libavb/avb_crypto.h
@@ -35,6 +35,21 @@
 extern "C" {
 #endif
 
+/* Size of a RSA-2048 signature. */
+#define AVB_RSA2048_NUM_BYTES 256
+
+/* Size of a RSA-4096 signature. */
+#define AVB_RSA4096_NUM_BYTES 512
+
+/* Size of a RSA-8192 signature. */
+#define AVB_RSA8192_NUM_BYTES 1024
+
+/* Size in bytes of a SHA-256 digest. */
+#define AVB_SHA256_DIGEST_SIZE 32
+
+/* Size in bytes of a SHA-512 digest. */
+#define AVB_SHA512_DIGEST_SIZE 64
+
 /* Algorithms that can be used in the vbmeta image for
  * verification. An algorithm consists of a hash type and a signature
  * type.
diff --git a/libavb/avb_ops.h b/libavb/avb_ops.h
index 3d5e964..908c66c 100644
--- a/libavb/avb_ops.h
+++ b/libavb/avb_ops.h
@@ -66,6 +66,9 @@
 /* Forward-declaration of operations in libavb_ab. */
 struct AvbABOps;
 
+/* Forward-declaration of operations in libavb_atx. */
+struct AvbAtxOps;
+
 /* High-level operations/functions/methods that are platform
  * dependent.
  */
@@ -81,6 +84,11 @@
    */
   struct AvbABOps* ab_ops;
 
+  /* If libavb_atx is used, this should point to the
+   * AvbAtxOps. Otherwise it must be set to NULL.
+   */
+  struct AvbAtxOps* atx_ops;
+
   /* Reads |num_bytes| from offset |offset| from partition with name
    * |partition| (NUL-terminated UTF-8 string). If |offset| is
    * negative, its absolute value should be interpreted as the number
diff --git a/libavb/avb_rsa.h b/libavb/avb_rsa.h
index 7e81b81..c2dcf47 100644
--- a/libavb/avb_rsa.h
+++ b/libavb/avb_rsa.h
@@ -42,17 +42,9 @@
 extern "C" {
 #endif
 
+#include "avb_crypto.h"
 #include "avb_sysdeps.h"
 
-/* Size of a RSA-2048 signature. */
-#define AVB_RSA2048_NUM_BYTES 256
-
-/* Size of a RSA-4096 signature. */
-#define AVB_RSA4096_NUM_BYTES 512
-
-/* Size of a RSA-8192 signature. */
-#define AVB_RSA8192_NUM_BYTES 1024
-
 /* Using the key given by |key|, verify a RSA signature |sig| of
  * length |sig_num_bytes| against an expected |hash| of length
  * |hash_num_bytes|. The padding to expect must be passed in using
diff --git a/libavb/avb_sha.h b/libavb/avb_sha.h
index b925bd2..c5a6a4c 100644
--- a/libavb/avb_sha.h
+++ b/libavb/avb_sha.h
@@ -37,16 +37,12 @@
 extern "C" {
 #endif
 
+#include "avb_crypto.h"
 #include "avb_sysdeps.h"
 
-/* Size in bytes of a SHA-256 digest. */
-#define AVB_SHA256_DIGEST_SIZE 32
-
 /* Block size in bytes of a SHA-256 digest. */
 #define AVB_SHA256_BLOCK_SIZE 64
 
-/* Size in bytes of a SHA-512 digest. */
-#define AVB_SHA512_DIGEST_SIZE 64
 
 /* Block size in bytes of a SHA-512 digest. */
 #define AVB_SHA512_BLOCK_SIZE 128
diff --git a/libavb_atx/avb_atx_ops.h b/libavb_atx/avb_atx_ops.h
new file mode 100644
index 0000000..e05ce99
--- /dev/null
+++ b/libavb_atx/avb_atx_ops.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_ATX_H) && !defined(AVB_COMPILATION)
+#error \
+    "Never include this file directly, include libavb_atx/libavb_atx.h instead."
+#endif
+
+#ifndef AVB_ATX_OPS_H_
+#define AVB_ATX_OPS_H_
+
+#include <libavb/libavb.h>
+
+#include "libavb_atx/avb_atx_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct AvbAtxOps;
+typedef struct AvbAtxOps AvbAtxOps;
+
+/* An extension to AvbOps required by avb_atx_validate_vbmeta_public_key(). */
+struct AvbAtxOps {
+  /* Operations from libavb. */
+  AvbOps* ops;
+
+  /* Reads permanent |attributes| data. There are no restrictions on where this
+   * data is stored. On success, returns AVB_IO_RESULT_OK and populates
+   * |attributes|.
+   */
+  AvbIOResult (*read_permanent_attributes)(
+      AvbAtxOps* atx_ops, AvbAtxPermanentAttributes* attributes);
+
+  /* Reads a |hash| of permanent attributes. This hash MUST be retrieved from a
+   * permanently read-only location (e.g. fuses) when a device is LOCKED. On
+   * success, returned AVB_IO_RESULT_OK and populates |hash|.
+   */
+  AvbIOResult (*read_permanent_attributes_hash)(
+      AvbAtxOps* atx_ops, uint8_t hash[AVB_SHA256_DIGEST_SIZE]);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_ATX_OPS_H_ */
diff --git a/libavb_atx/avb_atx_types.h b/libavb_atx/avb_atx_types.h
new file mode 100644
index 0000000..ae1b438
--- /dev/null
+++ b/libavb_atx/avb_atx_types.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_ATX_H) && !defined(AVB_COMPILATION)
+#error \
+    "Never include this file directly, include libavb_atx/libavb_atx.h instead."
+#endif
+
+#ifndef AVB_ATX_TYPES_H_
+#define AVB_ATX_TYPES_H_
+
+#include <libavb/libavb.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Size in bytes of an Android Things product ID. */
+#define AVB_ATX_PRODUCT_ID_SIZE 16
+
+/* Size in bytes of a serialized public key with a 2048-bit modulus. */
+#define AVB_ATX_PUBLIC_KEY_SIZE_2048 (sizeof(AvbRSAPublicKeyHeader) + 512)
+
+/* Size in bytes of a serialized public key with a 4096-bit modulus. */
+#define AVB_ATX_PUBLIC_KEY_SIZE_4096 (sizeof(AvbRSAPublicKeyHeader) + 1024)
+
+/* Data structure of Android Things permanent attributes. */
+typedef struct AvbAtxPermanentAttributes {
+  uint32_t version;
+  uint8_t product_root_public_key[AVB_ATX_PUBLIC_KEY_SIZE_4096];
+  uint8_t product_id[AVB_ATX_PRODUCT_ID_SIZE];
+} AVB_ATTR_PACKED AvbAtxPermanentAttributes;
+
+/* Data structure of signed fields in an Android Things certificate. */
+typedef struct AvbAtxCertificateSignedData {
+  uint32_t version;
+  uint8_t public_key[AVB_ATX_PUBLIC_KEY_SIZE_2048];
+  uint8_t subject[AVB_SHA256_DIGEST_SIZE];
+  uint8_t usage[AVB_SHA256_DIGEST_SIZE];
+  uint64_t key_version;
+} AVB_ATTR_PACKED AvbAtxCertificateSignedData;
+
+/* Data structure of a certificate signed by a 4096-bit key. */
+typedef struct AvbAtxCertificate4096 {
+  AvbAtxCertificateSignedData signed_data;
+  uint8_t signature[AVB_RSA4096_NUM_BYTES];
+} AVB_ATTR_PACKED AvbAtxCertificate4096;
+
+/* Data structure of a certificate signed by a 2048-bit key. */
+typedef struct AvbAtxCertificate2048 {
+  AvbAtxCertificateSignedData signed_data;
+  uint8_t signature[AVB_RSA2048_NUM_BYTES];
+} AVB_ATTR_PACKED AvbAtxCertificate2048;
+
+/* Data structure of Android Things public key metadata in vbmeta. */
+typedef struct AvbAtxPublicKeyMetadata {
+  uint32_t version;
+  AvbAtxCertificate4096 product_intermediate_key_certificate;
+  AvbAtxCertificate2048 product_signing_key_certificate;
+  uint64_t google_signing_key_version;
+} AVB_ATTR_PACKED AvbAtxPublicKeyMetadata;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_ATX_TYPES_H_ */
diff --git a/libavb_atx/avb_atx_validate.c b/libavb_atx/avb_atx_validate.c
new file mode 100644
index 0000000..7a29d66
--- /dev/null
+++ b/libavb_atx/avb_atx_validate.c
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "libavb_atx/avb_atx_validate.h"
+
+#include "libavb/avb_rsa.h"
+#include "libavb/avb_sha.h"
+#include "libavb/avb_sysdeps.h"
+#include "libavb/avb_util.h"
+
+/* Computes the SHA256 |hash| of |length| bytes of |data|. */
+static void sha256(const uint8_t* data,
+                   uint32_t length,
+                   uint8_t hash[AVB_SHA256_DIGEST_SIZE]) {
+  AvbSHA256Ctx context;
+  avb_sha256_init(&context);
+  avb_sha256_update(&context, data, length);
+  uint8_t* tmp = avb_sha256_final(&context);
+  avb_memcpy(hash, tmp, AVB_SHA256_DIGEST_SIZE);
+}
+
+/* Computes the SHA512 |hash| of |length| bytes of |data|. */
+static void sha512(const uint8_t* data,
+                   uint32_t length,
+                   uint8_t hash[AVB_SHA512_DIGEST_SIZE]) {
+  AvbSHA512Ctx context;
+  avb_sha512_init(&context);
+  avb_sha512_update(&context, data, length);
+  uint8_t* tmp = avb_sha512_final(&context);
+  avb_memcpy(hash, tmp, AVB_SHA512_DIGEST_SIZE);
+}
+
+/* Computes the SHA256 |hash| of a NUL-terminated |str|. */
+static void sha256_str(const char* str, uint8_t hash[AVB_SHA256_DIGEST_SIZE]) {
+  sha256((const uint8_t*)str, avb_strlen(str), hash);
+}
+
+/* Verifies structure and |expected_hash| of permanent |attributes|. */
+static bool verify_permanent_attributes(
+    const AvbAtxPermanentAttributes* attributes,
+    uint8_t expected_hash[AVB_SHA256_DIGEST_SIZE]) {
+  uint8_t hash[AVB_SHA256_DIGEST_SIZE];
+
+  if (attributes->version != 1) {
+    avb_error("Unsupported permanent attributes version.\n");
+    return false;
+  }
+  sha256((const uint8_t*)attributes, sizeof(AvbAtxPermanentAttributes), hash);
+  if (0 != avb_safe_memcmp(hash, expected_hash, AVB_SHA256_DIGEST_SIZE)) {
+    avb_error("Invalid permanent attributes.\n");
+    return false;
+  }
+  return true;
+}
+
+/* Verifies signature and fields of a PIK certificate. */
+static bool verify_pik_certificate(
+    AvbAtxCertificate4096* certificate,
+    uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE_4096],
+    uint64_t minimum_version) {
+  const AvbAlgorithmData* algorithm_data;
+  uint8_t certificate_hash[AVB_SHA512_DIGEST_SIZE];
+  uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE];
+
+  if (certificate->signed_data.version != 1) {
+    avb_error("Unsupported PIK certificate format.\n");
+    return false;
+  }
+  algorithm_data = avb_get_algorithm_data(AVB_ALGORITHM_TYPE_SHA512_RSA4096);
+  sha512((const uint8_t*)&certificate->signed_data,
+         sizeof(AvbAtxCertificateSignedData),
+         certificate_hash);
+  if (!avb_rsa_verify(authority,
+                      AVB_ATX_PUBLIC_KEY_SIZE_4096,
+                      certificate->signature,
+                      AVB_RSA4096_NUM_BYTES,
+                      certificate_hash,
+                      AVB_SHA512_DIGEST_SIZE,
+                      algorithm_data->padding,
+                      algorithm_data->padding_len)) {
+    avb_error("Invalid PIK certificate signature.\n");
+    return false;
+  }
+  sha256_str("com.google.android.things.vboot.ca", expected_usage);
+  if (0 != avb_safe_memcmp(certificate->signed_data.usage,
+                           expected_usage,
+                           AVB_SHA256_DIGEST_SIZE)) {
+    avb_error("Invalid PIK certificate usage.\n");
+    return false;
+  }
+  if (certificate->signed_data.key_version < minimum_version) {
+    avb_error("PIK rollback detected.\n");
+    return false;
+  }
+  return true;
+}
+
+/* Verifies signature and fields of a PSK certificate. */
+static bool verify_psk_certificate(
+    AvbAtxCertificate2048* certificate,
+    uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE_2048],
+    uint64_t minimum_version,
+    uint8_t product_id[AVB_ATX_PRODUCT_ID_SIZE]) {
+  const AvbAlgorithmData* algorithm_data;
+  uint8_t certificate_hash[AVB_SHA256_DIGEST_SIZE];
+  uint8_t expected_subject[AVB_SHA256_DIGEST_SIZE];
+  uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE];
+  if (certificate->signed_data.version != 1) {
+    avb_error("Unsupported PSK certificate format.\n");
+    return false;
+  }
+  algorithm_data = avb_get_algorithm_data(AVB_ALGORITHM_TYPE_SHA256_RSA2048);
+  sha256((const uint8_t*)&certificate->signed_data,
+         sizeof(AvbAtxCertificateSignedData),
+         certificate_hash);
+  if (!avb_rsa_verify(authority,
+                      AVB_ATX_PUBLIC_KEY_SIZE_2048,
+                      certificate->signature,
+                      AVB_RSA2048_NUM_BYTES,
+                      certificate_hash,
+                      AVB_SHA256_DIGEST_SIZE,
+                      algorithm_data->padding,
+                      algorithm_data->padding_len)) {
+    avb_error("Invalid PSK certificate signature.\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("Product ID mismatch.\n");
+    return false;
+  }
+  sha256_str("com.google.android.things.vboot", expected_usage);
+  if (0 != avb_safe_memcmp(certificate->signed_data.usage,
+                           expected_usage,
+                           AVB_SHA256_DIGEST_SIZE)) {
+    avb_error("Invalid PSK certificate usage.\n");
+    return false;
+  }
+  if (certificate->signed_data.key_version < minimum_version) {
+    avb_error("PSK rollback detected.\n");
+    return false;
+  }
+  return true;
+}
+
+AvbIOResult avb_atx_validate_vbmeta_public_key(
+    AvbOps* ops,
+    const uint8_t* public_key_data,
+    size_t public_key_length,
+    const uint8_t* public_key_metadata,
+    size_t public_key_metadata_length,
+    bool* out_is_trusted) {
+  AvbIOResult result = AVB_IO_RESULT_OK;
+  AvbAtxPermanentAttributes permanent_attributes;
+  uint8_t permanent_attributes_hash[AVB_SHA256_DIGEST_SIZE];
+  AvbAtxPublicKeyMetadata metadata;
+  uint64_t minimum_version;
+
+  /* Be pessimistic so we can exit early without having to remember to clear. */
+  *out_is_trusted = false;
+
+  /* Read and verify permanent attributes. */
+  result = ops->atx_ops->read_permanent_attributes(ops->atx_ops,
+                                                   &permanent_attributes);
+  if (result != AVB_IO_RESULT_OK) {
+    avb_error("Failed to read permanent attributes.\n");
+    return result;
+  }
+  result = ops->atx_ops->read_permanent_attributes_hash(
+      ops->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;
+  }
+
+  /* Sanity check public key metadata. */
+  if (public_key_metadata_length != sizeof(AvbAtxPublicKeyMetadata)) {
+    avb_error("Invalid public key metadata.\n");
+    return AVB_IO_RESULT_OK;
+  }
+  avb_memcpy(&metadata, public_key_metadata, sizeof(AvbAtxPublicKeyMetadata));
+  if (metadata.version != 1) {
+    avb_error("Unsupported public key metadata.\n");
+    return AVB_IO_RESULT_OK;
+  }
+
+  /* Verify the PIK certificate. */
+  result = ops->read_rollback_index(
+      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(&metadata.product_intermediate_key_certificate,
+                              permanent_attributes.product_root_public_key,
+                              minimum_version)) {
+    return AVB_IO_RESULT_OK;
+  }
+
+  /* Verify the PSK certificate. */
+  result = ops->read_rollback_index(
+      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_psk_certificate(
+          &metadata.product_signing_key_certificate,
+          metadata.product_intermediate_key_certificate.signed_data.public_key,
+          minimum_version,
+          permanent_attributes.product_id)) {
+    return AVB_IO_RESULT_OK;
+  }
+
+  /* Verify the PSK is the same key that verified vbmeta. */
+  if (public_key_length != AVB_ATX_PUBLIC_KEY_SIZE_2048) {
+    avb_error("Public key length mismatch.\n");
+    return AVB_IO_RESULT_OK;
+  }
+  if (0 != avb_safe_memcmp(
+               metadata.product_signing_key_certificate.signed_data.public_key,
+               public_key_data,
+               AVB_ATX_PUBLIC_KEY_SIZE_2048)) {
+    avb_error("Public key mismatch.\n");
+    return AVB_IO_RESULT_OK;
+  }
+
+  /* Verify the GSK minimum version. */
+  result = ops->read_rollback_index(
+      ops, AVB_ATX_GSK_VERSION_LOCATION, &minimum_version);
+  if (result != AVB_IO_RESULT_OK) {
+    avb_error("Failed to read GSK minimum version.\n");
+    return result;
+  }
+  if (metadata.google_signing_key_version < minimum_version) {
+    avb_error("Google signing key rollback detected.\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
new file mode 100644
index 0000000..07c154e
--- /dev/null
+++ b/libavb_atx/avb_atx_validate.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_ATX_H) && !defined(AVB_COMPILATION)
+#error \
+    "Never include this file directly, include libavb_atx/libavb_atx.h instead."
+#endif
+
+#ifndef AVB_ATX_VALIDATE_H_
+#define AVB_ATX_VALIDATE_H_
+
+#include "libavb_atx/avb_atx_ops.h"
+#include "libavb_atx/avb_atx_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Rollback index locations for Android Things key versions. */
+#define AVB_ATX_PIK_VERSION_LOCATION 0x1000
+#define AVB_ATX_PSK_VERSION_LOCATION 0x1001
+#define AVB_ATX_GSK_VERSION_LOCATION 0x1002
+
+/* An implementation of validate_vbmeta_public_key for Android Things. See
+ * libavb/avb_ops.h for details on validate_vbmeta_public_key in general. This
+ * implementation uses the metadata expected with Android Things vbmeta images
+ * to perform validation on the public key. The ATX ops must be implemented.
+ * That is, |ops->atx_ops| must be valid.
+ *
+ * There are a multiple values that need verification:
+ *   - Permanent Product Attributes: A hash of these attributes is fused into
+ *                                   hardware. Consistency is checked.
+ *   - Product Root Key (PRK): This key is provided in permanent attributes and
+ *                             is the root authority for all Android Things
+ *                             products.
+ *   - Product Intermediate Key (PIK): This key is a rotated intermediary. It is
+ *                                     certified by the PRK.
+ *   - Product Signing Key (PSK): This key is a rotated authority for a specific
+ *                                Android Things product. It is certified by a
+ *                                PIK and must match |public_key_data|.
+ *   - Google Signing Key (GSK): This key is a rotated authority for system and
+ *                               boot partitions built by Google. The key is
+ *                               already verified as part of vbmeta so only the
+ *                               key version needs verification here.
+ *   - Product ID: This value is provided in permanent attributes and is unique
+ *                 to a specific Android Things product. This value must match
+ *                 the subject of the PSK certificate.
+ */
+AvbIOResult avb_atx_validate_vbmeta_public_key(
+    AvbOps* ops,
+    const uint8_t* public_key_data,
+    size_t public_key_length,
+    const uint8_t* public_key_metadata,
+    size_t public_key_metadata_length,
+    bool* out_is_trusted);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_ATX_VALIDATE_H_ */
diff --git a/libavb_atx/libavb_atx.h b/libavb_atx/libavb_atx.h
new file mode 100644
index 0000000..839c0af
--- /dev/null
+++ b/libavb_atx/libavb_atx.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef LIBAVB_ATX_H_
+#define LIBAVB_ATX_H_
+
+#include <libavb/libavb.h>
+
+/* The AVB_INSIDE_LIBAVB_ATX_H preprocessor symbol is used to enforce
+ * library users to include only this file. All public interfaces, and
+ * only public interfaces, must be included here.
+ */
+
+#define AVB_INSIDE_LIBAVB_ATX_H
+#include "avb_atx_ops.h"
+#include "avb_atx_types.h"
+#include "avb_atx_validate.h"
+#undef AVB_INSIDE_LIBAVB_ATX_H
+
+#endif /* LIBAVB_ATX_H_ */
diff --git a/test/avb_atx_validate_unittest.cc b/test/avb_atx_validate_unittest.cc
new file mode 100644
index 0000000..6efa856
--- /dev/null
+++ b/test/avb_atx_validate_unittest.cc
@@ -0,0 +1,689 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <base/files/file_util.h>
+#include <gtest/gtest.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/sha.h>
+
+#include <libavb_atx/libavb_atx.h>
+
+#include "avb_unittest_util.h"
+#include "fake_avb_ops.h"
+
+namespace {
+
+const char kMetadataPath[] = "test/data/atx_metadata.bin";
+const char kPermanentAttributesPath[] =
+    "test/data/atx_permanent_attributes.bin";
+const char kPRKPrivateKeyPath[] = "test/data/testkey_rsa4096.pem";
+const char kPIKPrivateKeyPath[] = "test/data/testkey_rsa2048.pem";
+
+class ScopedRSA {
+ public:
+  ScopedRSA(const char* pem_key_path) {
+    FILE* file = fopen(pem_key_path, "r");
+    rsa_ = PEM_read_RSAPrivateKey(file, nullptr, nullptr, nullptr);
+    fclose(file);
+  }
+
+  ~ScopedRSA() {
+    if (rsa_) {
+      RSA_free(rsa_);
+    }
+  }
+
+  // PKCS #1 v1.5 signature using SHA512 if |rsa_| is 4096 bits, SHA256
+  // otherwise. Returns true on success.
+  bool Sign(const void* data_to_sign, size_t length, uint8_t signature[]) {
+    bool use_sha512 = (RSA_size(rsa_) == AVB_RSA4096_NUM_BYTES);
+    uint8_t digest[AVB_SHA512_DIGEST_SIZE];
+    unsigned int digest_size =
+        use_sha512 ? AVB_SHA512_DIGEST_SIZE : AVB_SHA256_DIGEST_SIZE;
+    const unsigned char* data_to_sign_buf =
+        reinterpret_cast<const unsigned char*>(data_to_sign);
+    if (use_sha512) {
+      SHA512(data_to_sign_buf, length, digest);
+    } else {
+      SHA256(data_to_sign_buf, length, digest);
+    }
+    unsigned int signature_length = 0;
+    return (1 == RSA_sign(use_sha512 ? NID_sha512 : NID_sha256,
+                          digest,
+                          digest_size,
+                          signature,
+                          &signature_length,
+                          rsa_));
+  }
+
+ private:
+  RSA* rsa_;
+};
+
+} /* namespace */
+
+namespace avb {
+
+class AvbAtxValidateTest : public ::testing::Test, public FakeAvbOpsDelegate {
+ public:
+  ~AvbAtxValidateTest() override {}
+
+  void SetUp() override {
+    ReadDefaultData();
+    ops_.set_delegate(this);
+    ops_.set_permanent_attributes(attributes_);
+    ops_.set_stored_rollback_indexes({{AVB_ATX_PIK_VERSION_LOCATION, 0},
+                                      {AVB_ATX_PSK_VERSION_LOCATION, 0},
+                                      {AVB_ATX_GSK_VERSION_LOCATION, 0}});
+  }
+
+  // FakeAvbOpsDelegate methods.
+  AvbIOResult read_from_partition(const char* partition,
+                                  int64_t offset,
+                                  size_t num_bytes,
+                                  void* buffer,
+                                  size_t* out_num_read) override {
+    // Expect method not used.
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  AvbIOResult write_to_partition(const char* partition,
+                                 int64_t offset,
+                                 size_t num_bytes,
+                                 const void* buffer) override {
+    // Expect method not used.
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  AvbIOResult validate_vbmeta_public_key(AvbOps* ops,
+                                         const uint8_t* public_key_data,
+                                         size_t public_key_length,
+                                         const uint8_t* public_key_metadata,
+                                         size_t public_key_metadata_length,
+                                         bool* out_key_is_trusted) override {
+    // Expect method not used.
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  AvbIOResult read_rollback_index(AvbOps* ops,
+                                  size_t rollback_index_slot,
+                                  uint64_t* out_rollback_index) override {
+    if ((fail_read_pik_rollback_index_ &&
+         rollback_index_slot == AVB_ATX_PIK_VERSION_LOCATION) ||
+        (fail_read_psk_rollback_index_ &&
+         rollback_index_slot == AVB_ATX_PSK_VERSION_LOCATION) ||
+        (fail_read_gsk_rollback_index_ &&
+         rollback_index_slot == AVB_ATX_GSK_VERSION_LOCATION)) {
+      return AVB_IO_RESULT_ERROR_IO;
+    }
+    return ops_.read_rollback_index(
+        ops, rollback_index_slot, out_rollback_index);
+  }
+
+  AvbIOResult write_rollback_index(AvbOps* ops,
+                                   size_t rollback_index_slot,
+                                   uint64_t rollback_index) override {
+    // Expect method not used.
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  AvbIOResult read_is_device_unlocked(AvbOps* ops,
+                                      bool* out_is_device_unlocked) override {
+    // Expect method not used.
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  AvbIOResult get_unique_guid_for_partition(AvbOps* ops,
+                                            const char* partition,
+                                            char* guid_buf,
+                                            size_t guid_buf_size) override {
+    // Expect method not used.
+    return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
+  }
+
+  AvbIOResult read_permanent_attributes(
+      AvbAtxPermanentAttributes* attributes) override {
+    if (fail_read_permanent_attributes_) {
+      return AVB_IO_RESULT_ERROR_IO;
+    }
+    return ops_.read_permanent_attributes(attributes);
+  }
+
+  AvbIOResult read_permanent_attributes_hash(
+      uint8_t hash[AVB_SHA256_DIGEST_SIZE]) override {
+    if (fail_read_permanent_attributes_hash_) {
+      return AVB_IO_RESULT_ERROR_IO;
+    }
+    return ops_.read_permanent_attributes_hash(hash);
+  }
+
+ protected:
+  virtual AvbIOResult Validate(bool* is_trusted) {
+    return avb_atx_validate_vbmeta_public_key(
+        ops_.avb_ops(),
+        metadata_.product_signing_key_certificate.signed_data.public_key,
+        AVB_ATX_PUBLIC_KEY_SIZE_2048,
+        reinterpret_cast<const uint8_t*>(&metadata_),
+        sizeof(metadata_),
+        is_trusted);
+  }
+
+  void SignPIKCertificate() {
+    memset(metadata_.product_intermediate_key_certificate.signature,
+           0,
+           AVB_RSA4096_NUM_BYTES);
+    ScopedRSA key(kPRKPrivateKeyPath);
+    ASSERT_TRUE(
+        key.Sign(&metadata_.product_intermediate_key_certificate.signed_data,
+                 sizeof(AvbAtxCertificateSignedData),
+                 metadata_.product_intermediate_key_certificate.signature));
+  }
+
+  void SignPSKCertificate() {
+    memset(metadata_.product_signing_key_certificate.signature,
+           0,
+           AVB_RSA2048_NUM_BYTES);
+    ScopedRSA key(kPIKPrivateKeyPath);
+    ASSERT_TRUE(key.Sign(&metadata_.product_signing_key_certificate.signed_data,
+                         sizeof(AvbAtxCertificateSignedData),
+                         metadata_.product_signing_key_certificate.signature));
+  }
+
+  FakeAvbOps ops_;
+  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_read_gsk_rollback_index_{false};
+
+ private:
+  void ReadDefaultData() {
+    std::string tmp;
+    ASSERT_TRUE(base::ReadFileToString(base::FilePath(kMetadataPath), &tmp));
+    ASSERT_EQ(tmp.size(), sizeof(AvbAtxPublicKeyMetadata));
+    memcpy(&metadata_, tmp.data(), tmp.size());
+    ASSERT_TRUE(
+        base::ReadFileToString(base::FilePath(kPermanentAttributesPath), &tmp));
+    ASSERT_EQ(tmp.size(), sizeof(AvbAtxPermanentAttributes));
+    memcpy(&attributes_, tmp.data(), tmp.size());
+  }
+};
+
+TEST_F(AvbAtxValidateTest, Success) {
+  bool is_trusted = false;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_TRUE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, SuccessAfterNewSign) {
+  std::string old_pik_sig(
+      reinterpret_cast<char*>(
+          metadata_.product_intermediate_key_certificate.signature),
+      AVB_RSA4096_NUM_BYTES);
+  std::string old_psk_sig(
+      reinterpret_cast<char*>(
+          metadata_.product_signing_key_certificate.signature),
+      AVB_RSA2048_NUM_BYTES);
+  SignPIKCertificate();
+  SignPSKCertificate();
+  std::string new_pik_sig(
+      reinterpret_cast<char*>(
+          metadata_.product_intermediate_key_certificate.signature),
+      AVB_RSA4096_NUM_BYTES);
+  std::string new_psk_sig(
+      reinterpret_cast<char*>(
+          metadata_.product_signing_key_certificate.signature),
+      AVB_RSA2048_NUM_BYTES);
+  EXPECT_EQ(old_pik_sig, new_pik_sig);
+  EXPECT_EQ(old_psk_sig, new_psk_sig);
+  bool is_trusted = false;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_TRUE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, FailReadPermamentAttributes) {
+  fail_read_permanent_attributes_ = true;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, FailReadPermamentAttributesHash) {
+  fail_read_permanent_attributes_hash_ = true;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, UnsupportedPermanentAttributesVersion) {
+  attributes_.version = 25;
+  ops_.set_permanent_attributes(attributes_);
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, PermanentAttributesHashMismatch) {
+  ops_.set_permanent_attributes_hash("bad_hash");
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+// A fixture with parameterized metadata length.
+class AvbAtxValidateTestWithMetadataLength
+    : public AvbAtxValidateTest,
+      public ::testing::WithParamInterface<size_t> {
+ protected:
+  AvbIOResult Validate(bool* is_trusted) override {
+    return avb_atx_validate_vbmeta_public_key(
+        ops_.avb_ops(),
+        metadata_.product_signing_key_certificate.signed_data.public_key,
+        AVB_ATX_PUBLIC_KEY_SIZE_2048,
+        reinterpret_cast<const uint8_t*>(&metadata_),
+        GetParam(),
+        is_trusted);
+  }
+};
+
+TEST_P(AvbAtxValidateTestWithMetadataLength, InvalidMetadataLength) {
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+// Test a bunch of invalid metadata length values.
+INSTANTIATE_TEST_CASE_P(P,
+                        AvbAtxValidateTestWithMetadataLength,
+                        ::testing::Values(0,
+                                          1,
+                                          sizeof(AvbAtxPublicKeyMetadata) - 1,
+                                          sizeof(AvbAtxPublicKeyMetadata) + 1,
+                                          -1));
+
+TEST_F(AvbAtxValidateTest, UnsupportedMetadataVersion) {
+  metadata_.version = 25;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, FailReadPIKRollbackIndex) {
+  fail_read_pik_rollback_index_ = true;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, UnsupportedPIKCertificateVersion) {
+  metadata_.product_intermediate_key_certificate.signed_data.version = 25;
+  SignPIKCertificate();
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPIKCert_ModifiedSubjectPublicKey) {
+  metadata_.product_intermediate_key_certificate.signed_data.public_key[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPIKCert_ModifiedSubject) {
+  metadata_.product_intermediate_key_certificate.signed_data.subject[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPIKCert_ModifiedUsage) {
+  metadata_.product_intermediate_key_certificate.signed_data.usage[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPIKCert_ModifiedKeyVersion) {
+  metadata_.product_intermediate_key_certificate.signed_data.key_version ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPIKCert_BadSignature) {
+  metadata_.product_intermediate_key_certificate.signature[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, PIKCertSubjectIgnored) {
+  metadata_.product_intermediate_key_certificate.signed_data.subject[0] ^= 1;
+  SignPIKCertificate();
+  bool is_trusted = false;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_TRUE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, PIKCertUnexpectedUsage) {
+  metadata_.product_intermediate_key_certificate.signed_data.usage[0] ^= 1;
+  SignPIKCertificate();
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, PIKRollback) {
+  ops_.set_stored_rollback_indexes(
+      {{AVB_ATX_PIK_VERSION_LOCATION,
+        metadata_.product_intermediate_key_certificate.signed_data.key_version +
+            1},
+       {AVB_ATX_PSK_VERSION_LOCATION, 0},
+       {AVB_ATX_GSK_VERSION_LOCATION, 0}});
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, FailReadPSKRollbackIndex) {
+  fail_read_psk_rollback_index_ = true;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, UnsupportedPSKCertificateVersion) {
+  metadata_.product_signing_key_certificate.signed_data.version = 25;
+  SignPSKCertificate();
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPSKCert_ModifiedSubjectPublicKey) {
+  metadata_.product_signing_key_certificate.signed_data.public_key[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPSKCert_ModifiedSubject) {
+  metadata_.product_signing_key_certificate.signed_data.subject[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPSKCert_ModifiedUsage) {
+  metadata_.product_signing_key_certificate.signed_data.usage[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPSKCert_ModifiedKeyVersion) {
+  metadata_.product_signing_key_certificate.signed_data.key_version ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, BadPSKCert_BadSignature) {
+  metadata_.product_signing_key_certificate.signature[0] ^= 1;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, PSKCertUnexpectedSubject) {
+  metadata_.product_signing_key_certificate.signed_data.subject[0] ^= 1;
+  SignPSKCertificate();
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, PSKCertUnexpectedUsage) {
+  metadata_.product_signing_key_certificate.signed_data.usage[0] ^= 1;
+  SignPSKCertificate();
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, PSKRollback) {
+  ops_.set_stored_rollback_indexes(
+      {{AVB_ATX_PIK_VERSION_LOCATION, 0},
+       {AVB_ATX_PSK_VERSION_LOCATION,
+        metadata_.product_signing_key_certificate.signed_data.key_version + 1},
+       {AVB_ATX_GSK_VERSION_LOCATION, 0}});
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+// A fixture with parameterized public key length.
+class AvbAtxValidateTestWithPublicKeyLength
+    : public AvbAtxValidateTest,
+      public ::testing::WithParamInterface<size_t> {
+ protected:
+  AvbIOResult Validate(bool* is_trusted) override {
+    return avb_atx_validate_vbmeta_public_key(
+        ops_.avb_ops(),
+        metadata_.product_signing_key_certificate.signed_data.public_key,
+        GetParam(),
+        reinterpret_cast<const uint8_t*>(&metadata_),
+        sizeof(metadata_),
+        is_trusted);
+  }
+};
+
+TEST_P(AvbAtxValidateTestWithPublicKeyLength, InvalidPublicKeyLength) {
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+// Test a bunch of invalid public key length values.
+INSTANTIATE_TEST_CASE_P(P,
+                        AvbAtxValidateTestWithPublicKeyLength,
+                        ::testing::Values(0,
+                                          1,
+                                          AVB_ATX_PUBLIC_KEY_SIZE_2048 - 1,
+                                          AVB_ATX_PUBLIC_KEY_SIZE_2048 + 1,
+                                          AVB_ATX_PUBLIC_KEY_SIZE_4096,
+                                          -1));
+
+TEST_F(AvbAtxValidateTest, PSKMismatch) {
+  uint8_t bad_key[AVB_ATX_PUBLIC_KEY_SIZE_2048] = {};
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK,
+            avb_atx_validate_vbmeta_public_key(
+                ops_.avb_ops(),
+                bad_key,
+                AVB_ATX_PUBLIC_KEY_SIZE_2048,
+                reinterpret_cast<const uint8_t*>(&metadata_),
+                sizeof(metadata_),
+                &is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, FailReadGSKRollbackIndex) {
+  fail_read_gsk_rollback_index_ = true;
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+TEST_F(AvbAtxValidateTest, GSKRollback) {
+  ops_.set_stored_rollback_indexes(
+      {{AVB_ATX_PIK_VERSION_LOCATION, 0},
+       {AVB_ATX_PSK_VERSION_LOCATION, 0},
+       {AVB_ATX_GSK_VERSION_LOCATION,
+        metadata_.google_signing_key_version + 1}});
+  bool is_trusted = true;
+  EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted));
+  EXPECT_FALSE(is_trusted);
+}
+
+// A fixture for testing avb_slot_verify() with ATX.
+class AvbAtxSlotVerifyTest : public BaseAvbToolTest, public FakeAvbOpsDelegate {
+ public:
+  ~AvbAtxSlotVerifyTest() override = default;
+
+  void SetUp() override {
+    BaseAvbToolTest::SetUp();
+    ReadAtxDefaultData();
+    ops_.set_partition_dir(testdir_);
+    ops_.set_delegate(this);
+    ops_.set_permanent_attributes(attributes_);
+    ops_.set_stored_rollback_indexes({{0, 0},
+                                      {1, 0},
+                                      {2, 0},
+                                      {3, 0},
+                                      {AVB_ATX_PIK_VERSION_LOCATION, 0},
+                                      {AVB_ATX_PSK_VERSION_LOCATION, 0},
+                                      {AVB_ATX_GSK_VERSION_LOCATION, 0}});
+    ops_.set_stored_is_device_unlocked(false);
+  }
+
+  // FakeAvbOpsDelegate methods. All forward to FakeAvbOps default except for
+  // validate_vbmeta_public_key().
+  AvbIOResult read_from_partition(const char* partition,
+                                  int64_t offset,
+                                  size_t num_bytes,
+                                  void* buffer,
+                                  size_t* out_num_read) override {
+    return ops_.read_from_partition(
+        partition, offset, num_bytes, buffer, out_num_read);
+  }
+
+  AvbIOResult write_to_partition(const char* partition,
+                                 int64_t offset,
+                                 size_t num_bytes,
+                                 const void* buffer) override {
+    return ops_.write_to_partition(partition, offset, num_bytes, buffer);
+  }
+
+  AvbIOResult validate_vbmeta_public_key(AvbOps* ops,
+                                         const uint8_t* public_key_data,
+                                         size_t public_key_length,
+                                         const uint8_t* public_key_metadata,
+                                         size_t public_key_metadata_length,
+                                         bool* out_key_is_trusted) override {
+    // Send to ATX implementation.
+    ++num_atx_calls_;
+    return avb_atx_validate_vbmeta_public_key(ops_.avb_ops(),
+                                              public_key_data,
+                                              public_key_length,
+                                              public_key_metadata,
+                                              public_key_metadata_length,
+                                              out_key_is_trusted);
+  }
+
+  AvbIOResult read_rollback_index(AvbOps* ops,
+                                  size_t rollback_index_slot,
+                                  uint64_t* out_rollback_index) override {
+    return ops_.read_rollback_index(
+        ops, rollback_index_slot, out_rollback_index);
+  }
+
+  AvbIOResult write_rollback_index(AvbOps* ops,
+                                   size_t rollback_index_slot,
+                                   uint64_t rollback_index) override {
+    return ops_.write_rollback_index(ops, rollback_index_slot, rollback_index);
+  }
+
+  AvbIOResult read_is_device_unlocked(AvbOps* ops,
+                                      bool* out_is_device_unlocked) override {
+    return ops_.read_is_device_unlocked(ops, out_is_device_unlocked);
+  }
+
+  AvbIOResult get_unique_guid_for_partition(AvbOps* ops,
+                                            const char* partition,
+                                            char* guid_buf,
+                                            size_t guid_buf_size) override {
+    return ops_.get_unique_guid_for_partition(
+        ops, partition, guid_buf, guid_buf_size);
+  }
+
+  AvbIOResult read_permanent_attributes(
+      AvbAtxPermanentAttributes* attributes) override {
+    return ops_.read_permanent_attributes(attributes);
+  }
+
+  AvbIOResult read_permanent_attributes_hash(
+      uint8_t hash[AVB_SHA256_DIGEST_SIZE]) override {
+    return ops_.read_permanent_attributes_hash(hash);
+  }
+
+ protected:
+  FakeAvbOps ops_;
+  AvbAtxPermanentAttributes attributes_;
+  int num_atx_calls_ = 0;
+
+ private:
+  void ReadAtxDefaultData() {
+    std::string tmp;
+    ASSERT_TRUE(
+        base::ReadFileToString(base::FilePath(kPermanentAttributesPath), &tmp));
+    ASSERT_EQ(tmp.size(), sizeof(AvbAtxPermanentAttributes));
+    memcpy(&attributes_, tmp.data(), tmp.size());
+  }
+};
+
+TEST_F(AvbAtxSlotVerifyTest, SlotVerifyWithAtx) {
+  std::string metadata_option = "--public_key_metadata=";
+  metadata_option += kMetadataPath;
+  GenerateVBMetaImage("vbmeta_a.img",
+                      "SHA256_RSA2048",
+                      0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      metadata_option);
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  const char* requested_partitions[] = {"boot", NULL};
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(),
+                            requested_partitions,
+                            "_a",
+                            false /* allow_verification_error */,
+                            &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  avb_slot_verify_data_free(slot_data);
+  EXPECT_EQ(1, num_atx_calls_);
+}
+
+}  // namespace avb
diff --git a/test/avbtool_unittest.cc b/test/avbtool_unittest.cc
index ee8a313..83cc881 100644
--- a/test/avbtool_unittest.cc
+++ b/test/avbtool_unittest.cc
@@ -1234,4 +1234,88 @@
       vbmeta_path.value().c_str());
 }
 
+TEST_F(AvbToolTest, MakeAtxPikCertificate) {
+  base::FilePath pubkey_path = testdir_.Append("tmp_pubkey.pem");
+  EXPECT_COMMAND(
+      0,
+      "openssl pkey -pubout -in test/data/testkey_rsa2048.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/small_blob.bin"
+                 " --subject_key %s"
+                 " --subject_key_version 42"
+                 " --subject_is_intermediate_authority"
+                 " --authority_key test/data/testkey_rsa4096.pem"
+                 " --output %s",
+                 pubkey_path.value().c_str(),
+                 output_path.value().c_str());
+
+  EXPECT_COMMAND(0,
+                 "diff test/data/atx_pik_certificate.bin %s",
+                 output_path.value().c_str());
+}
+
+TEST_F(AvbToolTest, MakeAtxPskCertificate) {
+  base::FilePath pubkey_path = testdir_.Append("tmp_pubkey.pem");
+  EXPECT_COMMAND(
+      0,
+      "openssl pkey -pubout -in test/data/testkey_rsa2048.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"
+                 " --authority_key test/data/testkey_rsa2048.pem"
+                 " --output %s",
+                 pubkey_path.value().c_str(),
+                 output_path.value().c_str());
+
+  EXPECT_COMMAND(0,
+                 "diff test/data/atx_psk_certificate.bin %s",
+                 output_path.value().c_str());
+}
+
+TEST_F(AvbToolTest, MakeAtxPermanentAttributes) {
+  base::FilePath pubkey_path = testdir_.Append("tmp_pubkey.pem");
+  EXPECT_COMMAND(
+      0,
+      "openssl pkey -pubout -in test/data/testkey_rsa4096.pem -out %s",
+      pubkey_path.value().c_str());
+
+  base::FilePath output_path = testdir_.Append("tmp_attributes.bin");
+  EXPECT_COMMAND(0,
+                 "./avbtool make_atx_permanent_attributes"
+                 " --root_authority_key %s"
+                 " --product_id test/data/atx_product_id.bin"
+                 " --output %s",
+                 pubkey_path.value().c_str(),
+                 output_path.value().c_str());
+
+  EXPECT_COMMAND(0,
+                 "diff test/data/atx_permanent_attributes.bin %s",
+                 output_path.value().c_str());
+}
+
+TEST_F(AvbToolTest, MakeAtxMetadata) {
+  base::FilePath output_path = testdir_.Append("tmp_metadata.bin");
+
+  EXPECT_COMMAND(
+      0,
+      "./avbtool make_atx_metadata"
+      " --intermediate_key_certificate test/data/atx_pik_certificate.bin"
+      " --product_key_certificate test/data/atx_psk_certificate.bin"
+      " --google_key_version 42"
+      " --output %s",
+      output_path.value().c_str());
+
+  EXPECT_COMMAND(
+      0, "diff test/data/atx_metadata.bin %s", output_path.value().c_str());
+}
+
 }  // namespace avb
diff --git a/test/data/atx_metadata.bin b/test/data/atx_metadata.bin
new file mode 100644
index 0000000..3b53bbc
--- /dev/null
+++ b/test/data/atx_metadata.bin
Binary files differ
diff --git a/test/data/atx_permanent_attributes.bin b/test/data/atx_permanent_attributes.bin
new file mode 100644
index 0000000..be63c86
--- /dev/null
+++ b/test/data/atx_permanent_attributes.bin
Binary files differ
diff --git a/test/data/atx_pik_certificate.bin b/test/data/atx_pik_certificate.bin
new file mode 100644
index 0000000..d0b4a7b
--- /dev/null
+++ b/test/data/atx_pik_certificate.bin
Binary files differ
diff --git a/test/data/atx_product_id.bin b/test/data/atx_product_id.bin
new file mode 100644
index 0000000..d5648fa
--- /dev/null
+++ b/test/data/atx_product_id.bin
@@ -0,0 +1 @@
+t‹ùAö®÷ ìí=±]
\ No newline at end of file
diff --git a/test/data/atx_psk_certificate.bin b/test/data/atx_psk_certificate.bin
new file mode 100644
index 0000000..abec3ce
--- /dev/null
+++ b/test/data/atx_psk_certificate.bin
Binary files differ
diff --git a/test/data/testkey_rsa2048_public.pem b/test/data/testkey_rsa2048_public.pem
new file mode 100644
index 0000000..38b7f02
--- /dev/null
+++ b/test/data/testkey_rsa2048_public.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxlVR3TIkouAOvH79vaJT
+gFhpfvVKQIeVkFRZPVXK/zY0Gvrh4JAqGjJoW/PfrQv5sdD36qtHH3a+G5hLZ6Ni
++t/mtfjucxZfuLGC3kmJ1T3XqEKZgXXI2IR7vVSoImREvDQGEDyJwtHzLANlkbGg
+0cghVhWZSCAndO8BenalC2v94/rtDfkPekH6dgU3Sf40T0sBSeSY94mOzTaqOR2p
+fV1rWlLRdWmo33zeHBv52Rlbt0dMuXAureXWiHztkm5GCBC1dgM+CaxNtizNEgC9
+1KcD0xuRCCM2WxH+r1lpszyIJDctYbrFmVEYl/kjQpafhy7Nsk1fqSTyRdriZSYm
+TQIDAQAB
+-----END PUBLIC KEY-----
diff --git a/test/data/testkey_rsa4096_public.pem b/test/data/testkey_rsa4096_public.pem
new file mode 100644
index 0000000..efd7144
--- /dev/null
+++ b/test/data/testkey_rsa4096_public.pem
@@ -0,0 +1,14 @@
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2ASv49OEbH4NiT3CjNMS
+VeliyfEPXswWcqtEfCxlSpS1FisAuwbvEwdTTPlkuSh6G4SYiNhnpCP5p0vcSg/3
+OhiuVKgV/rCtrDXaO60nvK/o0y83NNZRK2xaJ9eWBq9ruIDK+jC0sYWzTaqqwxY0
+Grjnx/r5CXerl5PrRK7PILzwgBHbIwxHcblt1ntgR4cWVpO3wiqasEwBDDDYk4fw
+7W6LvjBb9qav3YB8RV6PkZNeRP64ggfuecq/MXNiWOPNxLzCER2hSr/+J32h9jWj
+XsrcVy8+8Mldhmr4r2an7c247aFfupuFGtUJrpROO8/LXMl5gPfMpkqoatjTMRH5
+9gJjKhot0RpmGxZBvb33TcBK5SdJX39Y4yct5clmDlI4Fjj7FutTP+b96aJeJVnY
+eUX/A0wmogBajsJRoRX5e/RcgZsYRzXYLQXprQ81dBWjjovMJ9p8XeT6BNMFC7o6
+sklFL0fHDUE/l4BNP8G1u3BfpzevSCISRS71D4eS4oQB+RIPFBUkzomZ7rnEF3Bw
+Feq+xmwfYrP0LRaH+1YeRauuMuReke1TZl697a3mEjkNg8noa2wtpe7EWmaujJfX
+DWxJx/XEkjGLCe4z2qk3tkkY+A5gRcgzke8gVxC+eC2DJtbKYfkv4L8FMFJaEhwA
+p13MfC7FlYujO/BDLl7dANsCAwEAAQ==
+-----END PUBLIC KEY-----
diff --git a/test/fake_avb_ops.cc b/test/fake_avb_ops.cc
index b2a2080..cdd0850 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/sha.h>
 
 #include "fake_avb_ops.h"
 
@@ -214,6 +215,26 @@
   return AVB_IO_RESULT_OK;
 }
 
+AvbIOResult FakeAvbOps::read_permanent_attributes(
+    AvbAtxPermanentAttributes* attributes) {
+  *attributes = permanent_attributes_;
+  return AVB_IO_RESULT_OK;
+}
+
+AvbIOResult FakeAvbOps::read_permanent_attributes_hash(
+    uint8_t hash[AVB_SHA256_DIGEST_SIZE]) {
+  if (permanent_attributes_hash_.empty()) {
+    SHA256(reinterpret_cast<const unsigned char*>(&permanent_attributes_),
+           sizeof(AvbAtxPermanentAttributes),
+           hash);
+    return AVB_IO_RESULT_OK;
+  }
+  memset(hash, 0, AVB_SHA256_DIGEST_SIZE);
+  permanent_attributes_hash_.copy(reinterpret_cast<char*>(hash),
+                                  AVB_SHA256_DIGEST_SIZE);
+  return AVB_IO_RESULT_OK;
+}
+
 static AvbIOResult my_ops_read_from_partition(AvbOps* ops,
                                               const char* partition,
                                               int64_t offset,
@@ -283,8 +304,23 @@
       ->get_unique_guid_for_partition(ops, partition, guid_buf, guid_buf_size);
 }
 
+static AvbIOResult my_ops_read_permanent_attributes(
+    AvbAtxOps* atx_ops, AvbAtxPermanentAttributes* attributes) {
+  return FakeAvbOps::GetInstanceFromAvbOps(atx_ops->ops)
+      ->delegate()
+      ->read_permanent_attributes(attributes);
+}
+
+static AvbIOResult my_ops_read_permanent_attributes_hash(
+    AvbAtxOps* atx_ops, uint8_t hash[AVB_SHA256_DIGEST_SIZE]) {
+  return FakeAvbOps::GetInstanceFromAvbOps(atx_ops->ops)
+      ->delegate()
+      ->read_permanent_attributes_hash(hash);
+}
+
 FakeAvbOps::FakeAvbOps() {
   avb_ops_.ab_ops = &avb_ab_ops_;
+  avb_ops_.atx_ops = &avb_atx_ops_;
   avb_ops_.user_data = this;
   avb_ops_.read_from_partition = my_ops_read_from_partition;
   avb_ops_.write_to_partition = my_ops_write_to_partition;
@@ -299,6 +335,11 @@
   avb_ab_ops_.read_ab_metadata = avb_ab_data_read;
   avb_ab_ops_.write_ab_metadata = avb_ab_data_write;
 
+  avb_atx_ops_.ops = &avb_ops_;
+  avb_atx_ops_.read_permanent_attributes = my_ops_read_permanent_attributes;
+  avb_atx_ops_.read_permanent_attributes_hash =
+      my_ops_read_permanent_attributes_hash;
+
   delegate_ = this;
 }
 
diff --git a/test/fake_avb_ops.h b/test/fake_avb_ops.h
index 90cbc95..0a7246d 100644
--- a/test/fake_avb_ops.h
+++ b/test/fake_avb_ops.h
@@ -30,6 +30,7 @@
 #include <string>
 
 #include <libavb_ab/libavb_ab.h>
+#include <libavb_atx/libavb_atx.h>
 
 namespace avb {
 
@@ -72,6 +73,12 @@
                                                     const char* partition,
                                                     char* guid_buf,
                                                     size_t guid_buf_size) = 0;
+
+  virtual AvbIOResult read_permanent_attributes(
+      AvbAtxPermanentAttributes* attributes) = 0;
+
+  virtual AvbIOResult read_permanent_attributes_hash(
+      uint8_t hash[AVB_SHA256_DIGEST_SIZE]) = 0;
 };
 
 // Provides fake implementations of AVB ops. All instances of this class must be
@@ -96,6 +103,10 @@
     return &avb_ab_ops_;
   }
 
+  AvbAtxOps* avb_atx_ops() {
+    return &avb_atx_ops_;
+  }
+
   FakeAvbOpsDelegate* delegate() {
     return delegate_;
   }
@@ -131,6 +142,14 @@
     stored_is_device_unlocked_ = stored_is_device_unlocked;
   }
 
+  void set_permanent_attributes(const AvbAtxPermanentAttributes& attributes) {
+    permanent_attributes_ = attributes;
+  }
+
+  void set_permanent_attributes_hash(const std::string& hash) {
+    permanent_attributes_hash_ = hash;
+  }
+
   // FakeAvbOpsDelegate methods.
   AvbIOResult read_from_partition(const char* partition,
                                   int64_t offset,
@@ -166,9 +185,16 @@
                                             char* guid_buf,
                                             size_t guid_buf_size) override;
 
+  AvbIOResult read_permanent_attributes(
+      AvbAtxPermanentAttributes* attributes) override;
+
+  AvbIOResult read_permanent_attributes_hash(
+      uint8_t hash[AVB_SHA256_DIGEST_SIZE]) override;
+
  private:
   AvbOps avb_ops_;
   AvbABOps avb_ab_ops_;
+  AvbAtxOps avb_atx_ops_;
 
   FakeAvbOpsDelegate* delegate_;
 
@@ -180,6 +206,9 @@
   std::map<size_t, uint64_t> stored_rollback_indexes_;
 
   bool stored_is_device_unlocked_;
+
+  AvbAtxPermanentAttributes permanent_attributes_;
+  std::string permanent_attributes_hash_;
 };
 
 }  // namespace avb
diff --git a/test/user_code_test.cc b/test/user_code_test.cc
new file mode 100644
index 0000000..fd851f3
--- /dev/null
+++ b/test/user_code_test.cc
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// This test ensures that user code can include libavb*.h and compile without
+// defining AVB_COMPILATION (which user code must not).
+#include "libavb/libavb.h"
+#include "libavb_ab/libavb_ab.h"
+#include "libavb_atx/libavb_atx.h"