Snap for 10447354 from fe47b7a2c273caaea6c76a15abe25bd0bb501be9 to mainline-wifi-release

Change-Id: Ia7792b7e0b023677563ebc9fec8c1d159f9a12b8
diff --git a/OWNERS b/OWNERS
index 7d6099d..a9d0762 100644
--- a/OWNERS
+++ b/OWNERS
@@ -3,6 +3,8 @@
 # Sanitizers
 mitchp@google.com
 eugenis@google.com
+fmayer@google.com
+
 
 # Haiku members (fuzzing-on-Android)
 hamzeh@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..75ed57c
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,5 @@
+[Builtin Hooks]
+rustfmt = true
+
+[Builtin Hooks Options]
+rustfmt = --config-path=rustfmt.toml
diff --git a/fuzzing/brotli_java_compress_fuzzer/Android.bp b/fuzzing/brotli_java_compress_fuzzer/Android.bp
new file mode 100644
index 0000000..00c89dc
--- /dev/null
+++ b/fuzzing/brotli_java_compress_fuzzer/Android.bp
@@ -0,0 +1,20 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_fuzz {
+    name: "brotli_java_compress_fuzzer",
+    srcs: [
+        "BrotliJavaCompressFuzzer.java",
+    ],
+    host_supported: true,
+    device_supported: false,
+    static_libs: [
+        "jazzer",
+        "brotli-java",
+    ],
+    jni_libs: [
+        "libbrotli_encoder_jni",
+        "libc++",
+    ],
+}
diff --git a/fuzzing/brotli_java_compress_fuzzer/BrotliJavaCompressFuzzer.java b/fuzzing/brotli_java_compress_fuzzer/BrotliJavaCompressFuzzer.java
new file mode 100644
index 0000000..b86fa77
--- /dev/null
+++ b/fuzzing/brotli_java_compress_fuzzer/BrotliJavaCompressFuzzer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.io.IOException;
+import org.brotli.wrapper.enc.Encoder;
+
+public class BrotliJavaCompressFuzzer {
+  static {
+    System.loadLibrary("brotli_encoder_jni");
+  }
+  public static void fuzzerTestOneInput(FuzzedDataProvider data) throws IOException{
+    // quality range for brotli is limited in the original library
+    int quality = data.consumeInt(-1, 11);
+    // similar to quality, window size is also limited
+    int lgwin = data.consumeInt(10, 24);
+
+    boolean pickDefaultWin = data.consumeBoolean();
+    if (pickDefaultWin) {
+      lgwin = -1;
+    }
+
+    byte[] rawData = data.consumeRemainingAsBytes();
+    Encoder.Parameters params = new Encoder.Parameters();
+    params.setQuality(quality);
+    params.setWindow(lgwin);
+    Encoder.compress(rawData, params);
+  }
+}
diff --git a/fuzzing/example_fuzzer/Android.bp b/fuzzing/example_fuzzer/Android.bp
index a3efd3e..97e792c 100644
--- a/fuzzing/example_fuzzer/Android.bp
+++ b/fuzzing/example_fuzzer/Android.bp
@@ -19,7 +19,10 @@
     corpus: ["corpus/*"],
     dictionary: "example_fuzzer.dict",
     fuzz_config: {
-        fuzz_on_haiku_device: false,
-        fuzz_on_haiku_host: false,
+        description: "Test Fuzzer",
+        production_date: "6/8/2019",
+        critical: false,
+        fuzz_on_haiku_device: true,
+        fuzz_on_haiku_host: true,
     },
 }
diff --git a/fuzzing/example_java_fuzzer/Android.bp b/fuzzing/example_java_fuzzer/Android.bp
index 9bbb1a8..7c5a12f 100644
--- a/fuzzing/example_java_fuzzer/Android.bp
+++ b/fuzzing/example_java_fuzzer/Android.bp
@@ -16,7 +16,7 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-java_fuzz_host {
+java_fuzz {
     name: "example_java_fuzzer",
     srcs: ["ExampleFuzzer.java"],
     static_libs: [
@@ -26,6 +26,17 @@
         fuzz_on_haiku_device: false,
         fuzz_on_haiku_host: false,
     },
+    host_supported: true,
     corpus: ["testdata/*"],
     dictionary: "example_java_fuzzer.dict",
 }
+
+java_fuzz {
+    name: "example_java_fuzzer_device",
+    srcs: ["ExampleFuzzer.java"],
+    static_libs: [
+        "jazzer",
+    ],
+    corpus: ["testdata/*"],
+    dictionary: "example_java_fuzzer.dict",
+}
\ No newline at end of file
diff --git a/fuzzing/example_java_fuzzer/data_file.txt b/fuzzing/example_java_fuzzer/data_file.txt
new file mode 100644
index 0000000..951c35a
--- /dev/null
+++ b/fuzzing/example_java_fuzzer/data_file.txt
@@ -0,0 +1 @@
+this here is the data file
\ No newline at end of file
diff --git a/fuzzing/example_java_jni_fuzzer/Android.bp b/fuzzing/example_java_jni_fuzzer/Android.bp
new file mode 100644
index 0000000..eda8319
--- /dev/null
+++ b/fuzzing/example_java_jni_fuzzer/Android.bp
@@ -0,0 +1,36 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_fuzz {
+    name: "example_java_jni_fuzzer",
+    srcs: [
+        "ExampleJavaJniFuzzer.java",
+    ],
+    host_supported: true,
+    static_libs: [
+        "jazzer",
+    ],
+    fuzz_config: {
+        fuzz_on_haiku_device: false,
+        fuzz_on_haiku_host: false,
+    },
+    jni_libs: [
+        "libnative_asan",
+        "libc++",
+    ],
+}
+
+cc_library_shared {
+    name: "libnative_asan",
+    host_supported: true,
+    srcs: [
+        "example_jni.cpp",
+    ],
+    static_libs: [
+        "libnativehelper_lazy",
+    ],
+    cflags: [
+        "-Wno-unused-parameter",
+    ],
+}
diff --git a/fuzzing/example_java_jni_fuzzer/ExampleJavaJniFuzzer.java b/fuzzing/example_java_jni_fuzzer/ExampleJavaJniFuzzer.java
new file mode 100644
index 0000000..973ec4f
--- /dev/null
+++ b/fuzzing/example_java_jni_fuzzer/ExampleJavaJniFuzzer.java
@@ -0,0 +1,32 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+public class ExampleJavaJniFuzzer {
+  static {
+    System.loadLibrary("native_asan");
+  }
+
+  public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+    int val = data.consumeInt();
+    String stringData = data.consumeRemainingAsString();
+    if (val == 17759716 && stringData.length() > 10 && stringData.contains("jazzer")) {
+      // call native function which contains a crash
+      new ExampleJavaJniFuzzer().parse(stringData);
+    }
+  }
+
+  private native boolean parse(String bytes);
+}
\ No newline at end of file
diff --git a/fuzzing/example_java_jni_fuzzer/example_jni.cpp b/fuzzing/example_java_jni_fuzzer/example_jni.cpp
new file mode 100644
index 0000000..153e24d
--- /dev/null
+++ b/fuzzing/example_java_jni_fuzzer/example_jni.cpp
@@ -0,0 +1,44 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <limits>
+#include <string>
+#include <nativehelper/JNIHelp.h>
+#include <jni.h>
+#include <stdio.h>
+#include "example_jni.h"
+
+// simple function containing a crash that requires coverage and string compare
+// instrumentation for the fuzzer to find
+__attribute__((optnone)) void parseInternal(const std::string &input) {
+  constexpr int bar = std::numeric_limits<int>::max() - 5;
+  // Crashes with UBSan.
+  if (bar + input[0] == 300) {
+    return;
+  }
+  if (input[0] == 'a' && input[1] == 'b' && input[5] == 'c') {
+    if (input.find("intentional_crash_to_test_jni_fuzzing") != std::string::npos) {
+      // Crashes with ASan.
+      [[maybe_unused]] char foo = input[input.size() + 2];
+    }
+  }
+}
+
+JNIEXPORT jboolean JNICALL Java_ExampleJavaJniFuzzer_parse(
+    JNIEnv *env, jobject o, jstring bytes) {
+  const char *input(env->GetStringUTFChars(bytes, nullptr));
+  parseInternal(input);
+  env->ReleaseStringUTFChars(bytes, input);
+  return false;
+}
diff --git a/fuzzing/example_java_jni_fuzzer/example_jni.h b/fuzzing/example_java_jni_fuzzer/example_jni.h
new file mode 100644
index 0000000..10b3245
--- /dev/null
+++ b/fuzzing/example_java_jni_fuzzer/example_jni.h
@@ -0,0 +1,12 @@
+#include <jni.h>
+#ifndef _Included_ExampleFuzzerWithNative
+#define _Included_ExampleFuzzerWithNative
+#ifdef __cplusplus
+extern "C" {
+#endif
+JNIEXPORT jboolean JNICALL Java_ExampleJavaJniFuzzer_parse(
+    JNIEnv*, jobject o, jstring);
+#ifdef __cplusplus
+}
+#endif
+#endif
\ No newline at end of file
diff --git a/fuzzing/example_java_jni_fuzzer_no_crash/Android.bp b/fuzzing/example_java_jni_fuzzer_no_crash/Android.bp
new file mode 100644
index 0000000..d3ab937
--- /dev/null
+++ b/fuzzing/example_java_jni_fuzzer_no_crash/Android.bp
@@ -0,0 +1,37 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_fuzz {
+    name: "example_java_jni_no_crash_fuzzer",
+    srcs: [
+        "ExampleJavaJniFuzzer.java",
+    ],
+    host_supported: true,
+    device_supported: false,
+    static_libs: [
+        "jazzer",
+    ],
+    fuzz_config: {
+        fuzz_on_haiku_device: false,
+        fuzz_on_haiku_host: false,
+    },
+    jni_libs: [
+        "libnative_asan_no_crash",
+        "libc++",
+    ],
+}
+
+cc_library_shared {
+    name: "libnative_asan_no_crash",
+    host_supported: true,
+    srcs: [
+        "example_jni.cpp",
+    ],
+    static_libs: [
+        "libnativehelper_lazy",
+    ],
+    cflags: [
+        "-Wno-unused-parameter",
+    ],
+}
diff --git a/fuzzing/example_java_jni_fuzzer_no_crash/ExampleJavaJniFuzzer.java b/fuzzing/example_java_jni_fuzzer_no_crash/ExampleJavaJniFuzzer.java
new file mode 100644
index 0000000..6e81d7e
--- /dev/null
+++ b/fuzzing/example_java_jni_fuzzer_no_crash/ExampleJavaJniFuzzer.java
@@ -0,0 +1,32 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+public class ExampleJavaJniFuzzer {
+  static {
+    System.loadLibrary("native_asan_no_crash");
+  }
+
+  public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+    int val = data.consumeInt();
+    String stringData = data.consumeRemainingAsString();
+    if (val == 17759716 && stringData.length() > 10 && stringData.contains("jazzer")) {
+      // call native function which contains a crash
+      new ExampleJavaJniFuzzer().parse(stringData);
+    }
+  }
+
+  private native boolean parse(String bytes);
+}
\ No newline at end of file
diff --git a/fuzzing/example_java_jni_fuzzer_no_crash/example_jni.cpp b/fuzzing/example_java_jni_fuzzer_no_crash/example_jni.cpp
new file mode 100644
index 0000000..bb31f73
--- /dev/null
+++ b/fuzzing/example_java_jni_fuzzer_no_crash/example_jni.cpp
@@ -0,0 +1,39 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <limits>
+#include <string>
+#include <nativehelper/JNIHelp.h>
+#include <jni.h>
+#include <stdio.h>
+#include "example_jni.h"
+
+// simple function containing a crash that requires coverage and string compare
+// instrumentation for the fuzzer to find
+__attribute__((optnone)) void parseInternal(const std::string &input) {
+  constexpr int bar = std::numeric_limits<int>::max() - 5;
+  if (input[0] == 'a' && input[1] == 'b' && input[5] == 'c') {
+    if (input.find("should report no crash") != std::string::npos) {
+      [[maybe_unused]] char foo = input[input.size()];
+    }
+  }
+}
+
+JNIEXPORT jboolean JNICALL Java_ExampleJavaJniFuzzer_parse(
+    JNIEnv *env, jobject o, jstring bytes) {
+  const char *input(env->GetStringUTFChars(bytes, nullptr));
+  parseInternal(input);
+  env->ReleaseStringUTFChars(bytes, input);
+  return false;
+}
diff --git a/fuzzing/example_java_jni_fuzzer_no_crash/example_jni.h b/fuzzing/example_java_jni_fuzzer_no_crash/example_jni.h
new file mode 100644
index 0000000..10b3245
--- /dev/null
+++ b/fuzzing/example_java_jni_fuzzer_no_crash/example_jni.h
@@ -0,0 +1,12 @@
+#include <jni.h>
+#ifndef _Included_ExampleFuzzerWithNative
+#define _Included_ExampleFuzzerWithNative
+#ifdef __cplusplus
+extern "C" {
+#endif
+JNIEXPORT jboolean JNICALL Java_ExampleJavaJniFuzzer_parse(
+    JNIEnv*, jobject o, jstring);
+#ifdef __cplusplus
+}
+#endif
+#endif
\ No newline at end of file
diff --git a/fuzzing/example_rust_fuzzer/fuzzer.rs b/fuzzing/example_rust_fuzzer/fuzzer.rs
index e4cebcb..14c0a51 100644
--- a/fuzzing/example_rust_fuzzer/fuzzer.rs
+++ b/fuzzing/example_rust_fuzzer/fuzzer.rs
@@ -14,7 +14,8 @@
 
 #![allow(missing_docs)]
 #![no_main]
-#[macro_use] extern crate libfuzzer_sys;
+
+use libfuzzer_sys::fuzz_target;
 
 fn heap_oob() {
     let xs = vec![0, 1, 2, 3];
diff --git a/fuzzing/orphans/android_logger/Android.bp b/fuzzing/orphans/android_logger/Android.bp
new file mode 100644
index 0000000..dc5a3e4
--- /dev/null
+++ b/fuzzing/orphans/android_logger/Android.bp
@@ -0,0 +1,18 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_fuzz {
+    name: "android_logger_fuzzer",
+    srcs: ["android_logger_fuzzer.rs"],
+    rustlibs: [
+        "libarbitrary",
+        "libandroid_logger",
+        "liblog_rust",
+    ],
+    fuzz_config: {
+        cc: ["cukie@google.com"],
+        fuzz_on_haiku_device: true,
+        fuzz_on_haiku_host: true,
+    },
+}
\ No newline at end of file
diff --git a/fuzzing/orphans/android_logger/android_logger_fuzzer.rs b/fuzzing/orphans/android_logger/android_logger_fuzzer.rs
new file mode 100644
index 0000000..94dcee6
--- /dev/null
+++ b/fuzzing/orphans/android_logger/android_logger_fuzzer.rs
@@ -0,0 +1,135 @@
+#![no_main]
+#![allow(missing_docs)]
+
+use android_logger::{AndroidLogger, Config, Filter, FilterBuilder, LogId};
+use libfuzzer_sys::arbitrary::Arbitrary;
+use libfuzzer_sys::fuzz_target;
+use log::{Level, LevelFilter, Log, Record};
+use std::ffi::CString;
+
+// TODO: Remove once Level and LevelInput Arbitrary derivations are upstreamed.
+// https://github.com/rust-lang/log/issues/530
+#[derive(Arbitrary, Copy, Clone, Debug)]
+pub enum LevelInput {
+    Error,
+    Warn,
+    Info,
+    Debug,
+    Trace,
+}
+
+impl From<LevelInput> for Level {
+    fn from(l: LevelInput) -> Level {
+        match l {
+            LevelInput::Error => Level::Error,
+            LevelInput::Warn => Level::Warn,
+            LevelInput::Info => Level::Info,
+            LevelInput::Debug => Level::Debug,
+            LevelInput::Trace => Level::Trace,
+        }
+    }
+}
+
+#[derive(Arbitrary, Debug)]
+pub enum LevelFilterInput {
+    Off,
+    Error,
+    Warn,
+    Info,
+    Debug,
+    Trace,
+}
+
+impl From<&LevelFilterInput> for LevelFilter {
+    fn from(l: &LevelFilterInput) -> LevelFilter {
+        match l {
+            LevelFilterInput::Off => LevelFilter::Off,
+            LevelFilterInput::Error => LevelFilter::Error,
+            LevelFilterInput::Warn => LevelFilter::Warn,
+            LevelFilterInput::Info => LevelFilter::Info,
+            LevelFilterInput::Debug => LevelFilter::Debug,
+            LevelFilterInput::Trace => LevelFilter::Trace,
+        }
+    }
+}
+
+#[derive(Arbitrary, Copy, Clone, Debug)]
+pub enum LogIdInput {
+    Main,
+    Radio,
+    Events,
+    System,
+    Crash,
+}
+
+impl From<LogIdInput> for LogId {
+    fn from(l: LogIdInput) -> LogId {
+        match l {
+            LogIdInput::Main => LogId::Main,
+            LogIdInput::Radio => LogId::Radio,
+            LogIdInput::Events => LogId::Events,
+            LogIdInput::System => LogId::System,
+            LogIdInput::Crash => LogId::Crash,
+        }
+    }
+}
+
+#[derive(Arbitrary, Debug)]
+struct ConfigInput {
+    log_level: LevelInput,
+    log_id: LogIdInput,
+    filters: Vec<(Option<String>, LevelFilterInput)>,
+    tag: CString,
+}
+
+impl ConfigInput {
+    fn get_filter(&self) -> Filter {
+        let mut builder = FilterBuilder::new();
+        for (name, level) in &self.filters {
+            builder.filter(name.as_deref(), level.into());
+        }
+        builder.build()
+    }
+}
+
+impl From<ConfigInput> for Config {
+    fn from(config_input: ConfigInput) -> Config {
+        Config::default()
+            .with_filter(config_input.get_filter())
+            .with_min_level(config_input.log_level.into())
+            .with_tag(config_input.tag)
+            .with_log_id(config_input.log_id.into())
+    }
+}
+
+#[derive(Arbitrary, Debug)]
+struct RecordInput {
+    log_level: LevelInput,
+    target: String,
+    module_path: Option<String>,
+    file: Option<String>,
+    line: Option<u32>,
+    message: String,
+}
+
+#[derive(Arbitrary, Debug)]
+struct LoggerInput {
+    config_input: ConfigInput,
+    record_input: RecordInput,
+}
+
+fuzz_target!(|logger_input: LoggerInput| {
+    let config: Config = logger_input.config_input.into();
+    let logger = AndroidLogger::new(config);
+    let record_input = &logger_input.record_input;
+    logger.log(
+        &Record::builder()
+            .args(format_args!("{}", record_input.message))
+            .level(record_input.log_level.into())
+            .target(&record_input.target)
+            .file(record_input.file.as_deref())
+            .line(record_input.line)
+            .module_path(record_input.module_path.as_deref())
+            .build(),
+    );
+});
diff --git a/fuzzing/orphans/gptfdisk/Android.bp b/fuzzing/orphans/gptfdisk/Android.bp
deleted file mode 100644
index e1c5333..0000000
--- a/fuzzing/orphans/gptfdisk/Android.bp
+++ /dev/null
@@ -1,21 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_fuzz {
-    name: "sgdisk_fuzz",
-    host_supported: true,
-    srcs: [
-        "sgdisk_fuzz.cc",
-    ],
-    static_libs: [
-        "libgptf",
-    ],
-    shared_libs: ["libext2_uuid"],
-    corpus: ["corpus/*"],
-    target: {
-        android: {
-            cflags: ["-DGPTFDISK_FUZZER_DEVICE"],
-        },
-    },
-}
diff --git a/fuzzing/orphans/gptfdisk/corpus/init.img b/fuzzing/orphans/gptfdisk/corpus/init.img
deleted file mode 100644
index f87f897..0000000
--- a/fuzzing/orphans/gptfdisk/corpus/init.img
+++ /dev/null
Binary files differ
diff --git a/fuzzing/orphans/gptfdisk/sgdisk_fuzz.cc b/fuzzing/orphans/gptfdisk/sgdisk_fuzz.cc
deleted file mode 100644
index 6a02281..0000000
--- a/fuzzing/orphans/gptfdisk/sgdisk_fuzz.cc
+++ /dev/null
@@ -1,58 +0,0 @@
-#include <iostream>
-#include <fstream>
-#include <string.h>
-#include <string>
-#include <iostream>
-#include <sstream>
-#include <errno.h>
-#include "gptcl.h"
-#include <fcntl.h>
-#include <unistd.h>
-
-static int fuzz_gpt(char* partition_file) {
-    BasicMBRData mbrData;
-    GPTData gptData;
-    GPTPart partData;
-    int numParts = 0;
-    stringstream res;
-
-    gptData.JustLooking();
-    gptData.LoadPartitions((string) partition_file);
-    gptData.LoadMainTable();
-    gptData.GetDiskGUID();
-    numParts = gptData.GetNumParts();
-
-    //Extracted from the android_dump function in sgdisk.cc, hits more code
-    for (int i = 0; i < numParts; i++) {
-        partData = gptData[i];
-        if (partData.GetFirstLBA() > 0) {
-            partData.GetType();
-            partData.GetUniqueGUID();
-            partData.GetDescription();;
-        }
-    }
-    return 0;
-}
-
-#ifdef GPTFDISK_FUZZER_DEVICE
-#define TMPFILE_TEMPLATE "/data/local/tmp/gptfuzzXXXXXXXX\x00"
-#else
-#define TMPFILE_TEMPLATE "/dev/shm/gptfuzzXXXXXXXX\x00"
-#endif
-
-size_t TMPFILE_LEN = sizeof(TMPFILE_TEMPLATE);
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
-    char partition_tmp_file[TMPFILE_LEN];
-    int tmpfd;
-
-    memcpy(partition_tmp_file, TMPFILE_TEMPLATE, TMPFILE_LEN);
-    tmpfd = mkstemp(partition_tmp_file);
-    if(tmpfd < 0)
-        return -1;
-    write(tmpfd, data, size);
-    close(tmpfd);
-    fuzz_gpt(partition_tmp_file);
-    remove(partition_tmp_file);
-    return 0;
-}
diff --git a/fuzzing/orphans/hashbrown/hashbrown_hashmap_fuzzer.rs b/fuzzing/orphans/hashbrown/hashbrown_hashmap_fuzzer.rs
index 8267cf0..a79fa7f 100644
--- a/fuzzing/orphans/hashbrown/hashbrown_hashmap_fuzzer.rs
+++ b/fuzzing/orphans/hashbrown/hashbrown_hashmap_fuzzer.rs
@@ -14,7 +14,6 @@
 
 #![allow(missing_docs)]
 #![no_main]
-#![feature(bench_black_box)]
 
 use hashbrown::HashMap;
 use libfuzzer_sys::arbitrary::Arbitrary;
diff --git a/fuzzing/orphans/hashbrown/hashbrown_hashset_fuzzer.rs b/fuzzing/orphans/hashbrown/hashbrown_hashset_fuzzer.rs
index 3eb7baf..4bc4a65 100644
--- a/fuzzing/orphans/hashbrown/hashbrown_hashset_fuzzer.rs
+++ b/fuzzing/orphans/hashbrown/hashbrown_hashset_fuzzer.rs
@@ -14,7 +14,6 @@
 
 #![allow(missing_docs)]
 #![no_main]
-#![feature(bench_black_box)]
 
 use hashbrown::HashSet;
 use libfuzzer_sys::arbitrary::Arbitrary;
diff --git a/fuzzing/orphans/hashlink/hashlink_linkedhashmap_fuzzer.rs b/fuzzing/orphans/hashlink/hashlink_linkedhashmap_fuzzer.rs
index 65ea227..ef35fd1 100644
--- a/fuzzing/orphans/hashlink/hashlink_linkedhashmap_fuzzer.rs
+++ b/fuzzing/orphans/hashlink/hashlink_linkedhashmap_fuzzer.rs
@@ -14,7 +14,6 @@
 
 #![allow(missing_docs)]
 #![no_main]
-#![feature(bench_black_box)]
 
 use hashlink::LinkedHashMap;
 use libfuzzer_sys::arbitrary::Arbitrary;
diff --git a/fuzzing/orphans/hashlink/hashlink_linkedhashset_fuzzer.rs b/fuzzing/orphans/hashlink/hashlink_linkedhashset_fuzzer.rs
index 0135a9d..83260c8 100644
--- a/fuzzing/orphans/hashlink/hashlink_linkedhashset_fuzzer.rs
+++ b/fuzzing/orphans/hashlink/hashlink_linkedhashset_fuzzer.rs
@@ -14,7 +14,6 @@
 
 #![allow(missing_docs)]
 #![no_main]
-#![feature(bench_black_box)]
 
 use hashlink::LinkedHashSet;
 use libfuzzer_sys::arbitrary::Arbitrary;
diff --git a/fuzzing/orphans/hashlink/hashlink_lrucache_fuzzer.rs b/fuzzing/orphans/hashlink/hashlink_lrucache_fuzzer.rs
index a5cc957..19910f3 100644
--- a/fuzzing/orphans/hashlink/hashlink_lrucache_fuzzer.rs
+++ b/fuzzing/orphans/hashlink/hashlink_lrucache_fuzzer.rs
@@ -14,7 +14,6 @@
 
 #![allow(missing_docs)]
 #![no_main]
-#![feature(bench_black_box)]
 
 use hashlink::LruCache;
 use libfuzzer_sys::arbitrary::Arbitrary;
diff --git a/fuzzing/orphans/hex/Android.bp b/fuzzing/orphans/hex/Android.bp
new file mode 100644
index 0000000..5bb995b
--- /dev/null
+++ b/fuzzing/orphans/hex/Android.bp
@@ -0,0 +1,47 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_fuzz {
+    name: "hex_encoding_fuzzer",
+    srcs: ["hex_encoding_fuzzer.rs"],
+    rustlibs: [
+        "libhex",
+    ],
+    fuzz_config: {
+        cc: [
+            "aliceywang@google.com",
+        ],
+        fuzz_on_haiku_device: true,
+        fuzz_on_haiku_host: true,
+    },
+}
+
+rust_fuzz {
+    name: "hex_decoding_fuzzer",
+    srcs: ["hex_decoding_fuzzer.rs"],
+    rustlibs: [
+        "libhex",
+    ],
+    fuzz_config: {
+        cc: [
+            "aliceywang@google.com",
+        ],
+        fuzz_on_haiku_device: true,
+        fuzz_on_haiku_host: false,
+    },
+}
\ No newline at end of file
diff --git a/fuzzing/orphans/hex/hex_decoding_fuzzer.rs b/fuzzing/orphans/hex/hex_decoding_fuzzer.rs
new file mode 100644
index 0000000..a3df727
--- /dev/null
+++ b/fuzzing/orphans/hex/hex_decoding_fuzzer.rs
@@ -0,0 +1,22 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![allow(missing_docs)]
+#![no_main]
+
+use libfuzzer_sys::fuzz_target;
+
+fuzz_target!(|data: &[u8]| {
+    let _ = hex::decode(data);
+});
diff --git a/fuzzing/orphans/hex/hex_encoding_fuzzer.rs b/fuzzing/orphans/hex/hex_encoding_fuzzer.rs
new file mode 100644
index 0000000..a6c2e95
--- /dev/null
+++ b/fuzzing/orphans/hex/hex_encoding_fuzzer.rs
@@ -0,0 +1,24 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![allow(missing_docs)]
+#![no_main]
+
+use libfuzzer_sys::fuzz_target;
+
+fuzz_target!(|data: &[u8]| {
+    let encoded = hex::encode(data);
+    let decoded = hex::decode(encoded);
+    assert_eq!(&decoded.unwrap(), data);
+});
diff --git a/fuzzing/orphans/libexif/libexif_fuzzer.cpp b/fuzzing/orphans/libexif/libexif_fuzzer.cpp
index c010bbe..6bfbbca 100644
--- a/fuzzing/orphans/libexif/libexif_fuzzer.cpp
+++ b/fuzzing/orphans/libexif/libexif_fuzzer.cpp
@@ -2,6 +2,9 @@
 #include <libexif/exif-loader.h>
 #include <stddef.h>
 #include <stdlib.h>
+#include <string.h>
+
+constexpr size_t kMaxDataSize = 32;
 
 /* Extract all MakerNote tags */
 static void mnote_dump(ExifData *data) {
@@ -32,8 +35,15 @@
 }
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+  if (!data) {
+    return 0;
+  }
+
+  size = (size % kMaxDataSize);
+  uint8_t *buffer = (uint8_t *)malloc(size * sizeof(uint8_t));
+  memcpy(buffer, data, size);
   // Parse tags using (ultimately) exif_data_load_data()
-  auto image = exif_data_new_from_data(data, size);
+  auto image = exif_data_new_from_data(buffer, size);
   if (image) {
     // Exercise the EXIF tag manipulation code
     exif_data_get_mnote_data(image);
@@ -52,14 +62,15 @@
   // be identical to what has been loaded (and fuzzed) above.
   ExifLoader *loader = exif_loader_new();
   if (!loader) {
+    free(buffer);
     return 0;
   }
-  exif_loader_write(loader, const_cast<unsigned char *>(data), size);
+  exif_loader_write(loader, const_cast<unsigned char *>(buffer), size);
   image = exif_loader_get_data(loader);
   if (image) {
     exif_data_unref(image);
   }
   exif_loader_unref(loader);
-
+  free(buffer);
   return 0;
 }
