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 sources
18 use super::*;
19 use authgraph_core::{key, keyexchange as ke};
20
21 /// Run AuthGraph tests against the provided source, using a local test sink implementation.
test( local_sink: &mut ke::AuthGraphParticipant, source: binder::Strong<dyn IAuthGraphKeyExchange>, )22 pub fn test(
23 local_sink: &mut ke::AuthGraphParticipant,
24 source: binder::Strong<dyn IAuthGraphKeyExchange>,
25 ) {
26 test_mainline(local_sink, source.clone());
27 test_corrupt_sig(local_sink, source.clone());
28 test_corrupt_key(local_sink, source);
29 }
30
31 /// Perform mainline AuthGraph key exchange with the provided source.
32 /// Return the agreed AES keys in plaintext, together with the session ID.
test_mainline( local_sink: &mut ke::AuthGraphParticipant, source: binder::Strong<dyn IAuthGraphKeyExchange>, ) -> ([key::AesKey; 2], Vec<u8>)33 pub fn test_mainline(
34 local_sink: &mut ke::AuthGraphParticipant,
35 source: binder::Strong<dyn IAuthGraphKeyExchange>,
36 ) -> ([key::AesKey; 2], Vec<u8>) {
37 // Step 1: create an ephemeral ECDH key at the (remote) source.
38 let source_init_info = source
39 .create()
40 .expect("failed to create() with remote impl");
41 assert!(source_init_info.key.pubKey.is_some());
42 assert!(source_init_info.key.arcFromPBK.is_some());
43 let source_pub_key = extract_plain_pub_key(&source_init_info.key.pubKey);
44
45 // Step 2: pass the source's ECDH public key and other session info to the (local) sink.
46 let init_result = local_sink
47 .init(
48 &source_pub_key.plainPubKey,
49 &source_init_info.identity.identity,
50 &source_init_info.nonce,
51 source_init_info.version,
52 )
53 .expect("failed to init() with local impl");
54 let sink_init_info = init_result.session_init_info;
55 let sink_pub_key = sink_init_info
56 .ke_key
57 .pub_key
58 .expect("expect pub_key to be populated");
59
60 let sink_info = init_result.session_info;
61 assert!(!sink_info.session_id.is_empty());
62
63 // The AuthGraph core library will verify the session ID signature, but do it here too.
64 let sink_verification_key = key::Identity::from_slice(&sink_init_info.identity)
65 .expect("invalid identity CBOR")
66 .cert_chain
67 .root_key;
68 local_sink
69 .verify_signature_on_session_id(
70 &sink_verification_key,
71 &sink_info.session_id,
72 &sink_info.session_id_signature,
73 )
74 .expect("failed verification of signed session ID");
75
76 // Step 3: pass the sink's ECDH public key and other session info to the (remote) source, so it
77 // can calculate the same pair of symmetric keys.
78 let source_info = source
79 .finish(
80 &PubKey::PlainKey(PlainPubKey {
81 plainPubKey: sink_pub_key,
82 }),
83 &Identity {
84 identity: sink_init_info.identity,
85 },
86 &vec_to_signature(&sink_info.session_id_signature),
87 &sink_init_info.nonce,
88 sink_init_info.version,
89 &source_init_info.key,
90 )
91 .expect("failed to finish() with remote impl");
92 assert!(!source_info.sessionId.is_empty());
93
94 // The AuthGraph core library will verify the session ID signature, but do it here too.
95 let source_verification_key = local_sink
96 .peer_verification_key_from_identity(&source_init_info.identity.identity)
97 .expect("failed to get peer verification from identity");
98 local_sink
99 .verify_signature_on_session_id(
100 &source_verification_key,
101 &source_info.sessionId,
102 &source_info.signature.signature,
103 )
104 .expect("failed verification of signed session ID");
105
106 // Both ends should agree on the session ID.
107 assert_eq!(source_info.sessionId, sink_info.session_id);
108
109 // Step 4: pass the (remote) source's session ID signature back to the sink, so it can check it
110 // and update the symmetric keys so they're marked as authentication complete.
111 let sink_arcs = local_sink
112 .authentication_complete(&source_info.signature.signature, sink_info.shared_keys)
113 .expect("failed to authenticationComplete() with local sink");
114 // Decrypt and return the session keys.
115 let decrypted_shared_keys = local_sink
116 .decipher_shared_keys_from_arcs(&sink_arcs)
117 .expect("failed to decrypt shared key arcs")
118 .try_into();
119 let decrypted_shared_keys_array = match decrypted_shared_keys {
120 Ok(array) => array,
121 Err(_) => panic!("wrong number of decrypted shared key arcs"),
122 };
123 (decrypted_shared_keys_array, source_info.sessionId)
124 }
125
126 /// Perform mainline AuthGraph key exchange with the provided source, but provide an invalid session
127 /// ID signature.
test_corrupt_sig( local_sink: &mut ke::AuthGraphParticipant, source: binder::Strong<dyn IAuthGraphKeyExchange>, )128 pub fn test_corrupt_sig(
129 local_sink: &mut ke::AuthGraphParticipant,
130 source: binder::Strong<dyn IAuthGraphKeyExchange>,
131 ) {
132 // Step 1: create an ephemeral ECDH key at the (remote) source.
133 let source_init_info = source
134 .create()
135 .expect("failed to create() with remote impl");
136 assert!(source_init_info.key.pubKey.is_some());
137 assert!(source_init_info.key.arcFromPBK.is_some());
138 let source_pub_key = extract_plain_pub_key(&source_init_info.key.pubKey);
139
140 // Step 2: pass the source's ECDH public key and other session info to the (local) sink.
141 let init_result = local_sink
142 .init(
143 &source_pub_key.plainPubKey,
144 &source_init_info.identity.identity,
145 &source_init_info.nonce,
146 source_init_info.version,
147 )
148 .expect("failed to init() with local impl");
149 let sink_init_info = init_result.session_init_info;
150 let sink_pub_key = sink_init_info
151 .ke_key
152 .pub_key
153 .expect("expect pub_key to be populated");
154 let sink_info = init_result.session_info;
155 assert!(!sink_info.session_id.is_empty());
156
157 // Deliberately corrupt the sink's session ID signature.
158 let mut corrupt_signature = sink_info.session_id_signature.clone();
159 let sig_len = corrupt_signature.len();
160 corrupt_signature[sig_len - 1] ^= 0x01;
161
162 // Step 3: pass the sink's ECDH public key and other session info to the (remote) source, so it
163 // can calculate the same pair of symmetric keys.
164 let result = source.finish(
165 &PubKey::PlainKey(PlainPubKey {
166 plainPubKey: sink_pub_key,
167 }),
168 &Identity {
169 identity: sink_init_info.identity,
170 },
171 &vec_to_signature(&corrupt_signature),
172 &sink_init_info.nonce,
173 sink_init_info.version,
174 &source_init_info.key,
175 );
176 let err = result.expect_err("expect failure with corrupt signature");
177 assert_eq!(
178 err,
179 binder::Status::new_service_specific_error(Error::INVALID_SIGNATURE.0, None)
180 );
181 }
182
183 /// Perform mainline AuthGraph key exchange with the provided source, but give it back
184 /// a corrupted key.
test_corrupt_key( local_sink: &mut ke::AuthGraphParticipant, source: binder::Strong<dyn IAuthGraphKeyExchange>, )185 pub fn test_corrupt_key(
186 local_sink: &mut ke::AuthGraphParticipant,
187 source: binder::Strong<dyn IAuthGraphKeyExchange>,
188 ) {
189 // Step 1: create an ephemeral ECDH key at the (remote) source.
190 let source_init_info = source
191 .create()
192 .expect("failed to create() with remote impl");
193 assert!(source_init_info.key.pubKey.is_some());
194 assert!(source_init_info.key.arcFromPBK.is_some());
195 let source_pub_key = extract_plain_pub_key(&source_init_info.key.pubKey);
196
197 // Step 2: pass the source's ECDH public key and other session info to the (local) sink.
198 let init_result = local_sink
199 .init(
200 &source_pub_key.plainPubKey,
201 &source_init_info.identity.identity,
202 &source_init_info.nonce,
203 source_init_info.version,
204 )
205 .expect("failed to init() with local impl");
206 let sink_init_info = init_result.session_init_info;
207 let sink_pub_key = sink_init_info
208 .ke_key
209 .pub_key
210 .expect("expect pub_key to be populated");
211
212 let sink_info = init_result.session_info;
213 assert!(!sink_info.session_id.is_empty());
214
215 // The AuthGraph core library will verify the session ID signature, but do it here too.
216 let sink_verification_key = key::Identity::from_slice(&sink_init_info.identity)
217 .expect("invalid identity CBOR")
218 .cert_chain
219 .root_key;
220 local_sink
221 .verify_signature_on_session_id(
222 &sink_verification_key,
223 &sink_info.session_id,
224 &sink_info.session_id_signature,
225 )
226 .expect("failed verification of signed session ID");
227
228 // Deliberately corrupt the source's encrypted key.
229 let mut corrupt_key = source_init_info.key.clone();
230 match &mut corrupt_key.arcFromPBK {
231 Some(a) => {
232 let len = a.arc.len();
233 a.arc[len - 1] ^= 0x01;
234 }
235 None => panic!("no arc data"),
236 }
237
238 // Step 3: pass the sink's ECDH public key and other session info to the (remote) source, but
239 // give it back a corrupted version of its own key.
240 let result = source.finish(
241 &PubKey::PlainKey(PlainPubKey {
242 plainPubKey: sink_pub_key,
243 }),
244 &Identity {
245 identity: sink_init_info.identity,
246 },
247 &vec_to_signature(&sink_info.session_id_signature),
248 &sink_init_info.nonce,
249 sink_init_info.version,
250 &corrupt_key,
251 );
252
253 let err = result.expect_err("expect failure with corrupt key");
254 assert!(
255 err == binder::Status::new_service_specific_error(Error::INVALID_KE_KEY.0, None)
256 || err
257 == binder::Status::new_service_specific_error(
258 Error::INVALID_PRIV_KEY_ARC_IN_KEY.0,
259 None
260 )
261 );
262 }
263