Define the AIDL interface for AuthGraph key exchange. am: 0e2495f107 am: 5c44262ee7

Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/2606133

Change-Id: I0595109be548b5f50a2a7a73757e2dfac69f5cba
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/compatibility_matrices/compatibility_matrix.9.xml b/compatibility_matrices/compatibility_matrix.9.xml
index c79c68f..5ae169c 100644
--- a/compatibility_matrices/compatibility_matrix.9.xml
+++ b/compatibility_matrices/compatibility_matrix.9.xml
@@ -513,7 +513,7 @@
         <version>1</version>
         <interface>
             <name>IAuthGraphKeyExchange</name>
-            <instance>default</instance>
+            <instance>nonsecure</instance>
         </interface>
     </hal>
     <hal format="aidl" optional="true" updatable-via-apex="true">
diff --git a/security/authgraph/aidl/Android.bp b/security/authgraph/aidl/Android.bp
index 373d0c4..d94f640 100644
--- a/security/authgraph/aidl/Android.bp
+++ b/security/authgraph/aidl/Android.bp
@@ -45,3 +45,44 @@
         },
     },
 }
+
+// cc_defaults that includes the latest Authgraph AIDL library.
+// Modules that depend on Authgraph directly can include this cc_defaults to avoid
+// managing dependency versions explicitly.
+cc_defaults {
+    name: "authgraph_use_latest_hal_aidl_ndk_static",
+    static_libs: [
+        "android.hardware.security.authgraph-V1-ndk",
+    ],
+}
+
+cc_defaults {
+    name: "authgraph_use_latest_hal_aidl_ndk_shared",
+    shared_libs: [
+        "android.hardware.security.authgraph-V1-ndk",
+    ],
+}
+
+cc_defaults {
+    name: "authgraph_use_latest_hal_aidl_cpp_static",
+    static_libs: [
+        "android.hardware.security.authgraph-V1-cpp",
+    ],
+}
+
+cc_defaults {
+    name: "authgraph_use_latest_hal_aidl_cpp_shared",
+    shared_libs: [
+        "android.hardware.security.authgraph-V1-cpp",
+    ],
+}
+
+// A rust_defaults that includes the latest Authgraph AIDL library.
+// Modules that depend on Authgraph directly can include this rust_defaults to avoid
+// managing dependency versions explicitly.
+rust_defaults {
+    name: "authgraph_use_latest_hal_aidl_rust",
+    rustlibs: [
+        "android.hardware.security.authgraph-V1-rust",
+    ],
+}
diff --git a/security/authgraph/aidl/vts/functional/Android.bp b/security/authgraph/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..fc13759
--- /dev/null
+++ b/security/authgraph/aidl/vts/functional/Android.bp
@@ -0,0 +1,48 @@
+//
+// Copyright (C) 2023 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_test {
+    name: "VtsAidlAuthGraphSessionTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "authgraph_use_latest_hal_aidl_ndk_static",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+    ],
+    srcs: [
+        "AuthGraphSessionTest.cpp",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+        "libcrypto",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/security/authgraph/aidl/vts/functional/AuthGraphSessionTest.cpp b/security/authgraph/aidl/vts/functional/AuthGraphSessionTest.cpp
new file mode 100644
index 0000000..d9dea77
--- /dev/null
+++ b/security/authgraph/aidl/vts/functional/AuthGraphSessionTest.cpp
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+#define LOG_TAG "authgraph_session_test"
+#include <android-base/logging.h>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/security/authgraph/Error.h>
+#include <aidl/android/hardware/security/authgraph/IAuthGraphKeyExchange.h>
+#include <android/binder_manager.h>
+#include <binder/ProcessState.h>
+#include <gtest/gtest.h>
+#include <vector>
+
+namespace aidl::android::hardware::security::authgraph::test {
+using ::aidl::android::hardware::security::authgraph::Error;
+
+namespace {
+
+// Check that the signature in the encoded COSE_Sign1 data is correct, and that the payload matches.
+// TODO: maybe drop separate payload, and extract it from cose_sign1.payload (and return it).
+void CheckSignature(std::vector<uint8_t>& /*pub_cose_key*/, std::vector<uint8_t>& /*payload*/,
+                    std::vector<uint8_t>& /*cose_sign1*/) {
+    // TODO: implement me
+}
+
+void CheckSignature(std::vector<uint8_t>& pub_cose_key, std::vector<uint8_t>& payload,
+                    SessionIdSignature& signature) {
+    return CheckSignature(pub_cose_key, payload, signature.signature);
+}
+
+std::vector<uint8_t> SigningKeyFromIdentity(const Identity& identity) {
+    // TODO: This is a CBOR-encoded `Identity` which currently happens to be a COSE_Key with the
+    // pubkey This will change in future.
+    return identity.identity;
+}
+
+}  // namespace
+
+class AuthGraphSessionTest : public ::testing::TestWithParam<std::string> {
+  public:
+    enum ErrorType { AIDL_ERROR, BINDER_ERROR };
+
+    union ErrorValue {
+        Error aidl_error;
+        int32_t binder_error;
+    };
+
+    struct ReturnedError {
+        ErrorType err_type;
+        ErrorValue err_val;
+
+        friend bool operator==(const ReturnedError& lhs, const ReturnedError& rhs) {
+            return lhs.err_type == rhs.err_type;
+            switch (lhs.err_type) {
+                case ErrorType::AIDL_ERROR:
+                    return lhs.err_val.aidl_error == rhs.err_val.aidl_error;
+                case ErrorType::BINDER_ERROR:
+                    return lhs.err_val.binder_error == rhs.err_val.binder_error;
+            }
+        }
+    };
+
+    const ReturnedError OK = {.err_type = ErrorType::AIDL_ERROR, .err_val.aidl_error = Error::OK};
+
+    ReturnedError GetReturnError(const ::ndk::ScopedAStatus& result) {
+        if (result.isOk()) {
+            return OK;
+        }
+        int32_t exception_code = result.getExceptionCode();
+        int32_t error_code = result.getServiceSpecificError();
+        if (exception_code == EX_SERVICE_SPECIFIC && error_code != 0) {
+            ReturnedError re = {.err_type = ErrorType::AIDL_ERROR,
+                                .err_val.aidl_error = static_cast<Error>(error_code)};
+            return re;
+        }
+        ReturnedError re = {.err_type = ErrorType::BINDER_ERROR,
+                            .err_val.binder_error = exception_code};
+        return re;
+    }
+
+    // Build the parameters for the VTS test by enumerating the available HAL instances
+    static std::vector<std::string> build_params() {
+        auto params = ::android::getAidlHalInstanceNames(IAuthGraphKeyExchange::descriptor);
+        return params;
+    }
+
+    void SetUp() override {
+        ASSERT_TRUE(AServiceManager_isDeclared(GetParam().c_str()))
+                << "No instance declared for " << GetParam();
+        ::ndk::SpAIBinder binder(AServiceManager_waitForService(GetParam().c_str()));
+        authNode_ = IAuthGraphKeyExchange::fromBinder(binder);
+        ASSERT_NE(authNode_, nullptr) << "Failed to get Binder reference for " << GetParam();
+    }
+
+    void TearDown() override {}
+
+  protected:
+    std::shared_ptr<IAuthGraphKeyExchange> authNode_;
+};
+
+TEST_P(AuthGraphSessionTest, Mainline) {
+    std::shared_ptr<IAuthGraphKeyExchange> source = authNode_;
+    std::shared_ptr<IAuthGraphKeyExchange> sink = authNode_;
+
+    // Step 1: create an ephemeral ECDH key at the source.
+    SessionInitiationInfo source_init_info;
+    ASSERT_EQ(OK, GetReturnError(source->create(&source_init_info)));
+    ASSERT_TRUE(source_init_info.key.pubKey.has_value());
+    ASSERT_TRUE(source_init_info.key.arcFromPBK.has_value());
+
+    // Step 2: pass the source's ECDH public key and other session info to the sink.
+    KeInitResult init_result;
+    ASSERT_EQ(OK, GetReturnError(sink->init(source_init_info.key.pubKey.value(),
+                                            source_init_info.identity, source_init_info.nonce,
+                                            source_init_info.version, &init_result)));
+    SessionInitiationInfo sink_init_info = init_result.sessionInitiationInfo;
+    ASSERT_TRUE(sink_init_info.key.pubKey.has_value());
+    // The sink_init_info.arcFromPBK need not be populated, as the ephemeral key agreement
+    // key is no longer needed.
+
+    SessionInfo sink_info = init_result.sessionInfo;
+    ASSERT_EQ((int)sink_info.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
+    ASSERT_GT((int)sink_info.sessionId.size(), 0) << "Expect non-empty session ID from sink";
+    std::vector<uint8_t> sink_signing_key = SigningKeyFromIdentity(sink_init_info.identity);
+    CheckSignature(sink_signing_key, sink_info.sessionId, sink_info.signature);
+
+    // Step 3: pass the sink's ECDH public key and other session info to the source, so it can
+    // calculate the same pair of symmetric keys.
+    SessionInfo source_info;
+    ASSERT_EQ(OK, GetReturnError(source->finish(sink_init_info.key.pubKey.value(),
+                                                sink_init_info.identity, sink_info.signature,
+                                                sink_init_info.nonce, sink_init_info.version,
+                                                source_init_info.key, &source_info)));
+    ASSERT_EQ((int)source_info.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
+    ASSERT_GT((int)source_info.sessionId.size(), 0) << "Expect non-empty session ID from source";
+    std::vector<uint8_t> source_signing_key = SigningKeyFromIdentity(source_init_info.identity);
+    CheckSignature(source_signing_key, source_info.sessionId, source_info.signature);
+
+    // Both ends should agree on the session ID.
+    ASSERT_EQ(source_info.sessionId, sink_info.sessionId);
+
+    // Step 4: pass the source's session ID info back to the sink, so it can check it and
+    // update the symmetric keys so they're marked as authentication complete.
+    std::array<Arc, 2> auth_complete_result;
+    ASSERT_EQ(OK, GetReturnError(sink->authenticationComplete(
+                          source_info.signature, sink_info.sharedKeys, &auth_complete_result)));
+    ASSERT_EQ((int)auth_complete_result.size(), 2)
+            << "Expect two symmetric keys from authComplete()";
+    sink_info.sharedKeys = auth_complete_result;
+
+    // At this point the sink and source have agreed on the same pair of symmetric keys,
+    // encoded as `sink_info.sharedKeys` and `source_info.sharedKeys`.
+}
+
+TEST_P(AuthGraphSessionTest, ParallelSink) {
+    std::shared_ptr<IAuthGraphKeyExchange> source = authNode_;
+    std::shared_ptr<IAuthGraphKeyExchange> sink1 = authNode_;
+    std::shared_ptr<IAuthGraphKeyExchange> sink2 = authNode_;
+
+    // Step 1: create ephemeral ECDH keys at the source.
+    SessionInitiationInfo source_init1_info;
+    ASSERT_EQ(OK, GetReturnError(source->create(&source_init1_info)));
+    ASSERT_TRUE(source_init1_info.key.pubKey.has_value());
+    ASSERT_TRUE(source_init1_info.key.arcFromPBK.has_value());
+    SessionInitiationInfo source_init2_info;
+    ASSERT_EQ(OK, GetReturnError(source->create(&source_init2_info)));
+    ASSERT_TRUE(source_init2_info.key.pubKey.has_value());
+    ASSERT_TRUE(source_init2_info.key.arcFromPBK.has_value());
+
+    // Step 2: pass the source's ECDH public keys and other session info to the sinks.
+    KeInitResult init1_result;
+    ASSERT_EQ(OK, GetReturnError(sink1->init(source_init1_info.key.pubKey.value(),
+                                             source_init1_info.identity, source_init1_info.nonce,
+                                             source_init1_info.version, &init1_result)));
+    SessionInitiationInfo sink1_init_info = init1_result.sessionInitiationInfo;
+    ASSERT_TRUE(sink1_init_info.key.pubKey.has_value());
+
+    SessionInfo sink1_info = init1_result.sessionInfo;
+    ASSERT_EQ((int)sink1_info.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
+    ASSERT_GT((int)sink1_info.sessionId.size(), 0) << "Expect non-empty session ID from sink";
+    std::vector<uint8_t> sink1_signing_key = SigningKeyFromIdentity(sink1_init_info.identity);
+    CheckSignature(sink1_signing_key, sink1_info.sessionId, sink1_info.signature);
+    KeInitResult init2_result;
+    ASSERT_EQ(OK, GetReturnError(sink2->init(source_init2_info.key.pubKey.value(),
+                                             source_init2_info.identity, source_init2_info.nonce,
+                                             source_init2_info.version, &init2_result)));
+    SessionInitiationInfo sink2_init_info = init2_result.sessionInitiationInfo;
+    ASSERT_TRUE(sink2_init_info.key.pubKey.has_value());
+
+    SessionInfo sink2_info = init2_result.sessionInfo;
+    ASSERT_EQ((int)sink2_info.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
+    ASSERT_GT((int)sink2_info.sessionId.size(), 0) << "Expect non-empty session ID from sink";
+    std::vector<uint8_t> sink2_signing_key = SigningKeyFromIdentity(sink2_init_info.identity);
+    CheckSignature(sink2_signing_key, sink2_info.sessionId, sink2_info.signature);
+
+    // Step 3: pass each sink's ECDH public key and other session info to the source, so it can
+    // calculate the same pair of symmetric keys.
+    SessionInfo source_info1;
+    ASSERT_EQ(OK, GetReturnError(source->finish(sink1_init_info.key.pubKey.value(),
+                                                sink1_init_info.identity, sink1_info.signature,
+                                                sink1_init_info.nonce, sink1_init_info.version,
+                                                source_init1_info.key, &source_info1)));
+    ASSERT_EQ((int)source_info1.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
+    ASSERT_GT((int)source_info1.sessionId.size(), 0) << "Expect non-empty session ID from source";
+    std::vector<uint8_t> source_signing_key1 = SigningKeyFromIdentity(source_init1_info.identity);
+    CheckSignature(source_signing_key1, source_info1.sessionId, source_info1.signature);
+    SessionInfo source_info2;
+    ASSERT_EQ(OK, GetReturnError(source->finish(sink2_init_info.key.pubKey.value(),
+                                                sink2_init_info.identity, sink2_info.signature,
+                                                sink2_init_info.nonce, sink2_init_info.version,
+                                                source_init2_info.key, &source_info2)));
+    ASSERT_EQ((int)source_info2.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
+    ASSERT_GT((int)source_info2.sessionId.size(), 0) << "Expect non-empty session ID from source";
+    std::vector<uint8_t> source_signing_key2 = SigningKeyFromIdentity(source_init2_info.identity);
+    CheckSignature(source_signing_key2, source_info2.sessionId, source_info2.signature);
+
+    // Both ends should agree on the session ID.
+    ASSERT_EQ(source_info1.sessionId, sink1_info.sessionId);
+    ASSERT_EQ(source_info2.sessionId, sink2_info.sessionId);
+
+    // Step 4: pass the source's session ID info back to the sink, so it can check it and
+    // update the symmetric keys so they're marked as authentication complete.
+    std::array<Arc, 2> auth_complete_result1;
+    ASSERT_EQ(OK, GetReturnError(sink1->authenticationComplete(
+                          source_info1.signature, sink1_info.sharedKeys, &auth_complete_result1)));
+    ASSERT_EQ((int)auth_complete_result1.size(), 2)
+            << "Expect two symmetric keys from authComplete()";
+    sink1_info.sharedKeys = auth_complete_result1;
+    std::array<Arc, 2> auth_complete_result2;
+    ASSERT_EQ(OK, GetReturnError(sink2->authenticationComplete(
+                          source_info2.signature, sink2_info.sharedKeys, &auth_complete_result2)));
+    ASSERT_EQ((int)auth_complete_result2.size(), 2)
+            << "Expect two symmetric keys from authComplete()";
+    sink2_info.sharedKeys = auth_complete_result2;
+}
+
+TEST_P(AuthGraphSessionTest, ParallelSource) {
+    std::shared_ptr<IAuthGraphKeyExchange> source1 = authNode_;
+    std::shared_ptr<IAuthGraphKeyExchange> source2 = authNode_;
+    std::shared_ptr<IAuthGraphKeyExchange> sink = authNode_;
+
+    // Step 1: create an ephemeral ECDH key at each of the sources.
+    SessionInitiationInfo source1_init_info;
+    ASSERT_EQ(OK, GetReturnError(source1->create(&source1_init_info)));
+    ASSERT_TRUE(source1_init_info.key.pubKey.has_value());
+    ASSERT_TRUE(source1_init_info.key.arcFromPBK.has_value());
+    SessionInitiationInfo source2_init_info;
+    ASSERT_EQ(OK, GetReturnError(source1->create(&source2_init_info)));
+    ASSERT_TRUE(source2_init_info.key.pubKey.has_value());
+    ASSERT_TRUE(source2_init_info.key.arcFromPBK.has_value());
+
+    // Step 2: pass each source's ECDH public key and other session info to the sink.
+    KeInitResult init1_result;
+    ASSERT_EQ(OK, GetReturnError(sink->init(source1_init_info.key.pubKey.value(),
+                                            source1_init_info.identity, source1_init_info.nonce,
+                                            source1_init_info.version, &init1_result)));
+    SessionInitiationInfo sink_init1_info = init1_result.sessionInitiationInfo;
+    ASSERT_TRUE(sink_init1_info.key.pubKey.has_value());
+
+    SessionInfo sink_info1 = init1_result.sessionInfo;
+    ASSERT_EQ((int)sink_info1.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
+    ASSERT_GT((int)sink_info1.sessionId.size(), 0) << "Expect non-empty session ID from sink";
+    std::vector<uint8_t> sink_signing_key1 = SigningKeyFromIdentity(sink_init1_info.identity);
+    CheckSignature(sink_signing_key1, sink_info1.sessionId, sink_info1.signature);
+
+    KeInitResult init2_result;
+    ASSERT_EQ(OK, GetReturnError(sink->init(source2_init_info.key.pubKey.value(),
+                                            source2_init_info.identity, source2_init_info.nonce,
+                                            source2_init_info.version, &init2_result)));
+    SessionInitiationInfo sink_init2_info = init2_result.sessionInitiationInfo;
+    ASSERT_TRUE(sink_init2_info.key.pubKey.has_value());
+
+    SessionInfo sink_info2 = init2_result.sessionInfo;
+    ASSERT_EQ((int)sink_info2.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
+    ASSERT_GT((int)sink_info2.sessionId.size(), 0) << "Expect non-empty session ID from sink";
+    std::vector<uint8_t> sink_signing_key2 = SigningKeyFromIdentity(sink_init2_info.identity);
+    CheckSignature(sink_signing_key2, sink_info2.sessionId, sink_info2.signature);
+
+    // Step 3: pass the sink's ECDH public keys and other session info to the each of the sources.
+    SessionInfo source1_info;
+    ASSERT_EQ(OK, GetReturnError(source1->finish(sink_init1_info.key.pubKey.value(),
+                                                 sink_init1_info.identity, sink_info1.signature,
+                                                 sink_init1_info.nonce, sink_init1_info.version,
+                                                 source1_init_info.key, &source1_info)));
+    ASSERT_EQ((int)source1_info.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
+    ASSERT_GT((int)source1_info.sessionId.size(), 0) << "Expect non-empty session ID from source";
+    std::vector<uint8_t> source1_signing_key = SigningKeyFromIdentity(source1_init_info.identity);
+    CheckSignature(source1_signing_key, source1_info.sessionId, source1_info.signature);
+
+    SessionInfo source2_info;
+    ASSERT_EQ(OK, GetReturnError(source2->finish(sink_init2_info.key.pubKey.value(),
+                                                 sink_init2_info.identity, sink_info2.signature,
+                                                 sink_init2_info.nonce, sink_init2_info.version,
+                                                 source2_init_info.key, &source2_info)));
+    ASSERT_EQ((int)source2_info.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
+    ASSERT_GT((int)source2_info.sessionId.size(), 0) << "Expect non-empty session ID from source";
+    std::vector<uint8_t> source2_signing_key = SigningKeyFromIdentity(source2_init_info.identity);
+    CheckSignature(source2_signing_key, source2_info.sessionId, source2_info.signature);
+
+    // Both ends should agree on the session ID.
+    ASSERT_EQ(source1_info.sessionId, sink_info1.sessionId);
+    ASSERT_EQ(source2_info.sessionId, sink_info2.sessionId);
+
+    // Step 4: pass the each source's session ID info back to the sink, so it can check it and
+    // update the symmetric keys so they're marked as authentication complete.
+    std::array<Arc, 2> auth_complete_result1;
+    ASSERT_EQ(OK, GetReturnError(sink->authenticationComplete(
+                          source1_info.signature, sink_info1.sharedKeys, &auth_complete_result1)));
+    ASSERT_EQ((int)auth_complete_result1.size(), 2)
+            << "Expect two symmetric keys from authComplete()";
+    sink_info1.sharedKeys = auth_complete_result1;
+    std::array<Arc, 2> auth_complete_result2;
+    ASSERT_EQ(OK, GetReturnError(sink->authenticationComplete(
+                          source2_info.signature, sink_info2.sharedKeys, &auth_complete_result2)));
+    ASSERT_EQ((int)auth_complete_result2.size(), 2)
+            << "Expect two symmetric keys from authComplete()";
+    sink_info2.sharedKeys = auth_complete_result2;
+}
+
+TEST_P(AuthGraphSessionTest, FreshNonces) {
+    std::shared_ptr<IAuthGraphKeyExchange> source = authNode_;
+    std::shared_ptr<IAuthGraphKeyExchange> sink = authNode_;
+
+    SessionInitiationInfo source_init_info1;
+    ASSERT_EQ(OK, GetReturnError(source->create(&source_init_info1)));
+    SessionInitiationInfo source_init_info2;
+    ASSERT_EQ(OK, GetReturnError(source->create(&source_init_info2)));
+
+    // Two calls to create() should result in the same identity but different nonce values.
+    ASSERT_EQ(source_init_info1.identity, source_init_info2.identity);
+    ASSERT_NE(source_init_info1.nonce, source_init_info2.nonce);
+    ASSERT_NE(source_init_info1.key.pubKey, source_init_info2.key.pubKey);
+    ASSERT_NE(source_init_info1.key.arcFromPBK, source_init_info2.key.arcFromPBK);
+
+    KeInitResult init_result1;
+    ASSERT_EQ(OK, GetReturnError(sink->init(source_init_info1.key.pubKey.value(),
+                                            source_init_info1.identity, source_init_info1.nonce,
+                                            source_init_info1.version, &init_result1)));
+    KeInitResult init_result2;
+    ASSERT_EQ(OK, GetReturnError(sink->init(source_init_info2.key.pubKey.value(),
+                                            source_init_info2.identity, source_init_info2.nonce,
+                                            source_init_info2.version, &init_result2)));
+
+    // Two calls to init() should result in the same identity buf different nonces and session IDs.
+    ASSERT_EQ(init_result1.sessionInitiationInfo.identity,
+              init_result2.sessionInitiationInfo.identity);
+    ASSERT_NE(init_result1.sessionInitiationInfo.nonce, init_result2.sessionInitiationInfo.nonce);
+    ASSERT_NE(init_result1.sessionInfo.sessionId, init_result2.sessionInfo.sessionId);
+}
+
+INSTANTIATE_TEST_SUITE_P(PerInstance, AuthGraphSessionTest,
+                         testing::ValuesIn(AuthGraphSessionTest::build_params()),
+                         ::android::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AuthGraphSessionTest);
+
+}  // namespace aidl::android::hardware::security::authgraph::test
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/security/authgraph/default/Android.bp b/security/authgraph/default/Android.bp
new file mode 100644
index 0000000..9de3bc1
--- /dev/null
+++ b/security/authgraph/default/Android.bp
@@ -0,0 +1,46 @@
+//
+// Copyright (C) 2023 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+rust_binary {
+    name: "android.hardware.security.authgraph-service.nonsecure",
+    relative_install_path: "hw",
+    vendor: true,
+    init_rc: ["authgraph.rc"],
+    vintf_fragments: ["authgraph.xml"],
+    defaults: [
+        "authgraph_use_latest_hal_aidl_rust",
+    ],
+    rustlibs: [
+        "libandroid_logger",
+        "libauthgraph_core",
+        "libauthgraph_boringssl",
+        "libauthgraph_hal",
+        "libbinder_rs",
+        "liblibc",
+        "liblog_rust",
+    ],
+    srcs: [
+        "src/main.rs",
+    ],
+}
diff --git a/security/authgraph/default/authgraph.rc b/security/authgraph/default/authgraph.rc
new file mode 100644
index 0000000..0222994
--- /dev/null
+++ b/security/authgraph/default/authgraph.rc
@@ -0,0 +1,5 @@
+service vendor.authgraph /vendor/bin/hw/android.hardware.security.authgraph-service.nonsecure
+    interface aidl android.hardware.security.authgraph.IAuthGraph/nonsecure
+    class hal
+    user nobody
+    group nobody
diff --git a/security/authgraph/default/authgraph.xml b/security/authgraph/default/authgraph.xml
new file mode 100644
index 0000000..9529a0a
--- /dev/null
+++ b/security/authgraph/default/authgraph.xml
@@ -0,0 +1,10 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.security.authgraph</name>
+        <version>1</version>
+        <interface>
+            <name>IAuthGraphKeyExchange</name>
+            <instance>nonsecure</instance>
+        </interface>
+    </hal>
+</manifest>
diff --git a/security/authgraph/default/src/main.rs b/security/authgraph/default/src/main.rs
new file mode 100644
index 0000000..2112e58
--- /dev/null
+++ b/security/authgraph/default/src/main.rs
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+//! Default implementation of the AuthGraph key exchange HAL.
+//!
+//! This implementation of the HAL is only intended to allow testing and policy compliance.  A real
+//! implementation of the AuthGraph HAL would be implemented in a secure environment, and would not
+//! be independently registered with service manager (a secure component that uses AuthGraph would
+//! expose an entrypoint that allowed retrieval of the specific IAuthGraphKeyExchange instance that
+//! is correlated with the component).
+
+use android_hardware_security_authgraph::aidl::android::hardware::security::authgraph::{
+    Arc::Arc, IAuthGraphKeyExchange::BnAuthGraphKeyExchange,
+    IAuthGraphKeyExchange::IAuthGraphKeyExchange, Identity::Identity, KeInitResult::KeInitResult,
+    Key::Key, PubKey::PubKey, SessionIdSignature::SessionIdSignature, SessionInfo::SessionInfo,
+    SessionInitiationInfo::SessionInitiationInfo,
+};
+use authgraph_boringssl as boring;
+use authgraph_core::{key::MillisecondsSinceEpoch, keyexchange as ke, traits};
+use authgraph_hal::{err_to_binder, Innto, TryInnto};
+use log::{error, info};
+use std::ffi::CString;
+use std::sync::Mutex;
+
+static SERVICE_NAME: &str = "android.hardware.security.authgraph.IAuthGraphKeyExchange";
+static SERVICE_INSTANCE: &str = "nonsecure";
+
+/// Local error type for failures in the HAL service.
+#[derive(Debug, Clone)]
+struct HalServiceError(String);
+
+impl From<String> for HalServiceError {
+    fn from(s: String) -> Self {
+        Self(s)
+    }
+}
+
+fn main() {
+    if let Err(e) = inner_main() {
+        panic!("HAL service failed: {:?}", e);
+    }
+}
+
+fn inner_main() -> Result<(), HalServiceError> {
+    // Initialize Android logging.
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("authgraph-hal-nonsecure")
+            .with_min_level(log::Level::Info)
+            .with_log_id(android_logger::LogId::System),
+    );
+    // Redirect panic messages to logcat.
+    std::panic::set_hook(Box::new(|panic_info| {
+        error!("{}", panic_info);
+    }));
+
+    info!("Insecure AuthGraph key exchange HAL service is starting.");
+
+    info!("Starting thread pool now.");
+    binder::ProcessState::start_thread_pool();
+
+    // Register the service
+    let service = AuthGraphService::new_as_binder();
+    let service_name = format!("{}/{}", SERVICE_NAME, SERVICE_INSTANCE);
+    binder::add_service(&service_name, service.as_binder()).map_err(|e| {
+        format!(
+            "Failed to register service {} because of {:?}.",
+            service_name, e
+        )
+    })?;
+
+    info!("Successfully registered AuthGraph HAL services.");
+    binder::ProcessState::join_thread_pool();
+    info!("AuthGraph HAL service is terminating."); // should not reach here
+    Ok(())
+}
+
+/// Non-secure implementation of the AuthGraph key exchange service.
+struct AuthGraphService {
+    imp: Mutex<traits::TraitImpl>,
+}
+
+impl AuthGraphService {
+    /// Create a new instance.
+    fn new() -> Self {
+        Self {
+            imp: Mutex::new(traits::TraitImpl {
+                aes_gcm: Box::new(boring::BoringAes),
+                ecdh: Box::new(boring::BoringEcDh),
+                ecdsa: Box::new(boring::BoringEcDsa),
+                hmac: Box::new(boring::BoringHmac),
+                hkdf: Box::new(boring::BoringHkdf),
+                sha256: Box::new(boring::BoringSha256),
+                rng: Box::new(boring::BoringRng),
+                device: Box::<boring::test_device::AgDevice>::default(),
+                clock: Some(Box::new(StdClock)),
+            }),
+        }
+    }
+
+    /// Create a new instance wrapped in a proxy object.
+    pub fn new_as_binder() -> binder::Strong<dyn IAuthGraphKeyExchange> {
+        BnAuthGraphKeyExchange::new_binder(Self::new(), binder::BinderFeatures::default())
+    }
+}
+
+impl binder::Interface for AuthGraphService {}
+
+/// Extract (and require) an unsigned public key as bytes from a [`PubKey`].
+fn unsigned_pub_key(pub_key: &PubKey) -> binder::Result<&[u8]> {
+    match pub_key {
+        PubKey::PlainKey(key) => Ok(&key.plainPubKey),
+        PubKey::SignedKey(_) => Err(binder::Status::new_exception(
+            binder::ExceptionCode::ILLEGAL_ARGUMENT,
+            Some(&CString::new("expected unsigned public key").unwrap()),
+        )),
+    }
+}
+
+/// This nonsecure implementation of the AuthGraph HAL interface directly calls the AuthGraph
+/// reference implementation library code; a real implementation requires the AuthGraph
+/// code to run in a secure environment, not within Android.
+impl IAuthGraphKeyExchange for AuthGraphService {
+    fn create(&self) -> binder::Result<SessionInitiationInfo> {
+        info!("create()");
+        let mut imp = self.imp.lock().unwrap();
+        let info = ke::create(&mut *imp).map_err(err_to_binder)?;
+        Ok(info.innto())
+    }
+    fn init(
+        &self,
+        peer_pub_key: &PubKey,
+        peer_id: &Identity,
+        peer_nonce: &[u8],
+        peer_version: i32,
+    ) -> binder::Result<KeInitResult> {
+        info!("init(v={peer_version})");
+        let mut imp = self.imp.lock().unwrap();
+        let peer_pub_key = unsigned_pub_key(peer_pub_key)?;
+        let result = ke::init(
+            &mut *imp,
+            peer_pub_key,
+            &peer_id.identity,
+            &peer_nonce,
+            peer_version,
+        )
+        .map_err(err_to_binder)?;
+        Ok(result.innto())
+    }
+
+    fn finish(
+        &self,
+        peer_pub_key: &PubKey,
+        peer_id: &Identity,
+        peer_signature: &SessionIdSignature,
+        peer_nonce: &[u8],
+        peer_version: i32,
+        own_key: &Key,
+    ) -> binder::Result<SessionInfo> {
+        info!("finish(v={peer_version})");
+        let mut imp = self.imp.lock().unwrap();
+        let peer_pub_key = unsigned_pub_key(peer_pub_key)?;
+        let own_key: Key = own_key.clone();
+        let own_key: authgraph_core::key::Key = own_key.try_innto()?;
+        let session_info = ke::finish(
+            &mut *imp,
+            peer_pub_key,
+            &peer_id.identity,
+            &peer_signature.signature,
+            &peer_nonce,
+            peer_version,
+            own_key,
+        )
+        .map_err(err_to_binder)?;
+        Ok(session_info.innto())
+    }
+
+    fn authenticationComplete(
+        &self,
+        peer_signature: &SessionIdSignature,
+        shared_keys: &[Arc; 2],
+    ) -> binder::Result<[Arc; 2]> {
+        info!("authComplete()");
+        let mut imp = self.imp.lock().unwrap();
+        let shared_keys = [shared_keys[0].arc.clone(), shared_keys[1].arc.clone()];
+        let arcs = ke::authentication_complete(&mut *imp, &peer_signature.signature, shared_keys)
+            .map_err(err_to_binder)?;
+        Ok(arcs.map(|arc| Arc { arc }))
+    }
+}
+
+/// Monotonic clock.
+#[derive(Default)]
+pub struct StdClock;
+
+impl traits::MonotonicClock for StdClock {
+    fn now(&self) -> authgraph_core::key::MillisecondsSinceEpoch {
+        let mut time = libc::timespec {
+            tv_sec: 0,  // libc::time_t
+            tv_nsec: 0, // libc::c_long
+        };
+        let rc =
+        // Safety: `time` is a valid structure.
+            unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut time as *mut libc::timespec) };
+        if rc < 0 {
+            log::warn!("failed to get time!");
+            return MillisecondsSinceEpoch(0);
+        }
+        // The types in `libc::timespec` may be different on different architectures,
+        // so allow conversion to `i64`.
+        #[allow(clippy::unnecessary_cast)]
+        MillisecondsSinceEpoch((time.tv_sec as i64 * 1000) + (time.tv_nsec as i64 / 1000 / 1000))
+    }
+}