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