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, PetNameProvided) {
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 generates a pet name if no device ID is specified.
153  */
154 TEST(Initializer, PetNameCreation) {
155  RecordProperty("zephyr_key", "OTA-985,TST-145");
156  TemporaryDirectory temp_dir;
157 
158  // Make sure name is created.
159  Config conf("tests/config/basic.toml");
160  conf.storage.path = temp_dir.Path();
161  conf.provision.primary_ecu_serial = "testecuserial";
162  boost::filesystem::copy_file("tests/test_data/cred.zip", temp_dir.Path() / "cred.zip");
163  conf.provision.provision_path = temp_dir.Path() / "cred.zip";
164 
165  std::string test_name1, test_name2;
166  {
167  auto storage = INvStorage::newStorage(conf.storage);
168  auto http = std::make_shared<HttpFake>(temp_dir.Path());
169  KeyManager keys(storage, conf.keymanagerConfig());
170  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
171 
172  EXPECT_TRUE(storage->loadDeviceId(&test_name1));
173  EXPECT_NE(test_name1, "");
174  }
175 
176  // Make sure a new name is generated if the config does not specify a name and
177  // there is no device_id file.
178  TemporaryDirectory temp_dir2;
179  {
180  conf.storage.path = temp_dir2.Path();
181  boost::filesystem::copy_file("tests/test_data/cred.zip", temp_dir2.Path() / "cred.zip");
182  conf.provision.device_id = "";
183 
184  auto storage = INvStorage::newStorage(conf.storage);
185  auto http = std::make_shared<HttpFake>(temp_dir2.Path());
186  KeyManager keys(storage, conf.keymanagerConfig());
187  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
188 
189  EXPECT_TRUE(storage->loadDeviceId(&test_name2));
190  EXPECT_NE(test_name2, test_name1);
191  }
192 
193  // If the device_id is cleared in the config, but still present in the
194  // storage, re-initializing the config should read the device_id from storage.
195  {
196  conf.provision.device_id = "";
197  auto storage = INvStorage::newStorage(conf.storage);
198  auto http = std::make_shared<HttpFake>(temp_dir2.Path());
199  KeyManager keys(storage, conf.keymanagerConfig());
200  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
201 
202  std::string devid;
203  EXPECT_TRUE(storage->loadDeviceId(&devid));
204  EXPECT_EQ(devid, test_name2);
205  }
206 
207  // If the device_id is removed from storage, but the field is still present in
208  // the config, re-initializing the config should still read the device_id from
209  // config.
210  {
211  TemporaryDirectory temp_dir3;
212  conf.storage.path = temp_dir3.Path();
213  boost::filesystem::copy_file("tests/test_data/cred.zip", temp_dir3.Path() / "cred.zip");
214  conf.provision.device_id = test_name2;
215 
216  auto storage = INvStorage::newStorage(conf.storage);
217  auto http = std::make_shared<HttpFake>(temp_dir3.Path());
218  KeyManager keys(storage, conf.keymanagerConfig());
219  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
220 
221  std::string devid;
222  EXPECT_TRUE(storage->loadDeviceId(&devid));
223  EXPECT_EQ(devid, test_name2);
224  }
225 }
226 
227 enum class InitRetCode { kOk, kOccupied, kServerFailure, kStorageFailure, kSecondaryFailure, kBadP12, kPkcs11Failure };
228 
230  public:
231  HttpFakeDeviceRegistration(const boost::filesystem::path& test_dir_in) : HttpFake(test_dir_in) {}
232 
233  HttpResponse post(const std::string& url, const Json::Value& data) override {
234  if (url.find("/devices") != std::string::npos) {
235  if (retcode == InitRetCode::kOk) {
236  return HttpResponse(Utils::readFile("tests/test_data/cred.p12"), 200, CURLE_OK, "");
237  } else if (retcode == InitRetCode::kOccupied) {
238  Json::Value response;
239  response["code"] = "device_already_registered";
240  return HttpResponse(Utils::jsonToStr(response), 400, CURLE_OK, "");
241  } else {
242  return HttpResponse("", 400, CURLE_OK, "");
243  }
244  }
245  return HttpFake::post(url, data);
246  }
247 
248  InitRetCode retcode{InitRetCode::kOk};
249 };
250 
251 /* Detect and recover from failed device provisioning. */
252 TEST(Initializer, DeviceRegistration) {
253  TemporaryDirectory temp_dir;
254  auto http = std::make_shared<HttpFakeDeviceRegistration>(temp_dir.Path());
255  Config conf("tests/config/basic.toml");
256  conf.uptane.director_server = http->tls_server + "/director";
257  conf.uptane.repo_server = http->tls_server + "/repo";
258  conf.tls.server = http->tls_server;
259  conf.storage.path = temp_dir.Path();
260  conf.provision.primary_ecu_serial = "testecuserial";
261 
262  auto storage = INvStorage::newStorage(conf.storage);
263  KeyManager keys(storage, conf.keymanagerConfig());
264 
265  // Force a failure from the fake server due to device already registered.
266  {
267  http->retcode = InitRetCode::kOccupied;
268  EXPECT_THROW(Initializer(conf.provision, storage, http, keys, {}), Initializer::Error);
269  }
270 
271  // Force an arbitrary failure from the fake server.
272  {
273  http->retcode = InitRetCode::kServerFailure;
274  EXPECT_THROW(Initializer(conf.provision, storage, http, keys, {}), Initializer::ServerError);
275  }
276 
277  // Don't force a failure and make sure it actually works this time.
278  {
279  http->retcode = InitRetCode::kOk;
280  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
281  }
282 }
283 
285  public:
286  HttpFakeEcuRegistration(const boost::filesystem::path& test_dir_in) : HttpFake(test_dir_in) {}
287 
288  HttpResponse post(const std::string& url, const Json::Value& data) override {
289  if (url.find("/director/ecus") != std::string::npos) {
290  if (retcode == InitRetCode::kOk) {
291  return HttpResponse("", 200, CURLE_OK, "");
292  } else if (retcode == InitRetCode::kOccupied) {
293  Json::Value response;
294  response["code"] = "ecu_already_registered";
295  return HttpResponse(Utils::jsonToStr(response), 400, CURLE_OK, "");
296  } else {
297  return HttpResponse("", 400, CURLE_OK, "");
298  }
299  }
300  return HttpFake::post(url, data);
301  }
302 
303  InitRetCode retcode{InitRetCode::kOk};
304 };
305 
306 /* Detect and recover from failed ECU registration. */
307 TEST(Initializer, EcuRegisteration) {
308  TemporaryDirectory temp_dir;
309  auto http = std::make_shared<HttpFakeEcuRegistration>(temp_dir.Path());
310  Config conf("tests/config/basic.toml");
311  conf.uptane.director_server = http->tls_server + "/director";
312  conf.uptane.repo_server = http->tls_server + "/repo";
313  conf.tls.server = http->tls_server;
314  conf.storage.path = temp_dir.Path();
315  conf.provision.primary_ecu_serial = "testecuserial";
316 
317  auto storage = INvStorage::newStorage(conf.storage);
318  KeyManager keys(storage, conf.keymanagerConfig());
319 
320  // Force a failure from the fake server due to ECUs already registered.
321  {
322  http->retcode = InitRetCode::kOccupied;
323  EXPECT_THROW(Initializer(conf.provision, storage, http, keys, {}), Initializer::ServerError);
324  }
325 
326  // Force an arbitary failure from the fake server.
327  {
328  http->retcode = InitRetCode::kServerFailure;
329  EXPECT_THROW(Initializer(conf.provision, storage, http, keys, {}), Initializer::ServerError);
330  }
331 
332  // Don't force a failure and make sure it actually works this time.
333  {
334  http->retcode = InitRetCode::kOk;
335  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
336  }
337 }
338 
339 /**
340  * Verifies if the system hostname is used as a Primary ECU hardware ID
341  * if it's not specified in the configuration
342  *
343  * Checks actions:
344  *
345  * - [x] Use the system hostname as hardware ID if one is not provided
346  */
347 TEST(Initializer, HostnameAsHardwareID) {
348  TemporaryDirectory temp_dir;
349  Config conf("tests/config/basic.toml");
350  conf.storage.path = temp_dir.Path();
351 
352  boost::filesystem::copy_file("tests/test_data/cred.zip", temp_dir.Path() / "cred.zip");
353  conf.provision.provision_path = temp_dir.Path() / "cred.zip";
354 
355  {
356  auto storage = INvStorage::newStorage(conf.storage);
357  auto http = std::make_shared<HttpFake>(temp_dir.Path());
358  KeyManager keys(storage, conf.keymanagerConfig());
359 
360  EXPECT_TRUE(conf.provision.primary_ecu_hardware_id.empty());
361  EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {}));
362 
363  EcuSerials ecu_serials;
364  EXPECT_TRUE(storage->loadEcuSerials(&ecu_serials));
365  EXPECT_GE(ecu_serials.size(), 1);
366 
367  // A second element of the first tuple in ECU Serials tuple array is a Primary hardware ID.
368  // Each client of the storage class needs to know this information.
369  // If it changes then corresponding changes should be done in each storage client.
370  // perhaps it makes sense to introduce get/setPrimaryHardwareID method and incapsulate
371  // this tech info within storage (or maybe some other entity)
372  auto primaryHardwareID = ecu_serials[0].second;
373  auto hostname = Utils::getHostname();
374  EXPECT_EQ(primaryHardwareID, Uptane::HardwareIdentifier(hostname));
375  }
376 }
377 
378 #ifndef __NO_MAIN__
379 int main(int argc, char** argv) {
380  ::testing::InitGoogleTest(&argc, argv);
381  logger_init();
382  logger_set_threshold(boost::log::trivial::trace);
383  return RUN_ALL_TESTS();
384 }
385 #endif
General data structures.
Definition: types.cc:55
Configuration object for an aktualizr instance running on a Primary ECU.
Definition: config.h:74