Aktualizr
C++ SOTA Client
invstorage.cc
1 #include "invstorage.h"
2 
3 #include <unistd.h>
4 
5 #include "fsstorage_read.h"
6 #include "logging/logging.h"
7 #include "sqlstorage.h"
8 #include "utilities/utils.h"
9 
10 void INvStorage::importSimple(const boost::filesystem::path& base_path, store_data_t store_func, load_data_t load_func,
11  const utils::BasedPath& imported_data_path, const std::string& data_name) {
12  if (!(this->*load_func)(nullptr) && !imported_data_path.empty()) {
13  boost::filesystem::path abs_path = imported_data_path.get(base_path);
14  if (!boost::filesystem::exists(abs_path)) {
15  LOG_ERROR << "Couldn't import " << data_name << ": " << abs_path << " doesn't exist.";
16  return;
17  }
18  std::string content = Utils::readFile(abs_path.string());
19  (this->*store_func)(content);
20  LOG_DEBUG << "Successfully imported " << data_name << " from " << abs_path;
21  }
22 }
23 
24 void INvStorage::importUpdateSimple(const boost::filesystem::path& base_path, store_data_t store_func,
25  load_data_t load_func, const utils::BasedPath& imported_data_path,
26  const std::string& data_name) {
27  std::string prev_content;
28  std::string content;
29  bool update = false;
30  if (!(this->*load_func)(&prev_content)) {
31  update = true;
32  } else if (!imported_data_path.empty()) {
33  content = Utils::readFile(imported_data_path.get(base_path).string());
34  if (Crypto::sha256digest(content) != Crypto::sha256digest(prev_content)) {
35  update = true;
36  }
37  }
38 
39  if (update && !imported_data_path.empty()) {
40  boost::filesystem::path abs_path = imported_data_path.get(base_path);
41  if (!boost::filesystem::exists(abs_path)) {
42  LOG_ERROR << "Couldn't import " << data_name << ": " << abs_path << " doesn't exist.";
43  return;
44  }
45  if (content.empty()) {
46  content = Utils::readFile(abs_path.string());
47  }
48  (this->*store_func)(content);
49  LOG_DEBUG << "Successfully imported " << data_name << " from " << abs_path;
50  }
51 }
52 
53 void INvStorage::importUpdateCertificate(const boost::filesystem::path& base_path,
54  const utils::BasedPath& imported_data_path) {
55  std::string prev_content;
56  std::string content;
57  bool update = false;
58  if (!loadTlsCert(&prev_content)) {
59  update = true;
60  } else if (!imported_data_path.empty()) {
61  content = Utils::readFile(imported_data_path.get(base_path).string());
62  if (Crypto::sha256digest(content) != Crypto::sha256digest(prev_content)) {
63  update = true;
64  }
65  }
66 
67  if (update && !imported_data_path.empty()) {
68  boost::filesystem::path abs_path = imported_data_path.get(base_path);
69  if (!boost::filesystem::exists(abs_path)) {
70  LOG_ERROR << "Couldn't import client certificate: " << abs_path << " doesn't exist.";
71  return;
72  }
73  if (content.empty()) {
74  content = Utils::readFile(abs_path.string());
75  }
76 
77  // Make sure the device ID of the new cert hasn't changed.
78  const std::string new_device_id = Crypto::extractSubjectCN(content);
79  std::string old_device_id;
80  if (!loadDeviceId(&old_device_id)) {
81  LOG_DEBUG << "Unable to load previous device ID.";
82  } else if (new_device_id != old_device_id) {
83  throw std::runtime_error(std::string("Certificate at ") + abs_path.string() + " has a device ID of " +
84  new_device_id + " but the device currently is identified as " + old_device_id +
85  ". Changing device ID is not supported!");
86  }
87 
88  storeTlsCert(content);
89  LOG_DEBUG << "Successfully imported client certificate from " << abs_path;
90  }
91 }
92 
93 void INvStorage::importPrimaryKeys(const boost::filesystem::path& base_path, const utils::BasedPath& import_pubkey_path,
94  const utils::BasedPath& import_privkey_path) {
95  if (loadPrimaryKeys(nullptr, nullptr) || import_pubkey_path.empty() || import_privkey_path.empty()) {
96  return;
97  }
98  const boost::filesystem::path pubkey_abs_path = import_pubkey_path.get(base_path);
99  const boost::filesystem::path privkey_abs_path = import_privkey_path.get(base_path);
100  if (!boost::filesystem::exists(pubkey_abs_path)) {
101  LOG_ERROR << "Couldn't import data: " << pubkey_abs_path << " doesn't exist.";
102  return;
103  }
104  if (!boost::filesystem::exists(privkey_abs_path)) {
105  LOG_ERROR << "Couldn't import data: " << privkey_abs_path << " doesn't exist.";
106  return;
107  }
108  const std::string pub_content = Utils::readFile(pubkey_abs_path.string());
109  const std::string priv_content = Utils::readFile(privkey_abs_path.string());
110  storePrimaryKeys(pub_content, priv_content);
111  LOG_DEBUG << "Successfully imported Uptane keys from " << pubkey_abs_path << " and " << privkey_abs_path;
112 }
113 
114 void INvStorage::importInstalledVersions(const boost::filesystem::path& base_path) {
115  std::vector<Uptane::Target> installed_versions;
116  const boost::filesystem::path file_path = utils::BasedPath("installed_versions").get(base_path);
117  loadPrimaryInstallationLog(&installed_versions, false);
118  if (!installed_versions.empty()) {
119  return;
120  }
121  size_t current_index = SIZE_MAX;
122  fsReadInstalledVersions(file_path, &installed_versions, &current_index);
123  if (current_index < installed_versions.size()) {
124  // installed versions in legacy fs storage are all for primary
125  savePrimaryInstalledVersion(installed_versions[current_index], InstalledVersionUpdateMode::kCurrent);
126  boost::filesystem::remove(file_path);
127  LOG_DEBUG << "Successfully imported installed versions from " << file_path;
128  }
129 }
130 
131 void INvStorage::importData(const ImportConfig& import_config) {
132  importPrimaryKeys(import_config.base_path, import_config.uptane_public_key_path,
133  import_config.uptane_private_key_path);
134  importUpdateCertificate(import_config.base_path, import_config.tls_clientcert_path);
135  importUpdateSimple(import_config.base_path, &INvStorage::storeTlsCa, &INvStorage::loadTlsCa,
136  import_config.tls_cacert_path, "server CA certificate");
137  importUpdateSimple(import_config.base_path, &INvStorage::storeTlsPkey, &INvStorage::loadTlsPkey,
138  import_config.tls_pkey_path, "client TLS key");
139  importInstalledVersions(import_config.base_path);
140 }
141 
142 std::shared_ptr<INvStorage> INvStorage::newStorage(const StorageConfig& config, const bool readonly) {
143  switch (config.type) {
144  case StorageType::kSqlite: {
145  boost::filesystem::path db_path = config.sqldb_path.get(config.path);
146  if (!boost::filesystem::exists(db_path) && FSStorageRead::FSStoragePresent(config)) {
147  if (readonly) {
148  throw StorageException(
149  "Migration from FS is not possible because the SQL database is configured to be readonly");
150  }
151 
152  LOG_INFO << "Starting FS to SQL storage migration";
153  if (access(config.path.c_str(), R_OK | W_OK | X_OK) != 0) {
154  throw StorageException(std::string("Cannot read prior filesystem configuration from ") +
155  config.path.string() + " due to insufficient permissions.");
156  }
157  StorageConfig old_config = config;
158  old_config.type = StorageType::kFileSystem;
159  old_config.path = config.path;
160 
161  auto sql_storage = std::make_shared<SQLStorage>(config, readonly);
162  FSStorageRead fs_storage(old_config);
163  INvStorage::FSSToSQLS(fs_storage, *sql_storage);
164  return sql_storage;
165  }
166  if (!boost::filesystem::exists(db_path)) {
167  LOG_INFO << "Bootstrap empty SQL storage";
168  } else {
169  LOG_INFO << "Use existing SQL storage: " << db_path;
170  }
171  return std::make_shared<SQLStorage>(config, readonly);
172  }
173  case StorageType::kFileSystem:
174  default:
175  throw std::runtime_error("FSStorage has been removed in recent versions of aktualizr, please use SQLStorage");
176  }
177 }
178 
179 void INvStorage::FSSToSQLS(FSStorageRead& fs_storage, SQLStorage& sql_storage) {
180  std::string public_key;
181  std::string private_key;
182  if (fs_storage.loadPrimaryKeys(&public_key, &private_key)) {
183  sql_storage.storePrimaryKeys(public_key, private_key);
184  }
185 
186  std::string ca;
187  if (fs_storage.loadTlsCa(&ca)) {
188  sql_storage.storeTlsCa(ca);
189  }
190 
191  std::string cert;
192  if (fs_storage.loadTlsCert(&cert)) {
193  sql_storage.storeTlsCert(cert);
194  }
195 
196  std::string pkey;
197  if (fs_storage.loadTlsPkey(&pkey)) {
198  sql_storage.storeTlsPkey(pkey);
199  }
200 
201  std::string device_id;
202  if (fs_storage.loadDeviceId(&device_id)) {
203  sql_storage.storeDeviceId(device_id);
204  }
205 
206  EcuSerials serials;
207  if (fs_storage.loadEcuSerials(&serials)) {
208  sql_storage.storeEcuSerials(serials);
209  }
210 
211  if (fs_storage.loadEcuRegistered()) {
212  sql_storage.storeEcuRegistered();
213  }
214 
215  std::vector<MisconfiguredEcu> ecus;
216  if (fs_storage.loadMisconfiguredEcus(&ecus)) {
217  for (auto& ecu : ecus) {
218  sql_storage.saveMisconfiguredEcu(ecu);
219  }
220  }
221 
222  std::vector<Uptane::Target> installed_versions;
223  size_t current_index = SIZE_MAX;
224  size_t k = 0;
225  fs_storage.loadInstalledVersions(&installed_versions, &current_index);
226  for (auto it = installed_versions.cbegin(); it != installed_versions.cend(); it++, k++) {
227  auto mode = k == current_index ? InstalledVersionUpdateMode::kCurrent : InstalledVersionUpdateMode::kNone;
228  sql_storage.savePrimaryInstalledVersion(*it, mode);
229  }
230 
231  // migrate latest versions of all metadata
232  for (const auto& role : Uptane::Role::Roles()) {
233  if (role == Uptane::Role::Root()) {
234  continue;
235  }
236 
237  std::string meta;
238  for (auto repo : {Uptane::RepositoryType::Director(), Uptane::RepositoryType::Image()}) {
239  if (fs_storage.loadNonRoot(&meta, repo, role)) {
240  sql_storage.storeNonRoot(meta, repo, role);
241  }
242  }
243  }
244  // additionally migrate the whole Root metadata chain
245  std::string latest_root;
246  for (auto repo : {Uptane::RepositoryType::Director(), Uptane::RepositoryType::Image()}) {
247  if (fs_storage.loadLatestRoot(&latest_root, Uptane::RepositoryType::Director())) {
248  int latest_version = Uptane::extractVersionUntrusted(latest_root);
249  for (int version = 0; version <= latest_version; ++version) {
250  std::string root;
251  if (fs_storage.loadRoot(&root, repo, Uptane::Version(version))) {
252  sql_storage.storeRoot(root, repo, Uptane::Version(version));
253  }
254  }
255  }
256  }
257 
258  // if everything is ok, remove old files.
259  fs_storage.cleanUpAll();
260 }
261 
262 bool INvStorage::fsReadInstalledVersions(const boost::filesystem::path& filename,
263  std::vector<Uptane::Target>* installed_versions, size_t* current_version) {
264  if (access(filename.c_str(), R_OK) != 0) {
265  return false;
266  }
267  try {
268  const Json::Value installed_versions_json = Utils::parseJSONFile(filename.string());
269  std::vector<Uptane::Target> new_versions;
270  size_t k = 0;
271  for (auto it = installed_versions_json.begin(); it != installed_versions_json.end(); ++it, ++k) {
272  if (!(*it).isObject()) {
273  // We loaded old format, migrate to new one.
274  Json::Value t_json;
275  t_json["hashes"]["sha256"] = it.key();
276  Uptane::Target t((*it).asString(), t_json);
277  new_versions.push_back(t);
278  if (current_version != nullptr) {
279  *current_version = k;
280  }
281  } else {
282  if (current_version != nullptr && (*it)["is_current"].asBool()) {
283  *current_version = k;
284  }
285  Uptane::Target t(it.key().asString(), *it);
286  new_versions.push_back(t);
287  }
288  }
289  *installed_versions = new_versions;
290  } catch (const std::exception& ex) {
291  LOG_ERROR << "Unable to parse installed_versions: " << ex.what();
292  return false;
293  }
294 
295  return true;
296 }
Uptane::Version
Metadata version numbers.
Definition: tuf.h:120
StorageException
Definition: storage_exception.h:4
StorageConfig
Definition: config.h:111
utils::BasedPath
The BasedPath class Can represent an absolute or relative path, only readable through the BasePath::g...
Definition: types.h:31
Uptane::Target
Definition: types.h:379
SQLStorage
Definition: sqlstorage.h:18
FSStorageRead
Definition: fsstorage_read.h:7
ImportConfig
Definition: config.h:130