Merge "Add prng_seeder utility"
diff --git a/fsverity/Android.bp b/fsverity/Android.bp
index 040c99b..ce3b499 100644
--- a/fsverity/Android.bp
+++ b/fsverity/Android.bp
@@ -32,14 +32,6 @@
     proto: {
         canonical_path_from_root: false,
     },
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: true,
-        },
-    },
 }
 
 python_binary_host {
diff --git a/fsverity/TEST_MAPPING b/fsverity/TEST_MAPPING
new file mode 100644
index 0000000..b327cb8
--- /dev/null
+++ b/fsverity/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "ComposHostTestCases"
+    }
+  ]
+}
diff --git a/fsverity/fsverity_manifest_generator.py b/fsverity/fsverity_manifest_generator.py
index 0b01a55..79be591 100644
--- a/fsverity/fsverity_manifest_generator.py
+++ b/fsverity/fsverity_manifest_generator.py
@@ -58,7 +58,6 @@
   for f in sorted(args.inputs):
     # f is a full path for now; make it relative so it starts with {mount_point}/
     digest = digests.digests[os.path.relpath(f, args.base_dir)]
-    print(f"{os.path.relpath(f, args.base_dir)}")
     digest.digest = _digest(args.fsverity_path, f)
     digest.hash_alg = HASH_ALGORITHM
 
diff --git a/identity/Android.bp b/identity/Android.bp
index 512e3ad..4e4b79a 100644
--- a/identity/Android.bp
+++ b/identity/Android.bp
@@ -26,6 +26,7 @@
     name: "credstore",
     defaults: [
         "identity_defaults",
+        "identity_use_latest_hal_aidl_cpp_static",
         "keymint_use_latest_hal_aidl_ndk_shared",
         "keymint_use_latest_hal_aidl_cpp_static",
     ],
@@ -58,7 +59,6 @@
         "libutilscallstack",
     ],
     static_libs: [
-        "android.hardware.identity-V4-cpp",
         "android.hardware.keymaster-V3-cpp",
         "libcppbor_external",
     ],
diff --git a/identity/util/src/java/com/android/security/identity/internal/Iso18013.java b/identity/util/src/java/com/android/security/identity/internal/Iso18013.java
index 2561fcc..b47009b 100644
--- a/identity/util/src/java/com/android/security/identity/internal/Iso18013.java
+++ b/identity/util/src/java/com/android/security/identity/internal/Iso18013.java
@@ -146,36 +146,9 @@
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         try {
             baos.write(new byte[]{41});
-            ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW();
-            // Each coordinate may be encoded in 33*, 32, or fewer bytes.
-            //
-            //  * : it can be 33 bytes because toByteArray() guarantees "The array will contain the
-            //      minimum number of bytes required to represent this BigInteger, including at
-            //      least one sign bit, which is (ceil((this.bitLength() + 1)/8))" which means that
-            //      the MSB is always 0x00. This is taken care of by calling calling
-            //      stripLeadingZeroes().
-            //
-            // We need the encoding to be exactly 32 bytes since according to RFC 5480 section 2.2
-            // and SEC 1: Elliptic Curve Cryptography section 2.3.3 the encoding is 0x04 | X | Y
-            // where X and Y are encoded in exactly 32 byte, big endian integer values each.
-            //
-            byte[] xBytes = stripLeadingZeroes(w.getAffineX().toByteArray());
-            if (xBytes.length > 32) {
-                throw new RuntimeException("xBytes is " + xBytes.length + " which is unexpected");
-            }
-            for (int n = 0; n < 32 - xBytes.length; n++) {
-                baos.write(0x00);
-            }
-            baos.write(xBytes);
 
-            byte[] yBytes = stripLeadingZeroes(w.getAffineY().toByteArray());
-            if (yBytes.length > 32) {
-                throw new RuntimeException("yBytes is " + yBytes.length + " which is unexpected");
-            }
-            for (int n = 0; n < 32 - yBytes.length; n++) {
-                baos.write(0x00);
-            }
-            baos.write(yBytes);
+            ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW();
+            baos.write(Util.convertP256PublicKeyToDERFormat(w));
 
             baos.write(new byte[]{42, 44});
         } catch (IOException e) {
@@ -303,18 +276,4 @@
             throw new IllegalStateException("Error performing key agreement", e);
         }
     }