diff --git a/fuzzing/orphans/libffi/fuzz_ffi.cc b/fuzzing/orphans/libffi/fuzz_ffi.cc
index 9d83edb..b5d9e23 100644
--- a/fuzzing/orphans/libffi/fuzz_ffi.cc
+++ b/fuzzing/orphans/libffi/fuzz_ffi.cc
@@ -158,7 +158,6 @@
     // Prep Closure
     ffi_closure* pcl = NULL;
     void* code;
-    ffi_status ret;
 
     pcl = reinterpret_cast<ffi_closure*>(
             ffi_closure_alloc(sizeof(ffi_closure), &code));
@@ -170,15 +169,8 @@
             MAX_RESP_SIZE);
     std::vector<uint8_t> data_vector =
             dataProvider->ConsumeBytes<uint8_t>(buf_size);
-    ret = ffi_prep_closure_loc(
-            pcl,
-            cif,
-            closure_fn,
-            data_vector.data(),
-            code);
-    if (ret != FFI_OK) {
-        ffi_closure_free(pcl);
-    }
+    ffi_prep_closure_loc(pcl, cif, closure_fn, data_vector.data(), code);
+    ffi_closure_free(pcl);
 }
 
 void runRawFunctions(ffi_cif* cif, void* resp_buf, void** arg_array,
@@ -197,7 +189,6 @@
     #if FFI_CLOSURES
     ffi_raw_closure* pcl = NULL;
     void* code;
-    ffi_status ret;
 
     pcl = static_cast<ffi_raw_closure*>(
             ffi_closure_alloc(sizeof(ffi_raw_closure), &code));
@@ -208,15 +199,9 @@
             MAX_RESP_SIZE);
     std::vector<uint8_t> data_vector =
             dataProvider->ConsumeBytes<uint8_t>(buf_size);
-    ret = ffi_prep_raw_closure_loc(
-            pcl,
-            cif,
-            raw_closure_fn,
-            data_vector.data(),
-            code);
-    if (ret != FFI_OK) {
-        ffi_closure_free(pcl);
-    }
+    ffi_prep_raw_closure_loc(pcl, cif, raw_closure_fn, data_vector.data(),
+                             code);
+    ffi_closure_free(pcl);
 
     #endif  // FFI_CLOSURES
     #endif  // !FFI_NO_RAW_API && !FFI_NATIVE_RAW_API
@@ -242,7 +227,6 @@
     #if FFI_CLOSURES
     ffi_java_raw_closure* pcl = NULL;
     void* code;
-    ffi_status ret;
 
     pcl = static_cast<ffi_java_raw_closure*>(
             ffi_closure_alloc(sizeof(ffi_java_raw_closure), &code));
@@ -253,15 +237,11 @@
             MAX_RESP_SIZE);
     std::vector<uint8_t> data_vector =
             dataProvider->ConsumeBytes<uint8_t>(buf_size);
-    ret = ffi_prep_java_raw_closure_loc(
-            pcl,
-            cif,
-            raw_closure_fn,
-            data_vector.data(),
-            code);
-    if (ret != FFI_OK) {
-        ffi_closure_free(pcl);
-    }
+    ffi_prep_java_raw_closure_loc(pcl, cif, raw_closure_fn, data_vector.data(),
+                                  code);
+
+    ffi_closure_free(pcl);
+
 
     #endif  // FFI_CLOSURES
     #endif  // !FFI_NATIVE_RAW_API
diff --git a/fuzzing/orphans/libskia/Android.bp b/fuzzing/orphans/libskia/Android.bp
index f0020ef..cce7ba3 100644
--- a/fuzzing/orphans/libskia/Android.bp
+++ b/fuzzing/orphans/libskia/Android.bp
@@ -5,6 +5,10 @@
 cc_fuzz {
   name: "libskia_image_processor_fuzzer",
 
+  defaults: [
+    "skia_deps",
+  ],
+
   srcs: [
     "libskia_image_processor_fuzzer.cpp",
   ],
diff --git a/fuzzing/orphans/libskia/libskia_image_processor_fuzzer.cpp b/fuzzing/orphans/libskia/libskia_image_processor_fuzzer.cpp
index 6f3f2f2..a406cd6 100644
--- a/fuzzing/orphans/libskia/libskia_image_processor_fuzzer.cpp
+++ b/fuzzing/orphans/libskia/libskia_image_processor_fuzzer.cpp
@@ -20,9 +20,13 @@
 #include <cstdio>
 #include <string>
 
+#include "SkAlphaType.h"
 #include "SkAndroidCodec.h"
 #include "SkBitmap.h"
 #include "SkCodec.h"
+#include "SkColorType.h"
+#include "SkImageInfo.h"
+#include "SkStream.h"
 #include "SkString.h"
 
 #include "fuzzer/FuzzedDataProvider.h"
diff --git a/fuzzing/orphans/libufdt/Android.bp b/fuzzing/orphans/libufdt/Android.bp
new file mode 100644
index 0000000..92e19d1
--- /dev/null
+++ b/fuzzing/orphans/libufdt/Android.bp
@@ -0,0 +1,38 @@
+// Copyright 2022 The Android Open Source Project
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_fuzz {
+    name: "libufdt_fuzzer",
+    srcs: [
+        "libufdt_fuzzer.cpp",
+    ],
+    static_libs: [
+        "libfdt",
+        "libufdt_silent",
+        "libufdt_sysdeps",
+    ],
+    corpus: ["corpus/*"],
+    fuzz_config: {
+        cc: [ "mikemcternan@google.com" ],
+        target_modules: [ "libufdt", "libufdt_sysdeps" ],
+        description:
+            "Fuzzer that checks parsing of faulty FDT blobs and " +
+            "application of overlays to ensure no undefined or OOB " +
+            "behaviours.",
+        acknowledgement: [ "Mike McTernan of Google" ],
+    },
+    host_supported: true,
+}
+
+cc_binary {
+    name: "mkcorpus",
+    srcs: [
+        "mkcorpus.c",
+    ],
+    cflags: [
+        "-Wall",
+    ],
+    host_supported: true,
+}
diff --git a/fuzzing/orphans/libufdt/OWNERS b/fuzzing/orphans/libufdt/OWNERS
new file mode 100644
index 0000000..5b0f507
--- /dev/null
+++ b/fuzzing/orphans/libufdt/OWNERS
@@ -0,0 +1 @@
+mikemcternan@google.com
diff --git a/fuzzing/orphans/libufdt/corpus/fixups.bin b/fuzzing/orphans/libufdt/corpus/fixups.bin
new file mode 100644
index 0000000..97ae098
--- /dev/null
+++ b/fuzzing/orphans/libufdt/corpus/fixups.bin
Binary files differ
diff --git a/fuzzing/orphans/libufdt/corpus/local_fixup.bin b/fuzzing/orphans/libufdt/corpus/local_fixup.bin
new file mode 100644
index 0000000..b9d1960
--- /dev/null
+++ b/fuzzing/orphans/libufdt/corpus/local_fixup.bin
Binary files differ
diff --git a/fuzzing/orphans/libufdt/corpus/local_fixup_with_offset_memreserve.bin b/fuzzing/orphans/libufdt/corpus/local_fixup_with_offset_memreserve.bin
new file mode 100644
index 0000000..2d01d35
--- /dev/null
+++ b/fuzzing/orphans/libufdt/corpus/local_fixup_with_offset_memreserve.bin
Binary files differ
diff --git a/fuzzing/orphans/libufdt/corpus/many-nodes.bin b/fuzzing/orphans/libufdt/corpus/many-nodes.bin
new file mode 100644
index 0000000..d00f8b2
--- /dev/null
+++ b/fuzzing/orphans/libufdt/corpus/many-nodes.bin
Binary files differ
diff --git a/fuzzing/orphans/libufdt/libufdt_fuzzer.cpp b/fuzzing/orphans/libufdt/libufdt_fuzzer.cpp
new file mode 100644
index 0000000..eba75d8
--- /dev/null
+++ b/fuzzing/orphans/libufdt/libufdt_fuzzer.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdint>
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+extern "C" {
+#include "libufdt_sysdeps.h"
+#include "ufdt_overlay.h"
+}
+
+/* Count split value, plus 1 byte for dto and overlay each */
+constexpr uint32_t kMinData = sizeof(uint32_t) + 2;
+
+constexpr uint32_t kMaxData = 1024 * 512;
+
+/* libFuzzer driver.
+ * We need two dtb's to test merging, so split the input data block, using
+ * the first 4 bytes to give the dtb length, the rest being overlay.
+ * The mkcorpus helper program can construct these files.
+ */
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+  /* Bound input size */
+  if (size < kMinData || size > kMaxData) {
+    return 0;
+  }
+
+  FuzzedDataProvider fdp(data, size);
+
+  /* Read fixed length header */
+  auto hdr = fdp.ConsumeBytes<uint8_t>(4);
+
+  /* Extract the length, network byte order */
+  const uint32_t dtb_len = hdr[0] << 24 | hdr[1] << 16 | hdr[2] << 8 | hdr[3];
+
+  /* Ensure the dtb and overlay are non-zero length */
+  if (dtb_len == 0 || dtb_len >= size - 1) {
+    return 0;
+  }
+
+  auto dtb = fdp.ConsumeBytes<uint8_t>(dtb_len);
+  auto overlay = fdp.ConsumeRemainingBytes<uint8_t>();
+
+  /* Check headers */
+  auto fdt_dtb = ufdt_install_blob(dtb.data(), dtb.size());
+  auto fdt_overlay = ufdt_install_blob(overlay.data(), overlay.size());
+
+  if (!fdt_dtb || !fdt_overlay) {
+    return 0;
+  }
+
+  struct fdt_header *res =
+      ufdt_apply_overlay(fdt_dtb, dtb.size(), fdt_overlay, overlay.size());
+
+  if (res) {
+    dto_free(res);
+  }
+
+  return 0;
+}
diff --git a/fuzzing/orphans/libufdt/mkcorpus.c b/fuzzing/orphans/libufdt/mkcorpus.c
new file mode 100644
index 0000000..d9d2b00
--- /dev/null
+++ b/fuzzing/orphans/libufdt/mkcorpus.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <arpa/inet.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+static uint32_t get_file_length(const char *filename) {
+  struct stat sb;
+
+  if (stat(filename, &sb) == -1) {
+    fprintf(stderr, "stat(%s) failed: %m\n", filename);
+    exit(EXIT_FAILURE);
+  }
+
+  return sb.st_size;
+}
+
+static void append_file(FILE *out, const char *filename) {
+  FILE *f = fopen(filename, "rbe");
+  uint8_t buf[1024 * 8];
+
+  if (!f) {
+    fprintf(stderr, "fopen(%s) failed: %m\n", filename);
+    exit(EXIT_FAILURE);
+  }
+
+  while (!feof(f)) {
+    size_t n = fread(buf, 1, sizeof(buf), f);
+
+    if (fwrite(buf, n, 1, out) != 1) {
+      fprintf(stderr, "fwrite() failed: %m\n");
+      exit(EXIT_FAILURE);
+    }
+  }
+
+  fclose(f);
+}
+
+int main(int argc, char *argv[]) {
+  FILE *out;
+
+  if (argc != 4) {
+    fprintf(stderr,
+            "Usage: mkcorpus <dtb> <dto> <output>\n"
+            "\n"
+            "  This concatenates base and overlay file and adds a header to "
+            "create an\n"
+            "  input that can be used for fuzzing.\n");
+    exit(EXIT_FAILURE);
+  }
+
+  if (strcmp(argv[3], "-") == 0) {
+    out = stdout;
+  } else {
+    out = fopen(argv[3], "wbe");
+    if (!out) {
+      fprintf(stderr, "fopen(%s) failed: %m\n", argv[1]);
+      exit(EXIT_FAILURE);
+    }
+  }
+
+  uint32_t len = htonl(get_file_length(argv[1]));
+
+  if (fwrite(&len, sizeof(uint32_t), 1, out) != 1) {
+    fprintf(stderr, "fwrite() failed: %m\n");
+    exit(EXIT_FAILURE);
+  }
+
+  append_file(out, argv[1]);
+  append_file(out, argv[2]);
+
+  if (out != stdout) {
+    fclose(out);
+  }
+
+  return EXIT_SUCCESS;
+}
+
+/* END OF FILE */
diff --git a/fuzzing/orphans/smallvec/smallvec_fuzzer.rs b/fuzzing/orphans/smallvec/smallvec_fuzzer.rs
index be96564..ce62e3a 100644
--- a/fuzzing/orphans/smallvec/smallvec_fuzzer.rs
+++ b/fuzzing/orphans/smallvec/smallvec_fuzzer.rs
@@ -14,7 +14,6 @@
 
 #![allow(missing_docs)]
 #![no_main]
-#![feature(bench_black_box)]
 
 use libfuzzer_sys::arbitrary::Arbitrary;
 use libfuzzer_sys::fuzz_target;
diff --git a/fuzzing/s2_geometry_fuzzer/Android.bp b/fuzzing/s2_geometry_fuzzer/Android.bp
new file mode 100644
index 0000000..7f503e7
--- /dev/null
+++ b/fuzzing/s2_geometry_fuzzer/Android.bp
@@ -0,0 +1,28 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_fuzz {
+    name: "s2_cell_id_fuzzer",
+    srcs: ["S2CellIdFuzzer.java"],
+    host_supported: true,
+    device_supported: false,
+    static_libs: [
+        "jazzer",
+        "s2-geometry-library-java",
+    ],
+}
diff --git a/fuzzing/s2_geometry_fuzzer/S2CellIdFuzzer.java b/fuzzing/s2_geometry_fuzzer/S2CellIdFuzzer.java
new file mode 100644
index 0000000..b20f331
--- /dev/null
+++ b/fuzzing/s2_geometry_fuzzer/S2CellIdFuzzer.java
@@ -0,0 +1,29 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import com.google.common.geometry.S2CellId;
+
+public class S2CellIdFuzzer {
+
+    public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+        S2CellId id = new S2CellId(data.consumeLong());
+        while (data.remainingBytes() > 0) {
+                try
+                {
+                    id.fromToken(data.consumeString(Integer.MAX_VALUE));
+                } catch (NumberFormatException e) {}
+        }
+    }
+}
\ No newline at end of file
diff --git a/fuzzing/system_fuzzers/libwatchdog_perf_service/libwatchdog_perf_service_fuzzer.cpp b/fuzzing/system_fuzzers/libwatchdog_perf_service/libwatchdog_perf_service_fuzzer.cpp
index 2781ce0..fb245b8 100644
--- a/fuzzing/system_fuzzers/libwatchdog_perf_service/libwatchdog_perf_service_fuzzer.cpp
+++ b/fuzzing/system_fuzzers/libwatchdog_perf_service/libwatchdog_perf_service_fuzzer.cpp
@@ -19,7 +19,7 @@
 
 #include <iostream>
 
-#include "IoPerfCollection.h"
+#include "PerformanceProfiler.h"
 #include "ProcStatCollector.h"
 #include "UidIoStatsCollector.h"
 
@@ -81,15 +81,15 @@
       // Test UidIoStatsCollector
       TemporaryFile tf1;
       WriteStringToFile(uidIoStatsSnapshot, tf1.path);
-      UidIoStatsCollector uidIoStatsCollector(tf1.path);
-      assert(uidIoStatsCollector.enabled() == true);
-      uidIoStatsCollector.collect();
-      // Test ProcStat
+      sp<UidIoStatsCollector> uidIoStatsCollector =
+          sp<UidIoStatsCollector>::make(tf1.path);
+      uidIoStatsCollector->collect();
+      // Test procStatCollector
       TemporaryFile tf2;
       WriteStringToFile(procStatsSnapshot, tf2.path);
-      ProcStatCollector procStatCollector(tf2.path);
-      assert(procStatCollector.enabled() == true);
-      procStatCollector.collect();
+      sp<ProcStatCollector> procStatCollector =
+          sp<ProcStatCollector>::make(tf2.path);
+      procStatCollector->collect();
     }
     return 0;
 }
diff --git a/fuzzing/xmt_parser_fuzzer/Android.bp b/fuzzing/xmt_parser_fuzzer/Android.bp
new file mode 100644
index 0000000..28344da
--- /dev/null
+++ b/fuzzing/xmt_parser_fuzzer/Android.bp
@@ -0,0 +1,17 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_fuzz {
+    name: "xmt_parser_fuzzer",
+    srcs: [
+        "Fuzzer.java",
+    ],
+    host_supported: true,
+    device_supported: false,
+    static_libs: [
+        "xmp_toolkit",
+        "jazzer",
+    ],
+    corpus: ["testdata/*"],
+}
diff --git a/fuzzing/xmt_parser_fuzzer/Fuzzer.java b/fuzzing/xmt_parser_fuzzer/Fuzzer.java
new file mode 100644
index 0000000..d6b82e6
--- /dev/null
+++ b/fuzzing/xmt_parser_fuzzer/Fuzzer.java
@@ -0,0 +1,21 @@
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import com.adobe.xmp.impl.XMPMetaParser;
+import com.adobe.xmp.options.ParseOptions;
+import com.adobe.xmp.XMPException;
+
+public class Fuzzer {
+
+  public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+    ParseOptions parseOptions = new ParseOptions();
+    parseOptions.setAcceptLatin1(data.consumeBoolean()) ;
+    parseOptions.setFixControlChars(data.consumeBoolean()) ;
+    parseOptions.setRequireXMPMeta(data.consumeBoolean()) ;
+    parseOptions.setStrictAliasing(data.consumeBoolean()) ;
+    String input = data.consumeRemainingAsString();
+    try {
+      XMPMetaParser.parse(input, parseOptions);
+    } catch(XMPException e) {
+      // Do nothing
+    }
+  }
+}
\ No newline at end of file
diff --git a/fuzzing/xmt_parser_fuzzer/corpus/sample.xml b/fuzzing/xmt_parser_fuzzer/corpus/sample.xml
new file mode 100644
index 0000000..6f8c98e
--- /dev/null
+++ b/fuzzing/xmt_parser_fuzzer/corpus/sample.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="a.xsl" type="text/xsl"?>
+<!DOCTYPE catalog SYSTEM "b.dtd">
+<book>
+   <chapter description="random description" product_image="c.jpg">
+      <page title="D's">
+         <num>ABC123</num>
+         <count>11.11</count>
+         <length description="EF">
+            <color image="g.gif">ggghhh</color>
+         </length>
+         <length description="Iiiii">
+            <color image="i.iii">iiII</color>
+         </length>
+      </page>
+      <page title="JJJ">
+         <name>kkkk</name>
+         <count>22.22</count>
+         <length description="LLLL">
+            <color image="mmm.nnn">ABCDEF</color>
+         </length>
+      </page>
+   </chapter>
+</book>
\ No newline at end of file
diff --git a/remote_provisioning/OWNERS b/remote_provisioning/OWNERS
index 78a6711..8f4b43f 100644
--- a/remote_provisioning/OWNERS
+++ b/remote_provisioning/OWNERS
@@ -1,3 +1,4 @@
+ascull@google.com
 jbires@google.com
+sethmo@google.com
 swillden@google.com
