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