-
-    private static byte[] stripLeadingZeroes(byte[] value) {
-        int n = 0;
-        while (n < value.length && value[n] == 0) {
-            n++;
-        }
-        int newLen = value.length - n;
-        byte[] ret = new byte[newLen];
-        int m = 0;
-        while (n < value.length) {
-            ret[m++] = value[n++];
-        }
-        return ret;
-    }
 }
diff --git a/identity/util/src/java/com/android/security/identity/internal/Util.java b/identity/util/src/java/com/android/security/identity/internal/Util.java
index 94d7d15..ee12cd0 100644
--- a/identity/util/src/java/com/android/security/identity/internal/Util.java
+++ b/identity/util/src/java/com/android/security/identity/internal/Util.java
@@ -1130,6 +1130,48 @@
         Log.e(TAG, name + ": dumping " + data.length + " bytes\n" + fmt.toString());
     }
 
+    // Convert EC P256 public key to DER format binary format
+    public static byte[] convertP256PublicKeyToDERFormat(ECPoint w) {
+        byte[] ret = new byte[64];
+
+        // Each coordinate may be encoded in 33*, 32, or fewer bytes.
+        //
+        //  * : it can be 33 bytes because toByteArray() guarantees "The array will contain the
+        //      minimum number of bytes required to represent this BigInteger, including at
+        //      least one sign bit, which is (ceil((this.bitLength() + 1)/8))" which means that
+        //      the MSB is always 0x00. This is taken care of by calling calling
+        //      stripLeadingZeroes().
+        //
+        // We need the encoding to be exactly 32 bytes since according to RFC 5480 section 2.2
+        // and SEC 1: Elliptic Curve Cryptography section 2.3.3 the encoding is 0x04 | X | Y
+        // where X and Y are encoded in exactly 32 byte, big endian integer values each.
+        //
+        byte[] xBytes = stripLeadingZeroes(w.getAffineX().toByteArray());
+        if (xBytes.length > 32) {
+            throw new RuntimeException("xBytes is " + xBytes.length + " which is unexpected");
+        }
+        int numLeadingZeroBytes = 32 - xBytes.length;
+        for (int n = 0; n < numLeadingZeroBytes; n++) {
+            ret[n] = 0x00;
+        }
+        for (int n = 0; n < xBytes.length; n++) {
+            ret[numLeadingZeroBytes + n] = xBytes[n];
+        }
+
+        byte[] yBytes = stripLeadingZeroes(w.getAffineY().toByteArray());
+        if (yBytes.length > 32) {
+            throw new RuntimeException("yBytes is " + yBytes.length + " which is unexpected");
+        }
+        numLeadingZeroBytes = 32 - yBytes.length;
+        for (int n = 0; n < numLeadingZeroBytes; n++) {
+            ret[32 + n] = 0x00;
+        }
+        for (int n = 0; n < yBytes.length; n++) {
+            ret[32 + numLeadingZeroBytes + n] = yBytes[n];
+        }
+
+        return ret;
+    }
 
     // This returns a SessionTranscript which satisfy the requirement
     // that the uncompressed X and Y coordinates of the public key for the
