From f2117ff77cbf4bbe6ff44b51187a907d5c8f50e7 Mon Sep 17 00:00:00 2001 From: David Drysdale Date: Fri, 3 Nov 2023 12:18:24 +0000 Subject: [PATCH 1/4] AuthGraph: reduce dependency on authgraph_core The authgraph_hal library should only depend on libauthgraph_wire, not on libauthgraph_core, so adjust error processing accordingly. Test: VtsAidlAuthGraphSessionTest Bug: 284470121 Change-Id: I48056db6ceeab409d0f165f8e051809129643c6f --- security/authgraph/default/Android.bp | 1 + security/authgraph/default/src/main.rs | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/security/authgraph/default/Android.bp b/security/authgraph/default/Android.bp index 9de3bc11da..c0f2106c3e 100644 --- a/security/authgraph/default/Android.bp +++ b/security/authgraph/default/Android.bp @@ -36,6 +36,7 @@ rust_binary { "libauthgraph_core", "libauthgraph_boringssl", "libauthgraph_hal", + "libauthgraph_wire", "libbinder_rs", "liblibc", "liblog_rust", diff --git a/security/authgraph/default/src/main.rs b/security/authgraph/default/src/main.rs index 2112e58176..dc4a8e41fd 100644 --- a/security/authgraph/default/src/main.rs +++ b/security/authgraph/default/src/main.rs @@ -30,7 +30,7 @@ use android_hardware_security_authgraph::aidl::android::hardware::security::auth }; 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::{errcode_to_binder, Innto, TryInnto}; use log::{error, info}; use std::ffi::CString; use std::sync::Mutex; @@ -130,6 +130,13 @@ fn unsigned_pub_key(pub_key: &PubKey) -> binder::Result<&[u8]> { } } +fn err_to_binder(err: authgraph_core::error::Error) -> binder::Status { + if err.0 != authgraph_wire::ErrorCode::Ok && !err.1.is_empty() { + error!("failure {:?} message: '{}'", err.0, err.1); + } + errcode_to_binder(err.0) +} + /// 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. From 6c09af215df9f033389fcb5cff33fa9a333062f4 Mon Sep 17 00:00:00 2001 From: David Drysdale Date: Mon, 6 Nov 2023 09:57:10 +0000 Subject: [PATCH 2/4] AuthGraph: move code into library Use the core library's new service implementation, which wraps a channel to the TA. In this nonsecure case, the TA is local in-process, so use the core library's AuthGraphTa, and implement the SerializedChannel as just a direct invocation of the TA. Move this code into a _nonsecure library, so the main.rs just has the code needed to start the executable and register the service. Test: VtsAidlAuthGraphSessionTest Bug: 284470121 Change-Id: I738d3876872a8cd248f0ebec708676d1173b6e37 --- security/authgraph/default/Android.bp | 24 +++- security/authgraph/default/src/lib.rs | 81 +++++++++++++ security/authgraph/default/src/main.rs | 162 +------------------------ 3 files changed, 107 insertions(+), 160 deletions(-) create mode 100644 security/authgraph/default/src/lib.rs diff --git a/security/authgraph/default/Android.bp b/security/authgraph/default/Android.bp index c0f2106c3e..ac67136ada 100644 --- a/security/authgraph/default/Android.bp +++ b/security/authgraph/default/Android.bp @@ -22,6 +22,26 @@ package { 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,10 +53,8 @@ rust_binary { ], rustlibs: [ "libandroid_logger", - "libauthgraph_core", - "libauthgraph_boringssl", "libauthgraph_hal", - "libauthgraph_wire", + "libauthgraph_nonsecure", "libbinder_rs", "liblibc", "liblog_rust", diff --git a/security/authgraph/default/src/lib.rs b/security/authgraph/default/src/lib.rs new file mode 100644 index 0000000000..4cd0cb74ae --- /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>, +} + +impl LocalTa { + /// Create a new instance. + pub fn new() -> Self { + Self { + ta: Arc::new(Mutex::new(AuthGraphTa::new( + boring::trait_impls( + Box::::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> { + 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 dc4a8e41fd..873eb4eb77 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::{errcode_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 @@ fn inner_main() -> Result<(), HalServiceError> { 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,148 +80,3 @@ fn inner_main() -> Result<(), HalServiceError> { info!("AuthGraph HAL service is terminating."); // should not reach here Ok(()) } - -/// Non-secure implementation of the AuthGraph key exchange service. -struct AuthGraphService { - imp: Mutex, -} - -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::::default(), - clock: Some(Box::new(StdClock)), - }), - } - } - - /// Create a new instance wrapped in a proxy object. - pub fn new_as_binder() -> binder::Strong { - 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()), - )), - } -} - -fn err_to_binder(err: authgraph_core::error::Error) -> binder::Status { - if err.0 != authgraph_wire::ErrorCode::Ok && !err.1.is_empty() { - error!("failure {:?} message: '{}'", err.0, err.1); - } - errcode_to_binder(err.0) -} - -/// 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 { - 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 { - 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 { - 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)) - } -} From 6fb22dc9ef6e481ef6d37205f279f386871a5d4d Mon Sep 17 00:00:00 2001 From: David Drysdale Date: Mon, 6 Nov 2023 13:23:11 +0000 Subject: [PATCH 3/4] AuthGraph: add fuzzer Test: m android.hardware.authgraph-service.nonsecure_fuzzer Bug: 284470121 Change-Id: Ib702b5b0cf69a4a839326297c2d71355562b46c3 --- security/authgraph/default/Android.bp | 17 +++++++++++++ security/authgraph/default/src/fuzzer.rs | 31 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 security/authgraph/default/src/fuzzer.rs diff --git a/security/authgraph/default/Android.bp b/security/authgraph/default/Android.bp index ac67136ada..c4810759ec 100644 --- a/security/authgraph/default/Android.bp +++ b/security/authgraph/default/Android.bp @@ -63,3 +63,20 @@ rust_binary { "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 0000000000..6a9cfdd0b6 --- /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); +}); From fe41825f1c98550f6d6a0343fde9d99a76ee4681 Mon Sep 17 00:00:00 2001 From: David Drysdale Date: Tue, 7 Nov 2023 09:27:56 +0000 Subject: [PATCH 4/4] AuthGraph: add per-role VTS tests Add VTS tests that just exercise a single role, sink or source. Use the AuthGraph core library to provide the implementation of the other role. This means that the tests are best written in Rust. Put the test code into a library, so that other HALs which include AuthGraph as a component can exercise the AuthGraph parts in their own VTS tests. Test: VtsAidlAuthGraphRoleTest Bug: 284470121 Change-Id: I73f6aa277b41cc728587d707d7a6f82f0d18e08f --- .../authgraph/aidl/vts/functional/Android.bp | 33 +++ security/authgraph/aidl/vts/functional/lib.rs | 100 +++++++ .../aidl/vts/functional/role_test.rs | 78 ++++++ .../authgraph/aidl/vts/functional/sink.rs | 214 +++++++++++++++ .../authgraph/aidl/vts/functional/source.rs | 244 ++++++++++++++++++ 5 files changed, 669 insertions(+) create mode 100644 security/authgraph/aidl/vts/functional/lib.rs create mode 100644 security/authgraph/aidl/vts/functional/role_test.rs create mode 100644 security/authgraph/aidl/vts/functional/sink.rs create mode 100644 security/authgraph/aidl/vts/functional/source.rs diff --git a/security/authgraph/aidl/vts/functional/Android.bp b/security/authgraph/aidl/vts/functional/Android.bp index fc13759021..0e3480f006 100644 --- a/security/authgraph/aidl/vts/functional/Android.bp +++ b/security/authgraph/aidl/vts/functional/Android.bp @@ -46,3 +46,36 @@ cc_test { "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 0000000000..7b9b2b98be --- /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::::default(), + Some(Box::new(StdClock::default())), + ) +} + +fn build_plain_pub_key(pub_key: &Option>) -> PubKey { + PubKey::PlainKey(PlainPubKey { + plainPubKey: pub_key.clone().unwrap(), + }) +} + +fn extract_plain_pub_key(pub_key: &Option) -> &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; 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 0000000000..e95361ae07 --- /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::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 0000000000..5c81593d69 --- /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) { + 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, +) -> [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, +) { + // 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, +) { + // 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 0000000000..9aaaaee0d5 --- /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) { + 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, +) -> [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, +) { + // 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, +) { + // 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) + ); +}