Add compos_key_helper
Create a small library to do key-related operations (derive, sign,
verify). Add tests.
Create a small standlone executable to expose these functions.
Bug: 218494522
Test: atest compos_key_tests
Change-Id: I5c984178b822510fd32784d01cf4322e592e5d2a
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
index aec3c88..6550b4f 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -47,6 +47,7 @@
"composd_cmd",
// Used in VM
+ "compos_key_helper",
"compsvc",
],
diff --git a/compos/compos_key_helper/Android.bp b/compos/compos_key_helper/Android.bp
new file mode 100644
index 0000000..c53d88d
--- /dev/null
+++ b/compos/compos_key_helper/Android.bp
@@ -0,0 +1,47 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+ name: "compos_key_defaults",
+ apex_available: ["com.android.compos"],
+
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "libcrypto",
+ ],
+}
+
+cc_library {
+ name: "libcompos_key",
+ defaults: ["compos_key_defaults"],
+ srcs: ["compos_key.cpp"],
+
+ shared_libs: [
+ "android.hardware.security.dice-V1-ndk",
+ "android.security.dice-ndk",
+ ],
+}
+
+cc_binary {
+ name: "compos_key_helper",
+ defaults: ["compos_key_defaults"],
+ srcs: ["compos_key_main.cpp"],
+
+ static_libs: ["libcompos_key"],
+ shared_libs: [
+ "android.security.dice-ndk",
+ ],
+}
+
+cc_test {
+ name: "compos_key_tests",
+ defaults: ["compos_key_defaults"],
+ test_suites: [
+ "general-tests",
+ ],
+
+ srcs: ["compos_key_test.cpp"],
+ static_libs: ["libcompos_key"],
+}
diff --git a/compos/compos_key_helper/compos_key.cpp b/compos/compos_key_helper/compos_key.cpp
new file mode 100644
index 0000000..84a736d
--- /dev/null
+++ b/compos/compos_key_helper/compos_key.cpp
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+#include "compos_key.h"
+
+#include <aidl/android/security/dice/IDiceNode.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_manager.h>
+#include <openssl/digest.h>
+#include <openssl/hkdf.h>
+#include <openssl/mem.h>
+#include <unistd.h>
+
+using aidl::android::hardware::security::dice::BccHandover;
+using aidl::android::hardware::security::dice::InputValues;
+using aidl::android::security::dice::IDiceNode;
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+
+// Used to ensure the key we derive is distinct from any other.
+constexpr const char* kSigningKeyInfo = "CompOS signing key";
+
+Result<Ed25519KeyPair> deriveKeyFromSecret(const uint8_t* secret, size_t secret_size) {
+ // Ed25519 private keys are derived from a 32 byte seed:
+ // https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.5
+ std::array<uint8_t, 32> seed;
+
+ // We derive the seed from the secret using HKDF - see
+ // https://datatracker.ietf.org/doc/html/rfc5869#section-2.
+ if (!HKDF(seed.data(), seed.size(), EVP_sha256(), secret, secret_size, /*salt=*/nullptr,
+ /*salt_len=*/0, reinterpret_cast<const uint8_t*>(kSigningKeyInfo),
+ strlen(kSigningKeyInfo))) {
+ return Error() << "HKDF failed";
+ }
+
+ Ed25519KeyPair result;
+ ED25519_keypair_from_seed(result.public_key.data(), result.private_key.data(), seed.data());
+ return result;
+}
+
+Result<Signature> sign(const PrivateKey& private_key, const uint8_t* data, size_t data_size) {
+ Signature result;
+ if (!ED25519_sign(result.data(), data, data_size, private_key.data())) {
+ return Error() << "Failed to sign";
+ }
+ return result;
+}
+
+bool verify(const PublicKey& public_key, const Signature& signature, const uint8_t* data,
+ size_t data_size) {
+ return ED25519_verify(data, data_size, signature.data(), public_key.data()) == 1;
+}
+
+Result<Ed25519KeyPair> deriveKeyFromDice() {
+ ndk::SpAIBinder binder{AServiceManager_getService("android.security.dice.IDiceNode")};
+ auto dice_node = IDiceNode::fromBinder(binder);
+ if (!dice_node) {
+ return Error() << "Unable to connect to IDiceNode";
+ }
+
+ const std::vector<InputValues> empty_input_values;
+ BccHandover bcc;
+ auto status = dice_node->derive(empty_input_values, &bcc);
+ if (!status.isOk()) {
+ return Error() << "Derive failed: " << status.getDescription();
+ }
+
+ // We use the sealing CDI because we want stability - the key needs to be the same
+ // for any instance of the "same" VM.
+ return deriveKeyFromSecret(bcc.cdiSeal.data(), bcc.cdiSeal.size());
+}
diff --git a/compos/compos_key_helper/compos_key.h b/compos/compos_key_helper/compos_key.h
new file mode 100644
index 0000000..520f846
--- /dev/null
+++ b/compos/compos_key_helper/compos_key.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/result.h>
+#include <openssl/curve25519.h>
+
+#include <array>
+
+using PrivateKey = std::array<uint8_t, ED25519_PRIVATE_KEY_LEN>;
+using PublicKey = std::array<uint8_t, ED25519_PUBLIC_KEY_LEN>;
+using Signature = std::array<uint8_t, ED25519_SIGNATURE_LEN>;
+
+struct Ed25519KeyPair {
+ PrivateKey private_key;
+ PublicKey public_key;
+};
+
+android::base::Result<Ed25519KeyPair> deriveKeyFromSecret(const uint8_t* secret,
+ size_t secret_size);
+
+android::base::Result<Ed25519KeyPair> deriveKeyFromDice();
+
+android::base::Result<Signature> sign(const PrivateKey& private_key, const uint8_t* data,
+ size_t data_size);
+
+bool verify(const PublicKey& public_key, const Signature& signature, const uint8_t* data,
+ size_t data_size);
diff --git a/compos/compos_key_helper/compos_key_main.cpp b/compos/compos_key_helper/compos_key_main.cpp
new file mode 100644
index 0000000..70f7539
--- /dev/null
+++ b/compos/compos_key_helper/compos_key_main.cpp
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <string_view>
+
+#include "compos_key.h"
+
+using android::base::ErrnoError;
+using android::base::WriteFully;
+using namespace std::literals;
+
+int main(int argc, char** argv) {
+ android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));
+
+ if (argc == 2) {
+ if (argv[1] == "public_key"sv) {
+ auto key_pair = deriveKeyFromDice();
+ if (!key_pair.ok()) {
+ LOG(ERROR) << key_pair.error();
+ return 1;
+ }
+ if (!WriteFully(STDOUT_FILENO, key_pair->public_key.data(),
+ key_pair->public_key.size())) {
+ PLOG(ERROR) << "Write failed";
+ return 1;
+ }
+ return 0;
+ }
+ }
+
+ std::cerr << "Usage:\n"
+ "compos_key_helper public_key Write current public key to stdout\n";
+ return 1;
+}
diff --git a/compos/compos_key_helper/compos_key_test.cpp b/compos/compos_key_helper/compos_key_test.cpp
new file mode 100644
index 0000000..e5c4768
--- /dev/null
+++ b/compos/compos_key_helper/compos_key_test.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+#include "compos_key.h"
+
+#include <vector>
+
+#include "gtest/gtest.h"
+
+const std::vector<uint8_t> secret = {1, 2, 3};
+const std::vector<uint8_t> other_secret = {3, 2, 3};
+const std::vector<uint8_t> data = {42, 180, 65, 0};
+
+struct ComposKeyTest : public testing::Test {
+ Ed25519KeyPair key_pair;
+
+ void SetUp() override {
+ auto key_pair = deriveKeyFromSecret(secret.data(), secret.size());
+ ASSERT_TRUE(key_pair.ok()) << key_pair.error();
+ this->key_pair = *key_pair;
+ }
+};
+
+TEST_F(ComposKeyTest, SameSecretSameKey) {
+ auto other_key_pair = deriveKeyFromSecret(secret.data(), secret.size());
+ ASSERT_TRUE(other_key_pair.ok()) << other_key_pair.error();
+
+ ASSERT_EQ(key_pair.private_key, other_key_pair->private_key);
+ ASSERT_EQ(key_pair.public_key, other_key_pair->public_key);
+}
+
+TEST_F(ComposKeyTest, DifferentSecretDifferentKey) {
+ auto other_key_pair = deriveKeyFromSecret(other_secret.data(), other_secret.size());
+ ASSERT_TRUE(other_key_pair.ok()) << other_key_pair.error();
+
+ ASSERT_NE(key_pair.private_key, other_key_pair->private_key);
+ ASSERT_NE(key_pair.public_key, other_key_pair->public_key);
+}
+
+TEST_F(ComposKeyTest, CanVerifyValidSignature) {
+ auto signature = sign(key_pair.private_key, data.data(), data.size());
+ ASSERT_TRUE(signature.ok()) << signature.error();
+
+ bool verified = verify(key_pair.public_key, *signature, data.data(), data.size());
+ ASSERT_TRUE(verified);
+}
+
+TEST_F(ComposKeyTest, WrongSignatureDoesNotVerify) {
+ auto signature = sign(key_pair.private_key, data.data(), data.size());
+ ASSERT_TRUE(signature.ok()) << signature.error();
+
+ (*signature)[0] ^= 1;
+
+ bool verified = verify(key_pair.public_key, *signature, data.data(), data.size());
+ ASSERT_FALSE(verified);
+}
+
+TEST_F(ComposKeyTest, WrongDataDoesNotVerify) {
+ auto signature = sign(key_pair.private_key, data.data(), data.size());
+ ASSERT_TRUE(signature.ok()) << signature.error();
+
+ auto other_data = data;
+ other_data[0] ^= 1;
+
+ bool verified = verify(key_pair.public_key, *signature, other_data.data(), other_data.size());
+ ASSERT_FALSE(verified);
+}
+
+TEST_F(ComposKeyTest, WrongKeyDoesNotVerify) {
+ auto signature = sign(key_pair.private_key, data.data(), data.size());
+
+ auto other_key_pair = deriveKeyFromSecret(other_secret.data(), other_secret.size());
+ ASSERT_TRUE(other_key_pair.ok()) << other_key_pair.error();
+
+ bool verified = verify(other_key_pair->public_key, *signature, data.data(), data.size());
+ ASSERT_FALSE(verified);
+}
diff --git a/compos/compos_key_helper/tests/AndroidTest.xml b/compos/compos_key_helper/tests/AndroidTest.xml
new file mode 100644
index 0000000..3c1c657
--- /dev/null
+++ b/compos/compos_key_helper/tests/AndroidTest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Tests for compos_key">
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="module-name" value="compos_key_tests" />
+ </test>
+</configuration>