@@ -1142,36 +1184,9 @@
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         try {
             baos.write(new byte[]{42});
-            ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW();
-            // Each coordinate may be encoded in 33*, 32, or fewer bytes.
-            //
-            //  * : it can be 33 bytes because toByteArray() guarantees "The array will contain the
-            //      minimum number of bytes required to represent this BigInteger, including at
-            //      least one sign bit, which is (ceil((this.bitLength() + 1)/8))" which means that
-            //      the MSB is always 0x00. This is taken care of by calling calling
-            //      stripLeadingZeroes().
-            //
-            // We need the encoding to be exactly 32 bytes since according to RFC 5480 section 2.2
-            // and SEC 1: Elliptic Curve Cryptography section 2.3.3 the encoding is 0x04 | X | Y
-            // where X and Y are encoded in exactly 32 byte, big endian integer values each.
-            //
-            byte[] xBytes = stripLeadingZeroes(w.getAffineX().toByteArray());
-            if (xBytes.length > 32) {
-                throw new RuntimeException("xBytes is " + xBytes.length + " which is unexpected");
-            }
-            for (int n = 0; n < 32 - xBytes.length; n++) {
-                baos.write(0x00);
-            }
-            baos.write(xBytes);
 
-            byte[] yBytes = stripLeadingZeroes(w.getAffineY().toByteArray());
-            if (yBytes.length > 32) {
-                throw new RuntimeException("yBytes is " + yBytes.length + " which is unexpected");
-            }
-            for (int n = 0; n < 32 - yBytes.length; n++) {
-                baos.write(0x00);
-            }
-            baos.write(yBytes);
+            ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW();
+            baos.write(convertP256PublicKeyToDERFormat(w));
 
             baos.write(new byte[]{43, 44});
         } catch (IOException e) {
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index 43419b7..0e5afd9 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -27,6 +27,7 @@
     srcs: ["src/lib.rs"],
     defaults: [
         "keymint_use_latest_hal_aidl_rust",
+        "keystore2_use_latest_aidl_rust",
     ],
 
     rustlibs: [
@@ -39,7 +40,6 @@
         "android.security.maintenance-rust",
         "android.security.metrics-rust",
         "android.security.remoteprovisioning-rust",
-        "android.system.keystore2-V2-rust",
         "libanyhow",
         "libbinder_rs",
         "libkeystore2_aaid-rust",
@@ -79,9 +79,11 @@
     name: "libkeystore2_test_utils",
     crate_name: "keystore2_test_utils",
     srcs: ["test_utils/lib.rs"],
-    defaults: ["keymint_use_latest_hal_aidl_rust"],
+    defaults: [
+        "keymint_use_latest_hal_aidl_rust",
+        "keystore2_use_latest_aidl_rust",
+    ],
     rustlibs: [
-        "android.system.keystore2-V2-rust",
         "libbinder_rs",
         "libkeystore2_selinux",
         "liblog_rust",
@@ -89,8 +91,8 @@
         "librand",
         "libserde",
         "libserde_cbor",
-	"libthiserror",
-	"libanyhow",
+        "libthiserror",
+        "libanyhow",
     ],
 }
 
@@ -110,13 +112,15 @@
 rust_test {
     name: "keystore2_test_utils_test",
     srcs: ["test_utils/lib.rs"],
-    defaults: ["keymint_use_latest_hal_aidl_rust"],
+    defaults: [
+        "keymint_use_latest_hal_aidl_rust",
+        "keystore2_use_latest_aidl_rust",
+    ],
     test_suites: ["general-tests"],
     require_root: true,
     auto_gen_config: true,
     compile_multilib: "first",
     rustlibs: [
-        "android.system.keystore2-V2-rust",
         "libbinder_rs",
         "libkeystore2_selinux",
         "liblog_rust",
@@ -124,8 +128,8 @@
         "librand",
         "libserde",
         "libserde_cbor",
-	"libthiserror",
-	"libanyhow",
+        "libthiserror",
+        "libanyhow",
     ],
 }
 
diff --git a/keystore2/legacykeystore/lib.rs b/keystore2/legacykeystore/lib.rs
index 95f917a..ed5bd4f 100644
--- a/keystore2/legacykeystore/lib.rs
+++ b/keystore2/legacykeystore/lib.rs
@@ -393,7 +393,7 @@
         let uid = Self::get_effective_uid(uid).context("In list.")?;
         let mut result = self.list_legacy(uid).context("In list.")?;
         result.append(&mut db.list(uid).context("In list: Trying to get list of entries.")?);
-        result = result.into_iter().filter(|s| s.starts_with(prefix)).collect();
+        result.retain(|s| s.starts_with(prefix));
         result.sort_unstable();
         result.dedup();
         Ok(result)
diff --git a/keystore2/rustfmt.toml b/keystore2/rustfmt.toml
new file mode 100644
index 0000000..4335d66
--- /dev/null
+++ b/keystore2/rustfmt.toml
@@ -0,0 +1,5 @@
+# Android Format Style
+
+edition = "2021"
+use_small_heuristics = "Max"
+newline_style = "Unix"
\ No newline at end of file
diff --git a/keystore2/tests/legacy_blobs/Android.bp b/keystore2/tests/legacy_blobs/Android.bp
index f25b5d5..92f2cc3 100644
--- a/keystore2/tests/legacy_blobs/Android.bp
+++ b/keystore2/tests/legacy_blobs/Android.bp
@@ -33,7 +33,6 @@
     rustlibs: [
         "libkeystore2_with_test_utils",
         "libkeystore2_crypto_rust",
-        "android.system.keystore2-V2-rust",
         "android.security.maintenance-rust",
         "android.security.authorization-rust",
         "librustutils",
@@ -48,6 +47,7 @@
     ],
     defaults: [
         "keymint_use_latest_hal_aidl_rust",
+        "keystore2_use_latest_aidl_rust",
     ],
     require_root: true,
 }
