Aktualizr
C++ SOTA Client
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 << "Secondary " << getSerial() << " failed to respond to a version request; assuming version 1.";
115  protocol_version = 1;
116  } else {
117  LOG_INFO << "Secondary " << getSerial()
118  << " failed to respond to a version request; unable to determine protocol version.";
119  }
120  return;
121  }
122 
123  auto r = resp->versionResp();
124  const auto secondary_version = static_cast<uint32_t>(r->version);
125  if (secondary_version <= latest_version) {
126  LOG_DEBUG << "Using protocol version " << secondary_version << " for Secondary " << getSerial();
127  protocol_version = secondary_version;
128  } else {
129  LOG_ERROR << "Secondary protocol version is " << secondary_version << " but Primary only supports up to "
130  << latest_version << "! Communication will most likely fail!";
131  protocol_version = latest_version;
132  }
133 }
134 
135 data::InstallationResult IpUptaneSecondary::putMetadata(const Target& target) {
136  Uptane::MetaBundle meta_bundle;
137  if (!secondary_provider_->getMetadata(&meta_bundle, target)) {
139  "Unable to load stored metadata from Primary");
140  }
141 
142  getSecondaryVersion();
143 
144  LOG_INFO << "Sending Uptane metadata to the Secondary";
145  data::InstallationResult put_result;
146  if (protocol_version == 2) {
147  put_result = putMetadata_v2(meta_bundle);
148  } else if (protocol_version == 1) {
149  put_result = putMetadata_v1(meta_bundle);
150  } else {
151  LOG_ERROR << "Unexpected protocol version: " << protocol_version;
153  "Unexpected protocol version: " + std::to_string(protocol_version));
154  }
155  return put_result;
156 }
157 
158 data::InstallationResult IpUptaneSecondary::putMetadata_v1(const Uptane::MetaBundle& meta_bundle) {
159  Asn1Message::Ptr req(Asn1Message::Empty());
160  req->present(AKIpUptaneMes_PR_putMetaReq);
161 
162  auto m = req->putMetaReq();
163  m->director.present = director_PR_json;
164  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
165  SetString(&m->director.choice.json.root,
166  getMetaFromBundle(meta_bundle, Uptane::RepositoryType::Director(), Uptane::Role::Root()));
167  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
168  SetString(&m->director.choice.json.targets,
169  getMetaFromBundle(meta_bundle, Uptane::RepositoryType::Director(), Uptane::Role::Targets()));
170 
171  m->image.present = image_PR_json;
172  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
173  SetString(&m->image.choice.json.root,
174  getMetaFromBundle(meta_bundle, Uptane::RepositoryType::Image(), Uptane::Role::Root()));
175  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
176  SetString(&m->image.choice.json.timestamp,
177  getMetaFromBundle(meta_bundle, Uptane::RepositoryType::Image(), Uptane::Role::Timestamp()));
178  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
179  SetString(&m->image.choice.json.snapshot,
180  getMetaFromBundle(meta_bundle, Uptane::RepositoryType::Image(), Uptane::Role::Snapshot()));
181  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
182  SetString(&m->image.choice.json.targets,
183  getMetaFromBundle(meta_bundle, Uptane::RepositoryType::Image(), Uptane::Role::Targets()));
184 
185  auto resp = Asn1Rpc(req, getAddr());
186 
187  if (resp->present() != AKIpUptaneMes_PR_putMetaResp) {
188  LOG_ERROR << "Secondary " << getSerial() << " failed to respond to a request to receive metadata.";
191  "Secondary " + getSerial().ToString() + " failed to respond to a request to receive metadata.");
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 << "Secondary " << getSerial() << " failed to respond to a request to receive metadata.";
238  "Secondary " + getSerial().ToString() + " failed to respond to a request to receive metadata.");
239  }
240 
241  auto r = resp->putMetaResp2();
242  return data::InstallationResult(static_cast<data::ResultCode::Numeric>(r->result), ToString(r->description));
243 }
244 
245 Manifest IpUptaneSecondary::getManifest() const {
246  getSecondaryVersion();
247 
248  LOG_DEBUG << "Getting the manifest from Secondary with serial " << getSerial();
249  Asn1Message::Ptr req(Asn1Message::Empty());
250 
251  req->present(AKIpUptaneMes_PR_manifestReq);
252  auto resp = Asn1Rpc(req, getAddr());
253 
254  if (resp->present() != AKIpUptaneMes_PR_manifestResp) {
255  LOG_ERROR << "Secondary " << getSerial() << " failed to respond to a manifest request.";
256  return Json::Value();
257  }
258  auto r = resp->manifestResp();
259 
260  if (r->manifest.present != manifest_PR_json) {
261  LOG_ERROR << "Manifest wasn't in json format";
262  return Json::Value();
263  }
264  std::string manifest = ToString(r->manifest.choice.json); // NOLINT
265  return Utils::parseJSON(manifest);
266 }
267 
268 bool IpUptaneSecondary::ping() const {
269  Asn1Message::Ptr req(Asn1Message::Empty());
270  req->present(AKIpUptaneMes_PR_getInfoReq);
271 
272  auto m = req->getInfoReq();
273 
274  auto resp = Asn1Rpc(req, getAddr());
275 
276  return resp->present() == AKIpUptaneMes_PR_getInfoResp;
277 }
278 
279 data::InstallationResult IpUptaneSecondary::sendFirmware(const Uptane::Target& target) {
280  data::InstallationResult send_result;
281  if (protocol_version == 2) {
282  send_result = sendFirmware_v2(target);
283  } else if (protocol_version == 1) {
284  send_result = sendFirmware_v1(target);
285  } else {
286  LOG_ERROR << "Unexpected protocol version: " << protocol_version;
288  "Unexpected protocol version: " + std::to_string(protocol_version));
289  }
290  return send_result;
291 }
292 
293 data::InstallationResult IpUptaneSecondary::sendFirmware_v1(const Uptane::Target& target) {
294  std::string data_to_send;
295 
296  if (target.IsOstree()) {
297  // empty firmware means OSTree Secondaries: pack credentials instead
298  data_to_send = secondary_provider_->getTreehubCredentials();
299  } else {
300  std::stringstream sstr;
301  auto str = secondary_provider_->getTargetFileHandle(target);
302  sstr << str.rdbuf();
303  data_to_send = sstr.str();
304  }
305 
306  LOG_INFO << "Sending firmware to the Secondary, size: " << data_to_send.size();
307  Asn1Message::Ptr req(Asn1Message::Empty());
308  req->present(AKIpUptaneMes_PR_sendFirmwareReq);
309 
310  auto m = req->sendFirmwareReq();
311  SetString(&m->firmware, data_to_send);
312  auto resp = Asn1Rpc(req, getAddr());
313 
314  if (resp->present() != AKIpUptaneMes_PR_sendFirmwareResp) {
315  LOG_ERROR << "Secondary " << getSerial() << " failed to respond to a request to receive firmware.";
318  "Secondary " + getSerial().ToString() + " failed to respond to a request to receive firmware.");
319  }
320 
321  auto r = resp->sendFirmwareResp();
322  if (r->result == AKInstallationResult_success) {
323  return data::InstallationResult(data::ResultCode::Numeric::kOk, "");
324  }
326 }
327 
328 data::InstallationResult IpUptaneSecondary::sendFirmware_v2(const Uptane::Target& target) {
329  LOG_INFO << "Instructing Secondary " << getSerial() << " to receive target " << target.filename();
330  if (target.IsOstree()) {
331  return downloadOstreeRev(target);
332  } else {
333  return uploadFirmware(target);
334  }
335 }
336 
337 data::InstallationResult IpUptaneSecondary::install(const Uptane::Target& target) {
338  data::InstallationResult install_result;
339  if (protocol_version == 2) {
340  install_result = install_v2(target);
341  } else if (protocol_version == 1) {
342  install_result = install_v1(target);
343  } else {
344  LOG_ERROR << "Unexpected protocol version: " << protocol_version;
346  "Unexpected protocol version: " + std::to_string(protocol_version));
347  }
348 
349  return install_result;
350 }
351 
352 data::InstallationResult IpUptaneSecondary::install_v1(const Uptane::Target& target) {
353  LOG_INFO << "Invoking an installation of the target on the Secondary: " << target.filename();
354 
355  Asn1Message::Ptr req(Asn1Message::Empty());
356  req->present(AKIpUptaneMes_PR_installReq);
357 
358  // prepare request message
359  auto req_mes = req->installReq();
360  SetString(&req_mes->hash, target.filename());
361  // send request and receive response, a request-response type of RPC
362  auto resp = Asn1Rpc(req, getAddr());
363 
364  // invalid type of an response message
365  if (resp->present() != AKIpUptaneMes_PR_installResp) {
366  LOG_ERROR << "Secondary " << getSerial() << " failed to respond to an installation request.";
369  "Secondary " + getSerial().ToString() + " failed to respond to an installation request.");
370  }
371 
372  // deserialize the response message
373  auto r = resp->installResp();
374 
375  return data::InstallationResult(static_cast<data::ResultCode::Numeric>(r->result), "");
376 }
377 
378 data::InstallationResult IpUptaneSecondary::install_v2(const Uptane::Target& target) {
379  LOG_INFO << "Instructing Secondary " << getSerial() << " to install target " << target.filename();
380  return invokeInstallOnSecondary(target);
381 }
382 
383 data::InstallationResult IpUptaneSecondary::downloadOstreeRev(const Uptane::Target& target) {
384  LOG_INFO << "Instructing Secondary ( " << getSerial() << " ) to download OSTree commit ( " << target.sha256Hash()
385  << " )";
386  const std::string tls_creds = secondary_provider_->getTreehubCredentials();
387  Asn1Message::Ptr req(Asn1Message::Empty());
388  req->present(static_cast<AKIpUptaneMes_PR>(AKIpUptaneMes_PR_downloadOstreeRevReq));
389 
390  auto m = req->downloadOstreeRevReq();
391  SetString(&m->tlsCred, tls_creds);
392  auto resp = Asn1Rpc(req, getAddr());
393 
394  if (resp->present() != AKIpUptaneMes_PR_downloadOstreeRevResp) {
395  LOG_ERROR << "Secondary " << getSerial() << " failed to respond to a request to download an OSTree commit.";
397  data::ResultCode::Numeric::kUnknown,
398  "Secondary " + getSerial().ToString() + " failed to respond to a request to download an OSTree commit.");
399  }
400 
401  auto r = resp->downloadOstreeRevResp();
402  return data::InstallationResult(static_cast<data::ResultCode::Numeric>(r->result), ToString(r->description));
403 }
404 
405 data::InstallationResult IpUptaneSecondary::uploadFirmware(const Uptane::Target& target) {
406  LOG_INFO << "Uploading the target image (" << target.filename() << ") "
407  << "to the Secondary (" << getSerial() << ")";
408 
410 
411  auto image_reader = secondary_provider_->getTargetFileHandle(target);
412 
413  uint64_t image_size = target.length();
414  const size_t size = 1024;
415  size_t total_send_data = 0;
416  std::array<uint8_t, size> buf{};
417  auto upload_data_result = data::InstallationResult(data::ResultCode::Numeric::kOk, "");
418 
419  while (total_send_data < image_size && upload_data_result.isSuccess()) {
420  image_reader.read(reinterpret_cast<char*>(buf.data()), buf.size());
421  upload_data_result = uploadFirmwareData(buf.data(), static_cast<size_t>(image_reader.gcount()));
422  total_send_data += static_cast<size_t>(image_reader.gcount());
423  }
424  if (upload_data_result.isSuccess() && total_send_data == image_size) {
425  upload_result = data::InstallationResult(data::ResultCode::Numeric::kOk, "");
426  } else if (!upload_data_result.isSuccess()) {
427  upload_result = upload_data_result;
428  } else {
429  upload_result = data::InstallationResult(data::ResultCode::Numeric::kDownloadFailed, "Incomplete upload");
430  }
431  image_reader.close();
432  return upload_result;
433 }
434 
435 data::InstallationResult IpUptaneSecondary::uploadFirmwareData(const uint8_t* data, size_t size) {
436  Asn1Message::Ptr req(Asn1Message::Empty());
437  req->present(AKIpUptaneMes_PR_uploadDataReq);
438 
439  auto m = req->uploadDataReq();
440  OCTET_STRING_fromBuf(&m->data, reinterpret_cast<const char*>(data), static_cast<int>(size));
441  auto resp = Asn1Rpc(req, getAddr());
442 
443  if (resp->present() == AKIpUptaneMes_PR_NOTHING) {
444  LOG_ERROR << "Secondary " << getSerial() << " failed to respond to a request to receive firmware data.";
446  data::ResultCode::Numeric::kUnknown,
447  "Secondary " + getSerial().ToString() + " failed to respond to a request to receive firmware data.");
448  }
449  if (resp->present() != AKIpUptaneMes_PR_uploadDataResp) {
450  LOG_ERROR << "Secondary " << getSerial() << " returned an invalid response to a request to receive firmware data.";
453  "Secondary " + getSerial().ToString() + " returned an invalid reponse to a request to receive firmware data.");
454  }
455 
456  auto r = resp->uploadDataResp();
457  return data::InstallationResult(static_cast<data::ResultCode::Numeric>(r->result), ToString(r->description));
458 }
459 
460 data::InstallationResult IpUptaneSecondary::invokeInstallOnSecondary(const Uptane::Target& target) {
461  Asn1Message::Ptr req(Asn1Message::Empty());
462  req->present(AKIpUptaneMes_PR_installReq);
463 
464  // prepare request message
465  auto req_mes = req->installReq();
466  SetString(&req_mes->hash, target.filename());
467  // send request and receive response, a request-response type of RPC
468  auto resp = Asn1Rpc(req, getAddr());
469 
470  // invalid type of an response message
471  if (resp->present() != AKIpUptaneMes_PR_installResp2) {
472  LOG_ERROR << "Secondary " << getSerial() << " failed to respond to an installation request.";
474  data::ResultCode::Numeric::kUnknown,
475  "Secondary " + getSerial().ToString() + " failed to respond to an installation request.");
476  }
477 
478  // deserialize the response message
479  auto r = resp->installResp2();
480  return data::InstallationResult(static_cast<data::ResultCode::Numeric>(r->result), ToString(r->description));
481 }
482 
483 } // namespace Uptane
Asn1Message::Empty
static Asn1Message::Ptr Empty()
Create a new Asn1Message, in order to fill it with data and send it.
Definition: asn1_message.h:46
data::ResultCode::Numeric::kVerificationFailed
@ kVerificationFailed
Metadata verification failed.
data::InstallationResult
Definition: types.h:277
ConnectionSocket
Definition: utils.h:124
data
General data structures.
Definition: types.h:217
Uptane::RepositoryType
Definition: tuf.h:21
PublicKey
Definition: types.h:119
Uptane::Target::IsOstree
bool IsOstree() const
Is this an OSTree target? OSTree targets need special treatment because the hash doesn't represent th...
Definition: tuf.cc:179
data::ResultCode::Numeric::kInternalError
@ kInternalError
SWM Internal integrity error.
Uptane::Role
TUF Roles.
Definition: tuf.h:61
Uptane::Target
Definition: types.h:379
data::ResultCode::Numeric
Numeric
Definition: types.h:221
Uptane
Base data types that are used in The Update Framework (TUF), part of Uptane.
Definition: packagemanagerinterface.h:18
data::ResultCode::Numeric::kDownloadFailed
@ kDownloadFailed
Package download failed.