Merge changes from topic "ag-wire" into main
* changes:
AuthGraph: add per-role VTS tests
AuthGraph: add fuzzer
AuthGraph: move code into library
AuthGraph: reduce dependency on authgraph_core
diff --git a/security/authgraph/aidl/vts/functional/Android.bp b/security/authgraph/aidl/vts/functional/Android.bp
index fc13759..0e3480f 100644
--- a/security/authgraph/aidl/vts/functional/Android.bp
+++ b/security/authgraph/aidl/vts/functional/Android.bp
@@ -46,3 +46,36 @@
"vts",
],
}
+
+rust_test {
+ name: "VtsAidlAuthGraphRoleTest",
+ srcs: ["role_test.rs"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+ defaults: [
+ "authgraph_use_latest_hal_aidl_rust",
+ ],
+ rustlibs: [
+ "libauthgraph_vts_test",
+ "libbinder_rs",
+ ],
+}
+
+rust_library {
+ name: "libauthgraph_vts_test",
+ crate_name: "authgraph_vts_test",
+ srcs: ["lib.rs"],
+ defaults: [
+ "authgraph_use_latest_hal_aidl_rust",
+ ],
+ rustlibs: [
+ "libauthgraph_boringssl",
+ "libauthgraph_core",
+ "libauthgraph_hal",
+ "libauthgraph_nonsecure",
+ "libbinder_rs",
+ "libcoset",
+ ],
+}
diff --git a/security/authgraph/aidl/vts/functional/lib.rs b/security/authgraph/aidl/vts/functional/lib.rs
new file mode 100644
index 0000000..7b9b2b9
--- /dev/null
+++ b/security/authgraph/aidl/vts/functional/lib.rs
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+//! VTS test library for AuthGraph functionality.
+//!
+//! This test code is bundled as a library, not as `[cfg(test)]`, to allow it to be
+//! re-used inside the (Rust) VTS tests of components that use AuthGraph.
+
+use android_hardware_security_authgraph::aidl::android::hardware::security::authgraph::{
+ Error::Error, IAuthGraphKeyExchange::IAuthGraphKeyExchange, Identity::Identity,
+ PlainPubKey::PlainPubKey, PubKey::PubKey, SessionIdSignature::SessionIdSignature,
+};
+use authgraph_boringssl as boring;
+use authgraph_core::keyexchange as ke;
+use authgraph_core::{arc, key, traits};
+use authgraph_nonsecure::StdClock;
+use coset::CborSerializable;
+
+pub mod sink;
+pub mod source;
+
+/// Return a collection of AuthGraph trait implementations suitable for testing.
+pub fn test_impls() -> traits::TraitImpl {
+ // Note that the local implementation is using a clock with a potentially different epoch than
+ // the implementation under test.
+ boring::trait_impls(
+ Box::<boring::test_device::AgDevice>::default(),
+ Some(Box::new(StdClock::default())),
+ )
+}
+
+fn build_plain_pub_key(pub_key: &Option<Vec<u8>>) -> PubKey {
+ PubKey::PlainKey(PlainPubKey {
+ plainPubKey: pub_key.clone().unwrap(),
+ })
+}
+
+fn extract_plain_pub_key(pub_key: &Option<PubKey>) -> &PlainPubKey {
+ match pub_key {
+ Some(PubKey::PlainKey(pub_key)) => pub_key,
+ Some(PubKey::SignedKey(_)) => panic!("expect unsigned public key"),
+ None => panic!("expect pubKey to be populated"),
+ }
+}
+
+fn verification_key_from_identity(impls: &traits::TraitImpl, identity: &[u8]) -> key::EcVerifyKey {
+ let identity = key::Identity::from_slice(identity).expect("invalid identity CBOR");
+ impls
+ .device
+ .process_peer_cert_chain(&identity.cert_chain, &*impls.ecdsa)
+ .expect("failed to extract signing key")
+}
+
+fn vec_to_identity(data: &[u8]) -> Identity {
+ Identity {
+ identity: data.to_vec(),
+ }
+}
+
+fn vec_to_signature(data: &[u8]) -> SessionIdSignature {
+ SessionIdSignature {
+ signature: data.to_vec(),
+ }
+}
+
+/// Decrypt a pair of AES-256 keys encrypted with the AuthGraph PBK.
+pub fn decipher_aes_keys(imp: &traits::TraitImpl, arc: &[Vec<u8>; 2]) -> [key::AesKey; 2] {
+ [
+ decipher_aes_key(imp, &arc[0]),
+ decipher_aes_key(imp, &arc[1]),
+ ]
+}
+
+/// Decrypt an AES-256 key encrypted with the AuthGraph PBK.
+pub fn decipher_aes_key(imp: &traits::TraitImpl, arc: &[u8]) -> key::AesKey {
+ let pbk = imp.device.get_per_boot_key().expect("no PBK available");
+ let arc::ArcContent {
+ payload,
+ protected_headers: _,
+ unprotected_headers: _,
+ } = arc::decipher_arc(&pbk, arc, &*imp.aes_gcm).expect("failed to decrypt arc");
+ assert_eq!(payload.0.len(), 32);
+ let mut key = key::AesKey([0; 32]);
+ key.0.copy_from_slice(&payload.0);
+ assert_ne!(key.0, [0; 32], "agreed AES-256 key should be non-zero");
+ key
+}
diff --git a/security/authgraph/aidl/vts/functional/role_test.rs b/security/authgraph/aidl/vts/functional/role_test.rs
new file mode 100644
index 0000000..e95361a
--- /dev/null
+++ b/security/authgraph/aidl/vts/functional/role_test.rs
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+//! Tests of individual AuthGraph role (source or sink) functionality.
+
+#![cfg(test)]
+
+use authgraph_vts_test as vts;
+use android_hardware_security_authgraph::aidl::android::hardware::security::authgraph::{
+ IAuthGraphKeyExchange::IAuthGraphKeyExchange,
+};
+
+const AUTH_GRAPH_NONSECURE: &str =
+ "android.hardware.security.authgraph.IAuthGraphKeyExchange/nonsecure";
+
+/// Retrieve the /nonsecure instance of AuthGraph, which supports both sink and source roles.
+fn get_nonsecure() -> Option<binder::Strong<dyn IAuthGraphKeyExchange>> {
+ binder::get_interface(AUTH_GRAPH_NONSECURE).ok()
+}
+
+/// Macro to require availability of a /nonsecure instance of AuthGraph.
+///
+/// Note that this macro triggers `return` if not found.
+macro_rules! require_nonsecure {
+ {} => {
+ match get_nonsecure() {
+ Some(v) => v,
+ None => {
+ eprintln!("Skipping test as no /nonsecure impl found");
+ return;
+ }
+ }
+ }
+}
+
+#[test]
+fn test_nonsecure_source_mainline() {
+ let mut impls = vts::test_impls();
+ vts::source::test_mainline(&mut impls, require_nonsecure!());
+}
+#[test]
+fn test_nonsecure_source_corrupt_sig() {
+ let mut impls = vts::test_impls();
+ vts::source::test_corrupt_sig(&mut impls, require_nonsecure!());
+}
+#[test]
+fn test_nonsecure_source_corrupt_keys() {
+ let mut impls = vts::test_impls();
+ vts::source::test_corrupt_key(&mut impls, require_nonsecure!());
+}
+#[test]
+fn test_nonsecure_sink_mainline() {
+ let mut impls = vts::test_impls();
+ vts::sink::test_mainline(&mut impls, require_nonsecure!());
+}
+#[test]
+fn test_nonsecure_sink_corrupt_sig() {
+ let mut impls = vts::test_impls();
+ vts::sink::test_corrupt_sig(&mut impls, require_nonsecure!());
+}
+#[test]
+fn test_nonsecure_sink_corrupt_keys() {
+ let mut impls = vts::test_impls();
+ vts::sink::test_corrupt_keys(&mut impls, require_nonsecure!());
+}
diff --git a/security/authgraph/aidl/vts/functional/sink.rs b/security/authgraph/aidl/vts/functional/sink.rs
new file mode 100644
index 0000000..5c81593
--- /dev/null
+++ b/security/authgraph/aidl/vts/functional/sink.rs
@@ -0,0 +1,214 @@
+/*
+ * 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.
+ */
+
+//! VTS tests for sinks
+use super::*;
+use authgraph_core::traits;
+
+/// Run AuthGraph tests against the provided sink, using a local test source implementation.
+pub fn test(impls: &mut traits::TraitImpl, sink: binder::Strong<dyn IAuthGraphKeyExchange>) {
+ test_mainline(impls, sink.clone());
+ test_corrupt_sig(impls, sink.clone());
+ test_corrupt_keys(impls, sink);
+}
+
+/// Perform mainline AuthGraph key exchange with the provided sink and local implementation.
+/// Return the agreed AES keys in plaintext.
+pub fn test_mainline(
+ impls: &mut traits::TraitImpl,
+ sink: binder::Strong<dyn IAuthGraphKeyExchange>,
+) -> [key::AesKey; 2] {
+ // Step 1: create an ephemeral ECDH key at the (local) source.
+ let source_init_info = ke::create(impls).expect("failed to create() with local impl");
+
+ // Step 2: pass the source's ECDH public key and other session info to the (remote) sink.
+ let init_result = sink
+ .init(
+ &build_plain_pub_key(&source_init_info.ke_key.pub_key),
+ &vec_to_identity(&source_init_info.identity),
+ &source_init_info.nonce,
+ source_init_info.version,
+ )
+ .expect("failed to init() with remote impl");
+ let sink_init_info = init_result.sessionInitiationInfo;
+ let sink_pub_key = extract_plain_pub_key(&sink_init_info.key.pubKey);
+
+ let sink_info = init_result.sessionInfo;
+ assert!(!sink_info.sessionId.is_empty());
+
+ // The AuthGraph core library will verify the session ID signature, but do it here too.
+ let sink_verification_key =
+ verification_key_from_identity(&impls, &sink_init_info.identity.identity);
+ ke::verify_signature_on_session_id(
+ &sink_verification_key,
+ &sink_info.sessionId,
+ &sink_info.signature.signature,
+ &*impls.ecdsa,
+ )
+ .expect("failed verification of signed session ID");
+
+ // Step 3: pass the sink's ECDH public key and other session info to the (local) source, so it
+ // can calculate the same pair of symmetric keys.
+ let source_info = ke::finish(
+ impls,
+ &sink_pub_key.plainPubKey,
+ &sink_init_info.identity.identity,
+ &sink_info.signature.signature,
+ &sink_init_info.nonce,
+ sink_init_info.version,
+ source_init_info.ke_key,
+ )
+ .expect("failed to finish() with local impl");
+ assert!(!source_info.session_id.is_empty());
+
+ // The AuthGraph core library will verify the session ID signature, but do it here too.
+ let source_verification_key =
+ verification_key_from_identity(&impls, &source_init_info.identity);
+ ke::verify_signature_on_session_id(
+ &source_verification_key,
+ &source_info.session_id,
+ &source_info.session_id_signature,
+ &*impls.ecdsa,
+ )
+ .expect("failed verification of signed session ID");
+
+ // Both ends should agree on the session ID.
+ assert_eq!(source_info.session_id, sink_info.sessionId);
+
+ // Step 4: pass the (local) source's session ID signature back to the sink, so it can check it
+ // and update the symmetric keys so they're marked as authentication complete.
+ let _sink_arcs = sink
+ .authenticationComplete(
+ &vec_to_signature(&source_info.session_id_signature),
+ &sink_info.sharedKeys,
+ )
+ .expect("failed to authenticationComplete() with remote sink");
+
+ // Decrypt and return the session keys.
+ decipher_aes_keys(&impls, &source_info.shared_keys)
+}
+
+/// Perform mainline AuthGraph key exchange with the provided sink, but provide an invalid
+/// session ID signature.
+pub fn test_corrupt_sig(
+ impls: &mut traits::TraitImpl,
+ sink: binder::Strong<dyn IAuthGraphKeyExchange>,
+) {
+ // Step 1: create an ephemeral ECDH key at the (local) source.
+ let source_init_info = ke::create(impls).expect("failed to create() with local impl");
+
+ // Step 2: pass the source's ECDH public key and other session info to the (remote) sink.
+ let init_result = sink
+ .init(
+ &build_plain_pub_key(&source_init_info.ke_key.pub_key),
+ &vec_to_identity(&source_init_info.identity),
+ &source_init_info.nonce,
+ source_init_info.version,
+ )
+ .expect("failed to init() with remote impl");
+ let sink_init_info = init_result.sessionInitiationInfo;
+ let sink_pub_key = extract_plain_pub_key(&sink_init_info.key.pubKey);
+
+ let sink_info = init_result.sessionInfo;
+ assert!(!sink_info.sessionId.is_empty());
+
+ // Step 3: pass the sink's ECDH public key and other session info to the (local) source, so it
+ // can calculate the same pair of symmetric keys.
+ let source_info = ke::finish(
+ impls,
+ &sink_pub_key.plainPubKey,
+ &sink_init_info.identity.identity,
+ &sink_info.signature.signature,
+ &sink_init_info.nonce,
+ sink_init_info.version,
+ source_init_info.ke_key,
+ )
+ .expect("failed to finish() with local impl");
+ assert!(!source_info.session_id.is_empty());
+
+ // Build a corrupted version of the (local) source's session ID signature.
+ let mut corrupt_signature = source_info.session_id_signature.clone();
+ let sig_len = corrupt_signature.len();
+ corrupt_signature[sig_len - 1] ^= 0x01;
+
+ // Step 4: pass the (local) source's **invalid** session ID signature back to the sink,
+ // which should reject it.
+ let result =
+ sink.authenticationComplete(&vec_to_signature(&corrupt_signature), &sink_info.sharedKeys);
+ let err = result.expect_err("expect failure with corrupt signature");
+ assert_eq!(
+ err,
+ binder::Status::new_service_specific_error(Error::INVALID_SIGNATURE.0, None)
+ );
+}
+
+/// Perform mainline AuthGraph key exchange with the provided sink, but provide an invalid
+/// Arc for the sink's key.
+pub fn test_corrupt_keys(
+ impls: &mut traits::TraitImpl,
+ sink: binder::Strong<dyn IAuthGraphKeyExchange>,
+) {
+ // Step 1: create an ephemeral ECDH key at the (local) source.
+ let source_init_info = ke::create(impls).expect("failed to create() with local impl");
+
+ // Step 2: pass the source's ECDH public key and other session info to the (remote) sink.
+ let init_result = sink
+ .init(
+ &build_plain_pub_key(&source_init_info.ke_key.pub_key),
+ &vec_to_identity(&source_init_info.identity),
+ &source_init_info.nonce,
+ source_init_info.version,
+ )
+ .expect("failed to init() with remote impl");
+ let sink_init_info = init_result.sessionInitiationInfo;
+ let sink_pub_key = extract_plain_pub_key(&sink_init_info.key.pubKey);
+
+ let sink_info = init_result.sessionInfo;
+ assert!(!sink_info.sessionId.is_empty());
+
+ // Step 3: pass the sink's ECDH public key and other session info to the (local) source, so it
+ // can calculate the same pair of symmetric keys.
+ let source_info = ke::finish(
+ impls,
+ &sink_pub_key.plainPubKey,
+ &sink_init_info.identity.identity,
+ &sink_info.signature.signature,
+ &sink_init_info.nonce,
+ sink_init_info.version,
+ source_init_info.ke_key,
+ )
+ .expect("failed to finish() with local impl");
+ assert!(!source_info.session_id.is_empty());
+
+ // Deliberately corrupt the sink's shared key Arcs before returning them
+ let mut corrupt_keys = sink_info.sharedKeys.clone();
+ let len0 = corrupt_keys[0].arc.len();
+ let len1 = corrupt_keys[1].arc.len();
+ corrupt_keys[0].arc[len0 - 1] ^= 0x01;
+ corrupt_keys[1].arc[len1 - 1] ^= 0x01;
+
+ // Step 4: pass the (local) source's session ID signature back to the sink, but with corrupted
+ // keys, which should be rejected.
+ let result = sink.authenticationComplete(
+ &vec_to_signature(&source_info.session_id_signature),
+ &corrupt_keys,
+ );
+ let err = result.expect_err("expect failure with corrupt keys");
+ assert_eq!(
+ err,
+ binder::Status::new_service_specific_error(Error::INVALID_SHARED_KEY_ARCS.0, None)
+ );
+}
diff --git a/security/authgraph/aidl/vts/functional/source.rs b/security/authgraph/aidl/vts/functional/source.rs
new file mode 100644
index 0000000..9aaaaee
--- /dev/null
+++ b/security/authgraph/aidl/vts/functional/source.rs
@@ -0,0 +1,244 @@
+/*
+ * 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.
+ */
+
+//! VTS tests for sources
+use super::*;
+use authgraph_core::traits;
+
+/// Run AuthGraph tests against the provided source, using a local test sink implementation.
+pub fn test(impls: &mut traits::TraitImpl, source: binder::Strong<dyn IAuthGraphKeyExchange>) {
+ test_mainline(impls, source.clone());
+ test_corrupt_sig(impls, source.clone());
+ test_corrupt_key(impls, source);
+}
+
+/// Perform mainline AuthGraph key exchange with the provided source.
+/// Return the agreed AES keys in plaintext.
+pub fn test_mainline(
+ impls: &mut traits::TraitImpl,
+ source: binder::Strong<dyn IAuthGraphKeyExchange>,
+) -> [key::AesKey; 2] {
+ // Step 1: create an ephemeral ECDH key at the (remote) source.
+ let source_init_info = source
+ .create()
+ .expect("failed to create() with remote impl");
+ assert!(source_init_info.key.pubKey.is_some());
+ assert!(source_init_info.key.arcFromPBK.is_some());
+ let source_pub_key = extract_plain_pub_key(&source_init_info.key.pubKey);
+
+ // Step 2: pass the source's ECDH public key and other session info to the (local) sink.
+ let init_result = ke::init(
+ impls,
+ &source_pub_key.plainPubKey,
+ &source_init_info.identity.identity,
+ &source_init_info.nonce,
+ source_init_info.version,
+ )
+ .expect("failed to init() with local impl");
+ let sink_init_info = init_result.session_init_info;
+ let sink_pub_key = sink_init_info
+ .ke_key
+ .pub_key
+ .expect("expect pub_key to be populated");
+
+ let sink_info = init_result.session_info;
+ assert!(!sink_info.session_id.is_empty());
+
+ // The AuthGraph core library will verify the session ID signature, but do it here too.
+ let sink_verification_key = verification_key_from_identity(&impls, &sink_init_info.identity);
+ ke::verify_signature_on_session_id(
+ &sink_verification_key,
+ &sink_info.session_id,
+ &sink_info.session_id_signature,
+ &*impls.ecdsa,
+ )
+ .expect("failed verification of signed session ID");
+
+ // Step 3: pass the sink's ECDH public key and other session info to the (remote) source, so it
+ // can calculate the same pair of symmetric keys.
+ let source_info = source
+ .finish(
+ &PubKey::PlainKey(PlainPubKey {
+ plainPubKey: sink_pub_key,
+ }),
+ &Identity {
+ identity: sink_init_info.identity,
+ },
+ &vec_to_signature(&sink_info.session_id_signature),
+ &sink_init_info.nonce,
+ sink_init_info.version,
+ &source_init_info.key,
+ )
+ .expect("failed to finish() with remote impl");
+ assert!(!source_info.sessionId.is_empty());
+
+ // The AuthGraph core library will verify the session ID signature, but do it here too.
+ let source_verification_key =
+ verification_key_from_identity(&impls, &source_init_info.identity.identity);
+ ke::verify_signature_on_session_id(
+ &source_verification_key,
+ &source_info.sessionId,
+ &source_info.signature.signature,
+ &*impls.ecdsa,
+ )
+ .expect("failed verification of signed session ID");
+
+ // Both ends should agree on the session ID.
+ assert_eq!(source_info.sessionId, sink_info.session_id);
+
+ // Step 4: pass the (remote) source's session ID signature back to the sink, so it can check it
+ // and update the symmetric keys so they're marked as authentication complete.
+ let sink_arcs = ke::authentication_complete(
+ impls,
+ &source_info.signature.signature,
+ sink_info.shared_keys,
+ )
+ .expect("failed to authenticationComplete() with local sink");
+
+ // Decrypt and return the session keys.
+ decipher_aes_keys(&impls, &sink_arcs)
+}
+
+/// Perform mainline AuthGraph key exchange with the provided source, but provide an invalid session
+/// ID signature.
+pub fn test_corrupt_sig(
+ impls: &mut traits::TraitImpl,
+ source: binder::Strong<dyn IAuthGraphKeyExchange>,
+) {
+ // Step 1: create an ephemeral ECDH key at the (remote) source.
+ let source_init_info = source
+ .create()
+ .expect("failed to create() with remote impl");
+ assert!(source_init_info.key.pubKey.is_some());
+ assert!(source_init_info.key.arcFromPBK.is_some());
+ let source_pub_key = extract_plain_pub_key(&source_init_info.key.pubKey);
+
+ // Step 2: pass the source's ECDH public key and other session info to the (local) sink.
+ let init_result = ke::init(
+ impls,
+ &source_pub_key.plainPubKey,
+ &source_init_info.identity.identity,
+ &source_init_info.nonce,
+ source_init_info.version,
+ )
+ .expect("failed to init() with local impl");
+ let sink_init_info = init_result.session_init_info;
+ let sink_pub_key = sink_init_info
+ .ke_key
+ .pub_key
+ .expect("expect pub_key to be populated");
+ let sink_info = init_result.session_info;
+ assert!(!sink_info.session_id.is_empty());
+
+ // Deliberately corrupt the sink's session ID signature.
+ let mut corrupt_signature = sink_info.session_id_signature.clone();
+ let sig_len = corrupt_signature.len();
+ corrupt_signature[sig_len - 1] ^= 0x01;
+
+ // Step 3: pass the sink's ECDH public key and other session info to the (remote) source, so it
+ // can calculate the same pair of symmetric keys.
+ let result = source.finish(
+ &PubKey::PlainKey(PlainPubKey {
+ plainPubKey: sink_pub_key,
+ }),
+ &Identity {
+ identity: sink_init_info.identity,
+ },
+ &vec_to_signature(&corrupt_signature),
+ &sink_init_info.nonce,
+ sink_init_info.version,
+ &source_init_info.key,
+ );
+ let err = result.expect_err("expect failure with corrupt signature");
+ assert_eq!(
+ err,
+ binder::Status::new_service_specific_error(Error::INVALID_SIGNATURE.0, None)
+ );
+}
+
+/// Perform mainline AuthGraph key exchange with the provided source, but give it back
+/// a corrupted key.
+pub fn test_corrupt_key(
+ impls: &mut traits::TraitImpl,
+ source: binder::Strong<dyn IAuthGraphKeyExchange>,
+) {
+ // Step 1: create an ephemeral ECDH key at the (remote) source.
+ let source_init_info = source
+ .create()
+ .expect("failed to create() with remote impl");
+ assert!(source_init_info.key.pubKey.is_some());
+ assert!(source_init_info.key.arcFromPBK.is_some());
+ let source_pub_key = extract_plain_pub_key(&source_init_info.key.pubKey);
+
+ // Step 2: pass the source's ECDH public key and other session info to the (local) sink.
+ let init_result = ke::init(
+ impls,
+ &source_pub_key.plainPubKey,
+ &source_init_info.identity.identity,
+ &source_init_info.nonce,
+ source_init_info.version,
+ )
+ .expect("failed to init() with local impl");
+ let sink_init_info = init_result.session_init_info;
+ let sink_pub_key = sink_init_info
+ .ke_key
+ .pub_key
+ .expect("expect pub_key to be populated");
+
+ let sink_info = init_result.session_info;
+ assert!(!sink_info.session_id.is_empty());
+
+ // The AuthGraph core library will verify the session ID signature, but do it here too.
+ let sink_verification_key = verification_key_from_identity(&impls, &sink_init_info.identity);
+ ke::verify_signature_on_session_id(
+ &sink_verification_key,
+ &sink_info.session_id,
+ &sink_info.session_id_signature,
+ &*impls.ecdsa,
+ )
+ .expect("failed verification of signed session ID");
+
+ // Deliberately corrupt the source's encrypted key.
+ let mut corrupt_key = source_init_info.key.clone();
+ match &mut corrupt_key.arcFromPBK {
+ Some(a) => {
+ let len = a.arc.len();
+ a.arc[len - 1] ^= 0x01;
+ }
+ None => panic!("no arc data"),
+ }
+
+ // Step 3: pass the sink's ECDH public key and other session info to the (remote) source, but
+ // give it back a corrupted version of its own key.
+ let result = source.finish(
+ &PubKey::PlainKey(PlainPubKey {
+ plainPubKey: sink_pub_key,
+ }),
+ &Identity {
+ identity: sink_init_info.identity,
+ },
+ &vec_to_signature(&sink_info.session_id_signature),
+ &sink_init_info.nonce,
+ sink_init_info.version,
+ &corrupt_key,
+ );
+
+ let err = result.expect_err("expect failure with corrupt signature");
+ assert_eq!(
+ err,
+ binder::Status::new_service_specific_error(Error::INVALID_PRIV_KEY_ARC_IN_KEY.0, None)
+ );
+}
diff --git a/security/authgraph/default/Android.bp b/security/authgraph/default/Android.bp
index 9de3bc1..c481075 100644
--- a/security/authgraph/default/Android.bp
+++ b/security/authgraph/default/Android.bp
@@ -22,6 +22,26 @@
default_applicable_licenses: ["hardware_interfaces_license"],
}
+rust_library {
+ name: "libauthgraph_nonsecure",
+ crate_name: "authgraph_nonsecure",
+ defaults: [
+ "authgraph_use_latest_hal_aidl_rust",
+ ],
+ vendor_available: true,
+ rustlibs: [
+ "libandroid_logger",
+ "libauthgraph_boringssl",
+ "libauthgraph_core",
+ "libauthgraph_hal",
+ "libbinder_rs",
+ "liblibc",
+ "liblog_rust",
+ ],
+ srcs: ["src/lib.rs"],
+
+}
+
rust_binary {
name: "android.hardware.security.authgraph-service.nonsecure",
relative_install_path: "hw",
@@ -33,9 +53,8 @@
],
rustlibs: [
"libandroid_logger",
- "libauthgraph_core",
- "libauthgraph_boringssl",
"libauthgraph_hal",
+ "libauthgraph_nonsecure",
"libbinder_rs",
"liblibc",
"liblog_rust",
@@ -44,3 +63,20 @@
"src/main.rs",
],
}
+
+rust_fuzz {
+ name: "android.hardware.authgraph-service.nonsecure_fuzzer",
+ rustlibs: [
+ "libauthgraph_hal",
+ "libauthgraph_nonsecure",
+ "libbinder_random_parcel_rs",
+ "libbinder_rs",
+ ],
+ srcs: ["src/fuzzer.rs"],
+ fuzz_config: {
+ cc: [
+ "drysdale@google.com",
+ "hasinitg@google.com",
+ ],
+ },
+}
diff --git a/security/authgraph/default/src/fuzzer.rs b/security/authgraph/default/src/fuzzer.rs
new file mode 100644
index 0000000..6a9cfdd
--- /dev/null
+++ b/security/authgraph/default/src/fuzzer.rs
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+#![allow(missing_docs)]
+#![no_main]
+extern crate libfuzzer_sys;
+
+use authgraph_hal::service::AuthGraphService;
+use authgraph_nonsecure::LocalTa;
+use binder_random_parcel_rs::fuzz_service;
+use libfuzzer_sys::fuzz_target;
+use std::sync::{Arc, Mutex};
+
+fuzz_target!(|data: &[u8]| {
+ let local_ta = LocalTa::new();
+ let service = AuthGraphService::new_as_binder(Arc::new(Mutex::new(local_ta)));
+ fuzz_service(&mut service.as_binder(), data);
+});
diff --git a/security/authgraph/default/src/lib.rs b/security/authgraph/default/src/lib.rs
new file mode 100644
index 0000000..4cd0cb7
--- /dev/null
+++ b/security/authgraph/default/src/lib.rs
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+//! Common functionality for non-secure/testing instance of AuthGraph.
+
+use authgraph_boringssl as boring;
+use authgraph_core::{
+ key::MillisecondsSinceEpoch,
+ ta::{AuthGraphTa, Role},
+ traits,
+};
+use authgraph_hal::channel::SerializedChannel;
+use std::sync::{Arc, Mutex};
+use std::time::Instant;
+
+/// Monotonic clock with an epoch that starts at the point of construction.
+/// (This makes it unsuitable for use outside of testing, because the epoch
+/// will not match that of any other component.)
+pub struct StdClock(Instant);
+
+impl Default for StdClock {
+ fn default() -> Self {
+ Self(Instant::now())
+ }
+}
+
+impl traits::MonotonicClock for StdClock {
+ fn now(&self) -> MillisecondsSinceEpoch {
+ let millis: i64 = self
+ .0
+ .elapsed()
+ .as_millis()
+ .try_into()
+ .expect("failed to fit timestamp in i64");
+ MillisecondsSinceEpoch(millis)
+ }
+}
+
+/// Implementation of the AuthGraph TA that runs locally in-process (and which is therefore
+/// insecure).
+pub struct LocalTa {
+ ta: Arc<Mutex<AuthGraphTa>>,
+}
+
+impl LocalTa {
+ /// Create a new instance.
+ pub fn new() -> Self {
+ Self {
+ ta: Arc::new(Mutex::new(AuthGraphTa::new(
+ boring::trait_impls(
+ Box::<boring::test_device::AgDevice>::default(),
+ Some(Box::new(StdClock::default())),
+ ),
+ Role::Both,
+ ))),
+ }
+ }
+}
+
+/// Pretend to be a serialized channel to the TA, but actually just directly invoke the TA with
+/// incoming requests.
+impl SerializedChannel for LocalTa {
+ const MAX_SIZE: usize = usize::MAX;
+
+ fn execute(&mut self, req_data: &[u8]) -> binder::Result<Vec<u8>> {
+ Ok(self.ta.lock().unwrap().process(req_data))
+ }
+}
diff --git a/security/authgraph/default/src/main.rs b/security/authgraph/default/src/main.rs
index 2112e58..873eb4e 100644
--- a/security/authgraph/default/src/main.rs
+++ b/security/authgraph/default/src/main.rs
@@ -22,18 +22,10 @@
//! 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 authgraph_hal::service;
+use authgraph_nonsecure::LocalTa;
use log::{error, info};
-use std::ffi::CString;
-use std::sync::Mutex;
+use std::sync::{Arc, Mutex};
static SERVICE_NAME: &str = "android.hardware.security.authgraph.IAuthGraphKeyExchange";
static SERVICE_INSTANCE: &str = "nonsecure";
@@ -73,7 +65,8 @@
binder::ProcessState::start_thread_pool();
// Register the service
- let service = AuthGraphService::new_as_binder();
+ let local_ta = LocalTa::new();
+ let service = service::AuthGraphService::new_as_binder(Arc::new(Mutex::new(local_ta)));
let service_name = format!("{}/{}", SERVICE_NAME, SERVICE_INSTANCE);
binder::add_service(&service_name, service.as_binder()).map_err(|e| {
format!(
@@ -87,141 +80,3 @@
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))
- }
-}