Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
aktualizr_secondary.cc
1 #include "aktualizr_secondary.h"
2 
3 #include "crypto/keymanager.h"
4 #include "logging/logging.h"
5 #include "update_agent.h"
6 #include "uptane/manifest.h"
7 #include "utilities/utils.h"
8 
9 #include <sys/types.h>
10 #include <memory>
11 
12 AktualizrSecondary::AktualizrSecondary(AktualizrSecondaryConfig config, std::shared_ptr<INvStorage> storage,
13  std::shared_ptr<KeyManager> key_mngr, std::shared_ptr<UpdateAgent> update_agent)
14  : config_(std::move(config)),
15  storage_(std::move(storage)),
16  keys_(std::move(key_mngr)),
17  update_agent_(std::move(update_agent)),
18  dispatcher_(*this) {
19  uptaneInitialize();
20  manifest_issuer_ = std::make_shared<Uptane::ManifestIssuer>(keys_, ecu_serial_);
21  initPendingTargetIfAny();
22 
23  if (hasPendingUpdate()) {
24  LOG_INFO << "Found a pending target to be applied.";
25  // TODO(OTA-4545): refactor this to make it simpler as we don't need to persist/store
26  // an installation status of each ECU but store it just for a given secondary ECU
27  std::vector<Uptane::Target> installed_versions;
28  boost::optional<Uptane::Target> pending_target;
29  storage_->loadInstalledVersions(ecu_serial_.ToString(), nullptr, &pending_target);
30 
31  if (!!pending_target) {
32  data::InstallationResult install_res =
33  data::InstallationResult(data::ResultCode::Numeric::kUnknown, "Unknown installation error");
34  LOG_INFO << "Pending update found; attempting to apply it. Target hash: " << pending_target->sha256Hash();
35 
36  install_res = update_agent_->applyPendingInstall(*pending_target);
37 
38  if (install_res.result_code != data::ResultCode::Numeric::kNeedCompletion) {
39  storage_->saveEcuInstallationResult(ecu_serial_, install_res);
40 
41  if (install_res.success) {
42  LOG_INFO << "Pending update has been successfully applied: " << pending_target->sha256Hash();
43  storage_->saveInstalledVersion(ecu_serial_.ToString(), *pending_target, InstalledVersionUpdateMode::kCurrent);
44  } else {
45  LOG_ERROR << "Application of the pending update has failed: (" << install_res.result_code.toString() << ")"
46  << install_res.description;
47  storage_->saveInstalledVersion(ecu_serial_.ToString(), *pending_target, InstalledVersionUpdateMode::kNone);
48  }
49 
50  director_repo_.dropTargets(*storage_);
51  } else {
52  LOG_INFO << "Pending update hasn't been applied because a reboot hasn't been detected";
53  }
54  }
55  }
56 }
57 
58 Uptane::EcuSerial AktualizrSecondary::getSerial() const { return ecu_serial_; }
59 
60 Uptane::HardwareIdentifier AktualizrSecondary::getHwId() const { return hardware_id_; }
61 
62 PublicKey AktualizrSecondary::getPublicKey() const { return keys_->UptanePublicKey(); }
63 
64 std::tuple<Uptane::EcuSerial, Uptane::HardwareIdentifier, PublicKey> AktualizrSecondary::getInfo() const {
65  return std::tuple<Uptane::EcuSerial, Uptane::HardwareIdentifier, PublicKey>{getSerial(), getHwId(), getPublicKey()};
66 }
67 
68 Uptane::Manifest AktualizrSecondary::getManifest() const {
69  Uptane::InstalledImageInfo installed_image_info;
70  Uptane::Manifest manifest;
71  if (update_agent_->getInstalledImageInfo(installed_image_info)) {
72  manifest = manifest_issuer_->assembleAndSignManifest(installed_image_info);
73  }
74 
75  return manifest;
76 }
77 
78 bool AktualizrSecondary::putMetadata(const Metadata& metadata) { return doFullVerification(metadata); }
79 
80 bool AktualizrSecondary::sendFirmware(const std::string& firmware) {
81  if (!pending_target_.IsValid()) {
82  LOG_ERROR << "Aborting image download/receiving; no valid target found.";
83  return false;
84  }
85 
86  if (!update_agent_->download(pending_target_, firmware)) {
87  LOG_ERROR << "Failed to pull/store an update data";
88  pending_target_ = Uptane::Target::Unknown();
89  return false;
90  }
91 
92  LOG_INFO << "Download firmware " << pending_target_.filename() << " successful.";
93  return true;
94 }
95 
96 data::ResultCode::Numeric AktualizrSecondary::install(const std::string& target_name) {
97  if (!pending_target_.IsValid()) {
98  LOG_ERROR << "Aborting target image installation; no valid target found.";
100  }
101 
102  if (pending_target_.filename() != target_name) {
103  LOG_ERROR << "name of the target to install and a name of the pending target do not match";
105  }
106 
107  auto install_result = update_agent_->install(pending_target_);
108 
109  switch (install_result) {
110  case data::ResultCode::Numeric::kOk: {
111  storage_->saveInstalledVersion(ecu_serial_.ToString(), pending_target_, InstalledVersionUpdateMode::kCurrent);
112  pending_target_ = Uptane::Target::Unknown();
113  LOG_INFO << "The target has been successfully installed: " << target_name;
114  break;
115  }
116  case data::ResultCode::Numeric::kNeedCompletion: {
117  storage_->saveInstalledVersion(ecu_serial_.ToString(), pending_target_, InstalledVersionUpdateMode::kPending);
118  LOG_INFO << "The target has been successfully installed, but a reboot is required to be applied: " << target_name;
119  break;
120  }
121  default: {
122  LOG_INFO << "Failed to install the target: " << target_name;
123  }
124  }
125 
126  return install_result;
127 }
128 
129 void AktualizrSecondary::completeInstall() { update_agent_->completeInstall(); }
130 
131 bool AktualizrSecondary::doFullVerification(const Metadata& metadata) {
132  // 5.4.4.2. Full verification https://uptane.github.io/uptane-standard/uptane-standard.html#metadata_verification
133 
134  // 1. Load and verify the current time or the most recent securely attested time.
135  //
136  // We trust the time that the given system/OS/ECU provides, In ECU We Trust :)
137  TimeStamp now(TimeStamp::Now());
138 
139  // 2. Download and check the Root metadata file from the Director repository, following the procedure in
140  // Section 5.4.4.3. DirectorRepository::updateMeta() method implements this verification step, certain steps are
141  // missing though. see the method source code for details
142 
143  // 3. NOT SUPPORTED: Download and check the Timestamp metadata file from the Director repository, following the
144  // procedure in Section 5.4.4.4.
145  // 4. NOT SUPPORTED: Download and check the Snapshot metadata file from the Director repository, following the
146  // procedure in Section 5.4.4.5.
147  //
148  // 5. Download and check the Targets metadata file from the Director repository, following the procedure in
149  // Section 5.4.4.6. DirectorRepository::updateMeta() method implements this verification step
150  //
151  // The following steps of the Director's Targets metadata verification are missing in DirectorRepository::updateMeta()
152  // 6. If checking Targets metadata from the Director repository, verify that there are no delegations.
153  // 7. If checking Targets metadata from the Director repository, check that no ECU identifier is represented more
154  // than once.
155  try {
156  director_repo_.updateMeta(*storage_, metadata);
157  } catch (const std::exception& e) {
158  LOG_ERROR << "Failed to update Director metadata: " << e.what();
159  return false;
160  }
161 
162  // 6. Download and check the Root metadata file from the Image repository, following the procedure in Section 5.4.4.3.
163  // 7. Download and check the Timestamp metadata file from the Image repository, following the procedure in
164  // Section 5.4.4.4.
165  // 8. Download and check the Snapshot metadata file from the Image repository, following the procedure in
166  // Section 5.4.4.5.
167  // 9. Download and check the top-level Targets metadata file from the Image repository, following the procedure in
168  // Section 5.4.4.6.
169  try {
170  image_repo_.updateMeta(*storage_, metadata);
171  } catch (const std::exception& e) {
172  LOG_ERROR << "Failed to update Image repo metadata: " << e.what();
173  return false;
174  }
175 
176  // 10. Verify that Targets metadata from the Director and Image repositories match.
177  if (!director_repo_.matchTargetsWithImageTargets(*(image_repo_.getTargets()))) {
178  LOG_ERROR << "Targets metadata from the Director and Image repositories DOES NOT match ";
179  return false;
180  }
181 
182  auto targetsForThisEcu = director_repo_.getTargets(getSerial(), getHwId());
183 
184  if (targetsForThisEcu.size() != 1) {
185  LOG_ERROR << "Invalid number of targets (should be 1): " << targetsForThisEcu.size();
186  return false;
187  }
188 
189  if (!update_agent_->isTargetSupported(targetsForThisEcu[0])) {
190  LOG_ERROR << "The given target type is not supported: " << targetsForThisEcu[0].type();
191  return false;
192  }
193 
194  pending_target_ = targetsForThisEcu[0];
195 
196  LOG_DEBUG << "Metadata verified, new update found.";
197  return true;
198 }
199 
200 void AktualizrSecondary::uptaneInitialize() {
201  if (keys_->generateUptaneKeyPair().size() == 0) {
202  throw std::runtime_error("Failed to generate uptane key pair");
203  }
204 
205  // from uptane/initialize.cc but we only take care of our own serial/hwid
206  EcuSerials ecu_serials;
207 
208  if (storage_->loadEcuSerials(&ecu_serials)) {
209  ecu_serial_ = ecu_serials[0].first;
210  hardware_id_ = ecu_serials[0].second;
211  return;
212  }
213 
214  std::string ecu_serial_local = config_.uptane.ecu_serial;
215  if (ecu_serial_local.empty()) {
216  ecu_serial_local = keys_->UptanePublicKey().KeyId();
217  }
218 
219  std::string ecu_hardware_id = config_.uptane.ecu_hardware_id;
220  if (ecu_hardware_id.empty()) {
221  ecu_hardware_id = Utils::getHostname();
222  if (ecu_hardware_id == "") {
223  throw std::runtime_error("Failed to define ECU hardware ID");
224  }
225  }
226 
227  ecu_serials.emplace_back(Uptane::EcuSerial(ecu_serial_local), Uptane::HardwareIdentifier(ecu_hardware_id));
228  storage_->storeEcuSerials(ecu_serials);
229  ecu_serial_ = ecu_serials[0].first;
230  hardware_id_ = ecu_serials[0].second;
231 
232  // this is a way to find out and store a value of the target name that is installed
233  // at the initial/provisioning stage and included into a device manifest
234  // i.e. 'filepath' field or ["signed"]["installed_image"]["filepath"]
235  // this value must match the value pushed to the backend during the bitbaking process,
236  // specifically, at its OSTree push phase and is equal to
237  // GARAGE_TARGET_NAME ?= "${OSTREE_BRANCHNAME}" which in turn is equal to OSTREE_BRANCHNAME ?= "${SOTA_HARDWARE_ID}"
238  // therefore, by default GARAGE_TARGET_NAME == OSTREE_BRANCHNAME == SOTA_HARDWARE_ID
239  // If there is no match then the backend/UI will not render/highlight currently installed version at all/correctly
240  storage_->importInstalledVersions(config_.import.base_path);
241 }
242 
243 void AktualizrSecondary::initPendingTargetIfAny() {
244  try {
245  director_repo_.checkMetaOffline(*storage_);
246  } catch (const std::exception& e) {
247  LOG_INFO << "No valid metadata found in storage.";
248  return;
249  }
250 
251  auto targetsForThisEcu = director_repo_.getTargets(ecu_serial_, hardware_id_);
252 
253  if (targetsForThisEcu.size() != 1) {
254  LOG_ERROR << "Invalid number of targets (should be 1): " << targetsForThisEcu.size();
255  return;
256  }
257 
258  if (!update_agent_->isTargetSupported(targetsForThisEcu[0])) {
259  LOG_ERROR << "The given target type is not supported: " << targetsForThisEcu[0].type();
260  return;
261  }
262 
263  pending_target_ = targetsForThisEcu[0];
264 }
STL namespace.
SWM Internal integrity error.