Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
aktualizr_secondary_test.cc
1 #include <gmock/gmock.h>
2 #include <gtest/gtest.h>
3 
4 #include <boost/process.hpp>
5 
6 #include "aktualizr_secondary_file.h"
7 #include "crypto/keymanager.h"
8 #include "test_utils.h"
9 #include "update_agent.h"
10 #include "update_agent_file.h"
11 #include "uptane_repo.h"
12 #include "utilities/utils.h"
13 
14 using ::testing::NiceMock;
15 
17  public:
18  UpdateAgentMock(boost::filesystem::path target_filepath, std::string target_name)
19  : FileUpdateAgent(std::move(target_filepath), std::move(target_name)) {
20  ON_CALL(*this, receiveData).WillByDefault([this](const Uptane::Target& target, const uint8_t* data, size_t size) {
21  return FileUpdateAgent::receiveData(target, data, size);
22  });
23  ON_CALL(*this, install).WillByDefault([this](const Uptane::Target& target) {
24  return FileUpdateAgent::install(target);
25  });
26  }
27 
28  MOCK_METHOD(data::InstallationResult, receiveData, (const Uptane::Target& target, const uint8_t* data, size_t size));
29  MOCK_METHOD(data::InstallationResult, install, (const Uptane::Target& target));
30 };
31 
33  public:
36  config.pacman.type = PACKAGE_MANAGER_NONE;
37 
38  config.storage.path = storage_dir_.Path();
39  config.storage.type = StorageType::kSqlite;
40 
41  storage_ = INvStorage::newStorage(config.storage);
42 
43  update_agent_ = std::make_shared<NiceMock<UpdateAgentMock>>(config.storage.path / "firmware.txt", "");
44 
45  secondary_ = std::make_shared<AktualizrSecondaryFile>(config, storage_, update_agent_);
46  secondary_->initialize();
47  }
48 
49  std::shared_ptr<AktualizrSecondaryFile>& operator->() { return secondary_; }
50 
51  Uptane::Target getPendingVersion() const {
52  boost::optional<Uptane::Target> pending_target;
53 
54  storage_->loadInstalledVersions(secondary_->serial().ToString(), nullptr, &pending_target);
55  return *pending_target;
56  }
57 
58  std::string hardwareID() const { return secondary_->hwID().ToString(); }
59 
60  std::string serial() const { return secondary_->serial().ToString(); }
61 
62  boost::filesystem::path targetFilepath() const {
63  return storage_dir_.Path() / AktualizrSecondaryFile::FileUpdateDefaultFile;
64  }
65 
66  std::shared_ptr<NiceMock<UpdateAgentMock>> update_agent_;
67 
68  private:
69  TemporaryDirectory storage_dir_;
70  std::shared_ptr<AktualizrSecondaryFile> secondary_;
71  std::shared_ptr<INvStorage> storage_;
72 };
73 
74 class UptaneRepoWrapper {
75  public:
76  UptaneRepoWrapper() { uptane_repo_.generateRepo(KeyType::kED25519); }
77 
78  Metadata addImageFile(const std::string& targetname, const std::string& hardware_id, const std::string& serial,
79  size_t size = 2049, bool add_and_sign_target = true, bool add_invalid_images = false,
80  size_t delta = 2) {
81  const auto image_file_path = root_dir_ / targetname;
82  generateRandomFile(image_file_path, size);
83 
84  uptane_repo_.addImage(image_file_path, targetname, hardware_id, "", Delegation());
85  if (add_and_sign_target) {
86  uptane_repo_.addTarget(targetname, hardware_id, serial, "");
87  uptane_repo_.signTargets();
88  }
89 
90  if (add_and_sign_target && add_invalid_images) {
91  const auto smaller_image_file_path = image_file_path.string() + ".smaller";
92  const auto bigger_image_file_path = image_file_path.string() + ".bigger";
93  const auto broken_image_file_path = image_file_path.string() + ".broken";
94 
95  boost::filesystem::copy(image_file_path, smaller_image_file_path);
96  boost::filesystem::copy(image_file_path, bigger_image_file_path);
97  boost::filesystem::copy(image_file_path, broken_image_file_path);
98 
99  if (!boost::filesystem::exists(smaller_image_file_path)) {
100  LOG_ERROR << "File does not exists: " << smaller_image_file_path;
101  }
102 
103  boost::filesystem::resize_file(smaller_image_file_path, size - delta);
104  boost::filesystem::resize_file(bigger_image_file_path, size + delta);
105 
106  std::ofstream broken_image{broken_image_file_path,
107  std::ios_base::in | std::ios_base::out | std::ios_base::ate | std::ios_base::binary};
108  unsigned char data_to_inject[]{0xFF};
109  broken_image.seekp(static_cast<long>(-sizeof(data_to_inject)), std::ios_base::end);
110  broken_image.write(reinterpret_cast<const char*>(data_to_inject), sizeof(data_to_inject));
111  broken_image.close();
112  }
113 
114  return getCurrentMetadata();
115  }
116 
117  Uptane::MetaBundle getCurrentMetadata() const {
118  Uptane::MetaBundle meta_bundle;
119  std::string metadata;
120 
121  boost::filesystem::load_string_file(director_dir_ / "root.json", metadata);
122  meta_bundle.emplace(std::make_pair(Uptane::RepositoryType::Director(), Uptane::Role::Root()), std::move(metadata));
123  boost::filesystem::load_string_file(director_dir_ / "targets.json", metadata);
124  meta_bundle.emplace(std::make_pair(Uptane::RepositoryType::Director(), Uptane::Role::Targets()),
125  std::move(metadata));
126 
127  boost::filesystem::load_string_file(imagerepo_dir_ / "root.json", metadata);
128  meta_bundle.emplace(std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Root()), std::move(metadata));
129  boost::filesystem::load_string_file(imagerepo_dir_ / "timestamp.json", metadata);
130  meta_bundle.emplace(std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Timestamp()),
131  std::move(metadata));
132  boost::filesystem::load_string_file(imagerepo_dir_ / "snapshot.json", metadata);
133  meta_bundle.emplace(std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Snapshot()), std::move(metadata));
134  boost::filesystem::load_string_file(imagerepo_dir_ / "targets.json", metadata);
135  meta_bundle.emplace(std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Targets()), std::move(metadata));
136 
137  return meta_bundle;
138  }
139 
140  std::string getTargetImagePath(const std::string& targetname) const { return (root_dir_ / targetname).string(); }
141 
142  void refreshRoot(Uptane::RepositoryType repo) { uptane_repo_.refresh(repo, Uptane::Role::Root()); }
143 
144  private:
145  static void generateRandomFile(const boost::filesystem::path& filepath, size_t size) {
146  std::ofstream file{filepath.string(), std::ofstream::binary};
147 
148  if (!file.is_open() || !file.good()) {
149  throw std::runtime_error("Failed to create a file: " + filepath.string());
150  }
151 
152  const unsigned char symbols[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv";
153  unsigned char cur_symbol;
154 
155  for (unsigned int ii = 0; ii < size; ++ii) {
156  cur_symbol = symbols[static_cast<unsigned int>(rand()) % sizeof(symbols)];
157  file.put(static_cast<char>(cur_symbol));
158  }
159 
160  file.close();
161  }
162 
163  private:
164  TemporaryDirectory root_dir_;
165  boost::filesystem::path director_dir_{root_dir_ / "repo/director"};
166  boost::filesystem::path imagerepo_dir_{root_dir_ / "repo/repo"};
167  UptaneRepo uptane_repo_{root_dir_.Path(), "", ""};
168  Uptane::DirectorRepository director_repo_;
169 };
170 
171 class SecondaryTest : public ::testing::Test {
172  protected:
173  SecondaryTest() : update_agent_(*(secondary_.update_agent_)) {
174  uptane_repo_.addImageFile(default_target_, secondary_->hwID().ToString(), secondary_->serial().ToString(),
175  target_size, true, true, inavlid_target_size_delta);
176  }
177 
178  std::vector<Uptane::Target> getCurrentTargets() {
179  auto targets = Uptane::Targets(Utils::parseJSON(getMetaFromBundle(
180  uptane_repo_.getCurrentMetadata(), Uptane::RepositoryType::Director(), Uptane::Role::Targets())));
181  return targets.getTargets(secondary_->serial(), secondary_->hwID());
182  }
183 
184  Uptane::Target getDefaultTarget() {
185  auto targets = getCurrentTargets();
186  EXPECT_GT(targets.size(), 0);
187  return targets[0];
188  }
189 
190  Hash getDefaultTargetHash() { return Hash(Hash::Type::kSha256, getDefaultTarget().sha256Hash()); }
191 
192  data::ResultCode::Numeric sendImageFile(std::string target_name = default_target_) {
193  auto image_path = uptane_repo_.getTargetImagePath(target_name);
194  size_t total_size = boost::filesystem::file_size(image_path);
195 
196  std::ifstream file{image_path};
197 
198  uint8_t buf[send_buffer_size];
199  size_t read_and_send_data_size = 0;
200 
201  while (read_and_send_data_size < total_size) {
202  auto read_bytes = file.readsome(reinterpret_cast<char*>(buf), sizeof(buf));
203  if (read_bytes < 0) {
204  file.close();
206  }
207 
208  auto result = secondary_->receiveData(buf, static_cast<size_t>(read_bytes));
209  if (!result.isSuccess()) {
210  file.close();
211  return result.result_code.num_code;
212  }
213  read_and_send_data_size += static_cast<size_t>(read_bytes);
214  }
215 
216  file.close();
217 
219  if (read_and_send_data_size == total_size) {
220  result = data::ResultCode::Numeric::kOk;
221  }
222 
223  return result;
224  }
225 
226  protected:
227  static constexpr const char* const default_target_{"default-target"};
228  static constexpr const char* const bigger_target_{"default-target.bigger"};
229  static constexpr const char* const smaller_target_{"default-target.smaller"};
230  static constexpr const char* const broken_target_{"default-target.broken"};
231 
232  static const size_t target_size{2049};
233  static const size_t inavlid_target_size_delta{2};
234  static const size_t send_buffer_size{1024};
235 
236  AktualizrSecondaryWrapper secondary_;
237  UptaneRepoWrapper uptane_repo_;
238  NiceMock<UpdateAgentMock>& update_agent_;
239  TemporaryDirectory image_dir_;
240 };
241 
242 class SecondaryTestNegative : public ::testing::Test,
243  public ::testing::WithParamInterface<std::pair<Uptane::RepositoryType, Uptane::Role>> {
244  public:
245  SecondaryTestNegative() : update_agent_(*(secondary_.update_agent_)) {}
246 
247  protected:
248  class MetadataInvalidator : public Metadata {
249  public:
250  MetadataInvalidator(const Uptane::MetaBundle& valid_metadata, const Uptane::RepositoryType& repo,
251  const Uptane::Role& role)
252  : Metadata(valid_metadata), repo_type_(repo), role_(role) {}
253 
254  void getRoleMetadata(std::string* result, const Uptane::RepositoryType& repo, const Uptane::Role& role,
255  Uptane::Version version) const override {
256  Metadata::getRoleMetadata(result, repo, role, version);
257  if (!(repo_type_ == repo && role_ == role)) {
258  return;
259  }
260  (*result)[10] = 'f';
261  }
262 
263  private:
264  Uptane::RepositoryType repo_type_;
265  Uptane::Role role_;
266  };
267 
268  MetadataInvalidator currentMetadata() const {
269  return MetadataInvalidator(uptane_repo_.getCurrentMetadata(), GetParam().first, GetParam().second);
270  }
271 
272  AktualizrSecondaryWrapper secondary_;
273  UptaneRepoWrapper uptane_repo_;
274  NiceMock<UpdateAgentMock>& update_agent_;
275 };
276 
277 /**
278  * Parameterized test,
279  * The parameter is std::pair<Uptane::RepositoryType, Uptane::Role> to indicate which metadata to malform
280  *
281  * see INSTANTIATE_TEST_SUITE_P for the test instantiations with concrete parameter values
282  */
283 TEST_P(SecondaryTestNegative, MalformedMetadaJson) {
284  EXPECT_FALSE(secondary_->putMetadata(currentMetadata()).isSuccess());
285 
286  EXPECT_CALL(update_agent_, receiveData).Times(0);
287  EXPECT_CALL(update_agent_, install).Times(0);
288 
289  EXPECT_FALSE(secondary_->install().isSuccess());
290 }
291 
292 /**
293  * Instantiates the parameterized test for each specified value of std::pair<Uptane::RepositoryType, Uptane::Role>
294  * the parameter value indicates which metadata to malform
295  */
296 INSTANTIATE_TEST_SUITE_P(SecondaryTestMalformedMetadata, SecondaryTestNegative,
297  ::testing::Values(std::make_pair(Uptane::RepositoryType::Director(), Uptane::Role::Root()),
298  std::make_pair(Uptane::RepositoryType::Director(), Uptane::Role::Targets()),
299  std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Root()),
300  std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Timestamp()),
301  std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Snapshot()),
302  std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Targets())));
303 
304 TEST_F(SecondaryTest, fullUptaneVerificationPositive) {
305  EXPECT_CALL(update_agent_, receiveData)
306  .Times(target_size / send_buffer_size + (target_size % send_buffer_size ? 1 : 0));
307  EXPECT_CALL(update_agent_, install).Times(1);
308 
309  ASSERT_TRUE(secondary_->putMetadata(uptane_repo_.getCurrentMetadata()).isSuccess());
310  ASSERT_EQ(sendImageFile(), data::ResultCode::Numeric::kOk);
311  ASSERT_TRUE(secondary_->install().isSuccess());
312 
313  // check if a file was actually updated
314  ASSERT_TRUE(boost::filesystem::exists(secondary_.targetFilepath()));
315  auto target = getDefaultTarget();
316 
317  // check the updated file hash
318  auto target_hash = Hash(Hash::Type::kSha256, target.sha256Hash());
319  auto target_file_hash = Hash::generate(Hash::Type::kSha256, Utils::readFile(secondary_.targetFilepath()));
320  EXPECT_EQ(target_hash, target_file_hash);
321 
322  // check the secondary manifest
323  auto manifest = secondary_->getManifest();
324  EXPECT_EQ(manifest.installedImageHash(), target_file_hash);
325  EXPECT_EQ(manifest.filepath(), target.filename());
326 }
327 
328 TEST_F(SecondaryTest, TwoImagesAndOneTarget) {
329  // two images for the same ECU, just one of them is added as a target and signed
330  // default image and corresponding target has been already added, just add another image
331  uptane_repo_.addImageFile("second_image_00", secondary_->hwID().ToString(), secondary_->serial().ToString(),
332  target_size, false, false);
333  EXPECT_TRUE(secondary_->putMetadata(uptane_repo_.getCurrentMetadata()).isSuccess());
334 }
335 
336 TEST_F(SecondaryTest, IncorrectTargetQuantity) {
337  {
338  // two targets for the same ECU
339  uptane_repo_.addImageFile("second_target", secondary_->hwID().ToString(), secondary_->serial().ToString());
340 
341  EXPECT_FALSE(secondary_->putMetadata(uptane_repo_.getCurrentMetadata()).isSuccess());
342  }
343 
344  {
345  // zero targets for the ECU being tested
346  auto metadata = UptaneRepoWrapper().addImageFile("mytarget", secondary_->hwID().ToString(), "non-existing-serial");
347 
348  EXPECT_FALSE(secondary_->putMetadata(metadata).isSuccess());
349  }
350 
351  {
352  // zero targets for the ECU being tested
353  auto metadata = UptaneRepoWrapper().addImageFile("mytarget", "non-existig-hwid", secondary_->serial().ToString());
354 
355  EXPECT_FALSE(secondary_->putMetadata(metadata).isSuccess());
356  }
357 }
358 
359 TEST_F(SecondaryTest, DirectorRootVersionIncremented) {
360  uptane_repo_.refreshRoot(Uptane::RepositoryType::Director());
361  EXPECT_TRUE(secondary_->putMetadata(uptane_repo_.getCurrentMetadata()).isSuccess());
362 }
363 
364 TEST_F(SecondaryTest, ImageRootVersionIncremented) {
365  uptane_repo_.refreshRoot(Uptane::RepositoryType::Image());
366  EXPECT_TRUE(secondary_->putMetadata(uptane_repo_.getCurrentMetadata()).isSuccess());
367 }
368 
369 TEST_F(SecondaryTest, SmallerImageFileSize) {
370  EXPECT_CALL(update_agent_, receiveData)
371  .Times((target_size - inavlid_target_size_delta) / send_buffer_size +
372  ((target_size - inavlid_target_size_delta) % send_buffer_size ? 1 : 0));
373  EXPECT_CALL(update_agent_, install).Times(1);
374 
375  EXPECT_TRUE(secondary_->putMetadata(uptane_repo_.getCurrentMetadata()).isSuccess());
376 
377  EXPECT_EQ(sendImageFile(smaller_target_), data::ResultCode::Numeric::kOk);
378  EXPECT_FALSE(secondary_->install().isSuccess());
379 }
380 
381 TEST_F(SecondaryTest, BiggerImageFileSize) {
382  EXPECT_CALL(update_agent_, receiveData)
383  .Times((target_size + inavlid_target_size_delta) / send_buffer_size +
384  ((target_size + inavlid_target_size_delta) % send_buffer_size ? 1 : 0));
385  EXPECT_CALL(update_agent_, install).Times(1);
386 
387  EXPECT_TRUE(secondary_->putMetadata(uptane_repo_.getCurrentMetadata()).isSuccess());
388 
389  EXPECT_EQ(sendImageFile(bigger_target_), data::ResultCode::Numeric::kOk);
390  EXPECT_FALSE(secondary_->install().isSuccess());
391 }
392 
393 TEST_F(SecondaryTest, InvalidImageData) {
394  EXPECT_CALL(update_agent_, receiveData)
395  .Times(target_size / send_buffer_size + (target_size % send_buffer_size ? 1 : 0));
396  EXPECT_CALL(update_agent_, install).Times(1);
397 
398  EXPECT_TRUE(secondary_->putMetadata(uptane_repo_.getCurrentMetadata()).isSuccess());
399  EXPECT_EQ(sendImageFile(broken_target_), data::ResultCode::Numeric::kOk);
400  EXPECT_FALSE(secondary_->install().isSuccess());
401 }
402 
403 int main(int argc, char** argv) {
404  ::testing::InitGoogleTest(&argc, argv);
405 
406  logger_init();
407  logger_set_threshold(boost::log::trivial::info);
408 
409  return RUN_ALL_TESTS();
410 }
UptaneRepoWrapper
Definition: aktualizr_secondary_ostree_test.cc:146
Hash
The Hash class The hash of a file or Uptane metadata.
Definition: types.h:159
Uptane::DirectorRepository
Definition: directorrepository.h:13
data::ResultCode::Numeric::kGeneralError
@ kGeneralError
Other error.
data::InstallationResult
Definition: types.h:277
Uptane::Version
Metadata version numbers.
Definition: tuf.h:120
AktualizrSecondaryConfig
Definition: aktualizr_secondary_config.h:35
UpdateAgentMock
Definition: aktualizr_secondary_test.cc:16
data
General data structures.
Definition: types.h:217
Uptane::RepositoryType
Definition: tuf.h:21
SecondaryTest
Definition: aktualizr_secondary_test.cc:171
Uptane::Targets
Definition: tuf.h:271
SecondaryTestNegative::MetadataInvalidator
Definition: aktualizr_secondary_test.cc:248
SecondaryTestNegative
Definition: aktualizr_secondary_test.cc:242
TemporaryDirectory
Definition: utils.h:82
result
Results of libaktualizr API calls.
Definition: results.h:12
FileUpdateAgent
Definition: update_agent_file.h:6
Uptane::Role
TUF Roles.
Definition: tuf.h:61
Uptane::Target
Definition: types.h:379
data::ResultCode::Numeric
Numeric
Definition: types.h:221
Metadata
Definition: aktualizr_secondary_metadata.h:7
UptaneRepo
Definition: uptane_repo.h:7
AktualizrSecondaryWrapper
Definition: aktualizr_secondary_ostree_test.cc:91
Delegation
Definition: repo.h:19