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) + ); +} diff --git a/security/authgraph/default/Android.bp b/security/authgraph/default/Android.bp index 9de3bc11da..c4810759ec 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,9 +53,8 @@ rust_binary { ], rustlibs: [ "libandroid_logger", - "libauthgraph_core", - "libauthgraph_boringssl", "libauthgraph_hal", + "libauthgraph_nonsecure", "libbinder_rs", "liblibc", "liblog_rust", @@ -44,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); +}); 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 2112e58176..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::{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 @@ 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,141 +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()), - )), - } -} - -/// 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)) - } -}