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 #define LOG_TAG "authgraph_session_test"
17 #include <android-base/logging.h>
18 
19 #include <aidl/Gtest.h>
20 #include <aidl/Vintf.h>
21 #include <aidl/android/hardware/security/authgraph/Error.h>
22 #include <aidl/android/hardware/security/authgraph/IAuthGraphKeyExchange.h>
23 #include <android/binder_manager.h>
24 #include <binder/ProcessState.h>
25 #include <gtest/gtest.h>
26 #include <vector>
27 
28 namespace aidl::android::hardware::security::authgraph::test {
29 using ::aidl::android::hardware::security::authgraph::Error;
30 
31 namespace {
32 
33 // Check that the signature in the encoded COSE_Sign1 data is correct, and that the payload matches.
34 // TODO: maybe drop separate payload, and extract it from cose_sign1.payload (and return it).
CheckSignature(std::vector<uint8_t> &,std::vector<uint8_t> &,std::vector<uint8_t> &)35 void CheckSignature(std::vector<uint8_t>& /*pub_cose_key*/, std::vector<uint8_t>& /*payload*/,
36                     std::vector<uint8_t>& /*cose_sign1*/) {
37     // TODO: implement me
38 }
39 
CheckSignature(std::vector<uint8_t> & pub_cose_key,std::vector<uint8_t> & payload,SessionIdSignature & signature)40 void CheckSignature(std::vector<uint8_t>& pub_cose_key, std::vector<uint8_t>& payload,
41                     SessionIdSignature& signature) {
42     return CheckSignature(pub_cose_key, payload, signature.signature);
43 }
44 
SigningKeyFromIdentity(const Identity & identity)45 std::vector<uint8_t> SigningKeyFromIdentity(const Identity& identity) {
46     // TODO: This is a CBOR-encoded `Identity` which currently happens to be a COSE_Key with the
47     // pubkey This will change in future.
48     return identity.identity;
49 }
50 
51 }  // namespace
52 
53 class AuthGraphSessionTest : public ::testing::TestWithParam<std::string> {
54   public:
55     enum ErrorType { AIDL_ERROR, BINDER_ERROR };
56 
57     union ErrorValue {
58         Error aidl_error;
59         int32_t binder_error;
60     };
61 
62     struct ReturnedError {
63         ErrorType err_type;
64         ErrorValue err_val;
65 
operator ==(const ReturnedError & lhs,const ReturnedError & rhs)66         friend bool operator==(const ReturnedError& lhs, const ReturnedError& rhs) {
67             return lhs.err_type == rhs.err_type;
68             switch (lhs.err_type) {
69                 case ErrorType::AIDL_ERROR:
70                     return lhs.err_val.aidl_error == rhs.err_val.aidl_error;
71                 case ErrorType::BINDER_ERROR:
72                     return lhs.err_val.binder_error == rhs.err_val.binder_error;
73             }
74         }
75     };
76 
77     const ReturnedError OK = {.err_type = ErrorType::AIDL_ERROR, .err_val.aidl_error = Error::OK};
78 
GetReturnError(const::ndk::ScopedAStatus & result)79     ReturnedError GetReturnError(const ::ndk::ScopedAStatus& result) {
80         if (result.isOk()) {
81             return OK;
82         }
83         int32_t exception_code = result.getExceptionCode();
84         int32_t error_code = result.getServiceSpecificError();
85         if (exception_code == EX_SERVICE_SPECIFIC && error_code != 0) {
86             ReturnedError re = {.err_type = ErrorType::AIDL_ERROR,
87                                 .err_val.aidl_error = static_cast<Error>(error_code)};
88             return re;
89         }
90         ReturnedError re = {.err_type = ErrorType::BINDER_ERROR,
91                             .err_val.binder_error = exception_code};
92         return re;
93     }
94 
95     // Build the parameters for the VTS test by enumerating the available HAL instances
build_params()96     static std::vector<std::string> build_params() {
97         auto params = ::android::getAidlHalInstanceNames(IAuthGraphKeyExchange::descriptor);
98         return params;
99     }
100 
SetUp()101     void SetUp() override {
102         ASSERT_TRUE(AServiceManager_isDeclared(GetParam().c_str()))
103                 << "No instance declared for " << GetParam();
104         ::ndk::SpAIBinder binder(AServiceManager_waitForService(GetParam().c_str()));
105         authNode_ = IAuthGraphKeyExchange::fromBinder(binder);
106         ASSERT_NE(authNode_, nullptr) << "Failed to get Binder reference for " << GetParam();
107     }
108 
TearDown()109     void TearDown() override {}
110 
111   protected:
112     std::shared_ptr<IAuthGraphKeyExchange> authNode_;
113 };
114 
TEST_P(AuthGraphSessionTest,Mainline)115 TEST_P(AuthGraphSessionTest, Mainline) {
116     std::shared_ptr<IAuthGraphKeyExchange> source = authNode_;
117     std::shared_ptr<IAuthGraphKeyExchange> sink = authNode_;
118 
119     // Step 1: create an ephemeral ECDH key at the source.
120     SessionInitiationInfo source_init_info;
121     ASSERT_EQ(OK, GetReturnError(source->create(&source_init_info)));
122     ASSERT_TRUE(source_init_info.key.pubKey.has_value());
123     ASSERT_TRUE(source_init_info.key.arcFromPBK.has_value());
124 
125     // Step 2: pass the source's ECDH public key and other session info to the sink.
126     KeInitResult init_result;
127     ASSERT_EQ(OK, GetReturnError(sink->init(source_init_info.key.pubKey.value(),
128                                             source_init_info.identity, source_init_info.nonce,
129                                             source_init_info.version, &init_result)));
130     SessionInitiationInfo sink_init_info = init_result.sessionInitiationInfo;
131     ASSERT_TRUE(sink_init_info.key.pubKey.has_value());
132     // The sink_init_info.arcFromPBK need not be populated, as the ephemeral key agreement
133     // key is no longer needed.
134 
135     SessionInfo sink_info = init_result.sessionInfo;
136     ASSERT_EQ((int)sink_info.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
137     ASSERT_GT((int)sink_info.sessionId.size(), 0) << "Expect non-empty session ID from sink";
138     std::vector<uint8_t> sink_signing_key = SigningKeyFromIdentity(sink_init_info.identity);
139     CheckSignature(sink_signing_key, sink_info.sessionId, sink_info.signature);
140 
141     // Step 3: pass the sink's ECDH public key and other session info to the source, so it can
142     // calculate the same pair of symmetric keys.
143     SessionInfo source_info;
144     ASSERT_EQ(OK, GetReturnError(source->finish(sink_init_info.key.pubKey.value(),
145                                                 sink_init_info.identity, sink_info.signature,
146                                                 sink_init_info.nonce, sink_init_info.version,
147                                                 source_init_info.key, &source_info)));
148     ASSERT_EQ((int)source_info.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
149     ASSERT_GT((int)source_info.sessionId.size(), 0) << "Expect non-empty session ID from source";
150     std::vector<uint8_t> source_signing_key = SigningKeyFromIdentity(source_init_info.identity);
151     CheckSignature(source_signing_key, source_info.sessionId, source_info.signature);
152 
153     // Both ends should agree on the session ID.
154     ASSERT_EQ(source_info.sessionId, sink_info.sessionId);
155 
156     // Step 4: pass the source's session ID info back to the sink, so it can check it and
157     // update the symmetric keys so they're marked as authentication complete.
158     std::array<Arc, 2> auth_complete_result;
159     ASSERT_EQ(OK, GetReturnError(sink->authenticationComplete(
160                           source_info.signature, sink_info.sharedKeys, &auth_complete_result)));
161     ASSERT_EQ((int)auth_complete_result.size(), 2)
162             << "Expect two symmetric keys from authComplete()";
163     sink_info.sharedKeys = auth_complete_result;
164 
165     // At this point the sink and source have agreed on the same pair of symmetric keys,
166     // encoded as `sink_info.sharedKeys` and `source_info.sharedKeys`.
167 }
168 
TEST_P(AuthGraphSessionTest,ParallelSink)169 TEST_P(AuthGraphSessionTest, ParallelSink) {
170     std::shared_ptr<IAuthGraphKeyExchange> source = authNode_;
171     std::shared_ptr<IAuthGraphKeyExchange> sink1 = authNode_;
172     std::shared_ptr<IAuthGraphKeyExchange> sink2 = authNode_;
173 
174     // Step 1: create ephemeral ECDH keys at the source.
175     SessionInitiationInfo source_init1_info;
176     ASSERT_EQ(OK, GetReturnError(source->create(&source_init1_info)));
177     ASSERT_TRUE(source_init1_info.key.pubKey.has_value());
178     ASSERT_TRUE(source_init1_info.key.arcFromPBK.has_value());
179     SessionInitiationInfo source_init2_info;
180     ASSERT_EQ(OK, GetReturnError(source->create(&source_init2_info)));
181     ASSERT_TRUE(source_init2_info.key.pubKey.has_value());
182     ASSERT_TRUE(source_init2_info.key.arcFromPBK.has_value());
183 
184     // Step 2: pass the source's ECDH public keys and other session info to the sinks.
185     KeInitResult init1_result;
186     ASSERT_EQ(OK, GetReturnError(sink1->init(source_init1_info.key.pubKey.value(),
187                                              source_init1_info.identity, source_init1_info.nonce,
188                                              source_init1_info.version, &init1_result)));
189     SessionInitiationInfo sink1_init_info = init1_result.sessionInitiationInfo;
190     ASSERT_TRUE(sink1_init_info.key.pubKey.has_value());
191 
192     SessionInfo sink1_info = init1_result.sessionInfo;
193     ASSERT_EQ((int)sink1_info.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
194     ASSERT_GT((int)sink1_info.sessionId.size(), 0) << "Expect non-empty session ID from sink";
195     std::vector<uint8_t> sink1_signing_key = SigningKeyFromIdentity(sink1_init_info.identity);
196     CheckSignature(sink1_signing_key, sink1_info.sessionId, sink1_info.signature);
197     KeInitResult init2_result;
198     ASSERT_EQ(OK, GetReturnError(sink2->init(source_init2_info.key.pubKey.value(),
199                                              source_init2_info.identity, source_init2_info.nonce,
200                                              source_init2_info.version, &init2_result)));
201     SessionInitiationInfo sink2_init_info = init2_result.sessionInitiationInfo;
202     ASSERT_TRUE(sink2_init_info.key.pubKey.has_value());
203 
204     SessionInfo sink2_info = init2_result.sessionInfo;
205     ASSERT_EQ((int)sink2_info.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
206     ASSERT_GT((int)sink2_info.sessionId.size(), 0) << "Expect non-empty session ID from sink";
207     std::vector<uint8_t> sink2_signing_key = SigningKeyFromIdentity(sink2_init_info.identity);
208     CheckSignature(sink2_signing_key, sink2_info.sessionId, sink2_info.signature);
209 
210     // Step 3: pass each sink's ECDH public key and other session info to the source, so it can
211     // calculate the same pair of symmetric keys.
212     SessionInfo source_info1;
213     ASSERT_EQ(OK, GetReturnError(source->finish(sink1_init_info.key.pubKey.value(),
214                                                 sink1_init_info.identity, sink1_info.signature,
215                                                 sink1_init_info.nonce, sink1_init_info.version,
216                                                 source_init1_info.key, &source_info1)));
217     ASSERT_EQ((int)source_info1.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
218     ASSERT_GT((int)source_info1.sessionId.size(), 0) << "Expect non-empty session ID from source";
219     std::vector<uint8_t> source_signing_key1 = SigningKeyFromIdentity(source_init1_info.identity);
220     CheckSignature(source_signing_key1, source_info1.sessionId, source_info1.signature);
221     SessionInfo source_info2;
222     ASSERT_EQ(OK, GetReturnError(source->finish(sink2_init_info.key.pubKey.value(),
223                                                 sink2_init_info.identity, sink2_info.signature,
224                                                 sink2_init_info.nonce, sink2_init_info.version,
225                                                 source_init2_info.key, &source_info2)));
226     ASSERT_EQ((int)source_info2.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
227     ASSERT_GT((int)source_info2.sessionId.size(), 0) << "Expect non-empty session ID from source";
228     std::vector<uint8_t> source_signing_key2 = SigningKeyFromIdentity(source_init2_info.identity);
229     CheckSignature(source_signing_key2, source_info2.sessionId, source_info2.signature);
230 
231     // Both ends should agree on the session ID.
232     ASSERT_EQ(source_info1.sessionId, sink1_info.sessionId);
233     ASSERT_EQ(source_info2.sessionId, sink2_info.sessionId);
234 
235     // Step 4: pass the source's session ID info back to the sink, so it can check it and
236     // update the symmetric keys so they're marked as authentication complete.
237     std::array<Arc, 2> auth_complete_result1;
238     ASSERT_EQ(OK, GetReturnError(sink1->authenticationComplete(
239                           source_info1.signature, sink1_info.sharedKeys, &auth_complete_result1)));
240     ASSERT_EQ((int)auth_complete_result1.size(), 2)
241             << "Expect two symmetric keys from authComplete()";
242     sink1_info.sharedKeys = auth_complete_result1;
243     std::array<Arc, 2> auth_complete_result2;
244     ASSERT_EQ(OK, GetReturnError(sink2->authenticationComplete(
245                           source_info2.signature, sink2_info.sharedKeys, &auth_complete_result2)));
246     ASSERT_EQ((int)auth_complete_result2.size(), 2)
247             << "Expect two symmetric keys from authComplete()";
248     sink2_info.sharedKeys = auth_complete_result2;
249 }
250 
TEST_P(AuthGraphSessionTest,ParallelSource)251 TEST_P(AuthGraphSessionTest, ParallelSource) {
252     std::shared_ptr<IAuthGraphKeyExchange> source1 = authNode_;
253     std::shared_ptr<IAuthGraphKeyExchange> source2 = authNode_;
254     std::shared_ptr<IAuthGraphKeyExchange> sink = authNode_;
255 
256     // Step 1: create an ephemeral ECDH key at each of the sources.
257     SessionInitiationInfo source1_init_info;
258     ASSERT_EQ(OK, GetReturnError(source1->create(&source1_init_info)));
259     ASSERT_TRUE(source1_init_info.key.pubKey.has_value());
260     ASSERT_TRUE(source1_init_info.key.arcFromPBK.has_value());
261     SessionInitiationInfo source2_init_info;
262     ASSERT_EQ(OK, GetReturnError(source1->create(&source2_init_info)));
263     ASSERT_TRUE(source2_init_info.key.pubKey.has_value());
264     ASSERT_TRUE(source2_init_info.key.arcFromPBK.has_value());
265 
266     // Step 2: pass each source's ECDH public key and other session info to the sink.
267     KeInitResult init1_result;
268     ASSERT_EQ(OK, GetReturnError(sink->init(source1_init_info.key.pubKey.value(),
269                                             source1_init_info.identity, source1_init_info.nonce,
270                                             source1_init_info.version, &init1_result)));
271     SessionInitiationInfo sink_init1_info = init1_result.sessionInitiationInfo;
272     ASSERT_TRUE(sink_init1_info.key.pubKey.has_value());
273 
274     SessionInfo sink_info1 = init1_result.sessionInfo;
275     ASSERT_EQ((int)sink_info1.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
276     ASSERT_GT((int)sink_info1.sessionId.size(), 0) << "Expect non-empty session ID from sink";
277     std::vector<uint8_t> sink_signing_key1 = SigningKeyFromIdentity(sink_init1_info.identity);
278     CheckSignature(sink_signing_key1, sink_info1.sessionId, sink_info1.signature);
279 
280     KeInitResult init2_result;
281     ASSERT_EQ(OK, GetReturnError(sink->init(source2_init_info.key.pubKey.value(),
282                                             source2_init_info.identity, source2_init_info.nonce,
283                                             source2_init_info.version, &init2_result)));
284     SessionInitiationInfo sink_init2_info = init2_result.sessionInitiationInfo;
285     ASSERT_TRUE(sink_init2_info.key.pubKey.has_value());
286 
287     SessionInfo sink_info2 = init2_result.sessionInfo;
288     ASSERT_EQ((int)sink_info2.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
289     ASSERT_GT((int)sink_info2.sessionId.size(), 0) << "Expect non-empty session ID from sink";
290     std::vector<uint8_t> sink_signing_key2 = SigningKeyFromIdentity(sink_init2_info.identity);
291     CheckSignature(sink_signing_key2, sink_info2.sessionId, sink_info2.signature);
292 
293     // Step 3: pass the sink's ECDH public keys and other session info to the each of the sources.
294     SessionInfo source1_info;
295     ASSERT_EQ(OK, GetReturnError(source1->finish(sink_init1_info.key.pubKey.value(),
296                                                  sink_init1_info.identity, sink_info1.signature,
297                                                  sink_init1_info.nonce, sink_init1_info.version,
298                                                  source1_init_info.key, &source1_info)));
299     ASSERT_EQ((int)source1_info.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
300     ASSERT_GT((int)source1_info.sessionId.size(), 0) << "Expect non-empty session ID from source";
301     std::vector<uint8_t> source1_signing_key = SigningKeyFromIdentity(source1_init_info.identity);
302     CheckSignature(source1_signing_key, source1_info.sessionId, source1_info.signature);
303 
304     SessionInfo source2_info;
305     ASSERT_EQ(OK, GetReturnError(source2->finish(sink_init2_info.key.pubKey.value(),
306                                                  sink_init2_info.identity, sink_info2.signature,
307                                                  sink_init2_info.nonce, sink_init2_info.version,
308                                                  source2_init_info.key, &source2_info)));
309     ASSERT_EQ((int)source2_info.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
310     ASSERT_GT((int)source2_info.sessionId.size(), 0) << "Expect non-empty session ID from source";
311     std::vector<uint8_t> source2_signing_key = SigningKeyFromIdentity(source2_init_info.identity);
312     CheckSignature(source2_signing_key, source2_info.sessionId, source2_info.signature);
313 
314     // Both ends should agree on the session ID.
315     ASSERT_EQ(source1_info.sessionId, sink_info1.sessionId);
316     ASSERT_EQ(source2_info.sessionId, sink_info2.sessionId);
317 
318     // Step 4: pass the each source's session ID info back to the sink, so it can check it and
319     // update the symmetric keys so they're marked as authentication complete.
320     std::array<Arc, 2> auth_complete_result1;
321     ASSERT_EQ(OK, GetReturnError(sink->authenticationComplete(
322                           source1_info.signature, sink_info1.sharedKeys, &auth_complete_result1)));
323     ASSERT_EQ((int)auth_complete_result1.size(), 2)
324             << "Expect two symmetric keys from authComplete()";
325     sink_info1.sharedKeys = auth_complete_result1;
326     std::array<Arc, 2> auth_complete_result2;
327     ASSERT_EQ(OK, GetReturnError(sink->authenticationComplete(
328                           source2_info.signature, sink_info2.sharedKeys, &auth_complete_result2)));
329     ASSERT_EQ((int)auth_complete_result2.size(), 2)
330             << "Expect two symmetric keys from authComplete()";
331     sink_info2.sharedKeys = auth_complete_result2;
332 }
333 
TEST_P(AuthGraphSessionTest,FreshNonces)334 TEST_P(AuthGraphSessionTest, FreshNonces) {
335     std::shared_ptr<IAuthGraphKeyExchange> source = authNode_;
336     std::shared_ptr<IAuthGraphKeyExchange> sink = authNode_;
337 
338     SessionInitiationInfo source_init_info1;
339     ASSERT_EQ(OK, GetReturnError(source->create(&source_init_info1)));
340     SessionInitiationInfo source_init_info2;
341     ASSERT_EQ(OK, GetReturnError(source->create(&source_init_info2)));
342 
343     // Two calls to create() should result in the same identity but different nonce values.
344     ASSERT_EQ(source_init_info1.identity, source_init_info2.identity);
345     ASSERT_NE(source_init_info1.nonce, source_init_info2.nonce);
346     ASSERT_NE(source_init_info1.key.pubKey, source_init_info2.key.pubKey);
347     ASSERT_NE(source_init_info1.key.arcFromPBK, source_init_info2.key.arcFromPBK);
348 
349     KeInitResult init_result1;
350     ASSERT_EQ(OK, GetReturnError(sink->init(source_init_info1.key.pubKey.value(),
351                                             source_init_info1.identity, source_init_info1.nonce,
352                                             source_init_info1.version, &init_result1)));
353     KeInitResult init_result2;
354     ASSERT_EQ(OK, GetReturnError(sink->init(source_init_info2.key.pubKey.value(),
355                                             source_init_info2.identity, source_init_info2.nonce,
356                                             source_init_info2.version, &init_result2)));
357 
358     // Two calls to init() should result in the same identity buf different nonces and session IDs.
359     ASSERT_EQ(init_result1.sessionInitiationInfo.identity,
360               init_result2.sessionInitiationInfo.identity);
361     ASSERT_NE(init_result1.sessionInitiationInfo.nonce, init_result2.sessionInitiationInfo.nonce);
362     ASSERT_NE(init_result1.sessionInfo.sessionId, init_result2.sessionInfo.sessionId);
363 }
364 
365 INSTANTIATE_TEST_SUITE_P(PerInstance, AuthGraphSessionTest,
366                          testing::ValuesIn(AuthGraphSessionTest::build_params()),
367                          ::android::PrintInstanceNameToString);
368 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AuthGraphSessionTest);
369 
370 }  // namespace aidl::android::hardware::security::authgraph::test
371 
main(int argc,char ** argv)372 int main(int argc, char** argv) {
373     ::testing::InitGoogleTest(&argc, argv);
374     return RUN_ALL_TESTS();
375 }
376