xref: /aosp_15_r20/hardware/interfaces/security/authgraph/aidl/vts/functional/AuthGraphSessionTest.cpp (revision 4d7e907c777eeecc4c5bd7cf640a754fac206ff7)
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