diff --git a/provisioner/rkp_factory_extraction_lib.cpp b/provisioner/rkp_factory_extraction_lib.cpp
index 3bf3d7e..77d032b 100644
--- a/provisioner/rkp_factory_extraction_lib.cpp
+++ b/provisioner/rkp_factory_extraction_lib.cpp
@@ -19,6 +19,10 @@
 #include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
 #include <android/binder_manager.h>
 #include <cppbor.h>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
 #include <keymaster/cppcose/cppcose.h>
 #include <openssl/base64.h>
 #include <remote_prov/remote_prov_utils.h>
@@ -37,8 +41,12 @@
 using aidl::android::hardware::security::keymint::MacedPublicKey;
 using aidl::android::hardware::security::keymint::ProtectedData;
 using aidl::android::hardware::security::keymint::RpcHardwareInfo;
+using aidl::android::hardware::security::keymint::remote_prov::EekChain;
+using aidl::android::hardware::security::keymint::remote_prov::generateEekChain;
 using aidl::android::hardware::security::keymint::remote_prov::getProdEekChain;
 using aidl::android::hardware::security::keymint::remote_prov::jsonEncodeCsrWithBuild;
+using aidl::android::hardware::security::keymint::remote_prov::parseAndValidateFactoryDeviceInfo;
+using aidl::android::hardware::security::keymint::remote_prov::verifyFactoryProtectedData;
 
 using namespace cppbor;
 using namespace cppcose;
@@ -89,29 +97,30 @@
     return challenge;
 }
 
