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
This commit is contained in:
commit
0a44da8589
9 changed files with 824 additions and 152 deletions
|
@ -46,3 +46,36 @@ cc_test {
|
||||||
"vts",
|
"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",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
100
security/authgraph/aidl/vts/functional/lib.rs
Normal file
100
security/authgraph/aidl/vts/functional/lib.rs
Normal file
|
@ -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
|
||||||
|
}
|
78
security/authgraph/aidl/vts/functional/role_test.rs
Normal file
78
security/authgraph/aidl/vts/functional/role_test.rs
Normal file
|
@ -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!());
|
||||||
|
}
|
214
security/authgraph/aidl/vts/functional/sink.rs
Normal file
214
security/authgraph/aidl/vts/functional/sink.rs
Normal file
|
@ -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)
|
||||||
|
);
|
||||||
|
}
|
244
security/authgraph/aidl/vts/functional/source.rs
Normal file
244
security/authgraph/aidl/vts/functional/source.rs
Normal file
|
@ -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)
|
||||||
|
);
|
||||||
|
}
|
|
@ -22,6 +22,26 @@ package {
|
||||||
default_applicable_licenses: ["hardware_interfaces_license"],
|
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 {
|
rust_binary {
|
||||||
name: "android.hardware.security.authgraph-service.nonsecure",
|
name: "android.hardware.security.authgraph-service.nonsecure",
|
||||||
relative_install_path: "hw",
|
relative_install_path: "hw",
|
||||||
|
@ -33,9 +53,8 @@ rust_binary {
|
||||||
],
|
],
|
||||||
rustlibs: [
|
rustlibs: [
|
||||||
"libandroid_logger",
|
"libandroid_logger",
|
||||||
"libauthgraph_core",
|
|
||||||
"libauthgraph_boringssl",
|
|
||||||
"libauthgraph_hal",
|
"libauthgraph_hal",
|
||||||
|
"libauthgraph_nonsecure",
|
||||||
"libbinder_rs",
|
"libbinder_rs",
|
||||||
"liblibc",
|
"liblibc",
|
||||||
"liblog_rust",
|
"liblog_rust",
|
||||||
|
@ -44,3 +63,20 @@ rust_binary {
|
||||||
"src/main.rs",
|
"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",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
31
security/authgraph/default/src/fuzzer.rs
Normal file
31
security/authgraph/default/src/fuzzer.rs
Normal file
|
@ -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);
|
||||||
|
});
|
81
security/authgraph/default/src/lib.rs
Normal file
81
security/authgraph/default/src/lib.rs
Normal file
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,18 +22,10 @@
|
||||||
//! expose an entrypoint that allowed retrieval of the specific IAuthGraphKeyExchange instance that
|
//! expose an entrypoint that allowed retrieval of the specific IAuthGraphKeyExchange instance that
|
||||||
//! is correlated with the component).
|
//! is correlated with the component).
|
||||||
|
|
||||||
use android_hardware_security_authgraph::aidl::android::hardware::security::authgraph::{
|
use authgraph_hal::service;
|
||||||
Arc::Arc, IAuthGraphKeyExchange::BnAuthGraphKeyExchange,
|
use authgraph_nonsecure::LocalTa;
|
||||||
IAuthGraphKeyExchange::IAuthGraphKeyExchange, Identity::Identity, KeInitResult::KeInitResult,
|
|
||||||
Key::Key, PubKey::PubKey, SessionIdSignature::SessionIdSignature, SessionInfo::SessionInfo,
|
|
||||||
SessionInitiationInfo::SessionInitiationInfo,
|
|
||||||
};
|
|
||||||
use authgraph_boringssl as boring;
|
|
||||||
use authgraph_core::{key::MillisecondsSinceEpoch, keyexchange as ke, traits};
|
|
||||||
use authgraph_hal::{err_to_binder, Innto, TryInnto};
|
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use std::ffi::CString;
|
use std::sync::{Arc, Mutex};
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
static SERVICE_NAME: &str = "android.hardware.security.authgraph.IAuthGraphKeyExchange";
|
static SERVICE_NAME: &str = "android.hardware.security.authgraph.IAuthGraphKeyExchange";
|
||||||
static SERVICE_INSTANCE: &str = "nonsecure";
|
static SERVICE_INSTANCE: &str = "nonsecure";
|
||||||
|
@ -73,7 +65,8 @@ fn inner_main() -> Result<(), HalServiceError> {
|
||||||
binder::ProcessState::start_thread_pool();
|
binder::ProcessState::start_thread_pool();
|
||||||
|
|
||||||
// Register the service
|
// 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);
|
let service_name = format!("{}/{}", SERVICE_NAME, SERVICE_INSTANCE);
|
||||||
binder::add_service(&service_name, service.as_binder()).map_err(|e| {
|
binder::add_service(&service_name, service.as_binder()).map_err(|e| {
|
||||||
format!(
|
format!(
|
||||||
|
@ -87,141 +80,3 @@ fn inner_main() -> Result<(), HalServiceError> {
|
||||||
info!("AuthGraph HAL service is terminating."); // should not reach here
|
info!("AuthGraph HAL service is terminating."); // should not reach here
|
||||||
Ok(())
|
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue