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