-CsrResult composeCertificateRequest(const ProtectedData& protectedData,
-                                    const DeviceInfo& verifiedDeviceInfo,
-                                    const std::vector<uint8_t>& challenge,
-                                    const std::vector<uint8_t>& keysToSignMac) {
+CborResult<Array> composeCertificateRequest(const ProtectedData& protectedData,
+                                            const DeviceInfo& verifiedDeviceInfo,
+                                            const std::vector<uint8_t>& challenge,
+                                            const std::vector<uint8_t>& keysToSignMac,
+                                            IRemotelyProvisionedComponent* provisionable) {
     Array macedKeysToSign = Array()
                                 .add(Map().add(1, 5).encode())  // alg: hmac-sha256
                                 .add(Map())                     // empty unprotected headers
                                 .add(Null())                    // nil for the payload
                                 .add(keysToSignMac);            // MAC as returned from the HAL
 
-    auto [parsedVerifiedDeviceInfo, ignore1, errMsg] = parse(verifiedDeviceInfo.deviceInfo);
+    ErrMsgOr<std::unique_ptr<Map>> parsedVerifiedDeviceInfo =
+        parseAndValidateFactoryDeviceInfo(verifiedDeviceInfo.deviceInfo, provisionable);
     if (!parsedVerifiedDeviceInfo) {
-        std::cerr << "Error parsing device info: '" << errMsg << "'" << std::endl;
-        return {nullptr, errMsg};
+        return {nullptr, parsedVerifiedDeviceInfo.moveMessage()};
     }
 
-    auto [parsedProtectedData, ignore2, errMsg2] = parse(protectedData.protectedData);
+    auto [parsedProtectedData, ignore2, errMsg] = parse(protectedData.protectedData);
     if (!parsedProtectedData) {
-        std::cerr << "Error parsing protected data: '" << errMsg2 << "'" << std::endl;
+        std::cerr << "Error parsing protected data: '" << errMsg << "'" << std::endl;
         return {nullptr, errMsg};
     }
 
-    Array deviceInfo = Array().add(std::move(parsedVerifiedDeviceInfo)).add(Map());
+    Array deviceInfo = Array().add(parsedVerifiedDeviceInfo.moveValue()).add(Map());
 
     auto certificateRequest = std::make_unique<Array>();
     (*certificateRequest)
@@ -119,10 +128,10 @@
         .add(challenge)
         .add(std::move(parsedProtectedData))
         .add(std::move(macedKeysToSign));
-    return {std::move(certificateRequest), std::nullopt};
+    return {std::move(certificateRequest), ""};
 }
 
-CsrResult getCsr(std::string_view componentName, IRemotelyProvisionedComponent* irpc) {
+CborResult<Array> getCsr(std::string_view componentName, IRemotelyProvisionedComponent* irpc) {
     std::vector<uint8_t> keysToSignMac;
     std::vector<MacedPublicKey> emptyKeys;
     DeviceInfo verifiedDeviceInfo;
@@ -145,5 +154,42 @@
                   << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl;
         exit(-1);
     }
-    return composeCertificateRequest(protectedData, verifiedDeviceInfo, challenge, keysToSignMac);
+    return composeCertificateRequest(protectedData, verifiedDeviceInfo, challenge, keysToSignMac,
+                                     irpc);
 }
+
+void selfTestGetCsr(std::string_view componentName, IRemotelyProvisionedComponent* irpc) {
+    std::vector<uint8_t> keysToSignMac;
+    std::vector<MacedPublicKey> emptyKeys;
+    DeviceInfo verifiedDeviceInfo;
+    ProtectedData protectedData;
+    RpcHardwareInfo hwInfo;
+    ::ndk::ScopedAStatus status = irpc->getHardwareInfo(&hwInfo);
+    if (!status.isOk()) {
+        std::cerr << "Failed to get hardware info for '" << componentName
+                  << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl;
+        exit(-1);
+    }
+
+    const std::vector<uint8_t> eekId = {0, 1, 2, 3, 4, 5, 6, 7};
+    ErrMsgOr<EekChain> eekChain = generateEekChain(hwInfo.supportedEekCurve, /*length=*/3, eekId);
+    if (!eekChain) {
+        std::cerr << "Error generating test EEK certificate chain: " << eekChain.message();
+        exit(-1);
+    }
+    const std::vector<uint8_t> challenge = generateChallenge();
+    status = irpc->generateCertificateRequest(
+        /*test_mode=*/true, emptyKeys, eekChain->chain, challenge, &verifiedDeviceInfo,
+        &protectedData, &keysToSignMac);
+    if (!status.isOk()) {
+        std::cerr << "Error generating test cert chain for '" << componentName
+                  << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl;
+        exit(-1);
+    }
+
+    auto result = verifyFactoryProtectedData(verifiedDeviceInfo, /*keysToSign=*/{}, keysToSignMac,
+                                             protectedData, *eekChain, eekId,
+                                             hwInfo.supportedEekCurve, irpc, challenge);
+
+    std::cout << "Self test successful." << std::endl;
+}
\ No newline at end of file
diff --git a/provisioner/rkp_factory_extraction_lib.h b/provisioner/rkp_factory_extraction_lib.h
index fe15402..a803582 100644
--- a/provisioner/rkp_factory_extraction_lib.h
+++ b/provisioner/rkp_factory_extraction_lib.h
@@ -21,17 +21,17 @@
 
 #include <cstdint>
 #include <memory>
-#include <optional>
 #include <string>
+#include <string_view>
 #include <vector>
 
 constexpr size_t kChallengeSize = 16;
 
-// Contains the result of CSR generation, bundling up the result (on success)
-// with an error message (on failure).
-struct CsrResult {
-    std::unique_ptr<cppbor::Array> csr;
-    std::optional<std::string> errMsg;
+// Contains a the result of an operation that should return cborData on success.
+// Returns an an error message and null cborData on error.
+template <typename T> struct CborResult {
+    std::unique_ptr<T> cborData;
+    std::string errMsg;
 };
 
 // Return `buffer` encoded as a base64 string.
@@ -43,5 +43,11 @@
 // Get a certificate signing request for the given IRemotelyProvisionedComponent.
 // On error, the csr Array is null, and the string field contains a description of
 // what went wrong.
-CsrResult getCsr(std::string_view componentName,
-                 aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc);
\ No newline at end of file
+CborResult<cppbor::Array>
+getCsr(std::string_view componentName,
+       aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc);
+
+// Generates a test certificate chain and validates it, exiting the process on error.
+void selfTestGetCsr(
+    std::string_view componentName,
+    aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc);
\ No newline at end of file
diff --git a/provisioner/rkp_factory_extraction_lib_test.cpp b/provisioner/rkp_factory_extraction_lib_test.cpp
index c611097..b27b717 100644
--- a/provisioner/rkp_factory_extraction_lib_test.cpp
+++ b/provisioner/rkp_factory_extraction_lib_test.cpp
@@ -16,6 +16,8 @@
 
 #include "rkp_factory_extraction_lib.h"
 
+#include "gmock/gmock-matchers.h"
+#include "gmock/gmock-more-matchers.h"
 #include <aidl/android/hardware/security/keymint/DeviceInfo.h>
 #include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
 #include <aidl/android/hardware/security/keymint/MacedPublicKey.h>
@@ -24,6 +26,7 @@
 #include <gtest/gtest.h>
 
 #include <cstdint>
+#include <memory>
 #include <ostream>
 #include <set>
 #include <vector>
@@ -126,7 +129,20 @@
     Map cborDeviceInfo;
     cborDeviceInfo.add("product", "gShoe");
     cborDeviceInfo.add("version", 2);
-    const DeviceInfo kVerifiedDeviceInfo = {cborDeviceInfo.encode()};
+    cborDeviceInfo.add("brand", "Fake Brand");
+    cborDeviceInfo.add("manufacturer", "Fake Mfr");
+    cborDeviceInfo.add("model", "Fake Model");
+    cborDeviceInfo.add("device", "Fake Device");
+    cborDeviceInfo.add("vb_state", "orange");
+    cborDeviceInfo.add("bootloader_state", "unlocked");
+    cborDeviceInfo.add("vbmeta_digest", std::vector<uint8_t>{1, 2, 3, 4});
+    cborDeviceInfo.add("system_patch_level", 42);
+    cborDeviceInfo.add("boot_patch_level", 31415);
+    cborDeviceInfo.add("vendor_patch_level", 0);
+    cborDeviceInfo.add("fused", 0);
+    cborDeviceInfo.add("security_level", "tee");
+    cborDeviceInfo.add("os_version", "the best version");
+    const DeviceInfo kVerifiedDeviceInfo = {cborDeviceInfo.canonicalize().encode()};
 
     Array cborProtectedData;
     cborProtectedData.add(Bstr());   // protected
@@ -140,7 +156,10 @@
 
     // Set up mock, then call getSCsr
     auto mockRpc = SharedRefBase::make<MockIRemotelyProvisionedComponent>();
-    EXPECT_CALL(*mockRpc, getHardwareInfo(NotNull())).WillOnce(Return(ByMove(ScopedAStatus::ok())));
+    EXPECT_CALL(*mockRpc, getHardwareInfo(NotNull())).WillRepeatedly([](RpcHardwareInfo* hwInfo) {
+        hwInfo->versionNumber = 2;
+        return ScopedAStatus::ok();
+    });
     EXPECT_CALL(*mockRpc,
                 generateCertificateRequest(false,               // testMode
                                            IsEmpty(),           // keysToSign
@@ -157,7 +176,7 @@
                         Return(ByMove(ScopedAStatus::ok()))));  //
 
     auto [csr, csrErrMsg] = getCsr("mock component name", mockRpc.get());
-    ASSERT_THAT(csr, NotNull()) << csrErrMsg.value_or("");
+    ASSERT_THAT(csr, NotNull()) << csrErrMsg;
     ASSERT_THAT(csr->asArray(), Pointee(Property(&Array::size, Eq(4))));
 
     // Verify the input parameters that we received
@@ -172,7 +191,7 @@
 
     // Verified device info must match our mock value
     const Map* actualVerifiedDeviceInfo = deviceInfoArray->get(0)->asMap();
-    EXPECT_THAT(actualVerifiedDeviceInfo, Pointee(Property(&Map::size, Eq(2))));
+    EXPECT_THAT(actualVerifiedDeviceInfo, Pointee(Property(&Map::size, Eq(cborDeviceInfo.size()))));
     EXPECT_THAT(actualVerifiedDeviceInfo->get("product"), Pointee(Eq(Tstr("gShoe"))));
     EXPECT_THAT(actualVerifiedDeviceInfo->get("version"), Pointee(Eq(Uint(2))));
 
diff --git a/provisioner/rkp_factory_extraction_tool.cpp b/provisioner/rkp_factory_extraction_tool.cpp
index ee8d851..0fe7d74 100644
--- a/provisioner/rkp_factory_extraction_tool.cpp
+++ b/provisioner/rkp_factory_extraction_tool.cpp
@@ -34,7 +34,11 @@
 using namespace cppbor;
 using namespace cppcose;
 
-DEFINE_string(output_format, "csr", "How to format the output. Defaults to 'csr'.");
+DEFINE_string(output_format, "build+csr", "How to format the output. Defaults to 'build+csr'.");
+DEFINE_bool(self_test, false,
+            "If true, the tool does not output CSR data, but instead performs a self-test, "
+            "validating a test payload for correctness. This may be used to verify a device on the "
+            "factory line before attempting to upload the output to the device info service.");
 
 namespace {
 
@@ -79,14 +83,17 @@
         exit(-1);
     }
 
-    auto [request, errMsg] = getCsr(name, rkp_service.get());
-    if (!request) {
-        std::cerr << "Unable to build CSR for '" << fullName << ": "
-                  << errMsg.value_or("<Unknown Error>") << std::endl;
-        exit(-1);
-    }
+    if (FLAGS_self_test) {
+        selfTestGetCsr(name, rkp_service.get());
+    } else {
+        auto [request, errMsg] = getCsr(name, rkp_service.get());
+        if (!request) {
+            std::cerr << "Unable to build CSR for '" << fullName << ": " << errMsg << std::endl;
+            exit(-1);
+        }
 
-    writeOutput(std::string(name), *request);
+        writeOutput(std::string(name), *request);
+    }
 }
 
 }  // namespace