Aktualizr
C++ SOTA Client
initializer.cc
1 #include "initializer.h"
2 
3 #include <string>
4 
5 #include <openssl/bio.h>
6 #include <boost/scoped_array.hpp>
7 
8 #include "bootstrap/bootstrap.h"
9 #include "crypto/keymanager.h"
10 #include "logging/logging.h"
11 
12 // Postcondition: device_id is in the storage
13 void Initializer::initDeviceId() {
14  // If device_id is already stored, just return.
15  std::string device_id;
16  if (storage_->loadDeviceId(&device_id)) {
17  return;
18  }
19 
20  // If device_id is specified in the config, use that.
21  device_id = config_.device_id;
22  if (device_id.empty()) {
23  // Otherwise, try to read the device certificate if it is available.
24  try {
25  device_id = keys_.getCN();
26  } catch (const std::exception& e) {
27  // No certificate: for device credential provisioning, abort. For shared
28  // credential provisioning, generate a random name.
29  if (config_.mode == ProvisionMode::kSharedCred || config_.mode == ProvisionMode::kSharedCredReuse) {
30  device_id = Utils::genPrettyName();
31  } else if (config_.mode == ProvisionMode::kDeviceCred) {
32  throw e;
33  } else {
34  throw Error("Unknown provisioning method");
35  }
36  }
37  }
38 
39  storage_->storeDeviceId(device_id);
40 }
41 
42 void Initializer::resetDeviceId() { storage_->clearDeviceId(); }
43 
44 // Postcondition [(serial, hw_id)] is in the storage
45 void Initializer::initEcuSerials() {
46  EcuSerials stored_ecu_serials;
47  storage_->loadEcuSerials(&stored_ecu_serials);
48 
49  std::string primary_ecu_serial_local = config_.primary_ecu_serial;
50  if (primary_ecu_serial_local.empty()) {
51  primary_ecu_serial_local = keys_.UptanePublicKey().KeyId();
52  }
53 
54  std::string primary_ecu_hardware_id = config_.primary_ecu_hardware_id;
55  if (primary_ecu_hardware_id.empty()) {
56  primary_ecu_hardware_id = Utils::getHostname();
57  if (primary_ecu_hardware_id.empty()) {
58  throw Error("Could not get current host name, please configure an hardware ID explicitly");
59  }
60  }
61 
62  new_ecu_serials_.emplace_back(Uptane::EcuSerial(primary_ecu_serial_local),
63  Uptane::HardwareIdentifier(primary_ecu_hardware_id));
64  for (const auto& s : secondaries_) {
65  new_ecu_serials_.emplace_back(s.first, s.second->getHwId());
66  }
67 
68  register_ecus_ = stored_ecu_serials.empty();
69  if (!stored_ecu_serials.empty()) {
70  // We should probably clear the misconfigured_ecus table once we have
71  // consent working.
72  std::vector<bool> found(stored_ecu_serials.size(), false);
73 
74  EcuCompare primary_comp(new_ecu_serials_[0]);
75  EcuSerials::const_iterator store_it;
76  store_it = std::find_if(stored_ecu_serials.cbegin(), stored_ecu_serials.cend(), primary_comp);
77  if (store_it == stored_ecu_serials.cend()) {
78  LOG_INFO << "Configured Primary ECU serial " << new_ecu_serials_[0].first << " with hardware ID "
79  << new_ecu_serials_[0].second << " not found in storage.";
80  register_ecus_ = true;
81  } else {
82  found[static_cast<size_t>(store_it - stored_ecu_serials.cbegin())] = true;
83  }
84 
85  // Check all configured Secondaries to see if any are new.
86  for (auto it = secondaries_.cbegin(); it != secondaries_.cend(); ++it) {
87  EcuCompare secondary_comp(std::make_pair(it->second->getSerial(), it->second->getHwId()));
88  store_it = std::find_if(stored_ecu_serials.cbegin(), stored_ecu_serials.cend(), secondary_comp);
89  if (store_it == stored_ecu_serials.cend()) {
90  LOG_INFO << "Configured Secondary ECU serial " << it->second->getSerial() << " with hardware ID "
91  << it->second->getHwId() << " not found in storage.";
92  register_ecus_ = true;
93  } else {
94  found[static_cast<size_t>(store_it - stored_ecu_serials.cbegin())] = true;
95  }
96  }
97 
98  // Check all stored Secondaries not already matched to see if any have been
99  // removed. Store them in a separate table to keep track of them.
100  std::vector<bool>::iterator found_it;
101  for (found_it = found.begin(); found_it != found.end(); ++found_it) {
102  if (!*found_it) {
103  auto not_registered = stored_ecu_serials[static_cast<size_t>(found_it - found.begin())];
104  LOG_INFO << "ECU serial " << not_registered.first << " with hardware ID " << not_registered.second
105  << " in storage was not found in Secondary configuration.";
106  register_ecus_ = true;
107  storage_->saveMisconfiguredEcu({not_registered.first, not_registered.second, EcuState::kOld});
108  }
109  }
110  }
111 }
112 
113 // Postcondition: (public, private) is in the storage. It should not be stored until Secondaries are provisioned
114 void Initializer::initPrimaryEcuKeys() {
115  std::string key_pair;
116  try {
117  key_pair = keys_.generateUptaneKeyPair();
118  } catch (const std::exception& e) {
119  throw KeyGenerationError(e.what());
120  }
121 
122  if (key_pair.empty()) {
123  throw KeyGenerationError("Unknow error");
124  }
125 }
126 
127 bool Initializer::loadSetTlsCreds() {
128  keys_.copyCertsToCurl(*http_client_);
129  return keys_.isOk();
130 }
131 
132 // Postcondition: TLS credentials are in the storage
133 void Initializer::initTlsCreds() {
134  if (loadSetTlsCreds()) {
135  return;
136  }
137 
138  if (config_.mode == ProvisionMode::kDeviceCred) {
139  throw StorageError("Device credentials expected but not found");
140  }
141 
142  // Shared credential provisioning is required and possible => (automatically)
143  // provision with shared credentials.
144 
145  // Set bootstrap (shared) credentials.
146  Bootstrap boot(config_.provision_path, config_.p12_password);
147  http_client_->setCerts(boot.getCa(), CryptoSource::kFile, boot.getCert(), CryptoSource::kFile, boot.getPkey(),
148  CryptoSource::kFile);
149 
150  Json::Value data;
151  std::string device_id;
152  if (!storage_->loadDeviceId(&device_id)) {
153  throw StorageError("Unable to load device_id during shared credential provisioning");
154  }
155  data["deviceId"] = device_id;
156  data["ttl"] = config_.expiry_days;
157  HttpResponse response = http_client_->post(config_.server + "/devices", data);
158  if (!response.isOk()) {
159  Json::Value resp_code;
160  try {
161  resp_code = response.getJson()["code"];
162  } catch (const std::exception& ex) {
163  LOG_ERROR << "Unable to parse reponse code from device registration: " << ex.what();
164  throw ServerError(ex.what());
165  }
166  if (resp_code.isString() && resp_code.asString() == "device_already_registered") {
167  LOG_ERROR << "Device ID " << device_id << " is already registered.";
168  throw ServerOccupied();
169  }
170  const auto err = std::string("Shared credential provisioning failed: ") +
171  std::to_string(response.http_status_code) + " " + response.body;
172  throw ServerError(err);
173  }
174 
175  std::string pkey;
176  std::string cert;
177  std::string ca;
178  StructGuard<BIO> device_p12(BIO_new_mem_buf(response.body.c_str(), static_cast<int>(response.body.size())),
179  BIO_vfree);
180  if (!Crypto::parseP12(device_p12.get(), "", &pkey, &cert, &ca)) {
181  throw ServerError("Received malformed device credentials from the server");
182  }
183  storage_->storeTlsCreds(ca, cert, pkey);
184 
185  // Set provisioned (device) credentials.
186  if (!loadSetTlsCreds()) {
187  throw Error("Failed to configure HTTP client with device credentials.");
188  }
189 
190  if (config_.mode != ProvisionMode::kSharedCredReuse) {
191  // Remove shared provisioning credentials from the archive; we have no more
192  // use for them.
193  Utils::removeFileFromArchive(config_.provision_path, "autoprov_credentials.p12");
194  // Remove the treehub.json if it's still there. It shouldn't have been put on
195  // the device, but it has happened before.
196  try {
197  Utils::removeFileFromArchive(config_.provision_path, "treehub.json");
198  } catch (...) {
199  }
200  }
201 
202  LOG_INFO << "Provisioned successfully on Device Gateway.";
203 }
204 
205 void Initializer::resetTlsCreds() {
206  if (config_.mode != ProvisionMode::kDeviceCred) {
207  storage_->clearTlsCreds();
208  }
209 }
210 
211 // Postcondition: "ECUs registered" flag set in the storage
212 void Initializer::initEcuRegister() {
213  // Allow re-registration if the ECUs have changed.
214  if (!register_ecus_) {
215  LOG_DEBUG << "All ECUs are already registered with the server.";
216  return;
217  }
218 
219  PublicKey uptane_public_key = keys_.UptanePublicKey();
220 
221  if (uptane_public_key.Type() == KeyType::kUnknown) {
222  throw StorageError("Invalid key in storage");
223  }
224 
225  Json::Value all_ecus;
226  all_ecus["primary_ecu_serial"] = new_ecu_serials_[0].first.ToString();
227  all_ecus["ecus"] = Json::arrayValue;
228  {
229  Json::Value primary_ecu;
230  primary_ecu["hardware_identifier"] = new_ecu_serials_[0].second.ToString();
231  primary_ecu["ecu_serial"] = new_ecu_serials_[0].first.ToString();
232  primary_ecu["clientKey"] = keys_.UptanePublicKey().ToUptane();
233  all_ecus["ecus"].append(primary_ecu);
234  }
235 
236  for (const auto& info : sec_info_) {
237  Json::Value ecu;
238  ecu["hardware_identifier"] = info.hw_id.ToString();
239  ecu["ecu_serial"] = info.serial.ToString();
240  ecu["clientKey"] = info.pub_key.ToUptane();
241  all_ecus["ecus"].append(ecu);
242  }
243 
244  HttpResponse response = http_client_->post(config_.ecu_registration_endpoint, all_ecus);
245  if (!response.isOk()) {
246  Json::Value resp_code = response.getJson()["code"];
247  if (resp_code.isString() &&
248  (resp_code.asString() == "ecu_already_registered" || resp_code.asString() == "device_already_registered")) {
249  throw ServerError("One or more ECUs are unexpectedly already registered");
250  }
251  const auto err =
252  std::string("Error registering device: ") + std::to_string(response.http_status_code) + " " + response.body;
253  throw ServerError(err);
254  }
255 
256  // Only store the changes if we successfully registered the ECUs.
257  storage_->storeEcuSerials(new_ecu_serials_);
258  for (const auto& info : sec_info_) {
259  storage_->saveSecondaryInfo(info.serial, info.type, info.pub_key);
260  }
261  storage_->storeEcuRegistered();
262 
263  LOG_INFO << "ECUs have been successfully registered with the server.";
264 }
265 
266 void Initializer::initSecondaryInfo() {
267  for (const auto& s : secondaries_) {
268  const Uptane::EcuSerial serial = s.first;
269  SecondaryInterface& sec = *s.second;
270 
271  SecondaryInfo info;
272  // If upgrading from the older version of the storage without the
273  // secondary_ecus table, we need to migrate the data. This should be done
274  // regardless of whether we need to (re-)register the ECUs.
275  // The ECU serials should be already initialized by this point.
276  if (!storage_->loadSecondaryInfo(serial, &info) || info.type.empty() || info.pub_key.Type() == KeyType::kUnknown) {
277  info.serial = serial;
278  info.hw_id = sec.getHwId();
279  info.type = sec.Type();
280  const PublicKey& p = sec.getPublicKey();
281  if (p.Type() != KeyType::kUnknown) {
282  info.pub_key = p;
283  }
284  // If we don't need to register the ECUs, we still need to store this info
285  // to complete the migration.
286  if (!register_ecus_) {
287  storage_->saveSecondaryInfo(info.serial, info.type, info.pub_key);
288  }
289  }
290  // We will need this info later if the device is not yet provisioned
291  sec_info_.push_back(std::move(info));
292  }
293 }
294 
295 void Initializer::initEcuReportCounter() {
296  std::vector<std::pair<Uptane::EcuSerial, int64_t>> ecu_cnt;
297 
298  if (storage_->loadEcuReportCounter(&ecu_cnt)) {
299  return;
300  }
301 
302  EcuSerials ecu_serials;
303 
304  if (!storage_->loadEcuSerials(&ecu_serials) || ecu_serials.empty()) {
305  throw Error("Could not load ECU serials");
306  }
307 
308  storage_->saveEcuReportCounter(Uptane::EcuSerial(ecu_serials[0].first.ToString()), 0);
309 }
310 
311 // Postcondition: "ECUs registered" flag set in the storage
312 Initializer::Initializer(const ProvisionConfig& config_in, std::shared_ptr<INvStorage> storage_in,
313  std::shared_ptr<HttpInterface> http_client_in, KeyManager& keys_in,
314  const std::map<Uptane::EcuSerial, std::shared_ptr<SecondaryInterface>>& secondaries_in)
315  : config_(config_in),
316  storage_(std::move(storage_in)),
317  http_client_(std::move(http_client_in)),
318  keys_(keys_in),
319  secondaries_(secondaries_in) {
320  for (int i = 0; i < MaxInitializationAttempts; i++) {
321  initDeviceId();
322 
323  try {
324  initTlsCreds();
325  } catch (const ServerOccupied& e) {
326  // if a device with the same ID has already been registered to the server,
327  // generate a new one
328  resetDeviceId();
329  LOG_ERROR << "Device name is already registered. Retrying.";
330  continue;
331  }
332 
333  initPrimaryEcuKeys();
334 
335  initEcuSerials();
336 
337  initSecondaryInfo();
338 
339  initEcuRegister();
340 
341  initEcuReportCounter();
342 
343  return;
344  }
345 
346  throw Error(std::string("Initialization failed after ") + std::to_string(MaxInitializationAttempts) + " attempts");
347 }
ProvisionConfig
Definition: config.h:51
KeyManager
Definition: keymanager.h:13
EcuCompare
Definition: initializer.h:63
PublicKey::ToUptane
Json::Value ToUptane() const
Uptane Json representation of this public key.
Definition: crypto.cc:86
data
General data structures.
Definition: types.h:217
Uptane::HardwareIdentifier
Definition: types.h:315
HttpResponse
Definition: httpinterface.h:17
Uptane::EcuSerial
Definition: types.h:346
SecondaryInfo
Definition: types.h:462
PublicKey
Definition: types.h:119
Bootstrap
Definition: bootstrap.h:7
SecondaryInterface
Definition: secondaryinterface.h:9