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