Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
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 config, just use it, otherwise generate a random one
21  device_id = config_.device_id;
22  if (device_id.empty()) {
23  if (config_.mode == ProvisionMode::kSharedCred) {
24  device_id = Utils::genPrettyName();
25  } else if (config_.mode == ProvisionMode::kDeviceCred) {
26  device_id = keys_.getCN();
27  } else {
28  throw Error("Unknown provisioning method");
29  }
30  }
31 
32  storage_->storeDeviceId(device_id);
33 }
34 
35 void Initializer::resetDeviceId() { storage_->clearDeviceId(); }
36 
37 // Postcondition [(serial, hw_id)] is in the storage
38 void Initializer::initEcuSerials() {
39  EcuSerials ecu_serials;
40 
41  // TODO: the assumption now is that the set of connected ECUs doesn't change, but it might obviously
42  // not be the case. ECU discovery seems to be a big story and should be worked on accordingly.
43  if (storage_->loadEcuSerials(&ecu_serials)) {
44  return;
45  }
46 
47  std::string primary_ecu_serial_local = config_.primary_ecu_serial;
48  if (primary_ecu_serial_local.empty()) {
49  primary_ecu_serial_local = keys_.UptanePublicKey().KeyId();
50  }
51 
52  std::string primary_ecu_hardware_id = config_.primary_ecu_hardware_id;
53  if (primary_ecu_hardware_id.empty()) {
54  primary_ecu_hardware_id = Utils::getHostname();
55  if (primary_ecu_hardware_id == "") {
56  throw Error("Could not get current host name, please configure an hardware ID explicitely");
57  }
58  }
59 
60  ecu_serials.emplace_back(Uptane::EcuSerial(primary_ecu_serial_local),
61  Uptane::HardwareIdentifier(primary_ecu_hardware_id));
62 
63  for (const auto& s : secondaries_) {
64  ecu_serials.emplace_back(s.first, s.second->getHwId());
65  }
66 
67  storage_->storeEcuSerials(ecu_serials);
68 }
69 
70 void Initializer::resetEcuSerials() { storage_->clearEcuSerials(); }
71 
72 // Postcondition: (public, private) is in the storage. It should not be stored until secondaries are provisioned
73 void Initializer::initPrimaryEcuKeys() {
74  std::string key_pair;
75  try {
76  key_pair = keys_.generateUptaneKeyPair();
77  } catch (const std::exception& e) {
78  throw KeyGenerationError(e.what());
79  }
80 
81  if (key_pair.size() == 0U) {
82  throw KeyGenerationError("Unknow error");
83  }
84 }
85 
86 void Initializer::resetEcuKeys() { storage_->clearPrimaryKeys(); }
87 
88 bool Initializer::loadSetTlsCreds() {
89  keys_.copyCertsToCurl(*http_client_);
90  return keys_.isOk();
91 }
92 
93 // Postcondition: TLS credentials are in the storage
94 void Initializer::initTlsCreds() {
95  if (loadSetTlsCreds()) {
96  return;
97  }
98 
99  if (config_.mode != ProvisionMode::kSharedCred) {
100  throw StorageError("Shared credentials expected but not found");
101  }
102 
103  // Shared credential provision is required and possible => (automatically)
104  // provision with shared credentials.
105 
106  // set bootstrap credentials
107  Bootstrap boot(config_.provision_path, config_.p12_password);
108  http_client_->setCerts(boot.getCa(), CryptoSource::kFile, boot.getCert(), CryptoSource::kFile, boot.getPkey(),
109  CryptoSource::kFile);
110 
111  Json::Value data;
112  std::string device_id;
113  if (!storage_->loadDeviceId(&device_id)) {
114  throw StorageError("Unable to load device_id during shared credential provisioning");
115  }
116  data["deviceId"] = device_id;
117  data["ttl"] = config_.expiry_days;
118  HttpResponse response = http_client_->post(config_.server + "/devices", data);
119  if (!response.isOk()) {
120  Json::Value resp_code = response.getJson()["code"];
121  if (resp_code.isString() && resp_code.asString() == "device_already_registered") {
122  LOG_ERROR << "Device ID " << device_id << " is already registered.";
123  throw ServerOccupied();
124  }
125  const auto err = std::string("Shared credential provisioning failed: ") +
126  std::to_string(response.http_status_code) + " " + response.body;
127  throw ServerError(err);
128  }
129 
130  std::string pkey;
131  std::string cert;
132  std::string ca;
133  StructGuard<BIO> device_p12(BIO_new_mem_buf(response.body.c_str(), static_cast<int>(response.body.size())),
134  BIO_vfree);
135  if (!Crypto::parseP12(device_p12.get(), "", &pkey, &cert, &ca)) {
136  throw ServerError("Received malformed device credentials from the server");
137  }
138  storage_->storeTlsCreds(ca, cert, pkey);
139 
140  // set provisioned credentials
141  if (!loadSetTlsCreds()) {
142  throw Error("Failed to configure HTTP client with device credentials.");
143  }
144 
145  LOG_INFO << "Provisioned successfully on Device Gateway.";
146 }
147 
148 void Initializer::resetTlsCreds() {
149  if (config_.mode != ProvisionMode::kDeviceCred) {
150  storage_->clearTlsCreds();
151  }
152 }
153 
154 // Postcondition: "ECUs registered" flag set in the storage
155 void Initializer::initEcuRegister() {
156  if (storage_->loadEcuRegistered()) {
157  return;
158  }
159 
160  PublicKey uptane_public_key = keys_.UptanePublicKey();
161 
162  if (uptane_public_key.Type() == KeyType::kUnknown) {
163  throw StorageError("Invalid key in storage");
164  }
165 
166  EcuSerials ecu_serials;
167  // initEcuSerials should have been called by this point
168  if (!storage_->loadEcuSerials(&ecu_serials) || ecu_serials.size() < 1) {
169  throw StorageError("Could not load ECUs from storage");
170  }
171 
172  Json::Value all_ecus;
173  all_ecus["primary_ecu_serial"] = ecu_serials[0].first.ToString();
174  all_ecus["ecus"] = Json::arrayValue;
175  {
176  Json::Value primary_ecu;
177  primary_ecu["hardware_identifier"] = ecu_serials[0].second.ToString();
178  primary_ecu["ecu_serial"] = ecu_serials[0].first.ToString();
179  primary_ecu["clientKey"] = keys_.UptanePublicKey().ToUptane();
180  all_ecus["ecus"].append(primary_ecu);
181  }
182 
183  for (const auto& info : sec_info_) {
184  Json::Value ecu;
185  ecu["hardware_identifier"] = info.hw_id.ToString();
186  ecu["ecu_serial"] = info.serial.ToString();
187  ecu["clientKey"] = info.pub_key.ToUptane();
188  all_ecus["ecus"].append(ecu);
189  }
190 
191  HttpResponse response = http_client_->post(config_.ecu_registration_endpoint, all_ecus);
192  if (!response.isOk()) {
193  Json::Value resp_code = response.getJson()["code"];
194  if (resp_code.isString() &&
195  (resp_code.asString() == "ecu_already_registered" || resp_code.asString() == "device_already_registered")) {
196  throw ServerError("One or more ECUs are unexpectedly already registered");
197  }
198  const auto err =
199  std::string("Error registering device: ") + std::to_string(response.http_status_code) + " " + response.body;
200  throw ServerError(err);
201  }
202 
203  storage_->storeEcuRegistered();
204 
205  LOG_INFO << "ECUs have been successfully registered with the server.";
206 }
207 
208 void Initializer::initSecondaryInfo() {
209  for (const auto& s : secondaries_) {
210  const Uptane::EcuSerial serial = s.first;
211  Uptane::SecondaryInterface& sec = *s.second;
212 
213  SecondaryInfo info;
214  // We must check this on every initialization, as this info
215  // won't be there in case of an upgrade from an older version.
216  // On the first initialization ECU serials should be already
217  // initialized by this point.
218  if (!storage_->loadSecondaryInfo(serial, &info) || info.type == "" || info.pub_key.Type() == KeyType::kUnknown) {
219  info.serial = serial;
220  info.hw_id = sec.getHwId();
221  info.type = sec.Type();
222  const PublicKey& p = sec.getPublicKey();
223  if (p.Type() != KeyType::kUnknown) {
224  info.pub_key = p;
225  }
226  storage_->saveSecondaryInfo(info.serial, info.type, info.pub_key);
227  }
228  // We will need this info later if the device is not yet provisioned
229  sec_info_.push_back(std::move(info));
230  }
231 }
232 
233 void Initializer::initEcuReportCounter() {
234  std::vector<std::pair<Uptane::EcuSerial, int64_t>> ecu_cnt;
235 
236  if (storage_->loadEcuReportCounter(&ecu_cnt)) {
237  return;
238  }
239 
240  EcuSerials ecu_serials;
241 
242  if (!storage_->loadEcuSerials(&ecu_serials) || (ecu_serials.size() == 0)) {
243  throw Error("Could not load ECU serials");
244  }
245 
246  storage_->saveEcuReportCounter(Uptane::EcuSerial(ecu_serials[0].first.ToString()), 0);
247 }
248 
249 // Postcondition: "ECUs registered" flag set in the storage
250 Initializer::Initializer(const ProvisionConfig& config_in, std::shared_ptr<INvStorage> storage_in,
251  std::shared_ptr<HttpInterface> http_client_in, KeyManager& keys_in,
252  const std::map<Uptane::EcuSerial, std::shared_ptr<Uptane::SecondaryInterface>>& secondaries_in)
253  : config_(config_in),
254  storage_(std::move(storage_in)),
255  http_client_(std::move(http_client_in)),
256  keys_(keys_in),
257  secondaries_(secondaries_in) {
258  for (int i = 0; i < MaxInitializationAttempts; i++) {
259  initDeviceId();
260 
261  try {
262  initTlsCreds();
263  } catch (const ServerOccupied& e) {
264  // if a device with the same ID has already been registered to the server,
265  // generate a new one
266  resetDeviceId();
267  LOG_ERROR << "Device name is already registered. Retrying.";
268  continue;
269  }
270 
271  initPrimaryEcuKeys();
272 
273  initEcuSerials();
274 
275  initEcuReportCounter();
276 
277  initSecondaryInfo();
278 
279  initEcuRegister();
280 
281  return;
282  }
283 
284  throw Error(std::string("Initialization failed after ") + std::to_string(MaxInitializationAttempts) + " attempts");
285 }
General data structures.
Definition: types.cc:55
STL namespace.
Json::Value ToUptane() const
Uptane Json representation of this public key.
Definition: crypto.cc:75