1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 //! VTS tests for sinks
18 use super::*;
19 use authgraph_core::{key, keyexchange as ke};
20 
21 /// Run AuthGraph tests against the provided sink, using a local test source implementation.
test( local_source: &mut ke::AuthGraphParticipant, sink: binder::Strong<dyn IAuthGraphKeyExchange>, )22 pub fn test(
23     local_source: &mut ke::AuthGraphParticipant,
24     sink: binder::Strong<dyn IAuthGraphKeyExchange>,
25 ) {
26     test_mainline(local_source, sink.clone());
27     test_corrupt_sig(local_source, sink.clone());
28     test_corrupt_keys(local_source, sink);
29 }
30 
31 /// Perform mainline AuthGraph key exchange with the provided sink and local implementation.
32 /// Return the agreed AES keys in plaintext, together with the session ID.
test_mainline( local_source: &mut ke::AuthGraphParticipant, sink: binder::Strong<dyn IAuthGraphKeyExchange>, ) -> ([key::AesKey; 2], Vec<u8>)33 pub fn test_mainline(
34     local_source: &mut ke::AuthGraphParticipant,
35     sink: binder::Strong<dyn IAuthGraphKeyExchange>,
36 ) -> ([key::AesKey; 2], Vec<u8>) {
37     // Step 1: create an ephemeral ECDH key at the (local) source.
38     let source_init_info = local_source
39         .create()
40         .expect("failed to create() with local impl");
41 
42     // Step 2: pass the source's ECDH public key and other session info to the (remote) sink.
43     let init_result = sink
44         .init(
45             &build_plain_pub_key(&source_init_info.ke_key.pub_key),
46             &vec_to_identity(&source_init_info.identity),
47             &source_init_info.nonce,
48             source_init_info.version,
49         )
50         .expect("failed to init() with remote impl");
51     let sink_init_info = init_result.sessionInitiationInfo;
52     let sink_pub_key = extract_plain_pub_key(&sink_init_info.key.pubKey);
53 
54     let sink_info = init_result.sessionInfo;
55     assert!(!sink_info.sessionId.is_empty());
56 
57     // The AuthGraph core library will verify the session ID signature, but do it here too.
58     let sink_verification_key = local_source
59         .peer_verification_key_from_identity(&sink_init_info.identity.identity)
60         .expect("failed to get peer verification from identity");
61     local_source
62         .verify_signature_on_session_id(
63             &sink_verification_key,
64             &sink_info.sessionId,
65             &sink_info.signature.signature,
66         )
67         .expect("failed verification of signed session ID");
68 
69     // Step 3: pass the sink's ECDH public key and other session info to the (local) source, so it
70     // can calculate the same pair of symmetric keys.
71     let source_info = local_source
72         .finish(
73             &sink_pub_key.plainPubKey,
74             &sink_init_info.identity.identity,
75             &sink_info.signature.signature,
76             &sink_init_info.nonce,
77             sink_init_info.version,
78             source_init_info.ke_key,
79         )
80         .expect("failed to finish() with local impl");
81     assert!(!source_info.session_id.is_empty());
82 
83     // The AuthGraph core library will verify the session ID signature, but do it here too.
84     let source_verification_key = key::Identity::from_slice(&source_init_info.identity)
85         .expect("invalid identity CBOR")
86         .cert_chain
87         .root_key;
88     local_source
89         .verify_signature_on_session_id(
90             &source_verification_key,
91             &source_info.session_id,
92             &source_info.session_id_signature,
93         )
94         .expect("failed verification of signed session ID");
95 
96     // Both ends should agree on the session ID.
97     assert_eq!(source_info.session_id, sink_info.sessionId);
98 
99     // Step 4: pass the (local) source's session ID signature back to the sink, so it can check it
100     // and update the symmetric keys so they're marked as authentication complete.
101     let _sink_arcs = sink
102         .authenticationComplete(
103             &vec_to_signature(&source_info.session_id_signature),
104             &sink_info.sharedKeys,
105         )
106         .expect("failed to authenticationComplete() with remote sink");
107     // Decrypt and return the session keys.
108     let decrypted_shared_keys = local_source
109         .decipher_shared_keys_from_arcs(&source_info.shared_keys)
110         .expect("failed to decrypt shared key arcs")
111         .try_into();
112     let decrypted_shared_keys_array = match decrypted_shared_keys {
113         Ok(array) => array,
114         Err(_) => panic!("wrong number of decrypted shared key arcs"),
115     };
116     (decrypted_shared_keys_array, sink_info.sessionId)
117 }
118 
119 /// Perform mainline AuthGraph key exchange with the provided sink, but provide an invalid
120 /// session ID signature.
test_corrupt_sig( local_source: &mut ke::AuthGraphParticipant, sink: binder::Strong<dyn IAuthGraphKeyExchange>, )121 pub fn test_corrupt_sig(
122     local_source: &mut ke::AuthGraphParticipant,
123     sink: binder::Strong<dyn IAuthGraphKeyExchange>,
124 ) {
125     // Step 1: create an ephemeral ECDH key at the (local) source.
126     let source_init_info = local_source
127         .create()
128         .expect("failed to create() with local impl");
129 
130     // Step 2: pass the source's ECDH public key and other session info to the (remote) sink.
131     let init_result = sink
132         .init(
133             &build_plain_pub_key(&source_init_info.ke_key.pub_key),
134             &vec_to_identity(&source_init_info.identity),
135             &source_init_info.nonce,
136             source_init_info.version,
137         )
138         .expect("failed to init() with remote impl");
139     let sink_init_info = init_result.sessionInitiationInfo;
140     let sink_pub_key = extract_plain_pub_key(&sink_init_info.key.pubKey);
141 
142     let sink_info = init_result.sessionInfo;
143     assert!(!sink_info.sessionId.is_empty());
144 
145     // Step 3: pass the sink's ECDH public key and other session info to the (local) source, so it
146     // can calculate the same pair of symmetric keys.
147     let source_info = local_source
148         .finish(
149             &sink_pub_key.plainPubKey,
150             &sink_init_info.identity.identity,
151             &sink_info.signature.signature,
152             &sink_init_info.nonce,
153             sink_init_info.version,
154             source_init_info.ke_key,
155         )
156         .expect("failed to finish() with local impl");
157     assert!(!source_info.session_id.is_empty());
158 
159     // Build a corrupted version of the (local) source's session ID signature.
160     let mut corrupt_signature = source_info.session_id_signature.clone();
161     let sig_len = corrupt_signature.len();
162     corrupt_signature[sig_len - 1] ^= 0x01;
163 
164     // Step 4: pass the (local) source's **invalid** session ID signature back to the sink,
165     // which should reject it.
166     let result =
167         sink.authenticationComplete(&vec_to_signature(&corrupt_signature), &sink_info.sharedKeys);
168     let err = result.expect_err("expect failure with corrupt signature");
169     assert_eq!(
170         err,
171         binder::Status::new_service_specific_error(Error::INVALID_SIGNATURE.0, None)
172     );
173 }
174 
175 /// Perform mainline AuthGraph key exchange with the provided sink, but provide an invalid
176 /// Arc for the sink's key.
test_corrupt_keys( local_source: &mut ke::AuthGraphParticipant, sink: binder::Strong<dyn IAuthGraphKeyExchange>, )177 pub fn test_corrupt_keys(
178     local_source: &mut ke::AuthGraphParticipant,
179     sink: binder::Strong<dyn IAuthGraphKeyExchange>,
180 ) {
181     // Step 1: create an ephemeral ECDH key at the (local) source.
182     let source_init_info = local_source
183         .create()
184         .expect("failed to create() with local impl");
185 
186     // Step 2: pass the source's ECDH public key and other session info to the (remote) sink.
187     let init_result = sink
188         .init(
189             &build_plain_pub_key(&source_init_info.ke_key.pub_key),
190             &vec_to_identity(&source_init_info.identity),
191             &source_init_info.nonce,
192             source_init_info.version,
193         )
194         .expect("failed to init() with remote impl");
195     let sink_init_info = init_result.sessionInitiationInfo;
196     let sink_pub_key = extract_plain_pub_key(&sink_init_info.key.pubKey);
197 
198     let sink_info = init_result.sessionInfo;
199     assert!(!sink_info.sessionId.is_empty());
200 
201     // Step 3: pass the sink's ECDH public key and other session info to the (local) source, so it
202     // can calculate the same pair of symmetric keys.
203     let source_info = local_source
204         .finish(
205             &sink_pub_key.plainPubKey,
206             &sink_init_info.identity.identity,
207             &sink_info.signature.signature,
208             &sink_init_info.nonce,
209             sink_init_info.version,
210             source_init_info.ke_key,
211         )
212         .expect("failed to finish() with local impl");
213     assert!(!source_info.session_id.is_empty());
214 
215     // Deliberately corrupt the sink's shared key Arcs before returning them
216     let mut corrupt_keys = sink_info.sharedKeys.clone();
217     let len0 = corrupt_keys[0].arc.len();
218     let len1 = corrupt_keys[1].arc.len();
219     corrupt_keys[0].arc[len0 - 1] ^= 0x01;
220     corrupt_keys[1].arc[len1 - 1] ^= 0x01;
221 
222     // Step 4: pass the (local) source's session ID signature back to the sink, but with corrupted
223     // keys, which should be rejected.
224     let result = sink.authenticationComplete(
225         &vec_to_signature(&source_info.session_id_signature),
226         &corrupt_keys,
227     );
228     let err = result.expect_err("expect failure with corrupt keys");
229     assert_eq!(
230         err,
231         binder::Status::new_service_specific_error(Error::INVALID_SHARED_KEY_ARCS.0, None)
232     );
233 }
234