Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
initializer_test.cc
1 #include <gtest/gtest.h>
2 
3 #include <string>
4 
5 #include <boost/filesystem.hpp>
6 
7 #include "httpfake.h"
8 #include "primary/initializer.h"
9 #include "primary/sotauptaneclient.h"
10 #include "storage/invstorage.h"
11 #include "utilities/utils.h"
12 
13 /*
14  * Check that aktualizr creates provisioning data if they don't exist already.
15  */
16 TEST(Initializer, Success) {
17  RecordProperty("zephyr_key", "OTA-983,TST-153");
18  TemporaryDirectory temp_dir;
19  auto http = std::make_shared<HttpFake>(temp_dir.Path());
20  Config conf("tests/config/basic.toml");
21  conf.uptane.director_server = http->tls_server + "/director";
22  conf.uptane.repo_server = http->tls_server + "/repo";
23  conf.tls.server = http->tls_server;
24  conf.storage.path = temp_dir.Path();
25  conf.provision.primary_ecu_serial = "testecuserial";
26 
27  // First make sure nothing is already there.
28  auto storage = INvStorage::newStorage(conf.storage);
29  std::string pkey;
30  std::string cert;
31  std::string ca;
32  EXPECT_FALSE(storage->loadTlsCreds(&ca, &cert, &pkey));
33  std::string public_key;
34  std::string private_key;
35  EXPECT_FALSE(storage->loadPrimaryKeys(&public_key, &private_key));
36 
37  // Initialize.
38  KeyManager keys(storage, conf.keymanagerConfig());
39  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
40 
41  // Then verify that the storage contains what we expect.
42  EXPECT_TRUE(storage->loadTlsCreds(&ca, &cert, &pkey));
43  EXPECT_NE(ca, "");
44  EXPECT_NE(cert, "");
45  EXPECT_NE(pkey, "");
46  EXPECT_TRUE(storage->loadPrimaryKeys(&public_key, &private_key));
47  EXPECT_NE(public_key, "");
48  EXPECT_NE(private_key, "");
49 
50  const Json::Value ecu_data = Utils::parseJSONFile(temp_dir.Path() / "post.json");
51  EXPECT_EQ(ecu_data["ecus"].size(), 1);
52  EXPECT_EQ(ecu_data["ecus"][0]["clientKey"]["keyval"]["public"].asString(), public_key);
53  EXPECT_EQ(ecu_data["ecus"][0]["ecu_serial"].asString(), conf.provision.primary_ecu_serial);
54  EXPECT_NE(ecu_data["ecus"][0]["hardware_identifier"].asString(), "");
55  EXPECT_EQ(ecu_data["primary_ecu_serial"].asString(), conf.provision.primary_ecu_serial);
56 }
57 
58 /*
59  * Check that aktualizr does NOT change provisioning data if they DO exist
60  * already.
61  */
62 TEST(Initializer, InitializeTwice) {
63  RecordProperty("zephyr_key", "OTA-983,TST-154");
64  TemporaryDirectory temp_dir;
65  auto http = std::make_shared<HttpFake>(temp_dir.Path());
66  Config conf("tests/config/basic.toml");
67  conf.storage.path = temp_dir.Path();
68  conf.provision.primary_ecu_serial = "testecuserial";
69 
70  // First make sure nothing is already there.
71  auto storage = INvStorage::newStorage(conf.storage);
72  std::string pkey1;
73  std::string cert1;
74  std::string ca1;
75  EXPECT_FALSE(storage->loadTlsCreds(&ca1, &cert1, &pkey1));
76  std::string public_key1;
77  std::string private_key1;
78  EXPECT_FALSE(storage->loadPrimaryKeys(&public_key1, &private_key1));
79 
80  // Intialize and verify that the storage contains what we expect.
81  {
82  KeyManager keys(storage, conf.keymanagerConfig());
83  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
84 
85  EXPECT_TRUE(storage->loadTlsCreds(&ca1, &cert1, &pkey1));
86  EXPECT_NE(ca1, "");
87  EXPECT_NE(cert1, "");
88  EXPECT_NE(pkey1, "");
89  EXPECT_TRUE(storage->loadPrimaryKeys(&public_key1, &private_key1));
90  EXPECT_NE(public_key1, "");
91  EXPECT_NE(private_key1, "");
92  }
93 
94  // Intialize again and verify that nothing has changed.
95  {
96  KeyManager keys(storage, conf.keymanagerConfig());
97  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
98 
99  std::string pkey2;
100  std::string cert2;
101  std::string ca2;
102  EXPECT_TRUE(storage->loadTlsCreds(&ca2, &cert2, &pkey2));
103  std::string public_key2;
104  std::string private_key2;
105  EXPECT_TRUE(storage->loadPrimaryKeys(&public_key2, &private_key2));
106 
107  EXPECT_EQ(cert1, cert2);
108  EXPECT_EQ(ca1, ca2);
109  EXPECT_EQ(pkey1, pkey2);
110  EXPECT_EQ(public_key1, public_key2);
111  EXPECT_EQ(private_key1, private_key2);
112  }
113 }
114 
115 /**
116  * Check that aktualizr does not generate a pet name when device ID is
117  * specified.
118  */
119 TEST(Initializer, PetNameConfiguration) {
120  RecordProperty("zephyr_key", "OTA-985,TST-146");
121  TemporaryDirectory temp_dir;
122  const std::string test_name = "test-name-123";
123 
124  /* Make sure provided device ID is read as expected. */
125  Config conf("tests/config/device_id.toml");
126  conf.storage.path = temp_dir.Path();
127  conf.provision.primary_ecu_serial = "testecuserial";
128 
129  auto storage = INvStorage::newStorage(conf.storage);
130  auto http = std::make_shared<HttpFake>(temp_dir.Path());
131  KeyManager keys(storage, conf.keymanagerConfig());
132  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
133 
134  {
135  EXPECT_EQ(conf.provision.device_id, test_name);
136  std::string devid;
137  EXPECT_TRUE(storage->loadDeviceId(&devid));
138  EXPECT_EQ(devid, test_name);
139  }
140 
141  {
142  /* Make sure name is unchanged after re-initializing config. */
143  conf.postUpdateValues();
144  EXPECT_EQ(conf.provision.device_id, test_name);
145  std::string devid;
146  EXPECT_TRUE(storage->loadDeviceId(&devid));
147  EXPECT_EQ(devid, test_name);
148  }
149 }
150 
151 /**
152  * Check that aktualizr does not generate a pet name when device ID is
153  * already present in the device certificate's common name. This is the expected
154  * behavior required to support replacing a Primary ECU.
155  */
156 TEST(Initializer, PetNameDeviceCert) {
157  std::string test_name;
158  Config conf("tests/config/basic.toml");
159 
160  {
161  TemporaryDirectory temp_dir;
162  auto http = std::make_shared<HttpFake>(temp_dir.Path());
163  conf.storage.path = temp_dir.Path();
164  auto storage = INvStorage::newStorage(conf.storage);
165  KeyManager keys(storage, conf.keymanagerConfig());
166  storage->storeTlsCert(Utils::readFile("tests/test_data/prov/client.pem"));
167  test_name = keys.getCN();
168  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
169  std::string devid;
170  EXPECT_TRUE(storage->loadDeviceId(&devid));
171  EXPECT_EQ(devid, test_name);
172  }
173 
174  {
175  /* Make sure name is unchanged after re-initializing with the same device
176  * certificate but otherwise a completely fresh storage. */
177  TemporaryDirectory temp_dir;
178  auto http = std::make_shared<HttpFake>(temp_dir.Path());
179  conf.storage.path = temp_dir.Path();
180  auto storage = INvStorage::newStorage(conf.storage);
181  KeyManager keys(storage, conf.keymanagerConfig());
182  storage->storeTlsCert(Utils::readFile("tests/test_data/prov/client.pem"));
183  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
184  std::string devid;
185  EXPECT_TRUE(storage->loadDeviceId(&devid));
186  EXPECT_EQ(devid, test_name);
187  }
188 }
189 
190 /**
191  * Check that aktualizr generates a pet name if no device ID is specified.
192  */
193 TEST(Initializer, PetNameCreation) {
194  RecordProperty("zephyr_key", "OTA-985,TST-145");
195  TemporaryDirectory temp_dir;
196 
197  // Make sure name is created.
198  Config conf("tests/config/basic.toml");
199  conf.storage.path = temp_dir.Path();
200  conf.provision.primary_ecu_serial = "testecuserial";
201  boost::filesystem::copy_file("tests/test_data/cred.zip", temp_dir.Path() / "cred.zip");
202  conf.provision.provision_path = temp_dir.Path() / "cred.zip";
203 
204  std::string test_name1, test_name2;
205  {
206  auto storage = INvStorage::newStorage(conf.storage);
207  auto http = std::make_shared<HttpFake>(temp_dir.Path());
208  KeyManager keys(storage, conf.keymanagerConfig());
209  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
210 
211  EXPECT_TRUE(storage->loadDeviceId(&test_name1));
212  EXPECT_NE(test_name1, "");
213  }
214 
215  // Make sure a new name is generated if using a new database and the config
216  // does not specify a device ID.
217  TemporaryDirectory temp_dir2;
218  {
219  conf.storage.path = temp_dir2.Path();
220  boost::filesystem::copy_file("tests/test_data/cred.zip", temp_dir2.Path() / "cred.zip");
221  conf.provision.device_id = "";
222 
223  auto storage = INvStorage::newStorage(conf.storage);
224  auto http = std::make_shared<HttpFake>(temp_dir2.Path());
225  KeyManager keys(storage, conf.keymanagerConfig());
226  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
227 
228  EXPECT_TRUE(storage->loadDeviceId(&test_name2));
229  EXPECT_NE(test_name2, test_name1);
230  }
231 
232  // If the device_id is cleared in the config, but still present in the
233  // storage, re-initializing the config should read the device_id from storage.
234  {
235  conf.provision.device_id = "";
236  auto storage = INvStorage::newStorage(conf.storage);
237  auto http = std::make_shared<HttpFake>(temp_dir2.Path());
238  KeyManager keys(storage, conf.keymanagerConfig());
239  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
240 
241  std::string devid;
242  EXPECT_TRUE(storage->loadDeviceId(&devid));
243  EXPECT_EQ(devid, test_name2);
244  }
245 
246  // If the device_id is removed from storage, but the field is still present in
247  // the config, re-initializing the config should still read the device_id from
248  // config.
249  {
250  TemporaryDirectory temp_dir3;
251  conf.storage.path = temp_dir3.Path();
252  boost::filesystem::copy_file("tests/test_data/cred.zip", temp_dir3.Path() / "cred.zip");
253  conf.provision.device_id = test_name2;
254 
255  auto storage = INvStorage::newStorage(conf.storage);
256  auto http = std::make_shared<HttpFake>(temp_dir3.Path());
257  KeyManager keys(storage, conf.keymanagerConfig());
258  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
259 
260  std::string devid;
261  EXPECT_TRUE(storage->loadDeviceId(&devid));
262  EXPECT_EQ(devid, test_name2);
263  }
264 }
265 
266 enum class InitRetCode { kOk, kOccupied, kServerFailure, kStorageFailure, kSecondaryFailure, kBadP12, kPkcs11Failure };
267 
269  public:
270  HttpFakeDeviceRegistration(const boost::filesystem::path& test_dir_in) : HttpFake(test_dir_in) {}
271 
272  HttpResponse post(const std::string& url, const Json::Value& data) override {
273  if (url.find("/devices") != std::string::npos) {
274  if (retcode == InitRetCode::kOk) {
275  return HttpResponse(Utils::readFile("tests/test_data/cred.p12"), 200, CURLE_OK, "");
276  } else if (retcode == InitRetCode::kOccupied) {
277  Json::Value response;
278  response["code"] = "device_already_registered";
279  return HttpResponse(Utils::jsonToStr(response), 400, CURLE_OK, "");
280  } else {
281  return HttpResponse("", 400, CURLE_OK, "");
282  }
283  }
284  return HttpFake::post(url, data);
285  }
286 
287  InitRetCode retcode{InitRetCode::kOk};
288 };
289 
290 /* Detect and recover from failed device provisioning. */
291 TEST(Initializer, DeviceRegistration) {
292  TemporaryDirectory temp_dir;
293  auto http = std::make_shared<HttpFakeDeviceRegistration>(temp_dir.Path());
294  Config conf("tests/config/basic.toml");
295  conf.uptane.director_server = http->tls_server + "/director";
296  conf.uptane.repo_server = http->tls_server + "/repo";
297  conf.tls.server = http->tls_server;
298  conf.storage.path = temp_dir.Path();
299  conf.provision.primary_ecu_serial = "testecuserial";
300 
301  auto storage = INvStorage::newStorage(conf.storage);
302  KeyManager keys(storage, conf.keymanagerConfig());
303 
304  // Force a failure from the fake server due to device already registered.
305  {
306  http->retcode = InitRetCode::kOccupied;
307  EXPECT_THROW(Initializer(conf.provision, storage, http, keys, {}), Initializer::Error);
308  }
309 
310  // Force an arbitrary failure from the fake server.
311  {
312  http->retcode = InitRetCode::kServerFailure;
313  EXPECT_THROW(Initializer(conf.provision, storage, http, keys, {}), Initializer::ServerError);
314  }
315 
316  // Don't force a failure and make sure it actually works this time.
317  {
318  http->retcode = InitRetCode::kOk;
319  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
320  }
321 }
322 
324  public:
325  HttpFakeEcuRegistration(const boost::filesystem::path& test_dir_in) : HttpFake(test_dir_in) {}
326 
327  HttpResponse post(const std::string& url, const Json::Value& data) override {
328  if (url.find("/director/ecus") != std::string::npos) {
329  if (retcode == InitRetCode::kOk) {
330  return HttpResponse("", 200, CURLE_OK, "");
331  } else if (retcode == InitRetCode::kOccupied) {
332  Json::Value response;
333  response["code"] = "ecu_already_registered";
334  return HttpResponse(Utils::jsonToStr(response), 400, CURLE_OK, "");
335  } else {
336  return HttpResponse("", 400, CURLE_OK, "");
337  }
338  }
339  return HttpFake::post(url, data);
340  }
341 
342  InitRetCode retcode{InitRetCode::kOk};
343 };
344 
345 /* Detect and recover from failed ECU registration. */
346 TEST(Initializer, EcuRegisteration) {
347  TemporaryDirectory temp_dir;
348  auto http = std::make_shared<HttpFakeEcuRegistration>(temp_dir.Path());
349  Config conf("tests/config/basic.toml");
350  conf.uptane.director_server = http->tls_server + "/director";
351  conf.uptane.repo_server = http->tls_server + "/repo";
352  conf.tls.server = http->tls_server;
353  conf.storage.path = temp_dir.Path();
354  conf.provision.primary_ecu_serial = "testecuserial";
355 
356  auto storage = INvStorage::newStorage(conf.storage);
357  KeyManager keys(storage, conf.keymanagerConfig());
358 
359  // Force a failure from the fake server due to ECUs already registered.
360  {
361  http->retcode = InitRetCode::kOccupied;
362  EXPECT_THROW(Initializer(conf.provision, storage, http, keys, {}), Initializer::ServerError);
363  }
364 
365  // Force an arbitary failure from the fake server.
366  {
367  http->retcode = InitRetCode::kServerFailure;
368  EXPECT_THROW(Initializer(conf.provision, storage, http, keys, {}), Initializer::ServerError);
369  }
370 
371  // Don't force a failure and make sure it actually works this time.
372  {
373  http->retcode = InitRetCode::kOk;
374  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
375  }
376 }
377 
378 /* Use the system hostname as hardware ID if one is not provided. */
379 TEST(Initializer, HostnameAsHardwareID) {
380  TemporaryDirectory temp_dir;
381  Config conf("tests/config/basic.toml");
382  conf.storage.path = temp_dir.Path();
383 
384  boost::filesystem::copy_file("tests/test_data/cred.zip", temp_dir.Path() / "cred.zip");
385  conf.provision.provision_path = temp_dir.Path() / "cred.zip";
386 
387  {
388  auto storage = INvStorage::newStorage(conf.storage);
389  auto http = std::make_shared<HttpFake>(temp_dir.Path());
390  KeyManager keys(storage, conf.keymanagerConfig());
391 
392  EXPECT_TRUE(conf.provision.primary_ecu_hardware_id.empty());
393  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
394 
395  EcuSerials ecu_serials;
396  EXPECT_TRUE(storage->loadEcuSerials(&ecu_serials));
397  EXPECT_GE(ecu_serials.size(), 1);
398 
399  auto primaryHardwareID = ecu_serials[0].second;
400  auto hostname = Utils::getHostname();
401  EXPECT_EQ(primaryHardwareID, Uptane::HardwareIdentifier(hostname));
402  }
403 }
404 
405 #ifndef __NO_MAIN__
406 int main(int argc, char** argv) {
407  ::testing::InitGoogleTest(&argc, argv);
408  logger_init();
409  logger_set_threshold(boost::log::trivial::trace);
410  return RUN_ALL_TESTS();
411 }
412 #endif
Initializer::ServerError
Definition: initializer.h:32
HttpFake
Definition: httpfake.h:20
KeyManager
Definition: keymanager.h:13
HttpFakeDeviceRegistration
Definition: initializer_test.cc:268
data
General data structures.
Definition: types.h:217
Uptane::HardwareIdentifier
Definition: types.h:315
HttpResponse
Definition: httpinterface.h:17
HttpFakeEcuRegistration
Definition: initializer_test.cc:323
Config
Configuration object for an aktualizr instance running on a Primary ECU.
Definition: config.h:208
TemporaryDirectory
Definition: utils.h:82
Initializer
Definition: initializer.h:14
Initializer::Error
Definition: initializer.h:20