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