-wedsonaf@google.com
diff --git a/remote_provisioning/cert_validator/Android.bp b/remote_provisioning/cert_validator/Android.bp
deleted file mode 100644
index 7c59908..0000000
--- a/remote_provisioning/cert_validator/Android.bp
+++ /dev/null
@@ -1,88 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-rust_bindgen {
-    name: "libssl_bindgen",
-    crate_name: "ssl_bindgen",
-    wrapper_src: "libssl_wrapper.h",
-    source_stem: "bindings",
-    bindgen_flags: [
-        "--size_t-is-usize",
-        "--allowlist-function", "BN_bin2bn",
-        "--allowlist-function", "ECDSA_SIG_free",
-        "--allowlist-function", "ECDSA_SIG_new",
-        "--allowlist-function", "ECDSA_SIG_to_bytes",
-        "--allowlist-function", "ECDSA_verify",
-        "--allowlist-function", "EC_KEY_new_by_curve_name",
-        "--allowlist-function", "EC_KEY_free",
-        "--allowlist-function", "o2i_ECPublicKey",
-        "--allowlist-function", "ED25519_verify",
-        "--allowlist-function", "OPENSSL_free",
-        "--allowlist-function", "SHA256",
-        "--allowlist-var", "ED25519_PUBLIC_KEY_LEN",
-        "--allowlist-var", "ED25519_SIGNATURE_LEN",
-        "--allowlist-var", "NID_X9_62_prime256v1",
-    ],
-    cflags: ["-I/usr/include/android"],
-    shared_libs: ["libcrypto"],
-    host_supported: true,
-}
-
-rust_library {
-    name: "libcert_request_validator",
-    crate_name: "cert_request_validator",
-    srcs: ["src/lib.rs"],
-    host_supported: true,
-    rustlibs: [
-        "libanyhow",
-        "libthiserror",
-        "libciborium",
-        "libcoset",
-        "libchrono",
-        "libssl_bindgen",
-    ],
-}
-
-filegroup(
-    name = "testdata",
-    srcs = ["testdata/**/*"],
-)
-
-rust_test {
-    name: "libcert_request_validator_tests",
-    srcs: ["src/lib.rs"],
-    data: [":testdata"],
-    test_suites: ["general-tests"],
-    host_supported: true,
-    auto_gen_config: true,
-    compile_multilib: "first",
-    test_options: {
-        unit_test: false,
-    },
-    rustlibs: [
-        "libcert_request_validator",
-        "libanyhow",
-        "libthiserror",
-        "libciborium",
-        "libcoset",
-        "libchrono",
-        "libssl_bindgen",
-    ],
-}
-
-rust_binary {
-    name: "bcc_validator",
-    srcs: ["src/bcc_main.rs"],
-    host_supported: true,
-    rustlibs: [
-        "libcert_request_validator",
-        "libanyhow",
-        "libthiserror",
-        "libclap",
-        "libciborium",
-        "libcoset",
-        "libchrono",
-        "libssl_bindgen",
-    ],
-}
diff --git a/remote_provisioning/cert_validator/libssl_wrapper.h b/remote_provisioning/cert_validator/libssl_wrapper.h
deleted file mode 100644
index 9681e81..0000000
--- a/remote_provisioning/cert_validator/libssl_wrapper.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#include <openssl/bn.h>
-#include <openssl/curve25519.h>
-#include <openssl/ec_key.h>
-#include <openssl/ecdsa.h>
-#include <openssl/mem.h>
-#include <openssl/nid.h>
-#include <openssl/sha.h>
diff --git a/remote_provisioning/cert_validator/rustfmt.toml b/remote_provisioning/cert_validator/rustfmt.toml
deleted file mode 120000
index 99fc71e..0000000
--- a/remote_provisioning/cert_validator/rustfmt.toml
+++ /dev/null
@@ -1 +0,0 @@
-../../../../build/soong/scripts/rustfmt.toml
\ No newline at end of file
diff --git a/remote_provisioning/cert_validator/src/bcc.rs b/remote_provisioning/cert_validator/src/bcc.rs
deleted file mode 100644
index f157cf7..0000000
--- a/remote_provisioning/cert_validator/src/bcc.rs
+++ /dev/null
@@ -1,488 +0,0 @@
-//! This module provides functions for validating chains of bcc certificates
-
-use crate::dice;
-use crate::publickey;
-use crate::valueas::ValueAs;
-
-use self::entry::SubjectPublicKey;
-use anyhow::{anyhow, bail, ensure, Context, Result};
-use coset::AsCborValue;
-use coset::{
-    cbor::value::Value::{self, Array},
-    iana::{self, EnumI64},
-    Algorithm, CborSerializable,
-    CoseError::{self, EncodeFailed, UnexpectedItem},
-    CoseKey, CoseSign1, Header, RegisteredLabel,
-};
-use std::fmt;
-use std::io::Read;
-
-/// Represents a full Boot Certificate Chain (BCC). This consists of the root public key (which
-/// signs the first certificate), followed by a chain of BccEntry certificates. Apart from the
-/// first, the issuer of each cert if the subject of the previous one.
-pub struct Chain {
-    public_key: CoseKey,
-    entries: Vec<CoseSign1>,
-}
-
-impl Chain {
-    /// Read a Chain from a file containing the CBOR encoding. This fails if the representation is
-    /// ill-formed.
-    pub fn read(fname: &str) -> Result<Chain> {
-        let mut f = std::fs::File::open(fname)?;
-        let mut content = Vec::new();
-        f.read_to_end(&mut content)?;
-        Chain::from_slice(&content).map_err(cose_error)
-    }
-
-    /// Check all certificates are correctly signed, contain the required fields, and are otherwise
-    /// semantically correct.
-    pub fn check(&self) -> Result<Vec<entry::Payload>> {
-        let public_key = SubjectPublicKey::from_cose_key(self.public_key.clone());
-        public_key.check().context("Invalid root key")?;
-
-        let mut it = self.entries.iter();
-        let entry = it.next().unwrap();
-        let mut payload = entry::Payload::check_sign1_signature(&public_key, entry)
-            .context("Failed initial signature check.")?;
-        let mut payloads = Vec::with_capacity(self.entries.len());
-
-        for entry in it {
-            payload.check().context("Invalid BccPayload")?;
-            let next_payload = payload.check_sign1(entry)?;
-            payloads.push(payload);
-            payload = next_payload;
-        }
-        payloads.push(payload);
-        Ok(payloads)
-    }
-
-    /// Return the public key that can be used to verify the signature on the first certificate in
-    /// the chain.
-    pub fn get_root_public_key(&self) -> SubjectPublicKey {
-        SubjectPublicKey::from_cose_key(self.public_key.clone())
-    }
-}
-
-impl AsCborValue for Chain {
-    /*
-     * CDDL (from keymint/ProtectedData.aidl):
-     *
-     * Bcc = [
-     *     PubKeyEd25519 / PubKeyECDSA256, // DK_pub
-     *     + BccEntry,                     // Root -> leaf (KM_pub)
-     * ]
-     */
-
-    fn from_cbor_value(value: Value) -> Result<Self, CoseError> {
-        let a = match value {
-            Array(a) if a.len() >= 2 => a,
-            _ => return Err(UnexpectedItem("something", "an array with 2 or more items")),
-        };
-        let mut it = a.into_iter();
-        let public_key = CoseKey::from_cbor_value(it.next().unwrap())?;
-        let entries = it.map(CoseSign1::from_cbor_value).collect::<Result<Vec<_>, _>>()?;
-        Ok(Chain { public_key, entries })
-    }
-
-    fn to_cbor_value(self) -> Result<Value, CoseError> {
-        // TODO: Implement when needed
-        Err(EncodeFailed)
-    }
-}
-
-impl CborSerializable for Chain {}
-
-fn cose_error(ce: coset::CoseError) -> anyhow::Error {
-    anyhow!("CoseError: {:?}", ce)
-}
-
-/// Get the value corresponding to the provided label within the supplied CoseKey
-/// or error if it's not present.
-pub fn get_label_value(key: &coset::CoseKey, label: i64) -> Result<&Value> {
-    Ok(&key
-        .params
-        .iter()
-        .find(|(k, _)| k == &coset::Label::Int(label))
-        .ok_or_else(|| anyhow!("Label {:?} not found", label))?
-        .1)
-}
-
-/// Get the byte string for the corresponding label within the key if the label exists
-/// and the value is actually a byte array.
-pub fn get_label_value_as_bytes(key: &coset::CoseKey, label: i64) -> Result<&Vec<u8>> {
-    get_label_value(key, label)?.as_bytes().ok_or_else(|| anyhow!("Value not a bstr."))
-}
-/// This module wraps the certificate validation functions intended for BccEntry.
-pub mod entry {
-    use std::fmt::{Display, Formatter, Write};
-
-    use super::*;
-
-    /// Read a series of bcc file certificates and verify that the public key of
-    /// any given cert's payload in the series correctly signs the next cose
-    /// sign1 cert.
-    pub fn check_sign1_cert_chain(certs: &[&str]) -> Result<()> {
-        ensure!(!certs.is_empty());
-        let mut payload = Payload::from_sign1(&read(certs[0])?)
-            .context("Failed to read the first bccEntry payload")?;
-        for item in certs.iter().skip(1) {
-            payload.check().context("Validation of BccPayload entries failed.")?;
-            payload =
-                payload.check_sign1(&read(item).context("Failed to read the bccEntry payload")?)?;
-        }
-        Ok(())
-    }
-
-    /// Read a given cbor array containing bcc entries and verify that the public key
-    /// of any given cert's payload in the series correctly signs the next cose sign1
-    /// cert.
-    pub fn check_sign1_chain_array(cbor_arr: &[Value]) -> Result<()> {
-        ensure!(!cbor_arr.is_empty());
-
-        let mut writeme: Vec<u8> = Vec::new();
-        ciborium::ser::into_writer(&cbor_arr[0], &mut writeme)?;
-        let mut payload =
-            Payload::from_sign1(&CoseSign1::from_slice(&writeme).map_err(cose_error)?)
-                .context("Failed to read bccEntry payload")?;
-        for item in cbor_arr.iter().skip(1) {
-            payload.check().context("Validation of BccPayload entries failed")?;
-            writeme = Vec::new();
-            ciborium::ser::into_writer(item, &mut writeme)?;
-            let next_sign1 = &CoseSign1::from_slice(&writeme).map_err(cose_error)?;
-            payload = payload.check_sign1(next_sign1).context("Failed to read bccEntry payload")?;
-        }
-        Ok(())
-    }
-
-    /// Read a file name as string and create the BccEntry as COSE_sign1 structure.
-    pub fn read(fname: &str) -> Result<CoseSign1> {
-        let mut f = std::fs::File::open(fname)?;
-        let mut content = Vec::new();
-        f.read_to_end(&mut content)?;
-        CoseSign1::from_slice(&content).map_err(cose_error)
-    }
-
-    /// Validate the protected header of a bcc entry with respect to the provided
-    /// alg (typically originating from the subject public key of the payload).
-    pub fn check_protected_header(alg: &Option<Algorithm>, header: &Header) -> Result<()> {
-        ensure!(&header.alg == alg);
-        ensure!(header
-            .crit
-            .iter()
-            .all(|l| l == &RegisteredLabel::Assigned(iana::HeaderParameter::Alg)));
-        Ok(())
-    }
-    /// Struct describing BccPayload cbor of the BccEntry.
-    #[derive(Debug)]
-    pub struct Payload(Value);
-    impl Payload {
-        /// Construct the Payload from the parent BccEntry COSE_sign1 structure.
-        pub fn from_sign1(sign1: &CoseSign1) -> Result<Payload> {
-            Self::from_slice(sign1.payload.as_ref().ok_or_else(|| anyhow!("no payload"))?)
-        }
-
-        /// Validate entries in the Payload to be correct.
-        pub fn check(&self) -> Result<()> {
-            // Validate required fields.
-            self.map_lookup(dice::ISS)?.as_string()?;
-            self.map_lookup(dice::SUB)?.as_string()?;
-            SubjectPublicKey::from_payload(self)?.check().context("Public key failed checking")?;
-            self.map_lookup(dice::KEY_USAGE)?
-                .as_bytes()
-                .ok_or_else(|| anyhow!("Payload Key usage not bytes"))?;
-
-            // Validate required and optional fields. The required fields are those defined
-            // to be present for CDI_Certificates in the open-DICE profile.
-            // TODO: Check if the optional fields are present, and if so, ensure that
-            //       the operations applied to the mandatory fields actually reproduce the
-            //       values in the optional fields as specified in open-DICE.
-            self.0.map_lookup(dice::CODE_HASH).context("Code hash must be present.")?;
-            self.0.map_lookup(dice::CONFIG_DESC).context("Config descriptor must be present.")?;
-            self.0.map_lookup(dice::AUTHORITY_HASH).context("Authority hash must be present.")?;
-            self.0.map_lookup(dice::MODE).context("Mode must be present.")?;
-
-            // Verify that each key that does exist has the expected type.
-            self.0
-                .check_bytes_val_if_key_in_map(dice::CODE_HASH)
-                .context("Code Hash value not bytes.")?;
-            self.0
-                .check_bytes_val_if_key_in_map(dice::CODE_DESC)
-                .context("Code Descriptor value not bytes.")?;
-            self.0
-                .check_bytes_val_if_key_in_map(dice::CONFIG_HASH)
-                .context("Configuration Hash value not bytes.")?;
-            self.0
-                .check_bytes_val_if_key_in_map(dice::CONFIG_DESC)
-                .context("Configuration descriptor value not bytes.")?;
-            self.0
-                .check_bytes_val_if_key_in_map(dice::AUTHORITY_HASH)
-                .context("Authority Hash value not bytes.")?;
-            self.0
-                .check_bytes_val_if_key_in_map(dice::AUTHORITY_DESC)
-                .context("Authority descriptor value not bytes.")?;
-            self.0.check_bytes_val_if_key_in_map(dice::MODE).context("Mode value not bytes.")?;
-            Ok(())
-        }
-
-        /// Verify that the public key of this payload correctly signs the provided
-        /// BccEntry sign1 object.
-        pub fn check_sign1(&self, sign1: &CoseSign1) -> Result<Payload> {
-            let pkey = SubjectPublicKey::from_payload(self)
-                .context("Failed to construct Public key from the Bcc payload.")?;
-            let new_payload = Self::check_sign1_signature(&pkey, sign1)?;
-            ensure!(
-                self.map_lookup(dice::SUB)? == new_payload.map_lookup(dice::ISS)?,
-                "Subject/Issuer mismatch"
-            );
-            Ok(new_payload)
-        }
-
-        pub(super) fn check_sign1_signature(
-            pkey: &SubjectPublicKey,
-            sign1: &CoseSign1,
-        ) -> Result<Payload> {
-            check_protected_header(&pkey.0.alg, &sign1.protected.header)
-                .context("Validation of bcc entry protected header failed.")?;
-            let v = publickey::PublicKey::from_cose_key(&pkey.0)
-                .context("Extracting the Public key from coseKey failed.")?;
-            sign1
-                .verify_signature(b"", |s, m| v.verify(s, m, &pkey.0.alg))
-                .context("public key incorrectly signs the given cose_sign1 cert.")?;
-            let new_payload = Payload::from_sign1(sign1)
-                .context("Failed to extract bcc payload from cose_sign1")?;
-            Ok(new_payload)
-        }
-
-        fn from_slice(b: &[u8]) -> Result<Self> {
-            Ok(Payload(coset::cbor::de::from_reader(b).map_err(|e| anyhow!("CborError: {}", e))?))
-        }
-
-        fn map_lookup(&self, key: i64) -> Result<&Value> {
-            Ok(&self
-                .0
-                .as_map()
-                .ok_or_else(|| anyhow!("not a map"))?
-                .iter()
-                .find(|(k, _v)| k == &Value::from(key))
-                .ok_or_else(|| anyhow!("missing key {}", key))?
-                .1)
-        }
-    }
-
-    /// Struct wrapping the CoseKey for BccEntry.BccPayload.SubjectPublicKey
-    /// and the methods used for its validation.
-    pub struct SubjectPublicKey(CoseKey);
-    impl SubjectPublicKey {
-        pub(super) fn from_cose_key(cose_key: CoseKey) -> Self {
-            Self(cose_key)
-        }
-
-        /// Construct the SubjectPublicKey from the (bccEntry's) Payload.
-        pub fn from_payload(payload: &Payload) -> Result<SubjectPublicKey> {
-            let bytes = payload
-                .map_lookup(dice::SUBJECT_PUBLIC_KEY)?
-                .as_bytes()
-                .ok_or_else(|| anyhow!("public key not bytes"))?;
-            Self::from_slice(bytes)
-        }
-
-        fn from_slice(bytes: &[u8]) -> Result<SubjectPublicKey> {
-            Ok(SubjectPublicKey(CoseKey::from_slice(bytes).map_err(cose_error)?))
-        }
-
-        /// Perform validation on the items in the public key.
-        pub fn check(&self) -> Result<()> {
-            let pkey = &self.0;
-            if !pkey.key_ops.is_empty() {
-                ensure!(pkey
-                    .key_ops
-                    .contains(&coset::KeyOperation::Assigned(iana::KeyOperation::Verify)));
-            }
-            match pkey.kty {
-                coset::KeyType::Assigned(iana::KeyType::OKP) => {
-                    ensure!(pkey.alg == Some(coset::Algorithm::Assigned(iana::Algorithm::EdDSA)));
-                    let crv = get_label_value(pkey, iana::OkpKeyParameter::Crv as i64)?;
-                    ensure!(crv == &Value::from(iana::EllipticCurve::Ed25519 as i64));
-                }
-                coset::KeyType::Assigned(iana::KeyType::EC2) => {
-                    ensure!(pkey.alg == Some(coset::Algorithm::Assigned(iana::Algorithm::ES256)));
-                    let crv = get_label_value(pkey, iana::Ec2KeyParameter::Crv as i64)?;
-                    ensure!(crv == &Value::from(iana::EllipticCurve::P_256 as i64));
-                }
-                _ => bail!("Unexpected KeyType value: {:?}", pkey.kty),
-            }
-            Ok(())
-        }
-    }
-
-    struct ConfigDesc(Vec<(Value, Value)>);
-
-    impl AsCborValue for ConfigDesc {
-        /*
-         * CDDL (from keymint/ProtectedData.aidl):
-         *
-         *  bstr .cbor {      // Configuration Descriptor
-         *     ? -70002 : tstr,           // Component name
-         *     ? -70003 : int,            // Firmware version
-         *     ? -70004 : null,           // Resettable
-         * },
-         */
-
-        fn from_cbor_value(value: Value) -> Result<Self, CoseError> {
-            match value {
-                Value::Map(m) => Ok(Self(m)),
-                _ => Err(UnexpectedItem("something", "a map")),
-            }
-        }
-
-        fn to_cbor_value(self) -> Result<Value, CoseError> {
-            // TODO: Implement when needed
-            Err(EncodeFailed)
-        }
-    }
-
-    impl CborSerializable for ConfigDesc {}
-
-    impl Display for ConfigDesc {
-        fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
-            write_payload_label(f, dice::CONFIG_DESC)?;
-            f.write_str(":\n")?;
-            for (label, value) in &self.0 {
-                f.write_str("  ")?;
-                if let Ok(i) = label.as_i64() {
-                    write_config_desc_label(f, i)?;
-                } else {
-                    write_value(f, label)?;
-                }
-                f.write_str(": ")?;
-                write_value(f, value)?;
-                f.write_char('\n')?;
-            }
-            Ok(())
-        }
-    }
-
-    fn write_config_desc_label(f: &mut Formatter, label: i64) -> Result<(), fmt::Error> {
-        match label {
-            dice::COMPONENT_NAME => f.write_str("Component Name"),
-            dice::FIRMWARE_VERSION => f.write_str("Firmware Version"),
-            dice::RESETTABLE => f.write_str("Resettable"),
-            _ => write!(f, "{}", label),
-        }
-    }
-
-    impl Display for SubjectPublicKey {
-        fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
-            let pkey = &self.0;
-            if pkey.kty != coset::KeyType::Assigned(iana::KeyType::OKP)
-                || pkey.alg != Some(coset::Algorithm::Assigned(iana::Algorithm::EdDSA))
-            {
-                return Err(fmt::Error);
-            }
-
-            let mut separator = "";
-            for (label, value) in &pkey.params {
-                use coset::Label;
-                use iana::OkpKeyParameter;
-                if let Label::Int(i) = label {
-                    match OkpKeyParameter::from_i64(*i) {
-                        Some(OkpKeyParameter::Crv) => {
-                            if let Some(crv) =
-                                value.as_i64().ok().and_then(iana::EllipticCurve::from_i64)
-                            {
-                                f.write_str(separator)?;
-                                write!(f, "Curve: {:?}", crv)?;
-                                separator = " ";
-                            }
-                        }
-                        Some(OkpKeyParameter::X) => {
-                            if let Ok(x) = ValueAs::as_bytes(value) {
-                                f.write_str(separator)?;
-                                f.write_str("X: ")?;
-                                write_bytes_in_hex(f, x)?;
-                                separator = " ";
-                            }
-                        }
-                        _ => (),
-                    }
-                }
-            }
-
-            Ok(())
-        }
-    }
-
-    impl Display for Payload {
-        fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
-            for (label, value) in self.0.as_map().ok_or(fmt::Error)? {
-                if let Ok(i) = label.as_i64() {
-                    if i == dice::CONFIG_DESC {
-                        write_config_desc(f, value)?;
-                        continue;
-                    } else if i == dice::SUBJECT_PUBLIC_KEY {
-                        write_payload_label(f, i)?;
-                        f.write_str(": ")?;
-                        write_subject_public_key(f, value)?;
-                        continue;
-                    }
-                    write_payload_label(f, i)?;
-                } else {
-                    write_value(f, label)?;
-                }
-                f.write_str(": ")?;
-                write_value(f, value)?;
-                f.write_char('\n')?;
-            }
-            Ok(())
-        }
-    }
-
-    fn write_payload_label(f: &mut Formatter, label: i64) -> Result<(), fmt::Error> {
-        match label {
-            dice::ISS => f.write_str("Issuer"),
-            dice::SUB => f.write_str("Subject"),
-            dice::CODE_HASH => f.write_str("Code Hash"),
-            dice::CODE_DESC => f.write_str("Code Desc"),
-            dice::CONFIG_DESC => f.write_str("Config Desc"),
-            dice::CONFIG_HASH => f.write_str("Config Hash"),
-            dice::AUTHORITY_HASH => f.write_str("Authority Hash"),
-            dice::AUTHORITY_DESC => f.write_str("Authority Desc"),
-            dice::MODE => f.write_str("Mode"),
-            dice::SUBJECT_PUBLIC_KEY => f.write_str("Subject Public Key"),
-            dice::KEY_USAGE => f.write_str("Key Usage"),
-            _ => write!(f, "{}", label),
-        }
-    }
-
-    fn write_config_desc(f: &mut Formatter, value: &Value) -> Result<(), fmt::Error> {
-        let bytes = value.as_bytes().ok_or(fmt::Error)?;
-        let config_desc = ConfigDesc::from_slice(bytes).map_err(|_| fmt::Error)?;
-        write!(f, "{}", config_desc)
-    }
-
-    fn write_subject_public_key(f: &mut Formatter, value: &Value) -> Result<(), fmt::Error> {
-        let bytes = value.as_bytes().ok_or(fmt::Error)?;
-        let subject_public_key = SubjectPublicKey::from_slice(bytes).map_err(|_| fmt::Error)?;
-        write!(f, "{}", subject_public_key)
-    }
-
-    fn write_value(f: &mut Formatter, value: &Value) -> Result<(), fmt::Error> {
-        if let Some(bytes) = value.as_bytes() {
-            write_bytes_in_hex(f, bytes)
-        } else if let Some(text) = value.as_text() {
-            write!(f, "\"{}\"", text)
-        } else if let Ok(integer) = value.as_i64() {
-            write!(f, "{}", integer)
-        } else {
-            write!(f, "{:?}", value)
-        }
-    }
-
-    fn write_bytes_in_hex(f: &mut Formatter, bytes: &[u8]) -> Result<(), fmt::Error> {
-        for b in bytes {
-            write!(f, "{:02x}", b)?
-        }
-        Ok(())
-    }
-}
diff --git a/remote_provisioning/cert_validator/src/bcc_main.rs b/remote_provisioning/cert_validator/src/bcc_main.rs
deleted file mode 100644
index 954f7f0..0000000
--- a/remote_provisioning/cert_validator/src/bcc_main.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-//! An example binary that uses the libcert_request_validator to accept an
-//! array of bcc certificates from the command line and validates that the
-//! certificates are valid and that any given cert in the series correctly
-//! signs the next.
-
-use anyhow::{bail, Result};
-use cert_request_validator::bcc;
-use clap::{Arg, SubCommand};
-
-fn main() -> Result<()> {
-    let app = clap::App::new("bcc_validator")
-        .subcommand(
-            SubCommand::with_name("verify-chain")
-                .arg(Arg::with_name("dump").long("dump"))
-                .arg(Arg::with_name("chain")),
-        )
-        .subcommand(
-            SubCommand::with_name("verify-certs")
-                .arg(Arg::with_name("certs").multiple(true).min_values(1)),
-        );
-
-    let args = app.get_matches();
-    match args.subcommand() {
-        ("verify-chain", Some(sub_args)) => {
-            if let Some(chain) = sub_args.value_of("chain") {
-                let chain = bcc::Chain::read(chain)?;
-                let payloads = chain.check()?;
-                if sub_args.is_present("dump") {
-                    println!("Root public key: {}", chain.get_root_public_key());
-                    println!();
-                    for (i, payload) in payloads.iter().enumerate() {
-                        println!("Cert {}:", i);
-                        println!("{}", payload);
-                    }
-                }
-                return Ok(());
-            }
-        }
-        ("verify-certs", Some(sub_args)) => {
-            if let Some(certs) = sub_args.values_of("certs") {
-                let certs: Vec<_> = certs.collect();
-                return bcc::entry::check_sign1_cert_chain(&certs);
-            }
-        }
-        _ => {}
-    }
-    eprintln!("{}", args.usage());
-    bail!("Invalid arguments");
-}
diff --git a/remote_provisioning/cert_validator/src/deviceinfo.rs b/remote_provisioning/cert_validator/src/deviceinfo.rs
deleted file mode 100644
index c4c0a4a..0000000
--- a/remote_provisioning/cert_validator/src/deviceinfo.rs
+++ /dev/null
@@ -1,76 +0,0 @@
-//! Module containing validation functions for CertificateRequest.DeviceInfo
-
-use crate::valueas::ValueAs;
-use anyhow::{anyhow, ensure, Context, Result};
-use ciborium::value::Value;
-
-mod val {
-    pub const BOOTLOADER_STATE: [&str; 2] = ["locked", "unlocked"];
-    pub const FUSED: [u64; 2] = [1, 0];
-    pub const SECURITY_LEVEL: [&str; 2] = ["tee", "strongbox"];
-    pub const VBSTATE: [&str; 3] = ["green", "yellow", "orange"];
-}
-
-/// Unwrap the DeviceInfo array containing VerifiedDeviceInfo
-/// and UnverifiedDeviceInfo from DeviceInfo cbor.
-pub fn extract(filevalue: &Value) -> Result<&[Value]> {
-    let filevalue = filevalue.as_array_of_len(2)?;
-    Ok(filevalue)
-}
-
-/// Perform validation on DeviceInfo.
-pub fn check(deviceinfo: &[Value]) -> Result<()> {
-    let verified_info = &deviceinfo[0];
-    let unverified_info = &deviceinfo[1];
-    let version = verified_info.map_lookup(String::from("version"))?.as_i64()?;
-    verified_info.check_string_val_if_key_in_map(String::from("os_version"))?;
-
-    match version {
-        1 => {
-            verified_info.check_string_val_if_key_in_map(String::from("brand"))?;
-            verified_info.check_string_val_if_key_in_map(String::from("manufacturer"))?;
-            verified_info.check_string_val_if_key_in_map(String::from("product"))?;
-            verified_info.check_string_val_if_key_in_map(String::from("model"))?;
-            verified_info.check_string_val_if_key_in_map(String::from("board"))?;
-            verified_info.check_string_val_if_key_in_map(String::from("device"))?;
-            verified_info.check_arr_val_if_key_in_map(String::from("vb_state"), &val::VBSTATE)?;
-            verified_info.check_arr_val_if_key_in_map(
-                String::from("bootloader_state"),
-                &val::BOOTLOADER_STATE,
-            )?;
-            verified_info
-                .check_bytes_val_if_key_in_map(String::from("vbmeta_digest"))
-                .context("vbmeta_digest not bytes")?;
-            verified_info.check_date_val_if_key_in_map(String::from("system_patch_level"))?;
-            verified_info.check_date_val_if_key_in_map(String::from("boot_patch_level"))?;
-            verified_info.check_date_val_if_key_in_map(String::from("vendor_patch_level"))?;
-        }
-        2 => {
-            verified_info.map_lookup(String::from("brand"))?.as_string()?;
-            verified_info.map_lookup(String::from("manufacturer"))?.as_string()?;
-            verified_info.map_lookup(String::from("product"))?.as_string()?;
-            verified_info.map_lookup(String::from("model"))?.as_string()?;
-            verified_info.map_lookup(String::from("device"))?.as_string()?;
-            verified_info.check_arr_val_for_key_in_map(String::from("vb_state"), &val::VBSTATE)?;
-            verified_info.check_arr_val_for_key_in_map(
-                String::from("bootloader_state"),
-                &val::BOOTLOADER_STATE,
-            )?;
-            verified_info
-                .map_lookup(String::from("vbmeta_digest"))?
-                .as_bytes()
-                .ok_or_else(|| anyhow!("vbmeta_digest not bytes"))?;
-            verified_info.map_lookup(String::from("system_patch_level"))?.as_date()?;
-            verified_info.map_lookup(String::from("boot_patch_level"))?.as_date()?;
-            verified_info.map_lookup(String::from("vendor_patch_level"))?.as_date()?;
-            let fused = verified_info.map_lookup(String::from("fused"))?.as_u64()?;
-            ensure!(val::FUSED.iter().any(|&x| x == fused));
-        }
-        _ => return Err(anyhow!("Invalid version")),
-    }
-
-    let security_level = verified_info.map_lookup(String::from("security_level"))?.as_string()?;
-    ensure!(val::SECURITY_LEVEL.iter().any(|&x| x.eq(&security_level)));
-    unverified_info.check_string_val_if_key_in_map(String::from("fingerprint"))?;
-    Ok(())
-}
diff --git a/remote_provisioning/cert_validator/src/dice.rs b/remote_provisioning/cert_validator/src/dice.rs
deleted file mode 100644
index f52dee2..0000000
--- a/remote_provisioning/cert_validator/src/dice.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-//! Field constants used in Open profile for Dice for the BccPayload.
-
-/// Issuer.
-pub const ISS: i64 = 1;
-/// Subject.
-pub const SUB: i64 = 2;
-/// Code Hash.
-pub const CODE_HASH: i64 = -4670545;
-/// Code descriptor.
-pub const CODE_DESC: i64 = -4670546;
-/// Configuration Hash.
-pub const CONFIG_HASH: i64 = -4670547;
-/// Configuration Descriptor.
-pub const CONFIG_DESC: i64 = -4670548;
-/// Authority Hash.
-pub const AUTHORITY_HASH: i64 = -4670549;
-/// Authority Descriptor.
-pub const AUTHORITY_DESC: i64 = -4670550;
-/// Mode.
-pub const MODE: i64 = -4670551;
-/// Subject Public Key.
-pub const SUBJECT_PUBLIC_KEY: i64 = -4670552;
-/// Key usage.
-pub const KEY_USAGE: i64 = -4670553;
-
-/// Component name.
-pub const COMPONENT_NAME: i64 = -70002;
-/// Firmware version.
-pub const FIRMWARE_VERSION: i64 = -70003;
-/// Resettable.
-pub const RESETTABLE: i64 = -70004;
diff --git a/remote_provisioning/cert_validator/src/lib.rs b/remote_provisioning/cert_validator/src/lib.rs
deleted file mode 100644
index 35cf7f7..0000000
--- a/remote_provisioning/cert_validator/src/lib.rs
+++ /dev/null
@@ -1,145 +0,0 @@
-//! The cert validator library provides validation functions for the CBOR-CDDL
-//! based certificate request, allowing validation of BCC certificate chain,
-//! deviceinfo among other things.
-
-pub mod bcc;
-pub mod deviceinfo;
-pub mod dice;
-pub mod publickey;
-pub mod valueas;
-
-use anyhow::{Context, Result};
-use ciborium::{de::from_reader, value::Value};
-
-/// Reads the provided binary cbor-encoded file and returns a
-/// ciborium::Value struct wrapped in Result.
-pub fn file_value(fname: &str) -> Result<Value> {
-    let f = std::fs::File::open(fname)?;
-    from_reader(f).with_context(|| format!("Decoding {}", fname))
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::valueas::ValueAs;
-    use coset::{iana, Header, Label, RegisteredLabel};
-
-    #[test]
-    fn test_bcc_payload_check() {
-        let payload = bcc::entry::Payload::from_sign1(
-            &bcc::entry::read("testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_0.cert")
-                .unwrap(),
-        );
-        assert!(payload.is_ok());
-
-        let payload = payload.unwrap();
-        assert!(payload.check().is_ok());
-    }
-
-    #[test]
-    fn test_bcc_payload_check_sign1() {
-        let payload = bcc::entry::Payload::from_sign1(
-            &bcc::entry::read("testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_0.cert")
-                .unwrap(),
-        );
-        assert!(payload.is_ok(), "Payload not okay: {:?}", payload);
-        let payload = payload.unwrap().check_sign1(
-            &bcc::entry::read("testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_1.cert")
-                .unwrap(),
-        );
-        assert!(payload.is_ok(), "Payload not okay: {:?}", payload);
-        let payload = payload.unwrap().check_sign1(
-            &bcc::entry::read("testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_2.cert")
-                .unwrap(),
-        );
-        assert!(payload.is_ok(), "Payload not okay: {:?}", payload);
-    }
-
-    #[test]
-    fn test_check_sign1_cert_chain() {
-        let arr: Vec<&str> = vec![
-            "testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_0.cert",
-            "testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_1.cert",
-            "testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_2.cert",
-        ];
-        assert!(bcc::entry::check_sign1_cert_chain(&arr).is_ok());
-    }
-
-    #[test]
-    fn test_check_sign1_cert_chain_invalid() {
-        let arr: Vec<&str> = vec![
-            "testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_0.cert",
-            "testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_2.cert",
-        ];
-        assert!(bcc::entry::check_sign1_cert_chain(&arr).is_err());
-    }
-
-    #[test]
-    fn test_check_sign1_chain_array() {
-        let cbor_file = &file_value("testdata/open-dice/_CBOR_bcc_entry_cert_array.cert").unwrap();
-        let cbor_arr = ValueAs::as_array(cbor_file).unwrap();
-        assert_eq!(cbor_arr.len(), 3);
-        assert!(bcc::entry::check_sign1_chain_array(cbor_arr).is_ok());
-    }
-
-    #[test]
-    fn test_check_chain_valid() -> Result<()> {
-        let chain = bcc::Chain::read("testdata/bcc/valid.chain").unwrap();
-        let payloads = chain.check()?;
-        assert_eq!(payloads.len(), 8);
-        Ok(())
-    }
-
-    #[test]
-    fn test_check_chain_valid_p256() -> Result<()> {
-        let chain = bcc::Chain::read("testdata/bcc/valid_p256.chain").unwrap();
-        let payloads = chain.check()?;
-        assert_eq!(payloads.len(), 3);
-        Ok(())
-    }
-
-    #[test]
-    fn test_check_chain_bad_p256() {
-        let chain = bcc::Chain::read("testdata/bcc/bad_p256.chain").unwrap();
-        assert!(chain.check().is_err());
-    }
-
-    #[test]
-    fn test_check_chain_bad_pub_key() {
-        let chain = bcc::Chain::read("testdata/bcc/bad_pub_key.chain").unwrap();
-        assert!(chain.check().is_err());
-    }
-
-    #[test]
-    fn test_check_chain_bad_final_signature() {
-        let chain = bcc::Chain::read("testdata/bcc/bad_final_signature.chain").unwrap();
-        assert!(chain.check().is_err());
-    }
-
-    #[test]
-    fn deviceinfo_validation() {
-        let val = &file_value("testdata/device-info/_CBOR_device_info_0.cert").unwrap();
-        let deviceinfo = deviceinfo::extract(val);
-        assert!(deviceinfo.is_ok());
-        assert!(deviceinfo::check(deviceinfo.unwrap()).is_ok());
-    }
-
-    #[test]
-    fn test_check_bcc_entry_protected_header() -> Result<()> {
-        let eddsa = Some(coset::Algorithm::Assigned(iana::Algorithm::EdDSA));
-        let header = Header { alg: (&eddsa).clone(), ..Default::default() };
-        bcc::entry::check_protected_header(&eddsa, &header).context("Only alg allowed")?;
-        let header = Header { alg: Some(coset::Algorithm::PrivateUse(1000)), ..Default::default() };
-        assert!(bcc::entry::check_protected_header(&eddsa, &header).is_err());
-        let mut header = Header { alg: (&eddsa).clone(), ..Default::default() };
-        header.rest.push((Label::Int(1000), Value::from(2000u16)));
-        bcc::entry::check_protected_header(&eddsa, &header).context("non-crit header allowed")?;
-        let mut header = Header { alg: (&eddsa).clone(), ..Default::default() };
-        header.crit.push(RegisteredLabel::Assigned(iana::HeaderParameter::Alg));
-        bcc::entry::check_protected_header(&eddsa, &header).context("OK to say alg is critical")?;
-        let mut header = Header { alg: (&eddsa).clone(), ..Default::default() };
-        header.crit.push(RegisteredLabel::Assigned(iana::HeaderParameter::CounterSignature));
-        assert!(bcc::entry::check_protected_header(&eddsa, &header).is_err());
-        Ok(())
-    }
-}
diff --git a/remote_provisioning/cert_validator/src/publickey.rs b/remote_provisioning/cert_validator/src/publickey.rs
deleted file mode 100644
index 280e1dd..0000000
--- a/remote_provisioning/cert_validator/src/publickey.rs
+++ /dev/null
@@ -1,221 +0,0 @@
-//! This module describes the public key (PubKeyEd25519 or PubKeyECDSA256)
-//! used in the BccPayload. The key itself is stored as a simple byte array in
-//! a vector. For now, only PubKeyEd25519 types of cbor public keys are supported.
-
-use crate::bcc::get_label_value_as_bytes;
-use anyhow::{bail, ensure, Context, Result};
-use coset::{iana, Algorithm, CoseKey};
-use std::ptr;
-
-/// Length of an Ed25519 public key.
-pub const ED25519_PUBLIC_KEY_LEN: usize = ssl_bindgen::ED25519_PUBLIC_KEY_LEN as usize;
-/// Length of an Ed25519 signatures.
-pub const ED25519_SIG_LEN: usize = ssl_bindgen::ED25519_SIGNATURE_LEN as usize;
-/// Length of a P256 coordinate.
-pub const P256_COORD_LEN: usize = 32;
-/// Length of a P256 signature.
-pub const P256_SIG_LEN: usize = 64;
-
-enum PubKey {
-    Ed25519 { pub_key: [u8; ED25519_PUBLIC_KEY_LEN] },
-    P256 { x_coord: [u8; P256_COORD_LEN], y_coord: [u8; P256_COORD_LEN] },
-}
-/// Struct wrapping the public key byte array, and the relevant validation methods.
-pub struct PublicKey {
-    key: PubKey,
-}
-
-impl PublicKey {
-    /// Extract the PublicKey from Subject Public Key.
-    /// (CertificateRequest.BccEntry.payload[SubjectPublicKey].X)
-    pub fn from_cose_key(pkey: &CoseKey) -> Result<Self> {
-        let x = get_label_value_as_bytes(pkey, iana::OkpKeyParameter::X as i64)?;
-        match pkey.alg {
-            Some(coset::Algorithm::Assigned(iana::Algorithm::EdDSA)) => {
-                PublicKey::new(PubKey::Ed25519 {
-                    pub_key: x.as_slice().try_into().context(format!(
-                        "Failed to convert x_coord to array. Len: {:?}",
-                        x.len()
-                    ))?,
-                })
-            }
-            Some(coset::Algorithm::Assigned(iana::Algorithm::ES256)) => {
-                let y = get_label_value_as_bytes(pkey, iana::Ec2KeyParameter::Y as i64)?;
-                PublicKey::new(PubKey::P256 {
-                    x_coord: x.as_slice().try_into().context(format!(
-                        "Failed to convert x_coord to array. Len: {:?}",
-                        x.len()
-                    ))?,
-                    y_coord: y.as_slice().try_into().context(format!(
-                        "Failed to convert y_coord to array. Len: {:?}",
-                        y.len()
-                    ))?,
-                })
-            }
-            _ => bail!("Unsupported signature algorithm: {:?}", pkey.alg),
-        }
-    }
-
-    fn new(key: PubKey) -> Result<Self> {
-        Ok(Self { key })
-    }
-
-    fn sha256(message: &[u8]) -> Result<[u8; 32]> {
-        let mut digest: [u8; 32] = [0; 32];
-        // SAFETY: This function is safe due to message only being read, with the associated length
-        // on the slice passed in to ensure no buffer overreads. Additionally, the digest is sized
-        // accordingly to the output size of SHA256. No memory is allocated.
-        unsafe {
-            if ssl_bindgen::SHA256(message.as_ptr(), message.len(), digest.as_mut_ptr()).is_null() {
-                bail!("Failed to hash the message.");
-            }
-        }
-        Ok(digest)
-    }
-
-    fn raw_p256_sig_to_der(signature: &[u8]) -> Result<Vec<u8>> {
-        ensure!(
-            signature.len() == P256_SIG_LEN,
-            "Unexpected signature length: {:?}",
-            signature.len()
-        );
-        let mut der_sig: *mut u8 = ptr::null_mut();
-        let mut der_sig_len: usize = 0;
-        // SAFETY: The signature slice is verified to contain the expected length before it is
-        // indexed as read only memory for the boringssl code to generate a DER encoded signature.
-        // The final result from the boringssl operations is copied out to a standard vector so
-        // the specific boringSSL deallocators can be used on the memory buffers that were
-        // allocated, and a standard, safe Rust Vec can be returned.
-        unsafe {
-            let der_encoder = ssl_bindgen::ECDSA_SIG_new();
-            if der_encoder.is_null() {
-                bail!("Failed to allocate ECDSA_SIG");
-            }
-            let mut encoder_closure = || {
-                ssl_bindgen::BN_bin2bn(signature.as_ptr(), 32, (*der_encoder).r);
-                ssl_bindgen::BN_bin2bn(signature.as_ptr().offset(32), 32, (*der_encoder).s);
-                if (*der_encoder).r.is_null() || (*der_encoder).s.is_null() {
-                    bail!("Failed to allocate BigNum.");
-                }
-                // ECDSA_SIG_to_bytes takes a uint8_t** and allocates a buffer
-                if ssl_bindgen::ECDSA_SIG_to_bytes(
-                    &mut der_sig,
-                    &mut der_sig_len as *mut usize,
-                    der_encoder,
-                ) == 0
-                {
-                    bail!("Failed to encode ECDSA_SIG into a DER byte array.");
-                }
-                // Copy the data out of der_sig so that the unsafe pointer and associated memory
-                // can be properly freed.
-                let mut safe_copy = Vec::with_capacity(der_sig_len);
-                ptr::copy(der_sig, safe_copy.as_mut_ptr(), der_sig_len);
-                safe_copy.set_len(der_sig_len);
-                Ok(safe_copy)
-            };
-            let safe_copy = encoder_closure();
-            ssl_bindgen::ECDSA_SIG_free(der_encoder);
-            ssl_bindgen::OPENSSL_free(der_sig as *mut std::ffi::c_void);
-            safe_copy
-        }
-    }
-
-    fn verify_p256(signature: &[u8], message: &[u8], ec_point: &[u8]) -> Result<i32> {
-        // len(0x04 || r || s) should be 65 for a p256 public key.
-        ensure!(ec_point.len() == 65);
-        let mut key_bytes: *const u8 = ec_point.as_ptr();
-        let digest = PublicKey::sha256(message)?;
-        let der_sig = PublicKey::raw_p256_sig_to_der(signature)?;
-        // SAFETY: The following unsafe block allocates and creates an EC_KEY, using that struct
-        // in conjunction with read only access to length checked rust slices to verify the
-        // signature. The boringSSL allocated memory is then freed, regardless of failures during
-        // the verification process.
-        unsafe {
-            let mut key = ssl_bindgen::EC_KEY_new_by_curve_name(
-                ssl_bindgen::NID_X9_62_prime256v1.try_into()?,
-            );
-            // Use a closure just to simplify freeing allocated memory and error
-            // handling in the event an error occurs.
-            let mut verifier_closure = || {
-                if key.is_null() {
-                    bail!("Failed to allocate a new EC_KEY.");
-                }
-                if ssl_bindgen::o2i_ECPublicKey(
-                    &mut key,
-                    &mut key_bytes,
-                    ec_point.len().try_into()?,
-                )
-                .is_null()
-                {
-                    bail!("Failed to convert key byte array into an EC_KEY structure.");
-                }
-                Ok(ssl_bindgen::ECDSA_verify(
-                    0, /* type */
-                    digest.as_ptr(),
-                    digest.len(),
-                    der_sig.as_slice().as_ptr(),
-                    der_sig.len(),
-                    key,
-                ))
-            };
-            let result = verifier_closure();
-            ssl_bindgen::EC_KEY_free(key);
-            result
-        }
-    }
-
-    /// Verify that the signature obtained from signing the given message
-    /// with the PublicKey matches the signature provided.
-    pub fn verify(&self, signature: &[u8], message: &[u8], alg: &Option<Algorithm>) -> Result<()> {
-        match self.key {
-            PubKey::Ed25519 { pub_key } => {
-                ensure!(
-                    *alg == Some(coset::Algorithm::Assigned(iana::Algorithm::EdDSA)),
-                    "Unexpected algorithm. Ed25519 key, but alg is: {:?}",
-                    *alg
-                );
-                ensure!(
-                    signature.len() == ED25519_SIG_LEN,
-                    "Unexpected signature length: {:?}",
-                    signature.len()
-                );
-                ensure!(
-                    pub_key.len() == ED25519_PUBLIC_KEY_LEN,
-                    "Unexpected public key length {:?}:",
-                    pub_key.len()
-                );
-                ensure!(
-                    // SAFETY: The underlying API only reads from the provided pointers, which are
-                    // themselves standard slices with their corresponding expended lengths checked
-                    // before the function call.
-                    unsafe {
-                        ssl_bindgen::ED25519_verify(
-                            message.as_ptr(),
-                            message.len(),
-                            signature.as_ptr(),
-                            pub_key.as_ptr(),
-                        )
-                    } == 1,
-                    "Signature verification failed."
-                );
-            }
-            PubKey::P256 { x_coord, y_coord } => {
-                ensure!(
-                    *alg == Some(coset::Algorithm::Assigned(iana::Algorithm::ES256)),
-                    "Unexpected algorithm. P256 key, but alg is: {:?}",
-                    *alg
-                );
-                let mut ec_point_uncompressed: Vec<u8> = vec![0x04];
-                ec_point_uncompressed.extend_from_slice(&x_coord);
-                ec_point_uncompressed.extend_from_slice(&y_coord);
-                ensure!(ec_point_uncompressed.len() == 65);
-                let ec_point_slice = ec_point_uncompressed.as_slice();
-                ensure!(
-                    PublicKey::verify_p256(signature, message, ec_point_slice)? == 1,
-                    "Signature verification failed."
-                );
-            }
-        }
-        Ok(())
-    }
-}
diff --git a/remote_provisioning/cert_validator/src/valueas.rs b/remote_provisioning/cert_validator/src/valueas.rs
deleted file mode 100644
index c64c635..0000000
--- a/remote_provisioning/cert_validator/src/valueas.rs
+++ /dev/null
@@ -1,171 +0,0 @@
-//! Defines the interface for converting a given (cbor decoded)
-//! type into convenient data types.
-
-use anyhow::{anyhow, ensure, Result};
-use chrono::NaiveDate;
-use ciborium::value::Value;
-use thiserror::Error;
-
-#[derive(Error, Debug)]
-pub(crate) enum ValueAsError {
-    #[error("Wrong type")]
-    WrongType(),
-    #[error("Wrong length: expected {0} got {1}")]
-    WrongLength(usize, usize),
-    #[error("Key absent")]
-    KeyAbsent(),
-    #[error("Not the same: expected {0:?} got {1:?}")]
-    NotSameError(Value, Value),
-    #[error("Integer too big for i64")]
-    IntegerTooBigError(),
-}
-pub(crate) trait ValueAs
-where
-    Self: Sized,
-{
-    fn as_i64(&self) -> Result<i64, ValueAsError>;
-    fn as_u64(&self) -> Result<u64, ValueAsError>;
-    fn as_bytes(&self) -> Result<&[u8], ValueAsError>;
-    fn as_array(&self) -> Result<&[Self], ValueAsError>;
-    fn as_string(&self) -> Result<String, ValueAsError>;
-    fn as_date(&self) -> Result<NaiveDate, ValueAsError>;
-    fn as_array_of_len(&self, len: usize) -> Result<&[Self], ValueAsError>;
-    fn as_map(&self) -> Result<&[(Self, Self)], ValueAsError>;
-    fn map_lookup<T: Into<Self>>(&self, key: T) -> Result<&Self, ValueAsError>;
-    fn must_equal<T: Into<Self>>(&self, other: T) -> Result<(), ValueAsError>;
-    fn check_date_val_if_key_in_map<T: Into<Self>>(&self, key: T) -> Result<()>;
-    fn check_string_val_if_key_in_map<T: Into<Self>>(&self, key: T) -> Result<()>;
-    fn check_bytes_val_if_key_in_map<T: Into<Self>>(&self, key: T) -> Result<()>;
-    fn check_arr_val_if_key_in_map<T: Into<Self>>(&self, key: T, arr: &[&str]) -> Result<()>;
-    fn check_arr_val_for_key_in_map<T: Into<Self>>(&self, key: T, arr: &[&str]) -> Result<()>;
-}
-
-impl ValueAs for Value {
-    fn as_i64(&self) -> Result<i64, ValueAsError> {
-        if let Value::Integer(i) = self {
-            let i = i128::from(*i);
-            Ok(i.try_into().map_err(|_| ValueAsError::IntegerTooBigError())?)
-        } else {
-            Err(ValueAsError::WrongType())
-        }
-    }
-
-    fn as_u64(&self) -> Result<u64, ValueAsError> {
-        if let Value::Integer(i) = self {
-            let i = i128::from(*i);
-            Ok(i.try_into().map_err(|_| ValueAsError::IntegerTooBigError())?)
-        } else {
-            Err(ValueAsError::WrongType())
-        }
-    }
-
-    fn as_bytes(&self) -> Result<&[u8], ValueAsError> {
-        if let Value::Bytes(b) = self {
-            Ok(b)
-        } else {
-            Err(ValueAsError::WrongType())
-        }
-    }
-
-    fn as_array(&self) -> Result<&[Self], ValueAsError> {
-        if let Value::Array(a) = self {
-            Ok(a)
-        } else {
-            Err(ValueAsError::WrongType())
-        }
-    }
-
-    fn as_string(&self) -> Result<String, ValueAsError> {
-        if let Value::Text(s) = self {
-            Ok(s.to_string())
-        } else {
-            Err(ValueAsError::WrongType())
-        }
-    }
-
-    fn as_date(&self) -> Result<NaiveDate, ValueAsError> {
-        let date = NaiveDate::parse_from_str(&self.as_u64()?.to_string(), "%Y%m%d");
-        if let Ok(date) = date {
-            Ok(date)
-        } else {
-            Err(ValueAsError::WrongType())
-        }
-    }
-
-    fn as_array_of_len(&self, len: usize) -> Result<&[Self], ValueAsError> {
-        let a = ValueAs::as_array(self)?;
-
-        if a.len() == len {
-            Ok(a)
-        } else {
-            Err(ValueAsError::WrongLength(len, a.len()))
-        }
-    }
-
-    fn as_map(&self) -> Result<&[(Self, Self)], ValueAsError> {
-        if let Value::Map(a) = self {
-            Ok(a)
-        } else {
-            Err(ValueAsError::WrongType())
-        }
-    }
-
-    fn map_lookup<T: Into<Self>>(&self, key: T) -> Result<&Self, ValueAsError> {
-        let a = ValueAs::as_map(self)?;
-        let key: Value = key.into();
-        for (k, v) in a {
-            if k == &key {
-                return Ok(v);
-            }
-        }
-        Err(ValueAsError::KeyAbsent())
-    }
-
-    fn must_equal<T: Into<Self>>(&self, other: T) -> Result<(), ValueAsError> {
-        let other: Value = other.into();
-        if *self == other {
-            Ok(())
-        } else {
-            Err(ValueAsError::NotSameError(other, self.clone()))
-        }
-    }
-
-    fn check_date_val_if_key_in_map<T: Into<Self>>(&self, key: T) -> Result<()> {
-        let result = self.map_lookup(key);
-        if result.is_ok() {
-            result?.as_date()?;
-        }
-        Ok(())
-    }
-
-    fn check_string_val_if_key_in_map<T: Into<Self>>(&self, key: T) -> Result<()> {
-        let result = self.map_lookup(key);
-        if result.is_ok() {
-            result?.as_string()?;
-        }
-        Ok(())
-    }
-
-    fn check_arr_val_if_key_in_map<T: Into<Self>>(&self, key: T, arr: &[&str]) -> Result<()> {
-        let result = self.map_lookup(key);
-        if result.is_ok() {
-            let result = result?.as_string()?;
-            ensure!(arr.iter().any(|&x| x.eq(&result)));
-        }
-        Ok(())
-    }
-
-    fn check_bytes_val_if_key_in_map<T: Into<Self>>(&self, key: T) -> Result<()> {
-        let result = self.map_lookup(key);
-        if result.is_ok() {
-            result?.as_bytes().ok_or_else(|| anyhow!("map value not bytes"))?;
-        }
-        Ok(())
-    }
-
-    fn check_arr_val_for_key_in_map<T: Into<Self>>(&self, key: T, arr: &[&str]) -> Result<()> {
-        let result = self.map_lookup(key)?.as_string()?;
-        ensure!(arr.iter().any(|&x| x.eq(&result)));
-        Ok(())
-    }
-}
diff --git a/remote_provisioning/cert_validator/testdata/device-info/_CBOR_device_info_0.cert b/remote_provisioning/cert_validator/testdata/device-info/_CBOR_device_info_0.cert
deleted file mode 100644
index e7b1c4a..0000000
--- a/remote_provisioning/cert_validator/testdata/device-info/_CBOR_device_info_0.cert
+++ /dev/null
@@ -1 +0,0 @@
-‚®ebranddgooglmanufacturerdgooggproductepixelemodela6eboardbqcfdevicebNAhvb_stateegreenpbootloader_stateflockedjos_versioniAndroid11rsystem_patch_level4ˆØpboot_patch_level4frvendor_patch_level4ˆÚgversionnsecurity_levelctee¡kfingerprintgABCDEFG
\ No newline at end of file
diff --git a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_0.cert b/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_0.cert
deleted file mode 100644
index c0883c9..0000000
--- a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_0.cert
+++ /dev/null
Binary files differ
diff --git a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_1.cert b/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_1.cert
deleted file mode 100644
index 4a16295..0000000
--- a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_1.cert
+++ /dev/null
Binary files differ
diff --git a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_2.cert b/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_2.cert
deleted file mode 100644
index a1a598c..0000000
--- a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_2.cert
+++ /dev/null
Binary files differ
diff --git a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_3.cert b/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_3.cert
deleted file mode 100644
index 166aff9..0000000
--- a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_3.cert
+++ /dev/null
Binary files differ
diff --git a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_4.cert b/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_4.cert
deleted file mode 100644
index a7bfc7d..0000000
--- a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_4.cert
+++ /dev/null
Binary files differ
diff --git a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_5.cert b/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_5.cert
deleted file mode 100644
index 0e67c01..0000000
--- a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_5.cert
+++ /dev/null
Binary files differ
diff --git a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_6.cert b/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_6.cert
deleted file mode 100644
index bfe7d0a..0000000
--- a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_Ed25519_cert_full_cert_chain_6.cert
+++ /dev/null
Binary files differ
diff --git a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_bcc_entry_cert_array.cert b/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_bcc_entry_cert_array.cert
deleted file mode 100644
index ed16a8d..0000000
--- a/remote_provisioning/cert_validator/testdata/open-dice/_CBOR_bcc_entry_cert_array.cert
+++ /dev/null
Binary files differ
diff --git a/remote_provisioning/hwtrust/.gitignore b/remote_provisioning/hwtrust/.gitignore
new file mode 100644
index 0000000..274f9ed
--- /dev/null
+++ b/remote_provisioning/hwtrust/.gitignore
@@ -0,0 +1,2 @@
+# Cargo's default output directory.
+target/
diff --git a/remote_provisioning/hwtrust/Android.bp b/remote_provisioning/hwtrust/Android.bp
new file mode 100644
index 0000000..64b10e9
--- /dev/null
+++ b/remote_provisioning/hwtrust/Android.bp
@@ -0,0 +1,74 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libhwtrust_defaults",
+    host_supported: true,
+    srcs: ["src/lib.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libthiserror",
+        "libciborium",
+        "libcoset",
+        "libhex",
+        "libopenssl",
+    ],
+}
+
+rust_library {
+    name: "libhwtrust",
+    defaults: ["libhwtrust_defaults"],
+    crate_name: "hwtrust",
+    vendor_available: true,
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.compos",
+        "com.android.virt",
+    ],
+}
+
+rust_test {
+    name: "libhwtrust_tests",
+    defaults: ["libhwtrust_defaults"],
+    data: [":testdata"],
+    rustlibs: [
+        "libhwtrust",
+    ],
+}
+
+rust_defaults {
+    name: "hwtrust_defaults",
+    host_supported: true,
+    srcs: ["src/main.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libclap",
+        "libhwtrust",
+    ],
+}
+
+rust_binary {
+    name: "hwtrust",
+    defaults: ["hwtrust_defaults"],
+}
+
+rust_test {
+    name: "hwtrust_tests",
+    defaults: ["hwtrust_defaults"],
+}
+
+rust_test {
+    name: "hwtrust_cli_tests",
+    host_supported: true,
+    srcs: ["tests/hwtrust_cli.rs"],
+    data: [":testdata"],
+    data_bins: ["hwtrust"],
+    data_libs: ["libcrypto"],
+    compile_multilib: "first",
+}
+
+filegroup(
+    name = "testdata",
+    srcs = ["testdata/**/*"],
+)
diff --git a/remote_provisioning/hwtrust/Cargo.lock b/remote_provisioning/hwtrust/Cargo.lock
new file mode 100644
index 0000000..12d91ba
--- /dev/null
+++ b/remote_provisioning/hwtrust/Cargo.lock
@@ -0,0 +1,502 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "anyhow"
+version = "1.0.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "cc"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "ciborium"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
+[[package]]
+name = "clap"
+version = "4.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2"
+dependencies = [
+ "bitflags",
+ "clap_derive",
+ "clap_lex",
+ "is-terminal",
+ "once_cell",
+ "strsim",
+ "termcolor",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "coset"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "604cb30f7b6f4fee05b9ddf88cf6d3c0af0f98d9099e6f6a050e81e7a7cb938b"
+dependencies = [
+ "ciborium",
+ "ciborium-io",
+]
+
+[[package]]
+name = "errno"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "half"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
+
+[[package]]
+name = "heck"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+
+[[package]]
+name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hwtrust"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "ciborium",
+ "clap",
+ "coset",
+ "hex",
+ "openssl",
+ "thiserror",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
+dependencies = [
+ "hermit-abi",
+ "io-lifetimes",
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.139"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
+
+[[package]]
+name = "once_cell"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
+
+[[package]]
+name = "openssl"
+version = "0.10.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
+dependencies = [
+ "autocfg",
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "os_str_bytes"
+version = "6.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rustix"
+version = "0.36.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
diff --git a/remote_provisioning/hwtrust/Cargo.toml b/remote_provisioning/hwtrust/Cargo.toml
new file mode 100644
index 0000000..7d01144
--- /dev/null
+++ b/remote_provisioning/hwtrust/Cargo.toml
@@ -0,0 +1,18 @@
+# Warning: the Cargo build is secondary to the Android build and it's likely to
+# get out of sync or have dependency problems. If this happens, fixes and
+# reports of the problem are welcomed.
+
+[package]
+name = "hwtrust"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0"
+thiserror = "1.0"
+ciborium = "0.2.0"
+clap = { version = "4.1", features = ["derive"] }
+coset = "0.3.3"
+hex = "0.4.3"
+openssl = "0.10.45"
+
diff --git a/remote_provisioning/cert_validator/OWNERS b/remote_provisioning/hwtrust/OWNERS
similarity index 76%
rename from remote_provisioning/cert_validator/OWNERS
rename to remote_provisioning/hwtrust/OWNERS
index a7f8bc4..9fc3bb1 100644
--- a/remote_provisioning/cert_validator/OWNERS
+++ b/remote_provisioning/hwtrust/OWNERS
@@ -1,3 +1,4 @@
 asbel@google.com
 hasinitg@google.com
 alanstokes@google.com
+ascull@google.com
diff --git a/remote_provisioning/hwtrust/README.md b/remote_provisioning/hwtrust/README.md
new file mode 100644
index 0000000..0ed2dcf
--- /dev/null
+++ b/remote_provisioning/hwtrust/README.md
@@ -0,0 +1,34 @@
+# Hardware trust
+
+Reliable trust in a device's hardware is the basis of a growing set of features,
+for example remote key provisioning.
+
+## `libhwtrust`
+
+The library for handling, inspecting and validating data realted to the hardware
+root-of-trust and the features that rely on it is `libhwtrust`.
+
+## `hwtrust`
+
+There is a command-line utility that provides easy access to the logic in
+`libhwtrust` called `hwtrust`.
+
+Build it as part of Android with `m hwtrust` and run `hwtrust --help` to see a
+list of its functions.
+
+Alternatively, use Cargo by running `cargo run -- --help` in this directory to
+build and run the utility. If the Cargo build has errors, please help to keep it
+working by sending fixes or reporting the problem. Building as part of Android
+should always work as a fallback.
+
+### Verifying DICE chains
+
+`hwtrust` can be used to validate that a DICE chain is well-formed and check
+that the signatures verify correctly. To do so, place the CBOR-encoded DICE
+chain in a file, e.g. `chain.bin`, then call the tool.
+
+```shell
+hwtrust verify-dice-chain chain.bin
+```
+
+The exit code is zero if the chain passed verification and non-zero otherwise.
diff --git a/remote_provisioning/hwtrust/TEST_MAPPING b/remote_provisioning/hwtrust/TEST_MAPPING
new file mode 100644
index 0000000..608acb5
--- /dev/null
+++ b/remote_provisioning/hwtrust/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+  "presubmit": [
+    {
+      "name": "VtsHalRemotelyProvisionedComponentTargetTest"
+    },
+    {
+      "name": "ComposHostTestCases"
+    }
+  ]
+}
diff --git a/remote_provisioning/hwtrust/cxxbridge/Android.bp b/remote_provisioning/hwtrust/cxxbridge/Android.bp
new file mode 100644
index 0000000..8fa13c5
--- /dev/null
+++ b/remote_provisioning/hwtrust/cxxbridge/Android.bp
@@ -0,0 +1,49 @@
+package {
+    default_visibility: ["//visibility:private"],
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+genrule {
+    name: "libhwtrust_cxx_bridge_header",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) --header > $(out)",
+    srcs: ["lib.rs"],
+    out: ["hwtrust/lib.rs.h"],
+}
+
+genrule {
+    name: "libhwtrust_cxx_bridge_code",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) >> $(out)",
+    srcs: ["lib.rs"],
+    out: ["hwtrust/lib.rs.cpp"],
+}
+
+rust_ffi_static {
+    name: "libhwtrust_cxx_bridge",
+    crate_name: "hwtrust_cxx_bridge",
+    host_supported: true,
+    vendor_available: true,
+    srcs: ["lib.rs"],
+    rustlibs: [
+        "libcoset",
+        "libcxx",
+        "libhwtrust",
+    ]
+}
+
+cc_library {
+    name: "libhwtrust_cxx",
+    visibility: ["//hardware/interfaces/security/keymint/support"],
+    host_supported: true,
+    vendor_available: true,
+    srcs: ["hwtrust.cpp"],
+    export_include_dirs: ["include"],
+    generated_sources: ["libhwtrust_cxx_bridge_code"],
+    generated_headers: ["libhwtrust_cxx_bridge_header"],
+    whole_static_libs: ["libhwtrust_cxx_bridge"],
+    shared_libs: [
+        "libbase",
+        "libcrypto",
+    ],
+}
diff --git a/remote_provisioning/hwtrust/cxxbridge/hwtrust.cpp b/remote_provisioning/hwtrust/cxxbridge/hwtrust.cpp
new file mode 100644
index 0000000..9fd4e75
--- /dev/null
+++ b/remote_provisioning/hwtrust/cxxbridge/hwtrust.cpp
@@ -0,0 +1,50 @@
+#include <hwtrust/hwtrust.h>
+#include <hwtrust/lib.rs.h>
+
+using android::base::Error;
+using android::base::Result;
+
+namespace hwtrust {
+
+struct BoxedDiceChain {
+    ::rust::Box<rust::DiceChain> chain;
+};
+
+// Define with a full definition of BoxedDiceChain to satisfy unique_ptr.
+DiceChain::~DiceChain() {}
+
+DiceChain::DiceChain(std::unique_ptr<BoxedDiceChain> chain, size_t size) noexcept
+      : chain_(std::move(chain)), size_(size) {}
+
+Result<DiceChain> DiceChain::Verify(const std::vector<uint8_t>& chain, DiceChain::Kind kind) noexcept {
+  rust::DiceChainKind chainKind;
+  switch (kind) {
+    case DiceChain::Kind::kVsr13:
+      chainKind = rust::DiceChainKind::Vsr13;
+      break;
+    case DiceChain::Kind::kVsr14:
+      chainKind = rust::DiceChainKind::Vsr14;
+      break;
+  }
+  auto res = rust::VerifyDiceChain({chain.data(), chain.size()}, chainKind);
+  if (!res.error.empty()) {
+      return Error() << static_cast<std::string>(res.error);
+  }
+  BoxedDiceChain boxedChain = { std::move(res.chain) };
+  auto diceChain = std::make_unique<BoxedDiceChain>(std::move(boxedChain));
+  return DiceChain(std::move(diceChain), res.len);
+}
+
+Result<std::vector<std::vector<uint8_t>>> DiceChain::CosePublicKeys() const noexcept {
+  std::vector<std::vector<uint8_t>> result;
+  for (auto i = 0; i < size_; ++i) {
+    auto key = rust::GetDiceChainPublicKey(*chain_->chain, i);
+    if (key.empty()) {
+      return Error() << "Failed to get public key from chain entry " << i;
+    }
+    result.emplace_back(key.begin(), key.end());
+  }
+  return result;
+}
+
+} // namespace hwtrust
diff --git a/remote_provisioning/hwtrust/cxxbridge/include/hwtrust/hwtrust.h b/remote_provisioning/hwtrust/cxxbridge/include/hwtrust/hwtrust.h
new file mode 100644
index 0000000..548f10e
--- /dev/null
+++ b/remote_provisioning/hwtrust/cxxbridge/include/hwtrust/hwtrust.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include <android-base/result.h>
+
+namespace hwtrust {
+
+// Hide the details of the rust binding from clients with an opaque type.
+struct BoxedDiceChain;
+
+class DiceChain final {
+public:
+  enum class Kind {
+    kVsr13,
+    kVsr14,
+  };
+
+  static android::base::Result<DiceChain> Verify(const std::vector<uint8_t>& chain, DiceChain::Kind kind) noexcept;
+
+  ~DiceChain();
+  DiceChain(DiceChain&&) = default;
+
+  android::base::Result<std::vector<std::vector<uint8_t>>> CosePublicKeys() const noexcept;
+
+private:
+  DiceChain(std::unique_ptr<BoxedDiceChain> chain, size_t size) noexcept;
+
+  std::unique_ptr<BoxedDiceChain> chain_;
+  size_t size_;
+};
+
+} // namespace hwtrust
diff --git a/remote_provisioning/hwtrust/cxxbridge/lib.rs b/remote_provisioning/hwtrust/cxxbridge/lib.rs
new file mode 100644
index 0000000..1f3827a
--- /dev/null
+++ b/remote_provisioning/hwtrust/cxxbridge/lib.rs
@@ -0,0 +1,87 @@
+//! This library provides bindings for C++ code to comfortably and reasonably safely interface with
+//! the libhwtrust Rust library.
+
+use coset::CborSerializable;
+use hwtrust::dice::ChainForm;
+use hwtrust::session::{Options, Session};
+
+#[cxx::bridge(namespace = "hwtrust::rust")]
+mod ffi {
+    /// The set of validation rules to apply.
+    enum DiceChainKind {
+        /// The DICE chain specified by VSR 13.
+        Vsr13,
+        /// The DICE chain specified by VSR 14.
+        Vsr14,
+    }
+
+    /// The result type used by [`verify_dice_chain()`]. The standard [`Result`] is currently only
+    /// converted to exceptions by `cxxbridge` but we can't use exceptions so need to do something
+    /// custom.
+    struct VerifyDiceChainResult {
+        /// If non-empty, the description of the verification error that occurred.
+        error: String,
+        /// If [`error`] is empty, a handle to the verified chain.
+        chain: Box<DiceChain>,
+        /// If [`error`] is empty, the length of the chain.
+        len: usize,
+    }
+
+    extern "Rust" {
+        type DiceChain;
+
+        #[cxx_name = VerifyDiceChain]
+        fn verify_dice_chain(chain: &[u8], kind: DiceChainKind) -> VerifyDiceChainResult;
+
+        #[cxx_name = GetDiceChainPublicKey]
+        fn get_dice_chain_public_key(chain: &DiceChain, n: usize) -> Vec<u8>;
+    }
+}
+
+/// A DICE chain as exposed over the cxx bridge.
+pub struct DiceChain(Option<ChainForm>);
+
+fn verify_dice_chain(chain: &[u8], kind: ffi::DiceChainKind) -> ffi::VerifyDiceChainResult {
+    let session = Session {
+        options: match kind {
+            ffi::DiceChainKind::Vsr13 => Options::vsr13(),
+            ffi::DiceChainKind::Vsr14 => Options::vsr14(),
+            _ => {
+                return ffi::VerifyDiceChainResult {
+                    error: "invalid chain kind".to_string(),
+                    chain: Box::new(DiceChain(None)),
+                    len: 0,
+                }
+            }
+        },
+    };
+    match ChainForm::from_cbor(&session, chain) {
+        Ok(chain) => {
+            let len = match chain {
+                ChainForm::Proper(ref chain) => chain.payloads().len(),
+                ChainForm::Degenerate(_) => 1,
+            };
+            let chain = Box::new(DiceChain(Some(chain)));
+            ffi::VerifyDiceChainResult { error: "".to_string(), chain, len }
+        }
+        Err(e) => {
+            let error = format!("{:#}", e);
+            ffi::VerifyDiceChainResult { error, chain: Box::new(DiceChain(None)), len: 0 }
+        }
+    }
+}
+
+fn get_dice_chain_public_key(chain: &DiceChain, n: usize) -> Vec<u8> {
+    if let DiceChain(Some(chain)) = chain {
+        let key = match chain {
+            ChainForm::Proper(chain) => chain.payloads()[n].subject_public_key(),
+            ChainForm::Degenerate(chain) => chain.public_key(),
+        };
+        if let Ok(cose_key) = key.to_cose_key() {
+            if let Ok(bytes) = cose_key.to_vec() {
+                return bytes;
+            }
+        }
+    }
+    Vec::new()
+}
diff --git a/remote_provisioning/hwtrust/src/cbor.rs b/remote_provisioning/hwtrust/src/cbor.rs
new file mode 100644
index 0000000..987d62c
--- /dev/null
+++ b/remote_provisioning/hwtrust/src/cbor.rs
@@ -0,0 +1,58 @@
+//! Handling for data represented as CBOR. Cryptographic objects are encoded following COSE.
+
+mod dice;
+mod publickey;
+
+use ciborium::{de::from_reader, value::Value};
+use std::io::Read;
+
+fn cose_error(ce: coset::CoseError) -> anyhow::Error {
+    anyhow::anyhow!("CoseError: {:?}", ce)
+}
+
+type CiboriumError = ciborium::de::Error<std::io::Error>;
+
+/// Decodes the provided binary CBOR-encoded value and returns a
+/// ciborium::Value struct wrapped in Result.
+fn value_from_bytes(mut bytes: &[u8]) -> Result<Value, CiboriumError> {
+    let value = from_reader(bytes.by_ref())?;
+    // Ciborium tries to read one Value, but doesn't care if there is trailing data. We do.
+    if !bytes.is_empty() {
+        return Err(CiboriumError::Semantic(Some(0), "unexpected trailing data".to_string()));
+    }
+    Ok(value)
+}
+
+#[cfg(test)]
+fn serialize(value: Value) -> Vec<u8> {
+    let mut data = Vec::new();
+    ciborium::ser::into_writer(&value, &mut data).unwrap();
+    data
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use anyhow::Result;
+
+    #[test]
+    fn value_from_bytes_valid_succeeds() -> Result<()> {
+        let bytes = [0x82, 0x04, 0x02]; // [4, 2]
+        let val = value_from_bytes(&bytes)?;
+        let array = val.as_array().unwrap();
+        assert_eq!(array.len(), 2);
+        Ok(())
+    }
+
+    #[test]
+    fn value_from_bytes_truncated_fails() {
+        let bytes = [0x82, 0x04];
+        assert!(value_from_bytes(&bytes).is_err());
+    }
+
+    #[test]
+    fn value_from_bytes_trailing_bytes_fails() {
+        let bytes = [0x82, 0x04, 0x02, 0x00];
+        assert!(value_from_bytes(&bytes).is_err());
+    }
+}
diff --git a/remote_provisioning/hwtrust/src/cbor/dice.rs b/remote_provisioning/hwtrust/src/cbor/dice.rs
new file mode 100644
index 0000000..e35f7af
--- /dev/null
+++ b/remote_provisioning/hwtrust/src/cbor/dice.rs
@@ -0,0 +1,30 @@
+//! Parsing and encoding DICE chain from and to CBOR.
+
+use crate::cbor::cose_error;
+use crate::session::{KeyOpsType, Session};
+use anyhow::Result;
+use ciborium::value::Value;
+use coset::iana::{self, EnumI64};
+use coset::{AsCborValue, CoseKey, Label};
+
+mod chain;
+mod entry;
+mod field_value;
+
+/// Convert a `Value` into a `CoseKey`, respecting the `Session` options that might alter the
+/// validation rules for `CoseKey`s in the DICE chain.
+fn cose_key_from_cbor_value(session: &Session, mut value: Value) -> Result<CoseKey> {
+    if session.options.dice_chain_key_ops_type == KeyOpsType::IntOrArray {
+        // Convert any integer key_ops into an array of the same integer so that the coset library
+        // can handle it.
+        if let Value::Map(ref mut entries) = value {
+            for (label, value) in entries.iter_mut() {
+                let label = Label::from_cbor_value(label.clone()).map_err(cose_error)?;
+                if label == Label::Int(iana::KeyParameter::KeyOps.to_i64()) && value.is_integer() {
+                    *value = Value::Array(vec![value.clone()]);
+                }
+            }
+        }
+    }
+    CoseKey::from_cbor_value(value).map_err(cose_error)
+}
diff --git a/remote_provisioning/hwtrust/src/cbor/dice/chain.rs b/remote_provisioning/hwtrust/src/cbor/dice/chain.rs
new file mode 100644
index 0000000..1436f65
--- /dev/null
+++ b/remote_provisioning/hwtrust/src/cbor/dice/chain.rs
@@ -0,0 +1,251 @@
+use super::cose_key_from_cbor_value;
+use super::entry::Entry;
+use crate::cbor::dice::entry::PayloadFields;
+use crate::cbor::value_from_bytes;
+use crate::dice::{Chain, ChainForm, DegenerateChain, Payload};
+use crate::publickey::PublicKey;
+use crate::session::{ConfigFormat, Session};
+use anyhow::{bail, Context, Result};
+use ciborium::value::Value;
+
+impl ChainForm {
+    /// Decode and validate a CBOR-encoded DICE chain. The form of chain is inferred from the
+    /// structure of the data.
+    pub fn from_cbor(session: &Session, bytes: &[u8]) -> Result<Self> {
+        let (root_public_key, it) = root_and_entries_from_cbor(session, bytes)?;
+
+        if it.len() == 1 {
+            // The chain could be degenerate so interpret it as such until it's seen to be more
+            // than a single self-signed entry. Care is taken to not consume the iterator in case
+            // it ends up needing to be interpreted as a proper DICE chain.
+            let value = it.as_slice()[0].clone();
+            let entry = Entry::verify_cbor_value(value, &root_public_key)
+                .context("parsing degenerate entry")?;
+            let fields = PayloadFields::from_cbor(session, entry.payload(), ConfigFormat::Android)
+                .context("parsing degenerate payload")?;
+            let chain =
+                DegenerateChain::new(fields.issuer, fields.subject, fields.subject_public_key)
+                    .context("creating DegenerateChain")?;
+            if root_public_key.pkey().public_eq(chain.public_key().pkey()) {
+                return Ok(Self::Degenerate(chain));
+            }
+        }
+
+        Ok(Self::Proper(Chain::from_root_and_entries(session, root_public_key, it)?))
+    }
+}
+
+impl Chain {
+    /// Decode and validate a Chain from its CBOR representation. This ensures the CBOR is
+    /// well-formed, that all required fields are present, and all present fields contain
+    /// reasonable values. The signature of each certificate is validated and the payload
+    /// extracted. This does not perform any semantic validation of the data in the
+    /// certificates such as the Authority, Config and Code hashes.
+    pub fn from_cbor(session: &Session, bytes: &[u8]) -> Result<Self> {
+        let (root_public_key, it) = root_and_entries_from_cbor(session, bytes)?;
+        Self::from_root_and_entries(session, root_public_key, it)
+    }
+
+    fn from_root_and_entries(
+        session: &Session,
+        root: PublicKey,
+        values: std::vec::IntoIter<Value>,
+    ) -> Result<Self> {
+        let mut payloads = Vec::with_capacity(values.len());
+        let mut previous_public_key = &root;
+        for (n, value) in values.enumerate() {
+            let entry = Entry::verify_cbor_value(value, previous_public_key)
+                .with_context(|| format!("Invalid entry at index {}", n))?;
+            let config_format = if n == 0 {
+                session.options.first_dice_chain_cert_config_format
+            } else {
+                ConfigFormat::Android
+            };
+            let payload = Payload::from_cbor(session, entry.payload(), config_format)
+                .with_context(|| format!("Invalid payload at index {}", n))?;
+            payloads.push(payload);
+            let previous = payloads.last().unwrap();
+            previous_public_key = previous.subject_public_key();
+        }
+        Self::validate(root, payloads).context("Building chain")
+    }
+}
+
+fn root_and_entries_from_cbor(
+    session: &Session,
+    bytes: &[u8],
+) -> Result<(PublicKey, std::vec::IntoIter<Value>)> {
+    let value = value_from_bytes(bytes).context("Unable to decode top-level CBOR")?;
+    let array = match value {
+        Value::Array(array) if array.len() >= 2 => array,
+        _ => bail!("Expected an array of at least length 2, found: {:?}", value),
+    };
+    let mut it = array.into_iter();
+    let root_public_key = cose_key_from_cbor_value(session, it.next().unwrap())
+        .context("Error parsing root public key CBOR")?;
+    let root_public_key = PublicKey::from_cose_key(&root_public_key).context("Invalid root key")?;
+    Ok((root_public_key, it))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::cbor::serialize;
+    use crate::dice::{DiceMode, PayloadBuilder};
+    use crate::publickey::testkeys::{PrivateKey, ED25519_KEY_PEM, P256_KEY_PEM, P384_KEY_PEM};
+    use crate::session::{KeyOpsType, Options};
+    use ciborium::cbor;
+    use coset::iana::{self, EnumI64};
+    use coset::AsCborValue;
+    use std::fs;
+
+    #[test]
+    fn chain_form_valid_proper() {
+        let chain = fs::read("testdata/dice/valid_ed25519.chain").unwrap();
+        let session = Session { options: Options::default() };
+        let form = ChainForm::from_cbor(&session, &chain).unwrap();
+        assert!(matches!(form, ChainForm::Proper(_)));
+    }
+
+    #[test]
+    fn chain_form_valid_degenerate() {
+        let chain = fs::read("testdata/dice/cf_degenerate.chain").unwrap();
+        let session = Session { options: Options::default() };
+        let form = ChainForm::from_cbor(&session, &chain).unwrap();
+        assert!(matches!(form, ChainForm::Degenerate(_)));
+    }
+
+    #[test]
+    fn check_chain_valid_ed25519() {
+        let chain = fs::read("testdata/dice/valid_ed25519.chain").unwrap();
+        let session = Session { options: Options::default() };
+        let chain = Chain::from_cbor(&session, &chain).unwrap();
+        assert_eq!(chain.payloads().len(), 8);
+    }
+
+    #[test]
+    fn check_chain_valid_p256() {
+        let chain = fs::read("testdata/dice/valid_p256.chain").unwrap();
+        let session = Session { options: Options::default() };
+        let chain = Chain::from_cbor(&session, &chain).unwrap();
+        assert_eq!(chain.payloads().len(), 3);
+    }
+
+    #[test]
+    fn check_chain_bad_p256() {
+        let chain = fs::read("testdata/dice/bad_p256.chain").unwrap();
+        let session = Session { options: Options::default() };
+        Chain::from_cbor(&session, &chain).unwrap_err();
+    }
+
+    #[test]
+    fn check_chain_bad_pub_key() {
+        let chain = fs::read("testdata/dice/bad_pub_key.chain").unwrap();
+        let session = Session { options: Options::default() };
+        Chain::from_cbor(&session, &chain).unwrap_err();
+    }
+
+    #[test]
+    fn check_chain_bad_final_signature() {
+        let chain = fs::read("testdata/dice/bad_final_signature.chain").unwrap();
+        let session = Session { options: Options::default() };
+        Chain::from_cbor(&session, &chain).unwrap_err();
+    }
+
+    #[test]
+    fn chain_from_cbor_valid() {
+        let keys: Vec<_> = P256_KEY_PEM[..4].iter().copied().map(PrivateKey::from_pem).collect();
+        let mut pub_keys = keys.iter().map(PrivateKey::public_key);
+        let root_key = pub_keys.next().unwrap().to_cose_key().unwrap().to_cbor_value().unwrap();
+        let mut chain = vec![root_key];
+        chain.extend(pub_keys.enumerate().map(|(n, key)| {
+            let entry = Entry::from_payload(&valid_payload(n, key)).unwrap();
+            entry.sign(&keys[n]).to_cbor_value().unwrap()
+        }));
+        let session = Session { options: Options::default() };
+        Chain::from_cbor(&session, &serialize(Value::Array(chain))).unwrap();
+    }
+
+    #[test]
+    fn chain_from_cbor_valid_with_mixed_key_types() {
+        let keys = [ED25519_KEY_PEM[0], P256_KEY_PEM[0], P384_KEY_PEM[0]];
+        let keys: Vec<_> = keys.iter().copied().map(PrivateKey::from_pem).collect();
+        let mut pub_keys = keys.iter().map(PrivateKey::public_key);
+        let root_key = pub_keys.next().unwrap().to_cose_key().unwrap().to_cbor_value().unwrap();
+        let mut chain = vec![root_key];
+        chain.extend(pub_keys.enumerate().map(|(n, key)| {
+            let entry = Entry::from_payload(&valid_payload(n, key)).unwrap();
+            entry.sign(&keys[n]).to_cbor_value().unwrap()
+        }));
+        let session = Session { options: Options::default() };
+        Chain::from_cbor(&session, &serialize(Value::Array(chain))).unwrap();
+    }
+
+    #[test]
+    fn chain_from_cbor_root_key_integer_key_ops() {
+        let root_key = PrivateKey::from_pem(ED25519_KEY_PEM[0]);
+        let root_public_key = root_key.public_key().pkey().raw_public_key().unwrap();
+        let root_cose_key = cbor!({
+            iana::KeyParameter::Kty.to_i64() => iana::KeyType::OKP.to_i64(),
+            iana::KeyParameter::Alg.to_i64() => iana::Algorithm::EdDSA.to_i64(),
+            iana::KeyParameter::KeyOps.to_i64() => iana::KeyOperation::Verify.to_i64(),
+            iana::OkpKeyParameter::Crv.to_i64() => iana::EllipticCurve::Ed25519.to_i64(),
+            iana::OkpKeyParameter::X.to_i64() => Value::Bytes(root_public_key),
+        })
+        .unwrap();
+        let entry_pub_key = PrivateKey::from_pem(P256_KEY_PEM[0]).public_key();
+        let entry = Entry::from_payload(&valid_payload(0, entry_pub_key)).unwrap();
+        let chain = vec![root_cose_key, entry.sign(&root_key).to_cbor_value().unwrap()];
+        let cbor = serialize(Value::Array(chain));
+        let session = Session { options: Options::default() };
+        Chain::from_cbor(&session, &cbor).unwrap_err();
+        let session = Session {
+            options: Options {
+                dice_chain_key_ops_type: KeyOpsType::IntOrArray,
+                ..Options::default()
+            },
+        };
+        Chain::from_cbor(&session, &cbor).unwrap();
+    }
+
+    #[test]
+    fn chain_form_from_cbor_valid_degenerate() {
+        let key = PrivateKey::from_pem(P256_KEY_PEM[0]);
+        let pub_key = key.public_key();
+        let entry = Entry::from_payload(&valid_payload(0, pub_key.clone())).unwrap();
+        let chain = vec![
+            pub_key.to_cose_key().unwrap().to_cbor_value().unwrap(),
+            entry.sign(&key).to_cbor_value().unwrap(),
+        ];
+        let session = Session { options: Options::default() };
+        let form = ChainForm::from_cbor(&session, &serialize(Value::Array(chain))).unwrap();
+        assert!(matches!(form, ChainForm::Degenerate(_)));
+    }
+
+    /// Tests that a chain the length of a degenerate chain that does not contain a self-signed
+    /// certificate is a proper DICE chain and not a degenerate chain.
+    #[test]
+    fn chain_form_from_cbor_degenerate_length_but_not_self_signed() {
+        let root_key = PrivateKey::from_pem(P256_KEY_PEM[0]);
+        let entry_pub_key = PrivateKey::from_pem(ED25519_KEY_PEM[0]).public_key();
+        let entry = Entry::from_payload(&valid_payload(0, entry_pub_key)).unwrap();
+        let chain = vec![
+            root_key.public_key().to_cose_key().unwrap().to_cbor_value().unwrap(),
+            entry.sign(&root_key).to_cbor_value().unwrap(),
+        ];
+        let session = Session { options: Options::default() };
+        let form = ChainForm::from_cbor(&session, &serialize(Value::Array(chain))).unwrap();
+        assert!(matches!(form, ChainForm::Proper(_)));
+    }
+
+    fn valid_payload(index: usize, key: PublicKey) -> Payload {
+        PayloadBuilder::with_subject_public_key(key)
+            .issuer(format!("item {}", index))
+            .subject(format!("item {}", index + 1))
+            .mode(DiceMode::Normal)
+            .code_hash(vec![6; 64])
+            .authority_hash(vec![7; 64])
+            .build()
+            .unwrap()
+    }
+}
diff --git a/remote_provisioning/hwtrust/src/cbor/dice/entry.rs b/remote_provisioning/hwtrust/src/cbor/dice/entry.rs
new file mode 100644
index 0000000..1652df0
--- /dev/null
+++ b/remote_provisioning/hwtrust/src/cbor/dice/entry.rs
@@ -0,0 +1,665 @@
+use super::cose_key_from_cbor_value;
+use super::field_value::FieldValue;
+use crate::cbor::{cose_error, value_from_bytes};
+use crate::dice::{
+    ComponentVersion, ConfigDesc, ConfigDescBuilder, DiceMode, Payload, PayloadBuilder,
+};
+use crate::publickey::PublicKey;
+use crate::session::{ComponentVersionType, ConfigFormat, ModeType, Session};
+use anyhow::{anyhow, bail, Context, Result};
+use ciborium::value::Value;
+use coset::{AsCborValue, CoseSign1};
+use std::collections::hash_map::Entry::{Occupied, Vacant};
+use std::collections::HashMap;
+
+const ISS: i64 = 1;
+const SUB: i64 = 2;
+const CODE_HASH: i64 = -4670545;
+const CODE_DESC: i64 = -4670546;
+const CONFIG_HASH: i64 = -4670547;
+const CONFIG_DESC: i64 = -4670548;
+const AUTHORITY_HASH: i64 = -4670549;
+const AUTHORITY_DESC: i64 = -4670550;
+const MODE: i64 = -4670551;
+const SUBJECT_PUBLIC_KEY: i64 = -4670552;
+const KEY_USAGE: i64 = -4670553;
+
+const CONFIG_DESC_RESERVED_MAX: i64 = -70000;
+const CONFIG_DESC_RESERVED_MIN: i64 = -70999;
+const COMPONENT_NAME: i64 = -70002;
+const COMPONENT_VERSION: i64 = -70003;
+const RESETTABLE: i64 = -70004;
+
+pub(super) struct Entry {
+    payload: Vec<u8>,
+}
+
+impl Entry {
+    pub(super) fn verify_cbor_value(cbor: Value, key: &PublicKey) -> Result<Self> {
+        let sign1 = CoseSign1::from_cbor_value(cbor)
+            .map_err(cose_error)
+            .context("Given CBOR does not appear to be a COSE_sign1")?;
+        key.verify_cose_sign1(&sign1).context("cannot verify COSE_sign1")?;
+        match sign1.payload {
+            None => bail!("Missing payload"),
+            Some(payload) => Ok(Self { payload }),
+        }
+    }
+
+    pub(super) fn payload(&self) -> &[u8] {
+        &self.payload
+    }
+}
+
+impl Payload {
+    pub(super) fn from_cbor(
+        session: &Session,
+        bytes: &[u8],
+        config_format: ConfigFormat,
+    ) -> Result<Self> {
+        let f = PayloadFields::from_cbor(session, bytes, config_format)?;
+        PayloadBuilder::with_subject_public_key(f.subject_public_key)
+            .issuer(f.issuer)
+            .subject(f.subject)
+            .mode(f.mode.ok_or_else(|| anyhow!("mode required"))?)
+            .code_desc(f.code_desc)
+            .code_hash(f.code_hash.ok_or_else(|| anyhow!("code hash required"))?)
+            .config_desc(f.config_desc.ok_or_else(|| anyhow!("config desc required"))?)
+            .config_hash(f.config_hash)
+            .authority_desc(f.authority_desc)
+            .authority_hash(f.authority_hash.ok_or_else(|| anyhow!("authority hash required"))?)
+            .build()
+            .context("building payload")
+    }
+}
+
+pub(super) struct PayloadFields {
+    pub(super) issuer: String,
+    pub(super) subject: String,
+    pub(super) subject_public_key: PublicKey,
+    mode: Option<DiceMode>,
+    code_desc: Option<Vec<u8>>,
+    code_hash: Option<Vec<u8>>,
+    config_desc: Option<ConfigDesc>,
+    config_hash: Option<Vec<u8>>,
+    authority_desc: Option<Vec<u8>>,
+    authority_hash: Option<Vec<u8>>,
+}
+
+impl PayloadFields {
+    pub(super) fn from_cbor(
+        session: &Session,
+        bytes: &[u8],
+        config_format: ConfigFormat,
+    ) -> Result<Self> {
+        let mut issuer = FieldValue::new("issuer");
+        let mut subject = FieldValue::new("subject");
+        let mut subject_public_key = FieldValue::new("subject public key");
+        let mut mode = FieldValue::new("mode");
+        let mut code_desc = FieldValue::new("code desc");
+        let mut code_hash = FieldValue::new("code hash");
+        let mut config_desc = FieldValue::new("config desc");
+        let mut config_hash = FieldValue::new("config hash");
+        let mut authority_desc = FieldValue::new("authority desc");
+        let mut authority_hash = FieldValue::new("authority hash");
+        let mut key_usage = FieldValue::new("key usage");
+
+        let entries = cbor_map_from_slice(bytes)?;
+        for (key, value) in entries.into_iter() {
+            if let Some(Ok(key)) = key.as_integer().map(TryInto::try_into) {
+                let field = match key {
+                    ISS => &mut issuer,
+                    SUB => &mut subject,
+                    SUBJECT_PUBLIC_KEY => &mut subject_public_key,
+                    MODE => &mut mode,
+                    CODE_DESC => &mut code_desc,
+                    CODE_HASH => &mut code_hash,
+                    CONFIG_DESC => &mut config_desc,
+                    CONFIG_HASH => &mut config_hash,
+                    AUTHORITY_DESC => &mut authority_desc,
+                    AUTHORITY_HASH => &mut authority_hash,
+                    KEY_USAGE => &mut key_usage,
+                    _ => bail!("Unknown key {}", key),
+                };
+                field.set(value)?;
+            } else {
+                bail!("Invalid key: {:?}", key);
+            }
+        }
+
+        validate_key_usage(session, key_usage)?;
+
+        Ok(Self {
+            issuer: issuer.into_string().context("issuer")?,
+            subject: subject.into_string().context("subject")?,
+            subject_public_key: validate_subject_public_key(session, subject_public_key)?,
+            mode: validate_mode(session, mode).context("mode")?,
+            code_desc: code_desc.into_optional_bytes().context("code descriptor")?,
+            code_hash: code_hash.into_optional_bytes().context("code hash")?,
+            config_desc: validate_config_desc(session, config_desc, config_format)
+                .context("config descriptor")?,
+            config_hash: config_hash.into_optional_bytes().context("config hash")?,
+            authority_desc: authority_desc.into_optional_bytes().context("authority descriptor")?,
+            authority_hash: authority_hash.into_optional_bytes().context("authority hash")?,
+        })
+    }
+}
+
+fn validate_key_usage(session: &Session, key_usage: FieldValue) -> Result<()> {
+    let key_usage = key_usage.into_bytes().context("key usage")?;
+    let key_cert_sign = 1 << 5;
+    if key_usage.len() > 1
+        && session.options.dice_chain_allow_big_endian_key_usage
+        && key_usage[key_usage.len() - 1] == key_cert_sign
+        && key_usage.iter().take(key_usage.len() - 1).all(|&x| x == 0)
+    {
+        return Ok(());
+    }
+    if key_usage.is_empty()
+        || key_usage[0] != key_cert_sign
+        || !key_usage.iter().skip(1).all(|&x| x == 0)
+    {
+        bail!("key usage must only contain keyCertSign (bit 5)");
+    };
+    Ok(())
+}
+
+fn validate_subject_public_key(
+    session: &Session,
+    subject_public_key: FieldValue,
+) -> Result<PublicKey> {
+    let subject_public_key = subject_public_key.into_bytes().context("Subject public")?;
+    let subject_public_key = value_from_bytes(&subject_public_key).context("decode CBOR")?;
+    let subject_public_key = cose_key_from_cbor_value(session, subject_public_key)
+        .context("parsing subject public key")?;
+    PublicKey::from_cose_key(&subject_public_key)
+        .context("parsing subject public key from COSE_key")
+}
+
+fn validate_mode(session: &Session, mode: FieldValue) -> Result<Option<DiceMode>> {
+    Ok(if !mode.is_bytes() && session.options.dice_chain_mode_type == ModeType::IntOrBytes {
+        mode.into_optional_i64()?
+    } else {
+        mode.into_optional_bytes()?
+            .map(|mode| {
+                if mode.len() != 1 {
+                    bail!("Expected mode to be a single byte, actual byte count: {}", mode.len())
+                };
+                Ok(mode[0].into())
+            })
+            .transpose()?
+    }
+    .map(|mode| match mode {
+        1 => DiceMode::Normal,
+        2 => DiceMode::Debug,
+        3 => DiceMode::Recovery,
+        _ => DiceMode::NotConfigured,
+    }))
+}
+
+fn validate_config_desc(
+    session: &Session,
+    config_desc: FieldValue,
+    config_format: ConfigFormat,
+) -> Result<Option<ConfigDesc>> {
+    let config_desc = config_desc.into_optional_bytes()?;
+    config_desc
+        .map(|config_desc| {
+            let config =
+                config_desc_from_slice(session, &config_desc).context("parsing config descriptor");
+            if config.is_err() && config_format == ConfigFormat::Permissive {
+                Ok(ConfigDesc::default())
+            } else {
+                config
+            }
+        })
+        .transpose()
+}
+
+fn cbor_map_from_slice(bytes: &[u8]) -> Result<Vec<(Value, Value)>> {
+    let value = value_from_bytes(bytes).context("Error parsing CBOR into a map")?;
+    let entries = match value {
+        Value::Map(entries) => entries,
+        _ => bail!("Not a map: {:?}", value),
+    };
+    Ok(entries)
+}
+
+fn config_desc_from_slice(session: &Session, bytes: &[u8]) -> Result<ConfigDesc> {
+    let entries = cbor_map_from_slice(bytes)?;
+
+    let mut component_name = FieldValue::new("component name");
+    let mut component_version = FieldValue::new("component version");
+    let mut resettable = FieldValue::new("resettable");
+    let mut extensions = HashMap::new();
+
+    for (key, value) in entries.into_iter() {
+        if let Some(Ok(key)) = key.as_integer().map(TryInto::try_into) {
+            match key {
+                COMPONENT_NAME => {
+                    component_name.set(value).context("Error setting component name")?
+                }
+                COMPONENT_VERSION => {
+                    component_version.set(value).context("Error setting component version")?
+                }
+                RESETTABLE => resettable.set(value).context("Error setting resettable")?,
+                key if (CONFIG_DESC_RESERVED_MIN..=CONFIG_DESC_RESERVED_MAX).contains(&key) => {
+                    bail!("Reserved key {}", key);
+                }
+                _ => match extensions.entry(key) {
+                    Vacant(entry) => {
+                        entry.insert(value);
+                    }
+                    Occupied(entry) => {
+                        bail!("Duplicate values for {}: {:?} and {:?}", key, entry.get(), value)
+                    }
+                },
+            };
+        } else {
+            bail!("Invalid key: {:?}", key);
+        }
+    }
+
+    Ok(ConfigDescBuilder::new()
+        .component_name(component_name.into_optional_string().context("Component name")?)
+        .component_version(
+            validate_version(session, component_version).context("Component version")?,
+        )
+        .resettable(resettable.is_null().context("Resettable")?)
+        .build())
+}
+
+fn validate_version(session: &Session, field: FieldValue) -> Result<Option<ComponentVersion>> {
+    Ok(
+        if !field.is_integer()
+            && session.options.dice_chain_component_version_type
+                == ComponentVersionType::IntOrString
+        {
+            field.into_optional_string()?.map(ComponentVersion::String)
+        } else {
+            field.into_optional_i64()?.map(ComponentVersion::Integer)
+        },
+    )
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::cbor::serialize;
+    use crate::publickey::testkeys::{PrivateKey, ED25519_KEY_PEM};
+    use crate::session::{KeyOpsType, Options};
+    use ciborium::cbor;
+    use coset::iana::{self, EnumI64};
+    use coset::CborSerializable;
+    use std::collections::HashMap;
+
+    impl Entry {
+        pub(in super::super) fn from_payload(payload: &Payload) -> Result<Self> {
+            Ok(Self { payload: serialize(payload.to_cbor_value()?) })
+        }
+
+        pub(in super::super) fn sign(self, key: &PrivateKey) -> CoseSign1 {
+            key.sign_cose_sign1(self.payload)
+        }
+    }
+
+    impl Payload {
+        pub(in super::super) fn to_cbor_value(&self) -> Result<Value> {
+            let subject_public_key =
+                self.subject_public_key().to_cose_key()?.to_vec().map_err(cose_error)?;
+            let config_desc = serialize(encode_config_desc(self.config_desc()));
+            let mut map = vec![
+                (Value::from(ISS), Value::from(self.issuer())),
+                (Value::from(SUB), Value::from(self.subject())),
+                (Value::from(SUBJECT_PUBLIC_KEY), Value::from(subject_public_key)),
+                (Value::from(MODE), encode_mode(self.mode())),
+                (Value::from(CODE_HASH), Value::from(self.code_hash())),
+                (Value::from(CONFIG_DESC), Value::from(config_desc)),
+                (Value::from(AUTHORITY_HASH), Value::from(self.authority_hash())),
+                (Value::from(KEY_USAGE), Value::from(vec![0x20])),
+            ];
+            if let Some(code_desc) = self.code_desc() {
+                map.push((Value::from(CODE_DESC), Value::from(code_desc)));
+            }
+            if let Some(config_hash) = self.config_hash() {
+                map.push((Value::from(CONFIG_HASH), Value::from(config_hash)));
+            }
+            if let Some(authority_desc) = self.authority_desc() {
+                map.push((Value::from(AUTHORITY_DESC), Value::from(authority_desc)));
+            }
+            Ok(Value::Map(map))
+        }
+    }
+
+    fn encode_mode(mode: DiceMode) -> Value {
+        let mode = match mode {
+            DiceMode::NotConfigured => 0,
+            DiceMode::Normal => 1,
+            DiceMode::Debug => 2,
+            DiceMode::Recovery => 3,
+        };
+        Value::Bytes(vec![mode])
+    }
+
+    fn encode_config_desc(config_desc: &ConfigDesc) -> Value {
+        let mut map = Vec::new();
+        if let Some(component_name) = config_desc.component_name() {
+            map.push((Value::from(COMPONENT_NAME), Value::from(component_name)));
+        }
+        if let Some(component_version) = config_desc.component_version() {
+            map.push((
+                Value::from(COMPONENT_VERSION),
+                match component_version {
+                    ComponentVersion::Integer(n) => Value::from(*n),
+                    ComponentVersion::String(s) => Value::from(s.as_str()),
+                },
+            ))
+        }
+        if config_desc.resettable() {
+            map.push((Value::from(RESETTABLE), Value::Null));
+        }
+        Value::Map(map)
+    }
+
+    #[test]
+    fn valid_payload() {
+        let fields = valid_payload_fields();
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap();
+    }
+
+    #[test]
+    fn key_usage_only_key_cert_sign() {
+        let mut fields = valid_payload_fields();
+        fields.insert(KEY_USAGE, Value::Bytes(vec![0x20]));
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap();
+    }
+
+    #[test]
+    fn key_usage_too_long() {
+        let mut fields = valid_payload_fields();
+        fields.insert(KEY_USAGE, Value::Bytes(vec![0x20, 0x30, 0x40]));
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap_err();
+    }
+
+    #[test]
+    fn key_usage_lacks_key_cert_sign() {
+        let mut fields = valid_payload_fields();
+        fields.insert(KEY_USAGE, Value::Bytes(vec![0x10]));
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap_err();
+    }
+
+    #[test]
+    fn key_usage_not_just_key_cert_sign() {
+        let mut fields = valid_payload_fields();
+        fields.insert(KEY_USAGE, Value::Bytes(vec![0x21]));
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap_err();
+    }
+
+    #[test]
+    fn mode_not_configured() {
+        let mut fields = valid_payload_fields();
+        fields.insert(MODE, Value::Bytes(vec![0]));
+        let session = Session { options: Options::default() };
+        let payload =
+            Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap();
+        assert_eq!(payload.mode(), DiceMode::NotConfigured);
+    }
+
+    #[test]
+    fn mode_normal() {
+        let mut fields = valid_payload_fields();
+        fields.insert(MODE, Value::Bytes(vec![1]));
+        let session = Session { options: Options::default() };
+        let payload =
+            Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap();
+        assert_eq!(payload.mode(), DiceMode::Normal);
+    }
+
+    #[test]
+    fn mode_debug() {
+        let mut fields = valid_payload_fields();
+        fields.insert(MODE, Value::Bytes(vec![2]));
+        let session = Session { options: Options::default() };
+        let payload =
+            Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap();
+        assert_eq!(payload.mode(), DiceMode::Debug);
+    }
+
+    #[test]
+    fn mode_recovery() {
+        let mut fields = valid_payload_fields();
+        fields.insert(MODE, Value::Bytes(vec![3]));
+        let session = Session { options: Options::default() };
+        let payload =
+            Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap();
+        assert_eq!(payload.mode(), DiceMode::Recovery);
+    }
+
+    #[test]
+    fn mode_invalid_becomes_not_configured() {
+        let mut fields = valid_payload_fields();
+        fields.insert(MODE, Value::Bytes(vec![4]));
+        let session = Session { options: Options::default() };
+        let payload =
+            Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap();
+        assert_eq!(payload.mode(), DiceMode::NotConfigured);
+    }
+
+    #[test]
+    fn mode_multiple_bytes() {
+        let mut fields = valid_payload_fields();
+        fields.insert(MODE, Value::Bytes(vec![0, 1]));
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap_err();
+    }
+
+    #[test]
+    fn mode_int_debug() {
+        let mut fields = valid_payload_fields();
+        fields.insert(MODE, Value::from(2));
+        let cbor = serialize_fields(fields);
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+        let session = Session {
+            options: Options { dice_chain_mode_type: ModeType::IntOrBytes, ..Options::default() },
+        };
+        let payload = Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap();
+        assert_eq!(payload.mode(), DiceMode::Debug);
+    }
+
+    #[test]
+    fn subject_public_key_garbage() {
+        let mut fields = valid_payload_fields();
+        fields.insert(SUBJECT_PUBLIC_KEY, Value::Bytes(vec![17; 64]));
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap_err();
+    }
+
+    #[test]
+    fn key_usage_little_endian() {
+        let mut fields = valid_payload_fields();
+        fields.insert(KEY_USAGE, Value::Bytes(vec![0x20, 0x00, 0x00]));
+        let cbor = serialize_fields(fields);
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap();
+    }
+
+    #[test]
+    fn key_usage_little_endian_invalid() {
+        let mut fields = valid_payload_fields();
+        fields.insert(KEY_USAGE, Value::Bytes(vec![0x20, 0xbe, 0xef]));
+        let cbor = serialize_fields(fields);
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+    }
+
+    #[test]
+    fn key_usage_big_endian() {
+        let mut fields = valid_payload_fields();
+        fields.insert(KEY_USAGE, Value::Bytes(vec![0x00, 0x20]));
+        let cbor = serialize_fields(fields);
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+        let session = Session {
+            options: Options { dice_chain_allow_big_endian_key_usage: true, ..Options::default() },
+        };
+        Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap();
+    }
+
+    #[test]
+    fn key_usage_big_endian_invalid() {
+        let mut fields = valid_payload_fields();
+        fields.insert(KEY_USAGE, Value::Bytes(vec![0x00, 0xfe, 0x20]));
+        let cbor = serialize_fields(fields);
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+        let session = Session {
+            options: Options { dice_chain_allow_big_endian_key_usage: true, ..Options::default() },
+        };
+        Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+    }
+
+    #[test]
+    fn key_usage_invalid() {
+        let mut fields = valid_payload_fields();
+        fields.insert(KEY_USAGE, Value::Bytes(vec![0x00, 0x10]));
+        let cbor = serialize_fields(fields);
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+        let session = Session {
+            options: Options { dice_chain_allow_big_endian_key_usage: true, ..Options::default() },
+        };
+        Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+    }
+
+    #[test]
+    fn key_usage_empty() {
+        let mut fields = valid_payload_fields();
+        fields.insert(KEY_USAGE, Value::Bytes(vec![]));
+        let cbor = serialize_fields(fields);
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+        let session = Session {
+            options: Options { dice_chain_allow_big_endian_key_usage: true, ..Options::default() },
+        };
+        Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+    }
+
+    #[test]
+    fn config_desc_custom_field_above() {
+        let mut fields = valid_payload_fields();
+        let config_desc = serialize(cbor!({-69999 => "custom"}).unwrap());
+        fields.insert(CONFIG_DESC, Value::Bytes(config_desc));
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap();
+    }
+
+    #[test]
+    fn config_desc_reserved_field_max() {
+        let mut fields = valid_payload_fields();
+        let config_desc = serialize(cbor!({-70000 => "reserved"}).unwrap());
+        fields.insert(CONFIG_DESC, Value::Bytes(config_desc));
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap_err();
+    }
+
+    #[test]
+    fn config_desc_reserved_field_min() {
+        let mut fields = valid_payload_fields();
+        let config_desc = serialize(cbor!({-70999 => "reserved"}).unwrap());
+        fields.insert(CONFIG_DESC, Value::Bytes(config_desc));
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap_err();
+    }
+
+    #[test]
+    fn config_desc_custom_field_below() {
+        let mut fields = valid_payload_fields();
+        let config_desc = serialize(cbor!({-71000 => "custom"}).unwrap());
+        fields.insert(CONFIG_DESC, Value::Bytes(config_desc));
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &serialize_fields(fields), ConfigFormat::Android).unwrap();
+    }
+
+    #[test]
+    fn config_desc_not_android_spec() {
+        let mut fields = valid_payload_fields();
+        fields.insert(CONFIG_DESC, Value::Bytes(vec![0xcd; 64]));
+        let cbor = serialize_fields(fields);
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+        let payload = Payload::from_cbor(&session, &cbor, ConfigFormat::Permissive).unwrap();
+        assert_eq!(payload.config_desc(), &ConfigDesc::default());
+    }
+
+    #[test]
+    fn config_desc_component_version_string() {
+        let mut fields = valid_payload_fields();
+        let config_desc = serialize(cbor!({COMPONENT_VERSION => "It's version 4"}).unwrap());
+        fields.insert(CONFIG_DESC, Value::Bytes(config_desc));
+        let cbor = serialize_fields(fields);
+        let session = Session {
+            options: Options {
+                dice_chain_component_version_type: ComponentVersionType::Int,
+                ..Options::default()
+            },
+        };
+        Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+        let session = Session { options: Options::default() };
+        let payload = Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap();
+        assert_eq!(
+            payload.config_desc().component_version(),
+            Some(&ComponentVersion::String("It's version 4".to_string()))
+        );
+    }
+
+    #[test]
+    fn integer_key_ops() {
+        let mut fields = valid_payload_fields();
+        let subject_public_key = cbor!({
+            iana::KeyParameter::Kty.to_i64() => iana::KeyType::OKP.to_i64(),
+            iana::KeyParameter::Alg.to_i64() => iana::Algorithm::EdDSA.to_i64(),
+            iana::KeyParameter::KeyOps.to_i64() => iana::KeyOperation::Verify.to_i64(),
+            iana::OkpKeyParameter::Crv.to_i64() => iana::EllipticCurve::Ed25519.to_i64(),
+            iana::OkpKeyParameter::X.to_i64() => Value::Bytes(vec![0; 32]),
+        })
+        .unwrap();
+        fields.insert(SUBJECT_PUBLIC_KEY, Value::Bytes(serialize(subject_public_key)));
+        let cbor = serialize_fields(fields);
+        let session = Session { options: Options::default() };
+        Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap_err();
+        let session = Session {
+            options: Options {
+                dice_chain_key_ops_type: KeyOpsType::IntOrArray,
+                ..Options::default()
+            },
+        };
+        Payload::from_cbor(&session, &cbor, ConfigFormat::Android).unwrap();
+    }
+
+    fn valid_payload_fields() -> HashMap<i64, Value> {
+        let key = PrivateKey::from_pem(ED25519_KEY_PEM[0]).public_key();
+        let subject_public_key = key.to_cose_key().unwrap().to_vec().unwrap();
+        let config_desc = serialize(cbor!({COMPONENT_NAME => "component name"}).unwrap());
+        HashMap::from([
+            (ISS, Value::from("issuer")),
+            (SUB, Value::from("subject")),
+            (SUBJECT_PUBLIC_KEY, Value::Bytes(subject_public_key)),
+            (KEY_USAGE, Value::Bytes(vec![0x20])),
+            (CODE_HASH, Value::Bytes(vec![1; 64])),
+            (CONFIG_DESC, Value::Bytes(config_desc)),
+            (AUTHORITY_HASH, Value::Bytes(vec![2; 64])),
+            (MODE, Value::Bytes(vec![0])),
+        ])
+    }
+
+    fn serialize_fields(mut fields: HashMap<i64, Value>) -> Vec<u8> {
+        let value = Value::Map(fields.drain().map(|(k, v)| (Value::from(k), v)).collect());
+        serialize(value)
+    }
+}
diff --git a/remote_provisioning/hwtrust/src/cbor/dice/field_value.rs b/remote_provisioning/hwtrust/src/cbor/dice/field_value.rs
new file mode 100644
index 0000000..f3ba8d8
--- /dev/null
+++ b/remote_provisioning/hwtrust/src/cbor/dice/field_value.rs
@@ -0,0 +1,93 @@
+//! This module defines a helper for parsing fields in a CBOR map.
+
+use anyhow::{anyhow, bail, Result};
+use coset::cbor::value::Value;
+
+pub(super) struct FieldValue {
+    name: &'static str,
+    value: Option<Value>,
+}
+
+impl FieldValue {
+    pub fn new(name: &'static str) -> Self {
+        Self { name, value: None }
+    }
+
+    pub fn set(&mut self, value: Value) -> Result<()> {
+        if let Some(existing) = &self.value {
+            bail!("Duplicate values for {}: {:?} and {:?}", self.name, existing, value);
+        } else {
+            self.value = Some(value);
+            Ok(())
+        }
+    }
+
+    pub fn is_bytes(&self) -> bool {
+        self.value.as_ref().map_or(false, |v| v.is_bytes())
+    }
+
+    pub fn into_optional_bytes(self) -> Result<Option<Vec<u8>>> {
+        self.value
+            .map(|v| {
+                if let Value::Bytes(b) = v {
+                    Ok(b)
+                } else {
+                    bail!("{}: expected bytes, got {:?}", self.name, v)
+                }
+            })
+            .transpose()
+    }
+
+    pub fn into_bytes(self) -> Result<Vec<u8>> {
+        require_present(self.name, self.into_optional_bytes())
+    }
+
+    pub fn into_optional_string(self) -> Result<Option<String>> {
+        self.value
+            .map(|v| {
+                if let Value::Text(s) = v {
+                    Ok(s)
+                } else {
+                    bail!("{}: expected text, got {:?}", self.name, v)
+                }
+            })
+            .transpose()
+    }
+
+    pub fn into_string(self) -> Result<String> {
+        require_present(self.name, self.into_optional_string())
+    }
+
+    pub fn is_null(&self) -> Result<bool> {
+        // If there's no value, return false; if there is a null value, return true; anything else
+        // is an error.
+        self.value
+            .as_ref()
+            .map(|v| {
+                if *v == Value::Null {
+                    Ok(true)
+                } else {
+                    bail!("{}: expected null, got {:?}", self.name, v)
+                }
+            })
+            .unwrap_or(Ok(false))
+    }
+
+    pub fn is_integer(&self) -> bool {
+        self.value.as_ref().map_or(false, |v| v.is_integer())
+    }
+
+    pub fn into_optional_i64(self) -> Result<Option<i64>> {
+        self.value
+            .map(|v| {
+                let value =
+                    if let Value::Integer(i) = v { i128::from(i).try_into().ok() } else { None };
+                value.ok_or_else(|| anyhow!("{}: expected integer, got {:?}", self.name, v))
+            })
+            .transpose()
+    }
+}
+
+fn require_present<T>(name: &'static str, value: Result<Option<T>>) -> Result<T> {
+    value.and_then(|opt| opt.ok_or_else(|| anyhow!("{} must be present", name)))
+}
diff --git a/remote_provisioning/hwtrust/src/cbor/publickey.rs b/remote_provisioning/hwtrust/src/cbor/publickey.rs
new file mode 100644
index 0000000..709daff
--- /dev/null
+++ b/remote_provisioning/hwtrust/src/cbor/publickey.rs
@@ -0,0 +1,329 @@
+//! CBOR encoding and decoding of a [`PublicKey`].
+
+use crate::publickey::{EcKind, Kind, PublicKey};
+use anyhow::{anyhow, bail, ensure, Context, Result};
+use coset::cbor::value::Value;
+use coset::iana::{self, EnumI64};
+use coset::{Algorithm, CoseKey, CoseKeyBuilder, CoseSign1, KeyOperation, KeyType, Label};
+use openssl::bn::{BigNum, BigNumContext};
+use openssl::ec::{EcGroup, EcKey};
+use openssl::ecdsa::EcdsaSig;
+use openssl::nid::Nid;
+use openssl::pkey::{Id, PKey, Public};
+
+impl PublicKey {
+    pub(super) fn from_cose_key(cose_key: &CoseKey) -> Result<Self> {
+        if !cose_key.key_ops.is_empty() {
+            ensure!(cose_key.key_ops.contains(&KeyOperation::Assigned(iana::KeyOperation::Verify)));
+        }
+        let pkey = match cose_key.kty {
+            KeyType::Assigned(iana::KeyType::OKP) => pkey_from_okp_key(cose_key)?,
+            KeyType::Assigned(iana::KeyType::EC2) => pkey_from_ec2_key(cose_key)?,
+            _ => bail!("Unexpected KeyType value: {:?}", cose_key.kty),
+        };
+        pkey.try_into().context("Making PublicKey from PKey")
+    }
+
+    /// Verifies a COSE_Sign1 signature over its message. This function handles the conversion of
+    /// the signature format that is needed for some algorithms.
+    pub(in crate::cbor) fn verify_cose_sign1(&self, sign1: &CoseSign1) -> Result<()> {
+        ensure!(sign1.protected.header.crit.is_empty(), "No critical headers allowed");
+        ensure!(
+            sign1.protected.header.alg == Some(Algorithm::Assigned(iana_algorithm(self.kind()))),
+            "Algorithm mistmatch in protected header"
+        );
+        sign1.verify_signature(b"", |signature, message| match self.kind() {
+            Kind::Ec(k) => {
+                let der = ec_cose_signature_to_der(k, signature).context("Signature to DER")?;
+                self.verify(&der, message)
+            }
+            _ => self.verify(signature, message),
+        })
+    }
+
+    /// Convert the public key into a [`CoseKey`].
+    pub fn to_cose_key(&self) -> Result<CoseKey> {
+        let builder = match self.kind() {
+            Kind::Ed25519 => {
+                let label_crv = iana::OkpKeyParameter::Crv.to_i64();
+                let label_x = iana::OkpKeyParameter::X.to_i64();
+                let x = self.pkey().raw_public_key().context("Get ed25519 raw public key")?;
+                CoseKeyBuilder::new_okp_key()
+                    .param(label_crv, Value::from(iana::EllipticCurve::Ed25519.to_i64()))
+                    .param(label_x, Value::from(x))
+            }
+            Kind::Ec(ec) => {
+                let key = self.pkey().ec_key().unwrap();
+                let group = key.group();
+                let mut ctx = BigNumContext::new().context("Failed to create bignum context")?;
+                let mut x = BigNum::new().context("Failed to create x coord")?;
+                let mut y = BigNum::new().context("Failed to create y coord")?;
+                key.public_key()
+                    .affine_coordinates_gfp(group, &mut x, &mut y, &mut ctx)
+                    .context("Get EC coordinates")?;
+                let (crv, coord_len) = match ec {
+                    EcKind::P256 => (iana::EllipticCurve::P_256, 32),
+                    EcKind::P384 => (iana::EllipticCurve::P_384, 48),
+                };
+
+                let x = adjust_coord(x.to_vec(), coord_len);
+                let y = adjust_coord(y.to_vec(), coord_len);
+                CoseKeyBuilder::new_ec2_pub_key(crv, x, y)
+            }
+        };
+        Ok(builder
+            .algorithm(iana_algorithm(self.kind()))
+            .add_key_op(iana::KeyOperation::Verify)
+            .build())
+    }
+}
+
+fn adjust_coord(mut coordinate: Vec<u8>, length: usize) -> Vec<u8> {
+    // Use loops "just in case". However we should never see a coordinate with more than one
+    // extra leading byte. The chances of more than one trailing byte is also quite small --
+    // roughly 1/65000.
+    while coordinate.len() > length && coordinate[0] == 00 {
+        coordinate.remove(0);
+    }
+
+    while coordinate.len() < length {
+        coordinate.insert(0, 0);
+    }
+
+    coordinate
+}
+
+fn pkey_from_okp_key(cose_key: &CoseKey) -> Result<PKey<Public>> {
+    ensure!(cose_key.kty == KeyType::Assigned(iana::KeyType::OKP));
+    ensure!(cose_key.alg == Some(Algorithm::Assigned(iana::Algorithm::EdDSA)));
+    let crv = get_label_value(cose_key, Label::Int(iana::OkpKeyParameter::Crv.to_i64()))?;
+    let x = get_label_value_as_bytes(cose_key, Label::Int(iana::OkpKeyParameter::X.to_i64()))?;
+    ensure!(crv == &Value::from(iana::EllipticCurve::Ed25519.to_i64()));
+    PKey::public_key_from_raw_bytes(x, Id::ED25519).context("Failed to instantiate key")
+}
+
+fn pkey_from_ec2_key(cose_key: &CoseKey) -> Result<PKey<Public>> {
+    ensure!(cose_key.kty == KeyType::Assigned(iana::KeyType::EC2));
+    let crv = get_label_value(cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))?;
+    let x = get_label_value_as_bytes(cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
+    let y = get_label_value_as_bytes(cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
+    match cose_key.alg {
+        Some(Algorithm::Assigned(iana::Algorithm::ES256)) => {
+            ensure!(crv == &Value::from(iana::EllipticCurve::P_256.to_i64()));
+            pkey_from_ec_coords(Nid::X9_62_PRIME256V1, x, y).context("Failed to instantiate key")
+        }
+        Some(Algorithm::Assigned(iana::Algorithm::ES384)) => {
+            ensure!(crv == &Value::from(iana::EllipticCurve::P_384.to_i64()));
+            pkey_from_ec_coords(Nid::SECP384R1, x, y).context("Failed to instantiate key")
+        }
+        _ => bail!("Need to specify ES256 or ES384 in the key. Got {:?}", cose_key.alg),
+    }
+}
+
+fn pkey_from_ec_coords(nid: Nid, x: &[u8], y: &[u8]) -> Result<PKey<Public>> {
+    let group = EcGroup::from_curve_name(nid).context("Failed to construct curve group")?;
+    let x = BigNum::from_slice(x).context("Failed to create x coord")?;
+    let y = BigNum::from_slice(y).context("Failed to create y coord")?;
+    let key = EcKey::from_public_key_affine_coordinates(&group, &x, &y)
+        .context("Failed to create EC public key")?;
+    PKey::from_ec_key(key).context("Failed to create PKey")
+}
+
+/// Get the value corresponding to the provided label within the supplied CoseKey or error if it's
+/// not present.
+fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
+    Ok(&key
+        .params
+        .iter()
+        .find(|(k, _)| k == &label)
+        .ok_or_else(|| anyhow!("Label {:?} not found", label))?
+        .1)
+}
+
+/// Get the byte string for the corresponding label within the key if the label exists and the
+/// value is actually a byte array.
+fn get_label_value_as_bytes(key: &CoseKey, label: Label) -> Result<&[u8]> {
+    get_label_value(key, label)?
+        .as_bytes()
+        .ok_or_else(|| anyhow!("Value not a bstr."))
+        .map(Vec::as_slice)
+}
+
+fn ec_cose_signature_to_der(kind: EcKind, signature: &[u8]) -> Result<Vec<u8>> {
+    let coord_len = ec_coord_len(kind);
+    ensure!(signature.len() == coord_len * 2, "Unexpected signature length");
+    let r = BigNum::from_slice(&signature[..coord_len]).context("Creating BigNum for r")?;
+    let s = BigNum::from_slice(&signature[coord_len..]).context("Creating BigNum for s")?;
+    let signature = EcdsaSig::from_private_components(r, s).context("Creating ECDSA signature")?;
+    signature.to_der().context("Failed to DER encode signature")
+}
+
+fn ec_coord_len(kind: EcKind) -> usize {
+    match kind {
+        EcKind::P256 => 32,
+        EcKind::P384 => 48,
+    }
+}
+
+fn iana_algorithm(kind: Kind) -> iana::Algorithm {
+    match kind {
+        Kind::Ed25519 => iana::Algorithm::EdDSA,
+        Kind::Ec(EcKind::P256) => iana::Algorithm::ES256,
+        Kind::Ec(EcKind::P384) => iana::Algorithm::ES384,
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::publickey::testkeys::{
+        PrivateKey, ED25519_KEY_PEM, P256_KEY_PEM, P256_KEY_WITH_LEADING_ZEROS_PEM,
+        P384_KEY_WITH_LEADING_ZEROS_PEM,
+    };
+    use coset::{CoseSign1Builder, HeaderBuilder};
+
+    impl PrivateKey {
+        pub(in crate::cbor) fn sign_cose_sign1(&self, payload: Vec<u8>) -> CoseSign1 {
+            CoseSign1Builder::new()
+                .protected(HeaderBuilder::new().algorithm(iana_algorithm(self.kind())).build())
+                .payload(payload)
+                .create_signature(b"", |m| {
+                    let signature = self.sign(m).unwrap();
+                    match self.kind() {
+                        Kind::Ec(ec) => ec_der_signature_to_cose(ec, &signature),
+                        _ => signature,
+                    }
+                })
+                .build()
+        }
+    }
+
+    fn ec_der_signature_to_cose(kind: EcKind, signature: &[u8]) -> Vec<u8> {
+        let coord_len = ec_coord_len(kind).try_into().unwrap();
+        let signature = EcdsaSig::from_der(signature).unwrap();
+        let mut r = signature.r().to_vec_padded(coord_len).unwrap();
+        let mut s = signature.s().to_vec_padded(coord_len).unwrap();
+        r.append(&mut s);
+        r
+    }
+
+    #[test]
+    fn sign_and_verify_okp() {
+        let key = PrivateKey::from_pem(ED25519_KEY_PEM[0]);
+        let sign1 = key.sign_cose_sign1(b"signed payload".to_vec());
+        key.public_key().verify_cose_sign1(&sign1).unwrap();
+    }
+
+    #[test]
+    fn sign_and_verify_ec2() {
+        let key = PrivateKey::from_pem(P256_KEY_PEM[0]);
+        let sign1 = key.sign_cose_sign1(b"signed payload".to_vec());
+        key.public_key().verify_cose_sign1(&sign1).unwrap();
+    }
+
+    #[test]
+    fn verify_cose_sign1() {
+        let key = PrivateKey::from_pem(ED25519_KEY_PEM[0]);
+        let sign1 = CoseSign1Builder::new()
+            .protected(HeaderBuilder::new().algorithm(iana::Algorithm::EdDSA).build())
+            .payload(b"the message".to_vec())
+            .create_signature(b"", |m| key.sign(m).unwrap())
+            .build();
+        key.public_key().verify_cose_sign1(&sign1).unwrap();
+    }
+
+    #[test]
+    fn verify_cose_sign1_fails_with_wrong_algorithm() {
+        let key = PrivateKey::from_pem(ED25519_KEY_PEM[0]);
+        let sign1 = CoseSign1Builder::new()
+            .protected(HeaderBuilder::new().algorithm(iana::Algorithm::ES256).build())
+            .payload(b"the message".to_vec())
+            .create_signature(b"", |m| key.sign(m).unwrap())
+            .build();
+        key.public_key().verify_cose_sign1(&sign1).unwrap_err();
+    }
+
+    #[test]
+    fn verify_cose_sign1_with_non_crit_header() {
+        let key = PrivateKey::from_pem(ED25519_KEY_PEM[0]);
+        let sign1 = CoseSign1Builder::new()
+            .protected(
+                HeaderBuilder::new()
+                    .algorithm(iana::Algorithm::EdDSA)
+                    .value(1000, Value::from(2000))
+                    .build(),
+            )
+            .payload(b"the message".to_vec())
+            .create_signature(b"", |m| key.sign(m).unwrap())
+            .build();
+        key.public_key().verify_cose_sign1(&sign1).unwrap()
+    }
+
+    #[test]
+    fn verify_cose_sign1_fails_with_crit_header() {
+        let key = PrivateKey::from_pem(ED25519_KEY_PEM[0]);
+        let sign1 = CoseSign1Builder::new()
+            .protected(
+                HeaderBuilder::new()
+                    .algorithm(iana::Algorithm::EdDSA)
+                    .add_critical(iana::HeaderParameter::Alg)
+                    .build(),
+            )
+            .payload(b"the message".to_vec())
+            .create_signature(b"", |m| key.sign(m).unwrap())
+            .build();
+        key.public_key().verify_cose_sign1(&sign1).unwrap_err();
+    }
+
+    #[test]
+    fn to_and_from_okp_cose_key() {
+        let key = PrivateKey::from_pem(ED25519_KEY_PEM[0]).public_key();
+        let value = key.to_cose_key().unwrap();
+        let new_key = PublicKey::from_cose_key(&value).unwrap();
+        assert!(key.pkey().public_eq(new_key.pkey()));
+    }
+
+    #[test]
+    fn to_and_from_ec2_cose_key() {
+        let key = PrivateKey::from_pem(P256_KEY_PEM[0]).public_key();
+        let value = key.to_cose_key().unwrap();
+        let new_key = PublicKey::from_cose_key(&value).unwrap();
+        assert!(key.pkey().public_eq(new_key.pkey()));
+    }
+
+    #[test]
+    fn from_p256_pkey_with_leading_zeros() {
+        for pem in P256_KEY_WITH_LEADING_ZEROS_PEM {
+            let key = PrivateKey::from_pem(pem).public_key();
+            let cose_key = key.to_cose_key().unwrap();
+
+            let x =
+                get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))
+                    .unwrap();
+            assert_eq!(x.len(), 32, "X coordinate is the wrong size\n{}", pem);
+
+            let y =
+                get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))
+                    .unwrap();
+            assert_eq!(y.len(), 32, "Y coordinate is the wrong size\n{}", pem);
+        }
+    }
+
+    #[test]
+    fn from_p384_pkey_with_leading_zeros() {
+        for pem in P384_KEY_WITH_LEADING_ZEROS_PEM {
+            let key = PrivateKey::from_pem(pem).public_key();
+            let cose_key = key.to_cose_key().unwrap();
+
+            let x =
+                get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))
+                    .unwrap();
+            assert_eq!(x.len(), 48, "X coordinate is the wrong size\n{}", pem);
+
+            let y =
+                get_label_value_as_bytes(&cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))
+                    .unwrap();
+            assert_eq!(y.len(), 48, "Y coordinate is the wrong size\n{}", pem);
+        }
+    }
+}
diff --git a/remote_provisioning/hwtrust/src/dice.rs b/remote_provisioning/hwtrust/src/dice.rs
new file mode 100644
index 0000000..feaf984
--- /dev/null
+++ b/remote_provisioning/hwtrust/src/dice.rs
@@ -0,0 +1,8 @@
+//! This module provides functions for handling DICE chains.
+
+mod chain;
+mod entry;
+
+pub use chain::{Chain, ChainForm, DegenerateChain};
+pub use entry::{ComponentVersion, ConfigDesc, DiceMode, Payload};
+pub(crate) use entry::{ConfigDescBuilder, PayloadBuilder};
diff --git a/remote_provisioning/hwtrust/src/dice/chain.rs b/remote_provisioning/hwtrust/src/dice/chain.rs
new file mode 100644
index 0000000..0d36aee
--- /dev/null
+++ b/remote_provisioning/hwtrust/src/dice/chain.rs
@@ -0,0 +1,272 @@
+use crate::dice::Payload;
+use crate::publickey::PublicKey;
+use anyhow::Result;
+use std::collections::HashSet;
+use std::fmt::{self, Display, Formatter};
+use thiserror::Error;
+
+/// Enumeration of the different forms that a DICE chain can take.
+pub enum ChainForm {
+    /// A proper DICE chain with multiple layers of trust.
+    Proper(Chain),
+    /// A degenerate DICE chain consisting of a single self-signed certificate.
+    Degenerate(DegenerateChain),
+}
+
+/// Represents a DICE chain. This consists of the root public key (which signs the first
+/// certificate), followed by a chain of certificates.
+#[derive(Debug)]
+pub struct Chain {
+    root_public_key: PublicKey,
+    payloads: Vec<Payload>,
+}
+
+#[derive(Error, Debug, PartialEq, Eq)]
+pub(crate) enum ValidationError {
+    #[error("no payloads")]
+    NoPayloads,
+    #[error("issuer `{1}` is not previous subject `{2}` in payload {0}")]
+    IssuerMismatch(usize, String, String),
+    #[error("repeated subject in payload {0}")]
+    RepeatedSubject(usize, String),
+    #[error("repeated key in payload {0}")]
+    RepeatedKey(usize),
+}
+
+impl Chain {
+    /// Builds a [`Chain`] after checking that it is well-formed. The issuer of each entry must be
+    /// equal to the subject of the previous entry. The chain is not allowed to contain any
+    /// repeated subjects or subject public keys as that would suggest something untoward has
+    /// happened.
+    pub(crate) fn validate(
+        root_public_key: PublicKey,
+        payloads: Vec<Payload>,
+    ) -> Result<Self, ValidationError> {
+        if payloads.is_empty() {
+            return Err(ValidationError::NoPayloads);
+        }
+
+        let mut subjects = HashSet::with_capacity(payloads.len());
+        let mut keys = HashSet::with_capacity(1 + payloads.len());
+        keys.insert(root_public_key.to_pem());
+
+        let mut previous_subject: Option<&str> = None;
+        for (n, payload) in payloads.iter().enumerate() {
+            if let Some(previous_subject) = previous_subject {
+                if payload.issuer() != previous_subject {
+                    return Err(ValidationError::IssuerMismatch(
+                        n,
+                        payload.issuer().to_string(),
+                        previous_subject.to_string(),
+                    ));
+                }
+            }
+            if subjects.replace(payload.subject()).is_some() {
+                return Err(ValidationError::RepeatedSubject(n, payload.subject().to_string()));
+            }
+            if keys.replace(payload.subject_public_key().to_pem()).is_some() {
+                return Err(ValidationError::RepeatedKey(n));
+            }
+            previous_subject = Some(payload.subject());
+        }
+
+        Ok(Self { root_public_key, payloads })
+    }
+
+    /// Get the root public key which verifies the first certificate in the chain.
+    pub fn root_public_key(&self) -> &PublicKey {
+        &self.root_public_key
+    }
+
+    /// Get the payloads of the certificates in the chain, from root to leaf.
+    pub fn payloads(&self) -> &[Payload] {
+        &self.payloads
+    }
+
+    /// Get the payload from the final certificate in the chain.
+    pub fn leaf(&self) -> &Payload {
+        // There is always at least one payload.
+        self.payloads.last().unwrap()
+    }
+}
+
+impl Display for Chain {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+        writeln!(f, "Root public key:")?;
+        writeln!(f, "{}", self.root_public_key.to_pem())?;
+        for (i, payload) in self.payloads.iter().enumerate() {
+            writeln!(f, "Cert {}:", i)?;
+            writeln!(f, "{}", payload)?;
+        }
+        Ok(())
+    }
+}
+
+#[derive(Error, Debug, PartialEq, Eq)]
+pub(crate) enum DegenerateChainError {
+    #[error("issuer empty")]
+    IssuerEmpty,
+    #[error("subject empty")]
+    SubjectEmpty,
+}
+
+/// A degenerate DICE chain. These chains consist of a single, self-signed certificate and the
+/// entries contain less information than usual. They are expected from devices that haven't
+/// implemented everything necessary to produce a proper DICE Chain.
+#[derive(Debug)]
+pub struct DegenerateChain {
+    issuer: String,
+    subject: String,
+    subject_public_key: PublicKey,
+}
+
+impl DegenerateChain {
+    pub(crate) fn new<I: Into<String>, S: Into<String>>(
+        issuer: I,
+        subject: S,
+        subject_public_key: PublicKey,
+    ) -> Result<Self, DegenerateChainError> {
+        let issuer = issuer.into();
+        let subject = subject.into();
+        if issuer.is_empty() {
+            return Err(DegenerateChainError::IssuerEmpty);
+        }
+        if subject.is_empty() {
+            return Err(DegenerateChainError::SubjectEmpty);
+        }
+        Ok(Self { issuer, subject, subject_public_key })
+    }
+
+    /// Gets the issuer of the degenerate chain.
+    pub fn issuer(&self) -> &str {
+        &self.issuer
+    }
+
+    /// Gets the subject of the degenerate chain.
+    pub fn subject(&self) -> &str {
+        &self.subject
+    }
+
+    /// Gets the public key of the degenerate chain.
+    pub fn public_key(&self) -> &PublicKey {
+        &self.subject_public_key
+    }
+}
+
+impl Display for DegenerateChain {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+        writeln!(f, "Public key:")?;
+        writeln!(f, "{}", self.public_key().to_pem())?;
+        writeln!(f, "Issuer: {}", self.issuer)?;
+        writeln!(f, "Subject: {}", self.subject)?;
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::dice::{DiceMode, PayloadBuilder};
+    use crate::publickey::testkeys::{PrivateKey, ED25519_KEY_PEM, P256_KEY_PEM, P384_KEY_PEM};
+
+    #[test]
+    fn chain_validate_valid() {
+        let root_public_key = PrivateKey::from_pem(P256_KEY_PEM[0]).public_key();
+        let keys = P256_KEY_PEM[1..4].iter().copied().enumerate();
+        let payloads = keys.map(|(n, key)| valid_payload(n, key).build().unwrap()).collect();
+        Chain::validate(root_public_key, payloads).unwrap();
+    }
+
+    #[test]
+    fn chain_validate_valid_with_mixed_kinds_of_key() {
+        let root_public_key = PrivateKey::from_pem(ED25519_KEY_PEM[0]).public_key();
+        let keys = [P256_KEY_PEM[0], P384_KEY_PEM[0]].into_iter().enumerate();
+        let payloads = keys.map(|(n, key)| valid_payload(n, key).build().unwrap()).collect();
+        Chain::validate(root_public_key, payloads).unwrap();
+    }
+
+    #[test]
+    fn chain_validate_fails_without_payloads() {
+        let root_public_key = PrivateKey::from_pem(P256_KEY_PEM[0]).public_key();
+        let payloads = Vec::new();
+        let err = Chain::validate(root_public_key, payloads).unwrap_err();
+        assert_eq!(err, ValidationError::NoPayloads);
+    }
+
+    #[test]
+    fn chain_validate_fails_when_root_key_repeated() {
+        let key = P256_KEY_PEM[0];
+        let root_public_key = PrivateKey::from_pem(key).public_key();
+        let payloads = vec![valid_payload(0, key).build().unwrap()];
+        let err = Chain::validate(root_public_key, payloads).unwrap_err();
+        assert_eq!(err, ValidationError::RepeatedKey(0));
+    }
+
+    #[test]
+    fn chain_validate_fails_with_repeated_subject_public_keys() {
+        let repeated_key = P256_KEY_PEM[0];
+        let root_public_key = PrivateKey::from_pem(ED25519_KEY_PEM[0]).public_key();
+        let payloads = vec![
+            valid_payload(0, repeated_key).build().unwrap(),
+            valid_payload(1, repeated_key).build().unwrap(),
+        ];
+        let err = Chain::validate(root_public_key, payloads).unwrap_err();
+        assert_eq!(err, ValidationError::RepeatedKey(1));
+    }
+
+    #[test]
+    fn chain_validate_fails_with_repeated_subjects() {
+        let keys = &P256_KEY_PEM[..3];
+        let repeated = "match";
+        let root_public_key = PrivateKey::from_pem(ED25519_KEY_PEM[0]).public_key();
+        let payloads = vec![
+            valid_payload(0, keys[0]).subject(repeated).build().unwrap(),
+            valid_payload(1, keys[1]).issuer(repeated).build().unwrap(),
+            valid_payload(2, keys[2]).subject(repeated).build().unwrap(),
+        ];
+        let err = Chain::validate(root_public_key, payloads).unwrap_err();
+        assert_eq!(err, ValidationError::RepeatedSubject(2, repeated.into()));
+    }
+
+    #[test]
+    fn chain_validate_fails_with_mismatching_issuer_and_subject() {
+        let expected = "expected";
+        let wrong = "wrong";
+        let root_public_key = PrivateKey::from_pem(P256_KEY_PEM[0]).public_key();
+        let payloads = vec![
+            valid_payload(0, P256_KEY_PEM[1]).subject(expected).build().unwrap(),
+            valid_payload(1, P256_KEY_PEM[2]).issuer(wrong).build().unwrap(),
+        ];
+        let err = Chain::validate(root_public_key, payloads).unwrap_err();
+        assert_eq!(err, ValidationError::IssuerMismatch(1, wrong.into(), expected.into()));
+    }
+
+    fn valid_payload(index: usize, pem: &str) -> PayloadBuilder {
+        PayloadBuilder::with_subject_public_key(PrivateKey::from_pem(pem).public_key())
+            .issuer(format!("component {}", index))
+            .subject(format!("component {}", index + 1))
+            .mode(DiceMode::Normal)
+            .code_hash(vec![0; 64])
+            .authority_hash(vec![0; 64])
+    }
+
+    #[test]
+    fn degenerate_chain_valid() {
+        let key = PrivateKey::from_pem(ED25519_KEY_PEM[0]).public_key();
+        DegenerateChain::new("issuer", "subject", key).unwrap();
+    }
+
+    #[test]
+    fn degenerate_chain_empty_issuer() {
+        let key = PrivateKey::from_pem(ED25519_KEY_PEM[0]).public_key();
+        let err = DegenerateChain::new("", "subject", key).unwrap_err();
+        assert_eq!(err, DegenerateChainError::IssuerEmpty);
+    }
+
+    #[test]
+    fn degenerate_chain_empty_subject() {
+        let key = PrivateKey::from_pem(ED25519_KEY_PEM[0]).public_key();
+        let err = DegenerateChain::new("issuer", "", key).unwrap_err();
+        assert_eq!(err, DegenerateChainError::SubjectEmpty);
+    }
+}
diff --git a/remote_provisioning/hwtrust/src/dice/entry.rs b/remote_provisioning/hwtrust/src/dice/entry.rs
new file mode 100644
index 0000000..7596022
--- /dev/null
+++ b/remote_provisioning/hwtrust/src/dice/entry.rs
@@ -0,0 +1,418 @@
+use crate::publickey::PublicKey;
+use std::fmt::{self, Display, Formatter};
+use thiserror::Error;
+
+/// Enumeration of modes used in the DICE chain payloads.
+#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
+pub enum DiceMode {
+    /// This mode also acts as a catch-all for configurations which do not fit the other modes and
+    /// invalid modes.
+    #[default]
+    NotConfigured,
+    /// The device is operating normally under secure configuration.
+    Normal,
+    /// At least one criteria for [`Normal`] is not met and the device is not in a secure state.
+    Debug,
+    /// A recovery or maintenance mode of some kind.
+    Recovery,
+}
+
+/// The payload of a DICE chain entry.
+#[derive(Debug)]
+pub struct Payload {
+    issuer: String,
+    subject: String,
+    subject_public_key: PublicKey,
+    mode: DiceMode,
+    code_desc: Option<Vec<u8>>,
+    code_hash: Vec<u8>,
+    config_desc: ConfigDesc,
+    config_hash: Option<Vec<u8>>,
+    authority_desc: Option<Vec<u8>>,
+    authority_hash: Vec<u8>,
+}
+
+impl Payload {
+    /// Gets the issuer of the payload.
+    pub fn issuer(&self) -> &str {
+        &self.issuer
+    }
+
+    /// Gets the subject of the payload.
+    pub fn subject(&self) -> &str {
+        &self.subject
+    }
+
+    /// Gets the subject public key of the payload.
+    pub fn subject_public_key(&self) -> &PublicKey {
+        &self.subject_public_key
+    }
+
+    /// Gets the mode of the payload.
+    pub fn mode(&self) -> DiceMode {
+        self.mode
+    }
+
+    /// Gets the code descriptor of the payload.
+    pub fn code_desc(&self) -> Option<&[u8]> {
+        self.code_desc.as_deref()
+    }
+
+    /// Gets the code hash of the payload.
+    pub fn code_hash(&self) -> &[u8] {
+        &self.code_hash
+    }
+
+    /// Gets the configuration descriptor of the payload.
+    pub fn config_desc(&self) -> &ConfigDesc {
+        &self.config_desc
+    }
+
+    /// Gets the configuration hash of the payload.
+    pub fn config_hash(&self) -> Option<&[u8]> {
+        self.config_hash.as_deref()
+    }
+
+    /// Gets the authority descriptor of the payload.
+    pub fn authority_desc(&self) -> Option<&[u8]> {
+        self.authority_desc.as_deref()
+    }
+
+    /// Gets the authority hash of the payload.
+    pub fn authority_hash(&self) -> &[u8] {
+        &self.authority_hash
+    }
+}
+
+impl Display for Payload {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+        writeln!(f, "Issuer: {}", self.issuer)?;
+        writeln!(f, "Subject: {}", self.subject)?;
+        writeln!(f, "Mode: {:?}", self.mode)?;
+        if let Some(code_desc) = &self.code_desc {
+            writeln!(f, "Code Desc: {}", hex::encode(code_desc))?;
+        }
+        writeln!(f, "Code Hash: {}", hex::encode(&self.code_hash))?;
+        if let Some(config_hash) = &self.config_hash {
+            writeln!(f, "Config Hash: {}", hex::encode(config_hash))?;
+        }
+        if let Some(authority_desc) = &self.authority_desc {
+            writeln!(f, "Authority Desc: {}", hex::encode(authority_desc))?;
+        }
+        writeln!(f, "Authority Hash: {}", hex::encode(&self.authority_hash))?;
+        writeln!(f, "Config Desc:")?;
+        write!(f, "{}", &self.config_desc)?;
+        Ok(())
+    }
+}
+
+#[derive(Error, Debug, PartialEq, Eq)]
+pub(crate) enum PayloadBuilderError {
+    #[error("issuer empty")]
+    IssuerEmpty,
+    #[error("subject empty")]
+    SubjectEmpty,
+    #[error("bad code hash size")]
+    CodeHashSize,
+    #[error("bad config hash size")]
+    ConfigHashSize,
+    #[error("bad authority hash size")]
+    AuthorityHashSize,
+}
+
+pub(crate) struct PayloadBuilder(Payload);
+
+impl PayloadBuilder {
+    /// Constructs a new builder with the given subject public key.
+    pub fn with_subject_public_key(subject_public_key: PublicKey) -> Self {
+        Self(Payload {
+            issuer: Default::default(),
+            subject: Default::default(),
+            subject_public_key,
+            mode: Default::default(),
+            code_desc: Default::default(),
+            code_hash: Default::default(),
+            config_desc: Default::default(),
+            config_hash: Default::default(),
+            authority_desc: Default::default(),
+            authority_hash: Default::default(),
+        })
+    }
+
+    /// Builds the [`Payload`] after validating the fields.
+    pub fn build(self) -> Result<Payload, PayloadBuilderError> {
+        if self.0.issuer.is_empty() {
+            return Err(PayloadBuilderError::IssuerEmpty);
+        }
+        if self.0.subject.is_empty() {
+            return Err(PayloadBuilderError::SubjectEmpty);
+        }
+        let used_hash_size = self.0.code_hash.len();
+        if ![32, 48, 64].contains(&used_hash_size) {
+            return Err(PayloadBuilderError::CodeHashSize);
+        }
+        if let Some(ref config_hash) = self.0.config_hash {
+            if config_hash.len() != used_hash_size {
+                return Err(PayloadBuilderError::ConfigHashSize);
+            }
+        }
+        if self.0.authority_hash.len() != used_hash_size {
+            return Err(PayloadBuilderError::AuthorityHashSize);
+        }
+        Ok(self.0)
+    }
+
+    /// Sets the issuer of the payload.
+    #[must_use]
+    pub fn issuer<S: Into<String>>(mut self, issuer: S) -> Self {
+        self.0.issuer = issuer.into();
+        self
+    }
+
+    /// Sets the subject of the payload.
+    #[must_use]
+    pub fn subject<S: Into<String>>(mut self, subject: S) -> Self {
+        self.0.subject = subject.into();
+        self
+    }
+
+    /// Sets the mode of the payload.
+    #[must_use]
+    pub fn mode(mut self, mode: DiceMode) -> Self {
+        self.0.mode = mode;
+        self
+    }
+
+    /// Sets the code descriptor of the payload.
+    #[must_use]
+    pub fn code_desc(mut self, code_desc: Option<Vec<u8>>) -> Self {
+        self.0.code_desc = code_desc;
+        self
+    }
+
+    /// Sets the code hash of the payload.
+    #[must_use]
+    pub fn code_hash(mut self, code_hash: Vec<u8>) -> Self {
+        self.0.code_hash = code_hash;
+        self
+    }
+
+    /// Sets the configuration descriptor of the payload.
+    #[must_use]
+    pub fn config_desc(mut self, config_desc: ConfigDesc) -> Self {
+        self.0.config_desc = config_desc;
+        self
+    }
+
+    /// Sets the configuration hash of the payload.
+    #[must_use]
+    pub fn config_hash(mut self, config_hash: Option<Vec<u8>>) -> Self {
+        self.0.config_hash = config_hash;
+        self
+    }
+
+    /// Sets the authority descriptor of the payload.
+    #[must_use]
+    pub fn authority_desc(mut self, authority_desc: Option<Vec<u8>>) -> Self {
+        self.0.authority_desc = authority_desc;
+        self
+    }
+
+    /// Sets the authority hash of the payload.
+    #[must_use]
+    pub fn authority_hash(mut self, authority_hash: Vec<u8>) -> Self {
+        self.0.authority_hash = authority_hash;
+        self
+    }
+}
+
+/// Version of the component from the configuration descriptor.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ComponentVersion {
+    /// An integer component version number.
+    Integer(i64),
+    /// A free-form string component version.
+    String(String),
+}
+
+impl Display for ComponentVersion {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+        match self {
+            ComponentVersion::Integer(n) => write!(f, "{n}")?,
+            ComponentVersion::String(s) => write!(f, "{s}")?,
+        }
+        Ok(())
+    }
+}
+
+/// Fields from the configuration descriptor.
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub struct ConfigDesc {
+    component_name: Option<String>,
+    component_version: Option<ComponentVersion>,
+    resettable: bool,
+}
+
+impl ConfigDesc {
+    /// Gets the component name.
+    pub fn component_name(&self) -> Option<&str> {
+        self.component_name.as_deref()
+    }
+
+    /// Gets the component version.
+    pub fn component_version(&self) -> Option<&ComponentVersion> {
+        self.component_version.as_ref()
+    }
+
+    /// Returns whether the component is factory resettable.
+    pub fn resettable(&self) -> bool {
+        self.resettable
+    }
+}
+
+impl Display for ConfigDesc {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+        if let Some(component_name) = &self.component_name {
+            writeln!(f, "Component Name: {}", component_name)?;
+        }
+        if let Some(component_version) = &self.component_version {
+            writeln!(f, "Component Version: {}", component_version)?;
+        }
+        if self.resettable {
+            writeln!(f, "Resettable")?;
+        }
+        Ok(())
+    }
+}
+
+pub(crate) struct ConfigDescBuilder(ConfigDesc);
+
+impl ConfigDescBuilder {
+    /// Constructs a new builder with default values.
+    pub fn new() -> Self {
+        Self(ConfigDesc::default())
+    }
+
+    /// Builds the [`ConfigDesc`].
+    pub fn build(self) -> ConfigDesc {
+        self.0
+    }
+
+    /// Sets the component name.
+    #[must_use]
+    pub fn component_name(mut self, name: Option<String>) -> Self {
+        self.0.component_name = name;
+        self
+    }
+
+    /// Sets the component version.
+    #[must_use]
+    pub fn component_version(mut self, version: Option<ComponentVersion>) -> Self {
+        self.0.component_version = version;
+        self
+    }
+
+    /// Sets whether the component is factory resettable.
+    #[must_use]
+    pub fn resettable(mut self, resettable: bool) -> Self {
+        self.0.resettable = resettable;
+        self
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::publickey::testkeys::{PrivateKey, P256_KEY_PEM};
+
+    #[test]
+    fn payload_builder_valid() {
+        valid_payload().build().unwrap();
+    }
+
+    #[test]
+    fn payload_builder_valid_512_bit_hashes() {
+        valid_payload()
+            .code_hash(vec![1; 64])
+            .authority_hash(vec![2; 64])
+            .config_hash(Some(vec![3; 64]))
+            .build()
+            .unwrap();
+    }
+
+    #[test]
+    fn payload_builder_valid_384_bit_hashes() {
+        valid_payload()
+            .code_hash(vec![1; 48])
+            .authority_hash(vec![2; 48])
+            .config_hash(Some(vec![3; 48]))
+            .build()
+            .unwrap();
+    }
+
+    #[test]
+    fn payload_builder_valid_256_bit_hashes() {
+        valid_payload()
+            .code_hash(vec![1; 32])
+            .authority_hash(vec![2; 32])
+            .config_hash(Some(vec![3; 32]))
+            .build()
+            .unwrap();
+    }
+
+    #[test]
+    fn payload_builder_empty_issuer() {
+        let err = valid_payload().issuer("").build().unwrap_err();
+        assert_eq!(err, PayloadBuilderError::IssuerEmpty);
+    }
+
+    #[test]
+    fn payload_builder_empty_subject() {
+        let err = valid_payload().subject("").build().unwrap_err();
+        assert_eq!(err, PayloadBuilderError::SubjectEmpty);
+    }
+
+    #[test]
+    fn payload_builder_bad_code_hash_size() {
+        let err = valid_payload().code_hash(vec![1; 16]).build().unwrap_err();
+        assert_eq!(err, PayloadBuilderError::CodeHashSize);
+    }
+
+    #[test]
+    fn payload_builder_bad_authority_hash_size() {
+        let err = valid_payload().authority_hash(vec![1; 16]).build().unwrap_err();
+        assert_eq!(err, PayloadBuilderError::AuthorityHashSize);
+    }
+
+    #[test]
+    fn payload_builder_inconsistent_authority_hash_size() {
+        let err =
+            valid_payload().code_hash(vec![1; 32]).authority_hash(vec![1; 64]).build().unwrap_err();
+        assert_eq!(err, PayloadBuilderError::AuthorityHashSize);
+    }
+
+    #[test]
+    fn payload_builder_bad_config_hash_size() {
+        let err = valid_payload().config_hash(Some(vec![1; 16])).build().unwrap_err();
+        assert_eq!(err, PayloadBuilderError::ConfigHashSize);
+    }
+
+    #[test]
+    fn payload_builder_inconsistent_config_hash_size() {
+        let err = valid_payload()
+            .code_hash(vec![1; 64])
+            .config_hash(Some(vec![1; 32]))
+            .build()
+            .unwrap_err();
+        assert_eq!(err, PayloadBuilderError::ConfigHashSize);
+    }
+
+    fn valid_payload() -> PayloadBuilder {
+        let key = PrivateKey::from_pem(P256_KEY_PEM[0]).public_key();
+        PayloadBuilder::with_subject_public_key(key)
+            .issuer("issuer")
+            .subject("subject")
+            .code_hash(vec![1; 64])
+            .authority_hash(vec![2; 64])
+    }
+}
diff --git a/remote_provisioning/hwtrust/src/lib.rs b/remote_provisioning/hwtrust/src/lib.rs
new file mode 100644
index 0000000..282ecb5
--- /dev/null
+++ b/remote_provisioning/hwtrust/src/lib.rs
@@ -0,0 +1,8 @@
+//! A library for handling data related to the hardware root-of-trust. The DICE chain is the
+//! fundamental data structure that other features and services build on top of.
+
+pub mod dice;
+pub mod publickey;
+pub mod session;
+
+mod cbor;
diff --git a/remote_provisioning/hwtrust/src/main.rs b/remote_provisioning/hwtrust/src/main.rs
new file mode 100644
index 0000000..1f50f52
--- /dev/null
+++ b/remote_provisioning/hwtrust/src/main.rs
@@ -0,0 +1,80 @@
+//! A tool for handling data related to the hardware root-of-trust.
+
+use anyhow::Result;
+use clap::{Parser, Subcommand, ValueEnum};
+use hwtrust::dice;
+use hwtrust::session::{Options, Session};
+use std::fs;
+
+#[derive(Parser)]
+/// A tool for handling data related to the hardware root-of-trust
+#[clap(name = "hwtrust")]
+struct Args {
+    #[clap(subcommand)]
+    action: Action,
+}
+
+#[derive(Subcommand)]
+enum Action {
+    VerifyDiceChain(VerifyDiceChainArgs),
+}
+
+#[derive(Parser)]
+/// Verify that a DICE chain is well-formed
+///
+/// DICE chains are expected to follow the specification of the RKP HAL [1] which is based on the
+/// Open Profile for DICE [2].
+///
+/// [1] -- https://cs.android.com/android/platform/superproject/+/master:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
+/// [2] -- https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/specification.md
+struct VerifyDiceChainArgs {
+    /// Dump the DICE chain on the standard output
+    #[clap(long)]
+    dump: bool,
+
+    /// Path to a file containing a DICE chain
+    chain: String,
+
+    /// The VSR version to validate against. If omitted, the set of rules that are used have no
+    /// compromises or workarounds and new implementations should validate against them as it will
+    /// be the basis for future VSR versions.
+    #[clap(long, value_enum)]
+    vsr: Option<VsrVersion>,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
+enum VsrVersion {
+    /// VSR 13 / Android T / 2022
+    Vsr13,
+    /// VSR 14 / Android U / 2023
+    Vsr14,
+}
+
+fn main() -> Result<()> {
+    let args = Args::parse();
+    let Action::VerifyDiceChain(sub_args) = args.action;
+    let session = Session {
+        options: match sub_args.vsr {
+            Some(VsrVersion::Vsr13) => Options::vsr13(),
+            Some(VsrVersion::Vsr14) => Options::vsr14(),
+            None => Options::default(),
+        },
+    };
+    let chain = dice::Chain::from_cbor(&session, &fs::read(sub_args.chain)?)?;
+    println!("Success!");
+    if sub_args.dump {
+        print!("{}", chain);
+    }
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use clap::CommandFactory;
+
+    #[test]
+    fn verify_command() {
+        Args::command().debug_assert();
+    }
+}
diff --git a/remote_provisioning/hwtrust/src/publickey.rs b/remote_provisioning/hwtrust/src/publickey.rs
new file mode 100644
index 0000000..562ec37
--- /dev/null
+++ b/remote_provisioning/hwtrust/src/publickey.rs
@@ -0,0 +1,299 @@
+//! This module describes a public key that is restricted to one of the supported algorithms.
+
+use anyhow::{ensure, Context, Result};
+use openssl::hash::MessageDigest;
+use openssl::nid::Nid;
+use openssl::pkey::{HasParams, Id, PKey, PKeyRef, Public};
+use openssl::sign::Verifier;
+use std::error::Error;
+use std::fmt;
+
+/// Enumeration of the kinds of key that are supported.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub(crate) enum Kind {
+    Ed25519,
+    Ec(EcKind),
+}
+
+/// Enumeration of the kinds of elliptic curve keys that are supported.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub(crate) enum EcKind {
+    P256,
+    P384,
+}
+
+/// Struct wrapping the public key and relevant validation methods.
+#[derive(Clone, Debug)]
+pub struct PublicKey {
+    kind: Kind,
+    pkey: PKey<Public>,
+}
+
+impl PublicKey {
+    pub(crate) fn kind(&self) -> Kind {
+        self.kind
+    }
+
+    pub(crate) fn pkey(&self) -> &PKeyRef<Public> {
+        &self.pkey
+    }
+
+    /// Verify that the signature obtained from signing the given message
+    /// with the PublicKey matches the signature provided.
+    pub fn verify(&self, signature: &[u8], message: &[u8]) -> Result<()> {
+        let mut verifier = match self.kind {
+            Kind::Ed25519 => Verifier::new_without_digest(&self.pkey),
+            Kind::Ec(ec) => Verifier::new(digest_for_ec(ec), &self.pkey),
+        }
+        .with_context(|| format!("Failed to create verifier {:?}", self.kind))?;
+        let verified =
+            verifier.verify_oneshot(signature, message).context("Failed to verify signature")?;
+        ensure!(verified, "Signature verification failed.");
+        Ok(())
+    }
+
+    /// Serializes the public key into a PEM-encoded SubjectPublicKeyInfo structure.
+    pub fn to_pem(&self) -> String {
+        String::from_utf8(self.pkey.public_key_to_pem().unwrap()).unwrap()
+    }
+}
+
+/// The error type returned when converting from [`PKey'] to [`PublicKey`] fails.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct TryFromPKeyError(());
+
+impl fmt::Display for TryFromPKeyError {
+    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(fmt, "unsupported public key conversion attempted")
+    }
+}
+
+impl Error for TryFromPKeyError {}
+
+impl TryFrom<PKey<Public>> for PublicKey {
+    type Error = TryFromPKeyError;
+
+    fn try_from(pkey: PKey<Public>) -> Result<Self, Self::Error> {
+        let kind = pkey_kind(&pkey).ok_or(TryFromPKeyError(()))?;
+        Ok(Self { kind, pkey })
+    }
+}
+
+fn pkey_kind<T: HasParams>(pkey: &PKeyRef<T>) -> Option<Kind> {
+    match pkey.id() {
+        Id::ED25519 => Some(Kind::Ed25519),
+        Id::EC => match pkey.ec_key().unwrap().group().curve_name() {
+            Some(Nid::X9_62_PRIME256V1) => Some(Kind::Ec(EcKind::P256)),
+            Some(Nid::SECP384R1) => Some(Kind::Ec(EcKind::P384)),
+            _ => None,
+        },
+        _ => None,
+    }
+}
+
+fn digest_for_ec(ec: EcKind) -> MessageDigest {
+    match ec {
+        EcKind::P256 => MessageDigest::sha256(),
+        EcKind::P384 => MessageDigest::sha384(),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn from_ed25519_pkey() {
+        let pkey = load_public_pkey(testkeys::ED25519_KEY_PEM[0]);
+        let key: PublicKey = pkey.clone().try_into().unwrap();
+        assert_eq!(key.to_pem().as_bytes(), pkey.public_key_to_pem().unwrap());
+    }
+
+    #[test]
+    fn from_p256_pkey() {
+        let pkey = load_public_pkey(testkeys::P256_KEY_PEM[0]);
+        let key: PublicKey = pkey.clone().try_into().unwrap();
+        assert_eq!(key.to_pem().as_bytes(), pkey.public_key_to_pem().unwrap());
+    }
+
+    #[test]
+    fn from_p384_pkey() {
+        let pkey = load_public_pkey(testkeys::P384_KEY_PEM[0]);
+        let key: PublicKey = pkey.clone().try_into().unwrap();
+        assert_eq!(key.to_pem().as_bytes(), pkey.public_key_to_pem().unwrap());
+    }
+
+    #[test]
+    fn from_p521_pkey_not_supported() {
+        let pkey = load_public_pkey(testkeys::P521_KEY_PEM[0]);
+        assert!(PublicKey::try_from(pkey).is_err());
+    }
+
+    #[test]
+    fn from_rsa2048_pkey_not_supported() {
+        let pkey = load_public_pkey(testkeys::RSA2048_KEY_PEM[0]);
+        assert!(PublicKey::try_from(pkey).is_err());
+    }
+
+    pub fn load_public_pkey(pem: &str) -> PKey<Public> {
+        testkeys::public_from_private(&PKey::private_key_from_pem(pem.as_bytes()).unwrap())
+    }
+}
+
+/// Keys and key handling utilities for use in tests.
+#[cfg(test)]
+pub(crate) mod testkeys {
+    use super::*;
+    use openssl::pkey::Private;
+    use openssl::sign::Signer;
+
+    pub struct PrivateKey {
+        kind: Kind,
+        pkey: PKey<Private>,
+    }
+
+    impl PrivateKey {
+        pub fn from_pem(pem: &str) -> Self {
+            let pkey = PKey::private_key_from_pem(pem.as_bytes()).unwrap();
+            let kind = pkey_kind(&pkey).expect("unsupported private key");
+            Self { kind, pkey }
+        }
+
+        pub(crate) fn kind(&self) -> Kind {
+            self.kind
+        }
+
+        pub fn public_key(&self) -> PublicKey {
+            public_from_private(&self.pkey).try_into().unwrap()
+        }
+
+        pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>> {
+            let mut signer = match self.kind {
+                Kind::Ed25519 => Signer::new_without_digest(&self.pkey)?,
+                Kind::Ec(ec) => Signer::new(digest_for_ec(ec), &self.pkey)?,
+            };
+            signer.sign_oneshot_to_vec(message).context("signing message")
+        }
+    }
+
+    /// Gives the public key that matches the private key.
+    pub fn public_from_private(pkey: &PKey<Private>) -> PKey<Public> {
+        // It feels like there should be a more direct way to do this but I haven't found it.
+        PKey::public_key_from_der(&pkey.public_key_to_der().unwrap()).unwrap()
+    }
+
+    /// A selection of Ed25519 private keys.
+    pub const ED25519_KEY_PEM: &[&str] = &["-----BEGIN PRIVATE KEY-----\n\
+        MC4CAQAwBQYDK2VwBCIEILKW0KEeuieFxhDAzigQPE4XRTiQx+0/AlAjJqHmUWE6\n\
+        -----END PRIVATE KEY-----\n"];
+
+    /// A selection of elliptic curve P-256 private keys.
+    pub const P256_KEY_PEM: &[&str] = &[
+        "-----BEGIN PRIVATE KEY-----\n\
+        MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg+CO3ZBuAsimwPKAL\n\
+        IeDyCh4cRZ5EMd6llGu5MQCpibGhRANCAAQObPxc4bIPjupILrvKJjTrpTcyCf6q\n\
+        V552FlS67fGphwhg2LDfQ8adEdkuRfQvk+IvKJz8MDcPjErBG3Wlps1N\n\
+        -----END PRIVATE KEY-----\n",
+        "-----BEGIN PRIVATE KEY-----\n\
+        MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgw1OPIcfQv5twO68B\n\
+        H+xNstW3DLXC6e4PGEYG/VppYVahRANCAAQMyWyv4ffVMu+wVNhNEk2mQSaTmSl/\n\
+        dLdRbEowfqPwMzdqdQ3QlKSV4ZcU2lsJEuQMkZzmVPz02enY2qcKctmj\n\
+        -----END PRIVATE KEY-----\n",
+        "-----BEGIN PRIVATE KEY-----\n\
+        MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgbXO6ee7i7sY4YfFS\n\
+        Gn60ScPuL3QuYFMX4nJbcqPSQ7+hRANCAAS8i9xA8cIcWStbMG97YrttQsYEIR2a\n\
+        15+alxbb6b7422FuxBB0qG5nJ4m+Jd3Bp+N2lwx4rHBFDqU4cp8VlQav\n\
+        -----END PRIVATE KEY-----\n",
+        "-----BEGIN PRIVATE KEY-----\n\
+        MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg/JuxkbpPyyouat11\n\
+        szDR+OA7d/fuMk9IhGkH7z1xHzChRANCAASRlY0D7Uh5T/FmB6txGr21w6jqKW2x\n\
+        RXdsaZgCB6XnrXlkgkvuWDc0CTLSBWdPFgW6OX0fyXViglEBH95REyQr\n\
+        -----END PRIVATE KEY-----\n",
+    ];
+
+    /// A selection of EC keys that should have leading zeros in their coordinates
+    pub const P256_KEY_WITH_LEADING_ZEROS_PEM: &[&str] = &[
+        // 31 byte Y coordinate:
+        "-----BEGIN PRIVATE KEY-----\n\
+        MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCWbRSB3imI03F5YNVq\n\
+        8AN8ZbyzW/h+5BQ53caD5VkWJg==\n\
+        -----END PRIVATE KEY-----\n",
+        // 31 byte X coordinate:
+        "-----BEGIN PRIVATE KEY-----\n\
+        MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDe5E5WqNmCLxtsCNTc\n\
+        UOb9CPXCn6l3CZpbrp0aivb+Bw==\n\
+        -----END PRIVATE KEY-----\n",
+        // X & Y both have MSB set, and some stacks will add a padding byte
+        "-----BEGIN PRIVATE KEY-----\n\
+        MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCWOWcXPDEVZ4Qz3EBK\n\
+        uvSqhD9HmxDGxcNe3yxKi9pazw==\n\
+        -----END PRIVATE KEY-----\n",
+    ];
+
+    /// A selection of elliptic curve P-384 private keys.
+    pub const P384_KEY_PEM: &[&str] = &["-----BEGIN PRIVATE KEY-----\n\
+        MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBMZ414LiUpcuNTNq5W\n\
+        Ig/qbnbFn0MpuZZxUn5YZ8/+2/tyXFFHRyQoQ4YpNN1P/+qhZANiAAScPDyisb21\n\
+        GldmGksI5g82hjPRYscWNs/6pFxQTMcxABE+/1lWaryLR193ZD74VxVRIKDBluRs\n\
+        uuHi+VayOreTX1/qlUoxgBT+XTI0nTdLn6WwO6vVO1NIkGEVnYvB2eM=\n\
+        -----END PRIVATE KEY-----\n"];
+
+    /// A selection of EC keys that should have leading zeros in their coordinates
+    pub const P384_KEY_WITH_LEADING_ZEROS_PEM: &[&str] = &[
+        // 47 byte Y coordinate:
+        "-----BEGIN PRIVATE KEY-----\n\
+        ME4CAQAwEAYHKoZIzj0CAQYFK4EEACIENzA1AgEBBDCzgVHCz7wgmSdb7/IixYik\n\
+        3AuQceCtBTiFrJpgpGFluwgLUR0S2NpzIuty4M7xU74=\n\
+        -----END PRIVATE KEY-----\n",
+        // 47 byte X coordinate:
+        "-----BEGIN PRIVATE KEY-----\n\
+        ME4CAQAwEAYHKoZIzj0CAQYFK4EEACIENzA1AgEBBDBoW+8zbvwf5fYOS8YPyPEH\n\
+        jHP71Vr1MnRYRp/yG1wbthW2XEu0UWbp4qrZ5WTnZPg=\n\
+        -----END PRIVATE KEY-----\n",
+        // X & Y both have MSB set, and some stacks will add a padding byte
+        "-----BEGIN PRIVATE KEY-----\n\
+        ME4CAQAwEAYHKoZIzj0CAQYFK4EEACIENzA1AgEBBDD2A69j5M/6oc6/WGoYln4t\n\
+        Alnn0C6kpJz1EVC+eH6y0YNrcGamz8pPY4NkzUB/tj4=\n\
+        -----END PRIVATE KEY-----\n",
+    ];
+
+    /// A selection of elliptic curve P-521 private keys.
+    pub const P521_KEY_PEM: &[&str] = &["-----BEGIN PRIVATE KEY-----\n\
+        MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBQuD8Db3jT2yPYR5t\n\
+        Y1ZqESxOe4eBWzekFKg7cjVWsSiJEvWTPC1H7CLtXQBHZglO90dwMt4flL91xHkl\n\
+        iZOzyHahgYkDgYYABAHACwmmKkZu01fp1QTTTQ0cv7IAfYv9FEBz8yfhNGPnI2WY\n\
+        iH1/lYeCfYc9d33aSc/ELY9+vIFzVStJumS/B/WTewEhxVomlKPAkUJeLdCaK5av\n\
+        nlUNj7pNQ/5v5FZVxmoFJvAtUAnZqnJqo/QkLtEnmKlzpLte2LuwTPZhG35z0HeL\n\
+        2g==\n\
+        -----END PRIVATE KEY-----\n"];
+
+    /// A selection of 2048-bit RSA private keys.
+    pub const RSA2048_KEY_PEM: &[&str] = &["-----BEGIN PRIVATE KEY-----\n\
+        MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDbOJh7Ys7CuIju\n\
+        VVKMlFlZWwDEGBX5bVYD/xNBNNF1FY9bOcV/BG20IwoZkdV0N+vm8eWSuv/uwIJp\n\
+        sN2PMWPAEIWbPGGMnSdePpkwrdpFFywhEQqUrfdCFXZ8zeF85Nz5mL8ysl4vlMsL\n\
+        mbErCkrq++K0lzs+k7w/FtPCgs4M3WypJfZef5zM0CGWxpHZGoUGm0HW9fen4sv8\n\
+        hTmMGNY/r0SJhdZREGmiGCx2v+ksOEBon1r/6QKVTP8S73XFsyNCWyop0hYTakut\n\
+        D3HtJ5sWzu2RU8rrch3Txinz0jpGF8PATHk35YMw/9jwwwSqjDw+pOQcYk8SviAf\n\
+        glZf8aZlAgMBAAECggEAAS67PK67tuaOWywJSWHLsWGmqJ4I2tZiTzCT9EZ2MOVx\n\
+        +4ZChNfjHUsskXUp4XNL/FE0J3WvhEYjXR1u+L37nvqc48mJpjoPN7o/CMb6rM/J\n\
+        +ly9A2ZOvEB4ppOYDYh5QVDm7/otmvEMzJxuUOpvxYxqnJpAPgl9dBpNQ0nSt3YX\n\
+        jJS4+vuzQpwwSTfchpcCZYU3AX9DpQpxnrLX3/7d3GTs2NuedmSwRz+mCfwaOlFk\n\
+        jdrJ2uJJrDLcK6yhSdsE9aNgKkmX6aNLhxbbCFTyDNiGY5HHayyL3mVvyaeovYcn\n\
+        ZS+Z+0TJGCgXDRHHSFyIAsgVonxHfn49x9uvfpuMFQKBgQD2cVp26+aQgt46ajVv\n\
+        yn4fxbNpfovL0pgtSjo7ekZOWYJ3Is1SDmnni8k1ViKgUYC210dTTlrljxUil8T0\n\
+        83e03k2xasDi2c+h/7JFYJPDyZwIm1o19ciUwY73D54iJaRbrzEximFeA0h4LGKw\n\
+        Yjd4xkKMJw16CU00gInyI193BwKBgQDjuP0/QEEPpYvzpag5Ul4+h22K/tiOUrFj\n\
+        NuSgd+IvQG1hW48zHEa9vXvORQ/FteiQ708racz6ByqY+n2w6QdtdRMj7Hsyo2fk\n\
+        SEeNaLrR7Sif6MfkYajbSGFySDD82vj4Jt76vzdt3MjpZfs6ryPmnKLVPWNA3mnS\n\
+        4+u2J/+QMwKBgFfiJnugNnG0aaF1PKcoFAAqlYd6XEoMSL5l6QxK14WbP/5SR9wK\n\
+        TdQHsnI1zFVVm0wYy1O27o1MkCHs84zSwg6a9CPfyPdc60F/GMjK3wcD/4PGOs5h\n\
+        Xu1FdUE/rYnJ2KnleOqMyZooG5DXaz4xWEzWjubCCnlJleGyMP9LhADDAoGAR/jK\n\
+        iXgcV/6haeMcdOl0gdy5oWmENg8qo0nRHmplYTvCljei3at9LDC79WhcYMdqdow8\n\
+        AGOS9h7XtrvMh+JOh6it4Pe3xDxi9IJnoujLytditI+Uxbib7ppEuiLY4MGwWHWo\n\
+        maVftmhGU4X4zgZWmWc+C5k4SmNBHPcOI2cm3YMCgYB5/Ni+tBxng0S/PRAtwCeG\n\
+        dVnQnYvS2C5nHCn9D5rmAcVXUKrIJ1/1K4se8vQ15DDcpuBF1SejYTJzdUP8Zgcs\n\
+        p8wVq7neK8uSsmG+AfUgxMjbetoAVTP3L8+GbjocznR9auB7BEjFVO25iYSiTp/w\n\
+        NNzbIKQRDN+c3vUpneJcuw==\n\
+        -----END PRIVATE KEY-----\n"];
+}
diff --git a/remote_provisioning/hwtrust/src/session.rs b/remote_provisioning/hwtrust/src/session.rs
new file mode 100644
index 0000000..53f2371
--- /dev/null
+++ b/remote_provisioning/hwtrust/src/session.rs
@@ -0,0 +1,103 @@
+//! Defines the context type for a session handling hwtrust data structures.
+
+/// The context for a session handling hwtrust data structures.
+pub struct Session {
+    /// Options that control the behaviour during this session.
+    pub options: Options,
+}
+
+/// Options that control the behaviour of a session.
+#[derive(Default)]
+pub struct Options {
+    /// The expected format for the configuration descriptor in the first certificate of the DICE
+    /// chain. When the chain is ROM-rooted, the first certificate is generated by ROM so this
+    /// option can be used for compatibility with ROMs.
+    pub first_dice_chain_cert_config_format: ConfigFormat,
+
+    /// The types that are permitted for the key_ops field of COSE_Key objects in the DICE chain.
+    /// This option can be used for compatibility with the RKP HAL before v3 which diverged from
+    /// the COSE spec and allowed a single int instead of always requiring an array.
+    pub dice_chain_key_ops_type: KeyOpsType,
+
+    /// The types that are permitted for the mode field of the DICE certificates. This option can
+    /// be used for compatibility with the RKP HAL v3 which allowed some deviations from the Open
+    /// Profile for DICE specification.
+    pub dice_chain_mode_type: ModeType,
+
+    /// Whether to allow the key_usage field of the DICE certificates to be encoded in big-endian
+    /// byte order. This introduces ambiguity of the exact key usage being expressed but the keys
+    /// in the DICE chain are only used for verification so it may be preferable to allow for
+    /// compatibility with implementations that use the wrong endianness.
+    pub dice_chain_allow_big_endian_key_usage: bool,
+
+    /// The types that are permitted for the component version field in the configuration
+    /// descriptor. The specification has changed the allowed types over time and this option
+    /// can be used to select which rules to apply.
+    pub dice_chain_component_version_type: ComponentVersionType,
+}
+
+/// Format of the DICE configuration descriptor.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub enum ConfigFormat {
+    /// The configuration descriptor format specified by Android.
+    #[default]
+    Android,
+    /// Any configuration descriptor format is allowed.
+    Permissive,
+}
+
+/// Type allowed for the COSE_Key object key_ops field in the DICE chain.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub enum KeyOpsType {
+    /// The key_ops field must be an array as specified in the COSE RFC.
+    #[default]
+    Array,
+    /// The key_ops field can be either a single int or an array as specified in the COSE RFC.
+    IntOrArray,
+}
+
+/// Type allowed for the DICE certificate mode field.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub enum ModeType {
+    /// The mode field must be a byte string holding a single byte as specified by the Open Profile
+    /// for DICE.
+    #[default]
+    Bytes,
+    /// The mode field can be either an int or a byte string holding a single byte.
+    IntOrBytes,
+}
+
+/// Type allowed for the DICE certificate configuration descriptor's component version field.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub enum ComponentVersionType {
+    /// The component version can be either an int or a free-form string.
+    #[default]
+    IntOrString,
+    /// The component version must be an int.
+    Int,
+}
+
+impl Options {
+    /// The options use by VSR 13.
+    pub fn vsr13() -> Self {
+        Self {
+            // Context: b/262599829#comment65
+            dice_chain_key_ops_type: KeyOpsType::IntOrArray,
+            // Context: b/273552826
+            dice_chain_component_version_type: ComponentVersionType::Int,
+            ..Options::default()
+        }
+    }
+
+    /// The options use by VSR 14.
+    pub fn vsr14() -> Self {
+        Self {
+            // Context: b/261647022
+            first_dice_chain_cert_config_format: ConfigFormat::Permissive,
+            // Context: b/273552826
+            dice_chain_mode_type: ModeType::IntOrBytes,
+            dice_chain_allow_big_endian_key_usage: true,
+            ..Options::default()
+        }
+    }
+}
diff --git a/remote_provisioning/cert_validator/testdata/bcc/bad_final_signature.chain b/remote_provisioning/hwtrust/testdata/dice/bad_final_signature.chain
similarity index 100%
rename from remote_provisioning/cert_validator/testdata/bcc/bad_final_signature.chain
rename to remote_provisioning/hwtrust/testdata/dice/bad_final_signature.chain
Binary files differ
diff --git a/remote_provisioning/cert_validator/testdata/bcc/bad_p256.chain b/remote_provisioning/hwtrust/testdata/dice/bad_p256.chain
similarity index 100%
rename from remote_provisioning/cert_validator/testdata/bcc/bad_p256.chain
rename to remote_provisioning/hwtrust/testdata/dice/bad_p256.chain
Binary files differ
diff --git a/remote_provisioning/cert_validator/testdata/bcc/bad_pub_key.chain b/remote_provisioning/hwtrust/testdata/dice/bad_pub_key.chain
similarity index 100%
rename from remote_provisioning/cert_validator/testdata/bcc/bad_pub_key.chain
rename to remote_provisioning/hwtrust/testdata/dice/bad_pub_key.chain
Binary files differ
diff --git a/remote_provisioning/hwtrust/testdata/dice/cf_degenerate.chain b/remote_provisioning/hwtrust/testdata/dice/cf_degenerate.chain
new file mode 100644
index 0000000..e994dd4
--- /dev/null
+++ b/remote_provisioning/hwtrust/testdata/dice/cf_degenerate.chain
Binary files differ
diff --git a/remote_provisioning/cert_validator/testdata/bcc/valid.chain b/remote_provisioning/hwtrust/testdata/dice/valid_ed25519.chain
similarity index 100%
rename from remote_provisioning/cert_validator/testdata/bcc/valid.chain
rename to remote_provisioning/hwtrust/testdata/dice/valid_ed25519.chain
Binary files differ
diff --git a/remote_provisioning/cert_validator/testdata/bcc/valid_p256.chain b/remote_provisioning/hwtrust/testdata/dice/valid_p256.chain
similarity index 100%
rename from remote_provisioning/cert_validator/testdata/bcc/valid_p256.chain
rename to remote_provisioning/hwtrust/testdata/dice/valid_p256.chain
Binary files differ
diff --git a/remote_provisioning/hwtrust/tests/hwtrust_cli.rs b/remote_provisioning/hwtrust/tests/hwtrust_cli.rs
new file mode 100644
index 0000000..eca870b
--- /dev/null
+++ b/remote_provisioning/hwtrust/tests/hwtrust_cli.rs
@@ -0,0 +1,24 @@
+use std::process::Command;
+
+/// Gets the path of the `hwtrust` binary that works with `atest` and `Cargo`.
+fn hwtrust_bin() -> &'static str {
+    option_env!("CARGO_BIN_EXE_hwtrust").unwrap_or("./hwtrust")
+}
+
+#[test]
+fn exit_code_for_good_chain() {
+    let output = Command::new(hwtrust_bin())
+        .args(["verify-dice-chain", "testdata/dice/valid_ed25519.chain"])
+        .output()
+        .unwrap();
+    assert!(output.status.success());
+}
+
+#[test]
+fn exit_code_for_bad_chain() {
+    let output = Command::new(hwtrust_bin())
+        .args(["verify-dice-chain", "testdata/dice/bad_p256.chain"])
+        .output()
+        .unwrap();
+    assert!(!output.status.success());
+}
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 120000
index 0000000..ee92d9e
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1 @@
+../../build/soong/scripts/rustfmt.toml
\ No newline at end of file
diff --git a/sanitizer-status/sanitizer-status.cpp b/sanitizer-status/sanitizer-status.cpp
index 9e07e61..f631a0a 100644
--- a/sanitizer-status/sanitizer-status.cpp
+++ b/sanitizer-status/sanitizer-status.cpp
@@ -49,12 +49,12 @@
   printf("Heap UAF Test Failed\n");
 }
 
-// crashes if built with -fsanitize=address
+// crashes if built with -fsanitize={address,hwaddress,memtag-stack}
 void test_crash_stack() {
   volatile char stack[32];
   volatile char* p_stack = stack;
   p_stack[32] = p_stack[32];
-  printf("(HW)ASAN: Stack Test Failed\n");
+  printf("(HW)ASAN / Stack MTE: Stack Test Failed\n");
 }
 
 void test_crash_pthread_mutex_unlock() {
@@ -302,5 +302,22 @@
     failures += mte_failures;
   }
 
+  if (test_everything || have_option("stack_mte", argv, argc)) {
+    int stack_mte_failures = 0;
+
+    if (!(mte_supported() && !__has_feature(address_sanitizer) &&
+          !__has_feature(hwaddress_sanitizer))) {
+      stack_mte_failures += 1;
+      printf("MTE: Not supported\n");
+    }
+
+    stack_mte_failures += test(test_crash_stack);
+
+    if (!stack_mte_failures)
+      printf("Stack MTE: OK\n");
+
+    failures += stack_mte_failures;
+  }
+
   return failures > 0 ? EXIT_FAILURE : EXIT_SUCCESS;
 }