Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
ipuptanesecondary.cc
1 #include <arpa/inet.h>
2 #include <netinet/tcp.h>
3 
4 #include <array>
5 #include <memory>
6 
7 #include "ipuptanesecondary.h"
8 #include "logging/logging.h"
9 #include "storage/invstorage.h"
10 
11 namespace Uptane {
12 
13 SecondaryInterface::Ptr IpUptaneSecondary::connectAndCreate(const std::string& address, unsigned short port) {
14  LOG_INFO << "Connecting to and getting info about IP Secondary: " << address << ":" << port << "...";
15 
16  ConnectionSocket con_sock{address, port};
17 
18  if (con_sock.connect() == 0) {
19  LOG_INFO << "Connected to IP Secondary: "
20  << "(" << address << ":" << port << ")";
21  } else {
22  LOG_WARNING << "Failed to connect to a Secondary: " << std::strerror(errno);
23  return nullptr;
24  }
25 
26  return create(address, port, *con_sock);
27 }
28 
29 SecondaryInterface::Ptr IpUptaneSecondary::create(const std::string& address, unsigned short port, int con_fd) {
30  Asn1Message::Ptr req(Asn1Message::Empty());
31  req->present(AKIpUptaneMes_PR_getInfoReq);
32 
33  auto m = req->getInfoReq();
34  auto resp = Asn1Rpc(req, con_fd);
35 
36  if (resp->present() != AKIpUptaneMes_PR_getInfoResp) {
37  LOG_ERROR << "IP Secondary failed to respond to information request at " << address << ":" << port;
38  return std::make_shared<IpUptaneSecondary>(address, port, EcuSerial::Unknown(), HardwareIdentifier::Unknown(),
39  PublicKey("", KeyType::kUnknown));
40  }
41  auto r = resp->getInfoResp();
42 
43  EcuSerial serial = EcuSerial(ToString(r->ecuSerial));
44  HardwareIdentifier hw_id = HardwareIdentifier(ToString(r->hwId));
45  std::string key = ToString(r->key);
46  auto type = static_cast<KeyType>(r->keyType);
47  PublicKey pub_key = PublicKey(key, type);
48 
49  LOG_INFO << "Got ECU information from IP Secondary: "
50  << "hardware ID: " << hw_id << " serial: " << serial;
51 
52  return std::make_shared<IpUptaneSecondary>(address, port, serial, hw_id, pub_key);
53 }
54 
55 SecondaryInterface::Ptr IpUptaneSecondary::connectAndCheck(const std::string& address, unsigned short port,
56  EcuSerial serial, HardwareIdentifier hw_id,
57  PublicKey pub_key) {
58  // try to connect:
59  // - if it succeeds compare with what we expect
60  // - otherwise, keep using what we know
61  try {
62  auto sec = IpUptaneSecondary::connectAndCreate(address, port);
63  if (sec != nullptr) {
64  auto s = sec->getSerial();
65  if (s != serial && serial != EcuSerial::Unknown()) {
66  LOG_WARNING << "Expected IP Secondary at " << address << ":" << port << " with serial " << serial
67  << " but found " << s;
68  }
69  auto h = sec->getHwId();
70  if (h != hw_id && hw_id != HardwareIdentifier::Unknown()) {
71  LOG_WARNING << "Expected IP Secondary at " << address << ":" << port << " with hardware ID " << hw_id
72  << " but found " << h;
73  }
74  auto p = sec->getPublicKey();
75  if (p.Type() == KeyType::kUnknown) {
76  LOG_ERROR << "IP Secondary at " << address << ":" << port << " has an unknown key type!";
77  return nullptr;
78  } else if (p != pub_key && pub_key.Type() != KeyType::kUnknown) {
79  LOG_WARNING << "Expected IP Secondary at " << address << ":" << port << " with public key:\n"
80  << pub_key.Value() << "... but found:\n"
81  << p.Value();
82  }
83  return sec;
84  }
85  } catch (std::exception& e) {
86  LOG_WARNING << "Could not connect to IP Secondary at " << address << ":" << port << " with serial " << serial;
87  }
88 
89  return std::make_shared<IpUptaneSecondary>(address, port, std::move(serial), std::move(hw_id), std::move(pub_key));
90 }
91 
92 IpUptaneSecondary::IpUptaneSecondary(const std::string& address, unsigned short port, EcuSerial serial,
93  HardwareIdentifier hw_id, PublicKey pub_key)
94  : addr_{address, port}, serial_{std::move(serial)}, hw_id_{std::move(hw_id)}, pub_key_{std::move(pub_key)} {}
95 
96 /* Determine the best protocol version to use for this Secondary. This did not
97  * exist for v1 and thus only works for v2 and beyond. It would be great if we
98  * could just do this once, but we do not have a simple way to do that,
99  * especially because of Secondaries that need to reboot to complete
100  * installation. */
101 void IpUptaneSecondary::getSecondaryVersion() const {
102  LOG_DEBUG << "Negotiating the protocol version with Secondary " << getSerial();
103  const uint32_t latest_version = 2;
104  Asn1Message::Ptr req(Asn1Message::Empty());
105  req->present(AKIpUptaneMes_PR_versionReq);
106  auto m = req->versionReq();
107  m->version = latest_version;
108  auto resp = Asn1Rpc(req, getAddr());
109 
110  if (resp->present() != AKIpUptaneMes_PR_versionResp) {
111  // Bad response probably means v1, but make sure the Secondary is actually
112  // responsive before assuming that.
113  if (ping()) {
114  LOG_DEBUG << "Failed to get a response to a version request from Secondary " << getSerial()
115  << "; assuming version 1.";
116  protocol_version = 1;
117  } else {
118  LOG_INFO << "Failed to get a response to a version request from Secondary " << getSerial()
119  << "; unable to determine protocol version.";
120  }
121  return;
122  }
123 
124  auto r = resp->versionResp();
125  const auto secondary_version = static_cast<uint32_t>(r->version);
126  if (secondary_version <= latest_version) {
127  LOG_DEBUG << "Using protocol version " << secondary_version << " for Secondary " << getSerial();
128  protocol_version = secondary_version;
129  } else {
130  LOG_ERROR << "Secondary protocol version is " << secondary_version << " but Primary only supports up to "
131  << latest_version << "! Communication will most likely fail!";
132  protocol_version = latest_version;
133  }
134 }
135 
136 data::InstallationResult IpUptaneSecondary::putMetadata(const Target& target) {
137  Uptane::MetaBundle meta_bundle;
138  if (!secondary_provider_->getMetadata(&meta_bundle, target)) {
140  "Unable to load stored metadata from Primary");
141  }
142 
143  getSecondaryVersion();
144 
145  LOG_INFO << "Sending Uptane metadata to the Secondary";
146  data::InstallationResult put_result;
147  if (protocol_version == 2) {
148  put_result = putMetadata_v2(meta_bundle);
149  } else if (protocol_version == 1) {
150  put_result = putMetadata_v1(meta_bundle);
151  } else {
152  LOG_ERROR << "Unexpected protocol version: " << protocol_version;
154  "Unexpected protocol version: " + std::to_string(protocol_version));
155  }
156  return put_result;
157 }
158 
159 data::InstallationResult IpUptaneSecondary::putMetadata_v1(const Uptane::MetaBundle& meta_bundle) {
160  Asn1Message::Ptr req(Asn1Message::Empty());
161  req->present(AKIpUptaneMes_PR_putMetaReq);
162 
163  auto m = req->putMetaReq();
164  m->director.present = director_PR_json;
165  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
166  SetString(&m->director.choice.json.root,
167  getMetaFromBundle(meta_bundle, Uptane::RepositoryType::Director(), Uptane::Role::Root()));
168  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
169  SetString(&m->director.choice.json.targets,
170  getMetaFromBundle(meta_bundle, Uptane::RepositoryType::Director(), Uptane::Role::Targets()));
171 
172  m->image.present = image_PR_json;
173  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
174  SetString(&m->image.choice.json.root,
175  getMetaFromBundle(meta_bundle, Uptane::RepositoryType::Image(), Uptane::Role::Root()));
176  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
177  SetString(&m->image.choice.json.timestamp,
178  getMetaFromBundle(meta_bundle, Uptane::RepositoryType::Image(), Uptane::Role::Timestamp()));
179  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
180  SetString(&m->image.choice.json.snapshot,
181  getMetaFromBundle(meta_bundle, Uptane::RepositoryType::Image(), Uptane::Role::Snapshot()));
182  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
183  SetString(&m->image.choice.json.targets,
184  getMetaFromBundle(meta_bundle, Uptane::RepositoryType::Image(), Uptane::Role::Targets()));
185 
186  auto resp = Asn1Rpc(req, getAddr());
187 
188  if (resp->present() != AKIpUptaneMes_PR_putMetaResp) {
189  LOG_ERROR << "Failed to get response from sending metadata to Secondary";
191  "Failed to get response from sending metadata to Secondary");
192  }
193 
194  auto r = resp->putMetaResp();
195  if (r->result == AKInstallationResult_success) {
196  return data::InstallationResult(data::ResultCode::Numeric::kOk, "");
197  } else {
199  "Error sending metadata to Secondary");
200  }
201 }
202 
203 void IpUptaneSecondary::addMetadata(const Uptane::MetaBundle& meta_bundle, const Uptane::RepositoryType repo,
204  const Uptane::Role& role, AKMetaCollection_t& collection) {
205  auto meta_json = Asn1Allocation<AKMetaJson_t>();
206  SetString(&meta_json->role, role.ToString());
207  SetString(&meta_json->json, getMetaFromBundle(meta_bundle, repo, role));
208  ASN_SEQUENCE_ADD(&collection, meta_json);
209 }
210 
211 data::InstallationResult IpUptaneSecondary::putMetadata_v2(const Uptane::MetaBundle& meta_bundle) {
212  Asn1Message::Ptr req(Asn1Message::Empty());
213  req->present(AKIpUptaneMes_PR_putMetaReq2);
214 
215  auto m = req->putMetaReq2();
216  m->directorRepo.present = directorRepo_PR_collection;
217  m->imageRepo.present = imageRepo_PR_collection;
218 
219  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
220  addMetadata(meta_bundle, Uptane::RepositoryType::Director(), Uptane::Role::Root(), m->directorRepo.choice.collection);
221  addMetadata(meta_bundle, Uptane::RepositoryType::Director(), Uptane::Role::Targets(),
222  m->directorRepo.choice.collection); // NOLINT(cppcoreguidelines-pro-type-union-access)
223  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
224  addMetadata(meta_bundle, Uptane::RepositoryType::Image(), Uptane::Role::Root(), m->imageRepo.choice.collection);
225  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
226  addMetadata(meta_bundle, Uptane::RepositoryType::Image(), Uptane::Role::Timestamp(), m->imageRepo.choice.collection);
227  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
228  addMetadata(meta_bundle, Uptane::RepositoryType::Image(), Uptane::Role::Snapshot(), m->imageRepo.choice.collection);
229  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
230  addMetadata(meta_bundle, Uptane::RepositoryType::Image(), Uptane::Role::Targets(), m->imageRepo.choice.collection);
231 
232  auto resp = Asn1Rpc(req, getAddr());
233 
234  if (resp->present() != AKIpUptaneMes_PR_putMetaResp2) {
235  LOG_ERROR << "Failed to get response from sending metadata to Secondary";
237  "Failed to get response from sending metadata to Secondary");
238  }
239 
240  auto r = resp->putMetaResp2();
241  return data::InstallationResult(static_cast<data::ResultCode::Numeric>(r->result), ToString(r->description));
242 }
243 
244 Manifest IpUptaneSecondary::getManifest() const {
245  getSecondaryVersion();
246 
247  LOG_DEBUG << "Getting the manifest from Secondary with serial " << getSerial();
248  Asn1Message::Ptr req(Asn1Message::Empty());
249 
250  req->present(AKIpUptaneMes_PR_manifestReq);
251  auto resp = Asn1Rpc(req, getAddr());
252 
253  if (resp->present() != AKIpUptaneMes_PR_manifestResp) {
254  LOG_ERROR << "Failed to get a response to a manifest request to Secondary with serial " << getSerial();
255  return Json::Value();
256  }
257  auto r = resp->manifestResp();
258 
259  if (r->manifest.present != manifest_PR_json) {
260  LOG_ERROR << "Manifest wasn't in json format";
261  return Json::Value();
262  }
263  std::string manifest = ToString(r->manifest.choice.json); // NOLINT
264  return Utils::parseJSON(manifest);
265 }
266 
267 bool IpUptaneSecondary::ping() const {
268  Asn1Message::Ptr req(Asn1Message::Empty());
269  req->present(AKIpUptaneMes_PR_getInfoReq);
270 
271  auto m = req->getInfoReq();
272 
273  auto resp = Asn1Rpc(req, getAddr());
274 
275  return resp->present() == AKIpUptaneMes_PR_getInfoResp;
276 }
277 
278 data::InstallationResult IpUptaneSecondary::sendFirmware(const Uptane::Target& target) {
279  data::InstallationResult send_result;
280  if (protocol_version == 2) {
281  send_result = sendFirmware_v2(target);
282  } else if (protocol_version == 1) {
283  send_result = sendFirmware_v1(target);
284  } else {
285  LOG_ERROR << "Unexpected protocol version: " << protocol_version;
287  "Unexpected protocol version: " + std::to_string(protocol_version));
288  }
289  return send_result;
290 }
291 
292 data::InstallationResult IpUptaneSecondary::sendFirmware_v1(const Uptane::Target& target) {
293  std::string data_to_send;
294 
295  if (target.IsOstree()) {
296  // empty firmware means OSTree Secondaries: pack credentials instead
297  data_to_send = secondary_provider_->getTreehubCredentials();
298  } else {
299  std::stringstream sstr;
300  auto str = secondary_provider_->getTargetFileHandle(target);
301  sstr << str.rdbuf();
302  data_to_send = sstr.str();
303  }
304 
305  LOG_INFO << "Sending firmware to the Secondary, size: " << data_to_send.size();
306  Asn1Message::Ptr req(Asn1Message::Empty());
307  req->present(AKIpUptaneMes_PR_sendFirmwareReq);
308 
309  auto m = req->sendFirmwareReq();
310  SetString(&m->firmware, data_to_send);
311  auto resp = Asn1Rpc(req, getAddr());
312 
313  if (resp->present() != AKIpUptaneMes_PR_sendFirmwareResp) {
314  LOG_ERROR << "Failed to get response to sending firmware to Secondary";
316  "Failed to get response to sending firmware to Secondary");
317  }
318 
319  auto r = resp->sendFirmwareResp();
320  if (r->result == AKInstallationResult_success) {
321  return data::InstallationResult(data::ResultCode::Numeric::kOk, "");
322  }
324 }
325 
326 data::InstallationResult IpUptaneSecondary::sendFirmware_v2(const Uptane::Target& target) {
327  LOG_INFO << "Instructing Secondary " << getSerial() << " to receive target " << target.filename();
328  if (target.IsOstree()) {
329  return downloadOstreeRev(target);
330  } else {
331  return uploadFirmware(target);
332  }
333 }
334 
335 data::InstallationResult IpUptaneSecondary::install(const Uptane::Target& target) {
336  data::InstallationResult install_result;
337  if (protocol_version == 2) {
338  install_result = install_v2(target);
339  } else if (protocol_version == 1) {
340  install_result = install_v1(target);
341  } else {
342  LOG_ERROR << "Unexpected protocol version: " << protocol_version;
344  "Unexpected protocol version: " + std::to_string(protocol_version));
345  }
346 
347  return install_result;
348 }
349 
350 data::InstallationResult IpUptaneSecondary::install_v1(const Uptane::Target& target) {
351  LOG_INFO << "Invoking an installation of the target on the Secondary: " << target.filename();
352 
353  Asn1Message::Ptr req(Asn1Message::Empty());
354  req->present(AKIpUptaneMes_PR_installReq);
355 
356  // prepare request message
357  auto req_mes = req->installReq();
358  SetString(&req_mes->hash, target.filename());
359  // send request and receive response, a request-response type of RPC
360  auto resp = Asn1Rpc(req, getAddr());
361 
362  // invalid type of an response message
363  if (resp->present() != AKIpUptaneMes_PR_installResp) {
364  LOG_ERROR << "Failed to get response to an installation request to Secondary";
366  "Failed to get response to an installation request to Secondary");
367  }
368 
369  // deserialize the response message
370  auto r = resp->installResp();
371 
372  return data::InstallationResult(static_cast<data::ResultCode::Numeric>(r->result), "");
373 }
374 
375 data::InstallationResult IpUptaneSecondary::install_v2(const Uptane::Target& target) {
376  LOG_INFO << "Instructing Secondary " << getSerial() << " to install target " << target.filename();
377  return invokeInstallOnSecondary(target);
378 }
379 
380 data::InstallationResult IpUptaneSecondary::downloadOstreeRev(const Uptane::Target& target) {
381  LOG_INFO << "Instructing Secondary ( " << getSerial() << " ) to download OSTree commit ( " << target.sha256Hash()
382  << " )";
383  const std::string tls_creds = secondary_provider_->getTreehubCredentials();
384  Asn1Message::Ptr req(Asn1Message::Empty());
385  req->present(static_cast<AKIpUptaneMes_PR>(AKIpUptaneMes_PR_downloadOstreeRevReq));
386 
387  auto m = req->downloadOstreeRevReq();
388  SetString(&m->tlsCred, tls_creds);
389  auto resp = Asn1Rpc(req, getAddr());
390 
391  if (resp->present() != AKIpUptaneMes_PR_downloadOstreeRevResp) {
392  LOG_ERROR << "Failed to get response to download ostree revision request";
393  return data::InstallationResult(data::ResultCode::Numeric::kUnknown,
394  "Failed to get response to download ostree revision request");
395  }
396 
397  auto r = resp->downloadOstreeRevResp();
398  return data::InstallationResult(static_cast<data::ResultCode::Numeric>(r->result), ToString(r->description));
399 }
400 
401 data::InstallationResult IpUptaneSecondary::uploadFirmware(const Uptane::Target& target) {
402  LOG_INFO << "Uploading the target image (" << target.filename() << ") "
403  << "to the Secondary (" << getSerial() << ")";
404 
406 
407  auto image_reader = secondary_provider_->getTargetFileHandle(target);
408 
409  uint64_t image_size = target.length();
410  const size_t size = 1024;
411  size_t total_send_data = 0;
412  std::array<uint8_t, size> buf{};
413  auto upload_data_result = data::InstallationResult(data::ResultCode::Numeric::kOk, "");
414 
415  while (total_send_data < image_size && upload_data_result.isSuccess()) {
416  image_reader.read(reinterpret_cast<char*>(buf.data()), buf.size());
417  upload_data_result = uploadFirmwareData(buf.data(), static_cast<size_t>(image_reader.gcount()));
418  total_send_data += static_cast<size_t>(image_reader.gcount());
419  }
420  if (upload_data_result.isSuccess() && total_send_data == image_size) {
421  upload_result = data::InstallationResult(data::ResultCode::Numeric::kOk, "");
422  } else if (!upload_data_result.isSuccess()) {
423  upload_result = upload_data_result;
424  } else {
425  upload_result = data::InstallationResult(data::ResultCode::Numeric::kDownloadFailed, "Incomplete upload");
426  }
427  image_reader.close();
428  return upload_result;
429 }
430 
431 data::InstallationResult IpUptaneSecondary::uploadFirmwareData(const uint8_t* data, size_t size) {
432  Asn1Message::Ptr req(Asn1Message::Empty());
433  req->present(AKIpUptaneMes_PR_uploadDataReq);
434 
435  auto m = req->uploadDataReq();
436  OCTET_STRING_fromBuf(&m->data, reinterpret_cast<const char*>(data), static_cast<int>(size));
437  auto resp = Asn1Rpc(req, getAddr());
438 
439  if (resp->present() == AKIpUptaneMes_PR_NOTHING) {
440  LOG_ERROR << "Failed to get response to an uploading data request to Secondary";
441  return data::InstallationResult(data::ResultCode::Numeric::kUnknown,
442  "Failed to get response to an uploading data request to Secondary");
443  }
444  if (resp->present() != AKIpUptaneMes_PR_uploadDataResp) {
445  LOG_ERROR << "Invalid response to an uploading data request to Secondary";
447  "Invalid response to an uploading data request to Secondary");
448  }
449 
450  auto r = resp->uploadDataResp();
451  return data::InstallationResult(static_cast<data::ResultCode::Numeric>(r->result), ToString(r->description));
452 }
453 
454 data::InstallationResult IpUptaneSecondary::invokeInstallOnSecondary(const Uptane::Target& target) {
455  Asn1Message::Ptr req(Asn1Message::Empty());
456  req->present(AKIpUptaneMes_PR_installReq);
457 
458  // prepare request message
459  auto req_mes = req->installReq();
460  SetString(&req_mes->hash, target.filename());
461  // send request and receive response, a request-response type of RPC
462  auto resp = Asn1Rpc(req, getAddr());
463 
464  // invalid type of an response message
465  if (resp->present() != AKIpUptaneMes_PR_installResp2) {
466  LOG_ERROR << "Failed to get response to an installation request to Secondary";
467  return data::InstallationResult(data::ResultCode::Numeric::kUnknown,
468  "Failed to get response to an installation request to Secondary");
469  }
470 
471  // deserialize the response message
472  auto r = resp->installResp2();
473  return data::InstallationResult(static_cast<data::ResultCode::Numeric>(r->result), ToString(r->description));
474 }
475 
476 } // namespace Uptane
General data structures.
Definition: types.h:215
bool IsOstree() const
Is this an OSTree target? OSTree targets need special treatment because the hash doesn&#39;t represent th...
Definition: tuf.cc:170
static Asn1Message::Ptr Empty()
Create a new Asn1Message, in order to fill it with data and send it.
Definition: asn1_message.h:46
TUF Roles.
Definition: tuf.h:61
SWM Internal integrity error.
Base data types that are used in The Update Framework (TUF), part of Uptane.
Definition: types.h:304