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.h"
7 #include "aktualizr_secondary_factory.h"
8 #include "crypto/keymanager.h"
9 #include "test_utils.h"
10 #include "update_agent.h"
11 #include "update_agent_file.h"
12 #include "uptane_repo.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, download).WillByDefault([this](const Uptane::Target& target, const std::string& data) {
21  return FileUpdateAgent::download(target, data);
22  });
23  ON_CALL(*this, install).WillByDefault([this](const Uptane::Target& target) {
24  return FileUpdateAgent::install(target);
25  });
26  }
27 
28  MOCK_METHOD(bool, download, (const Uptane::Target& target, const std::string& data));
29  MOCK_METHOD(data::ResultCode::Numeric, 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  auto key_mngr = std::make_shared<KeyManager>(storage_, config.keymanagerConfig());
43  update_agent = std::make_shared<NiceMock<UpdateAgentMock>>(config.storage.path / "firmware.txt", "");
44 
45  secondary_ = std::make_shared<AktualizrSecondary>(config, storage_, key_mngr, update_agent);
46  }
47 
48  std::shared_ptr<AktualizrSecondary>& operator->() { return secondary_; }
49 
50  Uptane::Target getPendingVersion() const {
51  boost::optional<Uptane::Target> pending_target;
52 
53  storage_->loadInstalledVersions(secondary_->getSerial().ToString(), nullptr, &pending_target);
54  return *pending_target;
55  }
56 
57  std::string hardwareID() const { return secondary_->getHwId().ToString(); }
58 
59  std::string serial() const { return secondary_->getSerial().ToString(); }
60 
61  boost::filesystem::path targetFilepath() const {
62  return storage_dir_.Path() / AktualizrSecondaryFactory::BinaryUpdateDefaultFile;
63  }
64 
65  std::shared_ptr<NiceMock<UpdateAgentMock>> update_agent;
66 
67  private:
68  TemporaryDirectory storage_dir_;
69  AktualizrSecondary::Ptr secondary_;
70  std::shared_ptr<INvStorage> storage_;
71 };
72 
73 class UptaneRepoWrapper {
74  public:
75  UptaneRepoWrapper() { uptane_repo_.generateRepo(KeyType::kED25519); }
76 
77  Metadata addImageFile(const std::string& targetname, const std::string& hardware_id, const std::string& serial,
78  bool add_and_sign_target = true) {
79  const auto image_file_path = root_dir_ / targetname;
80  boost::filesystem::ofstream(image_file_path) << "some data";
81 
82  uptane_repo_.addImage(image_file_path, targetname, hardware_id, "", Delegation());
83  if (add_and_sign_target) {
84  uptane_repo_.addTarget(targetname, hardware_id, serial, "");
85  uptane_repo_.signTargets();
86  }
87 
88  return getCurrentMetadata();
89  }
90 
91  Uptane::RawMetaPack getCurrentMetadata() const {
92  Uptane::RawMetaPack metadata;
93 
94  boost::filesystem::load_string_file(director_dir_ / "root.json", metadata.director_root);
95  boost::filesystem::load_string_file(director_dir_ / "targets.json", metadata.director_targets);
96 
97  boost::filesystem::load_string_file(imagerepo_dir_ / "root.json", metadata.image_root);
98  boost::filesystem::load_string_file(imagerepo_dir_ / "timestamp.json", metadata.image_timestamp);
99  boost::filesystem::load_string_file(imagerepo_dir_ / "snapshot.json", metadata.image_snapshot);
100  boost::filesystem::load_string_file(imagerepo_dir_ / "targets.json", metadata.image_targets);
101 
102  return metadata;
103  }
104 
105  std::string getImageData(const std::string& targetname) const {
106  std::string image_data;
107  boost::filesystem::load_string_file(root_dir_ / targetname, image_data);
108  return image_data;
109  }
110 
111  void refreshRoot(Uptane::RepositoryType repo) { uptane_repo_.refresh(repo, Uptane::Role::Root()); }
112 
113  private:
114  TemporaryDirectory root_dir_;
115  boost::filesystem::path director_dir_{root_dir_ / "repo/director"};
116  boost::filesystem::path imagerepo_dir_{root_dir_ / "repo/repo"};
117  UptaneRepo uptane_repo_{root_dir_.Path(), "", ""};
118  Uptane::DirectorRepository director_repo_;
119 };
120 
121 class SecondaryTest : public ::testing::Test {
122  protected:
123  SecondaryTest() : update_agent_(*(secondary_.update_agent)) {
124  uptane_repo_.addImageFile(default_target_, secondary_->getHwId().ToString(), secondary_->getSerial().ToString());
125  }
126 
127  std::string getImageData(const std::string& targetname = default_target_) const {
128  return uptane_repo_.getImageData(targetname);
129  }
130 
131  std::vector<Uptane::Target> getCurrentTargets() {
132  auto targets = Uptane::Targets(Utils::parseJSON(uptane_repo_.getCurrentMetadata().director_targets));
133  return targets.getTargets(secondary_->getSerial(), secondary_->getHwId());
134  }
135 
136  Uptane::Target getDefaultTarget() {
137  auto targets = getCurrentTargets();
138  EXPECT_GT(targets.size(), 0);
139  return targets[0];
140  }
141 
142  Hash getDefaultTargetHash() { return Hash(Hash::Type::kSha256, getDefaultTarget().sha256Hash()); }
143 
144  protected:
145  static constexpr const char* const default_target_{"default-target"};
146  AktualizrSecondaryWrapper secondary_;
147  UptaneRepoWrapper uptane_repo_;
148  NiceMock<UpdateAgentMock>& update_agent_;
149 };
150 
151 class SecondaryTestNegative : public ::testing::Test,
152  public ::testing::WithParamInterface<std::pair<Uptane::RepositoryType, Uptane::Role>> {
153  public:
154  SecondaryTestNegative() : update_agent_(*(secondary_.update_agent)) {}
155 
156  protected:
157  class MetadataInvalidator : public Metadata {
158  public:
159  MetadataInvalidator(const Uptane::RawMetaPack& valid_metadata, const Uptane::RepositoryType& repo,
160  const Uptane::Role& role)
161  : Metadata(valid_metadata), repo_type_(repo), role_(role) {}
162 
163  void getRoleMetadata(std::string* result, const Uptane::RepositoryType& repo, const Uptane::Role& role,
164  Uptane::Version version) const override {
165  Metadata::getRoleMetadata(result, repo, role, version);
166  if (!(repo_type_ == repo && role_ == role)) {
167  return;
168  }
169  (*result)[10] = 'f';
170  }
171 
172  private:
173  Uptane::RepositoryType repo_type_;
174  Uptane::Role role_;
175  };
176 
177  MetadataInvalidator currentMetadata() const {
178  return MetadataInvalidator(uptane_repo_.getCurrentMetadata(), GetParam().first, GetParam().second);
179  }
180 
181  AktualizrSecondaryWrapper secondary_;
182  UptaneRepoWrapper uptane_repo_;
183  NiceMock<UpdateAgentMock>& update_agent_;
184 };
185 
186 /**
187  * Parameterized test,
188  * The parameter is std::pair<Uptane::RepositoryType, Uptane::Role> to indicate which metadata to malform
189  *
190  * see INSTANTIATE_TEST_SUITE_P for the test instantiations with concrete parameter values
191  */
192 TEST_P(SecondaryTestNegative, MalformedMetadaJson) {
193  EXPECT_FALSE(secondary_->putMetadata(currentMetadata()));
194 
195  EXPECT_CALL(update_agent_, download).Times(0);
196  EXPECT_CALL(update_agent_, install).Times(0);
197 
198  EXPECT_FALSE(secondary_->sendFirmware("firmware"));
199 
200  EXPECT_NE(secondary_->install("target"), data::ResultCode::Numeric::kOk);
201 }
202 
203 /**
204  * Instantiates the parameterized test for each specified value of std::pair<Uptane::RepositoryType, Uptane::Role>
205  * the parameter value indicates which metadata to malform
206  */
207 INSTANTIATE_TEST_SUITE_P(SecondaryTestMalformedMetadata, SecondaryTestNegative,
208  ::testing::Values(std::make_pair(Uptane::RepositoryType::Director(), Uptane::Role::Root()),
209  std::make_pair(Uptane::RepositoryType::Director(), Uptane::Role::Targets()),
210  std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Root()),
211  std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Timestamp()),
212  std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Snapshot()),
213  std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Targets())));
214 
215 TEST_F(SecondaryTest, fullUptaneVerificationPositive) {
216  EXPECT_CALL(update_agent_, download).Times(1);
217  EXPECT_CALL(update_agent_, install).Times(1);
218 
219  ASSERT_TRUE(secondary_->putMetadata(uptane_repo_.getCurrentMetadata()));
220  ASSERT_TRUE(secondary_->sendFirmware(getImageData()));
221  ASSERT_EQ(secondary_->install(default_target_), data::ResultCode::Numeric::kOk);
222 
223  // check if a file was actually updated
224  ASSERT_TRUE(boost::filesystem::exists(secondary_.targetFilepath()));
225  auto target = getDefaultTarget();
226 
227  // check the updated file hash
228  auto target_hash = Hash(Hash::Type::kSha256, target.sha256Hash());
229  auto target_file_hash = Hash::generate(Hash::Type::kSha256, Utils::readFile(secondary_.targetFilepath()));
230  EXPECT_EQ(target_hash, target_file_hash);
231 
232  // check the secondary manifest
233  auto manifest = secondary_->getManifest();
234  EXPECT_EQ(manifest.installedImageHash(), target_file_hash);
235  EXPECT_EQ(manifest.filepath(), target.filename());
236 }
237 
238 TEST_F(SecondaryTest, TwoImagesAndOneTarget) {
239  // two images for the same ECU, just one of them is added as a target and signed
240  // default image and corresponding target has been already added, just add another image
241  uptane_repo_.addImageFile("second_image_00", secondary_->getHwId().ToString(), secondary_->getSerial().ToString(),
242  false);
243  EXPECT_TRUE(secondary_->putMetadata(uptane_repo_.getCurrentMetadata()));
244 }
245 
246 TEST_F(SecondaryTest, IncorrectTargetQuantity) {
247  {
248  // two targets for the same ECU
249  uptane_repo_.addImageFile("second_target", secondary_->getHwId().ToString(), secondary_->getSerial().ToString());
250 
251  EXPECT_FALSE(secondary_->putMetadata(uptane_repo_.getCurrentMetadata()));
252  }
253 
254  {
255  // zero targets for the ECU being tested
256  auto metadata =
257  UptaneRepoWrapper().addImageFile("mytarget", secondary_->getHwId().ToString(), "non-existing-serial");
258 
259  EXPECT_FALSE(secondary_->putMetadata(metadata));
260  }
261 
262  {
263  // zero targets for the ECU being tested
264  auto metadata =
265  UptaneRepoWrapper().addImageFile("mytarget", "non-existig-hwid", secondary_->getSerial().ToString());
266 
267  EXPECT_FALSE(secondary_->putMetadata(metadata));
268  }
269 }
270 
271 TEST_F(SecondaryTest, DirectorRootVersionIncremented) {
272  uptane_repo_.refreshRoot(Uptane::RepositoryType::Director());
273  EXPECT_TRUE(secondary_->putMetadata(uptane_repo_.getCurrentMetadata()));
274 }
275 
276 TEST_F(SecondaryTest, ImageRootVersionIncremented) {
277  uptane_repo_.refreshRoot(Uptane::RepositoryType::Image());
278  EXPECT_TRUE(secondary_->putMetadata(uptane_repo_.getCurrentMetadata()));
279 }
280 
281 TEST_F(SecondaryTest, InvalidImageFileSize) {
282  EXPECT_CALL(update_agent_, download).Times(1);
283  EXPECT_CALL(update_agent_, install).Times(0);
284 
285  EXPECT_TRUE(secondary_->putMetadata(uptane_repo_.getCurrentMetadata()));
286  auto image_data = getImageData();
287  image_data.append("\n");
288  EXPECT_FALSE(secondary_->sendFirmware(image_data));
289  EXPECT_NE(secondary_->install(default_target_), data::ResultCode::Numeric::kOk);
290 }
291 
292 TEST_F(SecondaryTest, InvalidImageData) {
293  EXPECT_CALL(update_agent_, download).Times(1);
294  EXPECT_CALL(update_agent_, install).Times(0);
295 
296  EXPECT_TRUE(secondary_->putMetadata(uptane_repo_.getCurrentMetadata()));
297  auto image_data = getImageData();
298  image_data.operator[](3) = '0';
299  EXPECT_FALSE(secondary_->sendFirmware(image_data));
300  EXPECT_NE(secondary_->install(default_target_), data::ResultCode::Numeric::kOk);
301 }
302 
303 int main(int argc, char** argv) {
304  ::testing::InitGoogleTest(&argc, argv);
305 
306  logger_init();
307  logger_set_threshold(boost::log::trivial::info);
308 
309  return RUN_ALL_TESTS();
310 }
UptaneRepoWrapper
Definition: aktualizr_secondary_ostree_test.cc:145
Hash
The hash of a file or Uptane metadata.
Definition: crypto.h:65
Uptane::DirectorRepository
Definition: directorrepository.h:13
Uptane::Version
Metadata version numbers.
Definition: tuf.h:119
AktualizrSecondaryConfig
Definition: aktualizr_secondary_config.h:40
UpdateAgentMock
Definition: aktualizr_secondary_test.cc:16
data
General data structures.
Definition: types.cc:55
Uptane::RawMetaPack
Definition: tuf.h:507
Uptane::RepositoryType
Definition: tuf.h:20
SecondaryTest
Definition: aktualizr_secondary_test.cc:121
Uptane::Targets
Definition: tuf.h:412
SecondaryTestNegative::MetadataInvalidator
Definition: aktualizr_secondary_test.cc:157
SecondaryTestNegative
Definition: aktualizr_secondary_test.cc:151
TemporaryDirectory
Definition: utils.h:82
result
Results of libaktualizr API calls.
Definition: results.h:13
FileUpdateAgent
Definition: update_agent_file.h:6
Uptane::Role
TUF Roles.
Definition: tuf.h:60
Uptane::Target
Definition: tuf.h:210
data::ResultCode::Numeric
Numeric
Definition: types.h:128
Metadata
Definition: aktualizr_secondary_metadata.h:8
UptaneRepo
Definition: uptane_repo.h:7
AktualizrSecondaryWrapper
Definition: aktualizr_secondary_ostree_test.cc:92
Delegation
Definition: repo.h:19