Aktualizr
C++ SOTA Client
imagerepository.cc
1 #include "imagerepository.h"
2 
3 namespace Uptane {
4 
5 void ImageRepository::resetMeta() {
6  resetRoot();
7  targets.reset();
8  snapshot = Snapshot();
9  timestamp = TimestampMeta();
10 }
11 
12 void ImageRepository::verifyTimestamp(const std::string& timestamp_raw) {
13  try {
14  // Verify the signature:
15  timestamp =
16  TimestampMeta(RepositoryType::Image(), Utils::parseJSON(timestamp_raw), std::make_shared<MetaWithKeys>(root));
17  } catch (const Exception& e) {
18  LOG_ERROR << "Signature verification for Timestamp metadata failed";
19  throw;
20  }
21 }
22 
23 void ImageRepository::checkTimestampExpired() {
24  if (timestamp.isExpired(TimeStamp::Now())) {
25  throw Uptane::ExpiredMetadata(type.toString(), Role::TIMESTAMP);
26  }
27 }
28 
29 void ImageRepository::fetchSnapshot(INvStorage& storage, const IMetadataFetcher& fetcher, const int local_version) {
30  std::string image_snapshot;
31  const int64_t snapshot_size = (snapshotSize() > 0) ? snapshotSize() : kMaxSnapshotSize;
32  fetcher.fetchLatestRole(&image_snapshot, snapshot_size, RepositoryType::Image(), Role::Snapshot());
33  const int remote_version = extractVersionUntrusted(image_snapshot);
34 
35  // 6. Check that each Targets metadata filename listed in the previous Snapshot metadata file is also listed in this
36  // Snapshot metadata file. If this condition is not met, discard the new Snapshot metadata file, abort the update
37  // cycle, and report the failure. (Checks for a rollback attack.)
38  // See also https://github.com/uptane/deployment-considerations/pull/39/files.
39  // If the Snapshot is rotated, delegations may be safely removed.
40  // https://saeljira.it.here.com/browse/OTA-4121
41  verifySnapshot(image_snapshot, false);
42 
43  if (local_version > remote_version) {
44  throw Uptane::SecurityException(RepositoryType::IMAGE, "Rollback attempt");
45  } else if (local_version < remote_version) {
46  storage.storeNonRoot(image_snapshot, RepositoryType::Image(), Role::Snapshot());
47  }
48 }
49 
50 void ImageRepository::verifySnapshot(const std::string& snapshot_raw, bool prefetch) {
51  const std::string canonical = Utils::jsonToCanonicalStr(Utils::parseJSON(snapshot_raw));
52  bool hash_exists = false;
53  for (const auto& it : timestamp.snapshot_hashes()) {
54  switch (it.type()) {
55  case Hash::Type::kSha256:
56  if (Hash(Hash::Type::kSha256, boost::algorithm::hex(Crypto::sha256digest(canonical))) != it) {
57  if (!prefetch) {
58  LOG_ERROR << "Hash verification for Snapshot metadata failed";
59  }
60  throw Uptane::SecurityException(RepositoryType::IMAGE, "Snapshot metadata hash verification failed");
61  }
62  hash_exists = true;
63  break;
64  case Hash::Type::kSha512:
65  if (Hash(Hash::Type::kSha512, boost::algorithm::hex(Crypto::sha512digest(canonical))) != it) {
66  if (!prefetch) {
67  LOG_ERROR << "Hash verification for Snapshot metadata failed";
68  }
69  throw Uptane::SecurityException(RepositoryType::IMAGE, "Snapshot metadata hash verification failed");
70  }
71  hash_exists = true;
72  break;
73  default:
74  break;
75  }
76  }
77 
78  if (!hash_exists) {
79  LOG_ERROR << "No hash found for shapshot.json";
80  throw Uptane::SecurityException(RepositoryType::IMAGE, "Snapshot metadata hash verification failed");
81  }
82 
83  try {
84  // Verify the signature:
85  snapshot = Snapshot(RepositoryType::Image(), Utils::parseJSON(snapshot_raw), std::make_shared<MetaWithKeys>(root));
86  } catch (const Exception& e) {
87  LOG_ERROR << "Signature verification for Snapshot metadata failed";
88  throw;
89  }
90 
91  if (snapshot.version() != timestamp.snapshot_version()) {
92  throw Uptane::VersionMismatch(RepositoryType::IMAGE, Uptane::Role::SNAPSHOT);
93  }
94 }
95 
96 void ImageRepository::checkSnapshotExpired() {
97  if (snapshot.isExpired(TimeStamp::Now())) {
98  throw Uptane::ExpiredMetadata(type.toString(), Role::SNAPSHOT);
99  }
100 }
101 
102 void ImageRepository::fetchTargets(INvStorage& storage, const IMetadataFetcher& fetcher, const int local_version) {
103  std::string image_targets;
104  const Role targets_role = Role::Targets();
105 
106  auto targets_size = getRoleSize(Role::Targets());
107  if (targets_size <= 0) {
108  targets_size = kMaxImageTargetsSize;
109  }
110 
111  fetcher.fetchLatestRole(&image_targets, targets_size, RepositoryType::Image(), targets_role);
112 
113  const int remote_version = extractVersionUntrusted(image_targets);
114 
115  verifyTargets(image_targets, false);
116 
117  if (local_version > remote_version) {
118  throw Uptane::SecurityException(RepositoryType::IMAGE, "Rollback attempt");
119  } else if (local_version < remote_version) {
120  storage.storeNonRoot(image_targets, RepositoryType::Image(), targets_role);
121  }
122 }
123 
124 void ImageRepository::verifyRoleHashes(const std::string& role_data, const Uptane::Role& role, bool prefetch) const {
125  const std::string canonical = Utils::jsonToCanonicalStr(Utils::parseJSON(role_data));
126  // Hashes are not required. If present, however, we may as well check them.
127  // This provides no security benefit, but may help with fault detection.
128  for (const auto& it : snapshot.role_hashes(role)) {
129  switch (it.type()) {
130  case Hash::Type::kSha256:
131  if (Hash(Hash::Type::kSha256, boost::algorithm::hex(Crypto::sha256digest(canonical))) != it) {
132  if (!prefetch) {
133  LOG_ERROR << "Hash verification for " << role.ToString() << " metadata failed";
134  }
135  throw Uptane::SecurityException(RepositoryType::IMAGE, "Hash metadata mismatch");
136  }
137  break;
138  case Hash::Type::kSha512:
139  if (Hash(Hash::Type::kSha512, boost::algorithm::hex(Crypto::sha512digest(canonical))) != it) {
140  if (!prefetch) {
141  LOG_ERROR << "Hash verification for " << role.ToString() << " metadata failed";
142  }
143  throw Uptane::SecurityException(RepositoryType::IMAGE, "Hash metadata mismatch");
144  }
145  break;
146  default:
147  break;
148  }
149  }
150 }
151 
152 int ImageRepository::getRoleVersion(const Uptane::Role& role) const { return snapshot.role_version(role); }
153 
154 int64_t ImageRepository::getRoleSize(const Uptane::Role& role) const { return snapshot.role_size(role); }
155 
156 void ImageRepository::verifyTargets(const std::string& targets_raw, bool prefetch) {
157  try {
158  verifyRoleHashes(targets_raw, Uptane::Role::Targets(), prefetch);
159 
160  auto targets_json = Utils::parseJSON(targets_raw);
161 
162  // Verify the signature:
163  auto signer = std::make_shared<MetaWithKeys>(root);
164  targets = std::make_shared<Uptane::Targets>(
165  Targets(RepositoryType::Image(), Uptane::Role::Targets(), targets_json, signer));
166 
167  if (targets->version() != snapshot.role_version(Uptane::Role::Targets())) {
168  throw Uptane::VersionMismatch(RepositoryType::IMAGE, Uptane::Role::TARGETS);
169  }
170  } catch (const Exception& e) {
171  LOG_ERROR << "Signature verification for Image repo Targets metadata failed";
172  throw;
173  }
174 }
175 
176 std::shared_ptr<Uptane::Targets> ImageRepository::verifyDelegation(const std::string& delegation_raw,
177  const Uptane::Role& role,
178  const Targets& parent_target) {
179  try {
180  const Json::Value delegation_json = Utils::parseJSON(delegation_raw);
181  const std::string canonical = Utils::jsonToCanonicalStr(delegation_json);
182 
183  // Verify the signature:
184  auto signer = std::make_shared<MetaWithKeys>(parent_target);
185  return std::make_shared<Uptane::Targets>(Targets(RepositoryType::Image(), role, delegation_json, signer));
186  } catch (const Exception& e) {
187  LOG_ERROR << "Signature verification for Image repo delegated Targets metadata failed";
188  throw;
189  }
190 
191  return std::shared_ptr<Uptane::Targets>(nullptr);
192 }
193 
194 void ImageRepository::checkTargetsExpired() {
195  if (targets->isExpired(TimeStamp::Now())) {
196  throw Uptane::ExpiredMetadata(type.toString(), Role::TARGETS);
197  }
198 }
199 
200 void ImageRepository::updateMeta(INvStorage& storage, const IMetadataFetcher& fetcher) {
201  resetMeta();
202 
203  updateRoot(storage, fetcher, RepositoryType::Image());
204 
205  // Update Image repo Timestamp metadata
206  {
207  std::string image_timestamp;
208 
209  fetcher.fetchLatestRole(&image_timestamp, kMaxTimestampSize, RepositoryType::Image(), Role::Timestamp());
210  int remote_version = extractVersionUntrusted(image_timestamp);
211 
212  int local_version;
213  std::string image_timestamp_stored;
214  if (storage.loadNonRoot(&image_timestamp_stored, RepositoryType::Image(), Role::Timestamp())) {
215  local_version = extractVersionUntrusted(image_timestamp_stored);
216  } else {
217  local_version = -1;
218  }
219 
220  verifyTimestamp(image_timestamp);
221 
222  if (local_version > remote_version) {
223  throw Uptane::SecurityException(RepositoryType::IMAGE, "Rollback attempt");
224  } else if (local_version < remote_version) {
225  storage.storeNonRoot(image_timestamp, RepositoryType::Image(), Role::Timestamp());
226  }
227 
228  checkTimestampExpired();
229  }
230 
231  // Update Image repo Snapshot metadata
232  {
233  // First check if we already have the latest version according to the
234  // Timestamp metadata.
235  bool fetch_snapshot = true;
236  int local_version;
237  std::string image_snapshot_stored;
238  if (storage.loadNonRoot(&image_snapshot_stored, RepositoryType::Image(), Role::Snapshot())) {
239  try {
240  verifySnapshot(image_snapshot_stored, true);
241  fetch_snapshot = false;
242  LOG_DEBUG << "Skipping Image repo Snapshot download; stored version is still current.";
243  } catch (const Uptane::Exception& e) {
244  LOG_ERROR << "Image repo Snapshot verification failed: " << e.what();
245  }
246  local_version = snapshot.version();
247  } else {
248  local_version = -1;
249  }
250 
251  // If we don't, attempt to fetch the latest.
252  if (fetch_snapshot) {
253  fetchSnapshot(storage, fetcher, local_version);
254  }
255 
256  checkSnapshotExpired();
257  }
258 
259  // Update Image repo Targets metadata
260  {
261  // First check if we already have the latest version according to the
262  // Snapshot metadata.
263  bool fetch_targets = true;
264  int local_version = -1;
265  std::string image_targets_stored;
266  if (storage.loadNonRoot(&image_targets_stored, RepositoryType::Image(), Role::Targets())) {
267  try {
268  verifyTargets(image_targets_stored, true);
269  fetch_targets = false;
270  LOG_DEBUG << "Skipping Image repo Targets download; stored version is still current.";
271  } catch (const std::exception& e) {
272  LOG_ERROR << "Image repo Target verification failed: " << e.what();
273  }
274  if (targets) {
275  local_version = targets->version();
276  }
277  }
278 
279  // If we don't, attempt to fetch the latest.
280  if (fetch_targets) {
281  fetchTargets(storage, fetcher, local_version);
282  }
283 
284  checkTargetsExpired();
285  }
286 }
287 
288 void ImageRepository::checkMetaOffline(INvStorage& storage) {
289  resetMeta();
290  // Load Image repo Root metadata
291  {
292  std::string image_root;
293  if (!storage.loadLatestRoot(&image_root, RepositoryType::Image())) {
294  throw Uptane::SecurityException(RepositoryType::IMAGE, "Could not load latest root");
295  }
296 
297  initRoot(RepositoryType(RepositoryType::IMAGE), image_root);
298 
299  if (rootExpired()) {
300  throw Uptane::ExpiredMetadata(RepositoryType::IMAGE, Role::Root().ToString());
301  }
302  }
303 
304  // Load Image repo Timestamp metadata
305  {
306  std::string image_timestamp;
307  if (!storage.loadNonRoot(&image_timestamp, RepositoryType::Image(), Role::Timestamp())) {
308  throw Uptane::SecurityException(RepositoryType::IMAGE, "Could not load Timestamp role");
309  }
310 
311  verifyTimestamp(image_timestamp);
312 
313  checkTimestampExpired();
314  }
315 
316  // Load Image repo Snapshot metadata
317  {
318  std::string image_snapshot;
319 
320  if (!storage.loadNonRoot(&image_snapshot, RepositoryType::Image(), Role::Snapshot())) {
321  throw Uptane::SecurityException(RepositoryType::IMAGE, "Could not load Snapshot role");
322  }
323 
324  verifySnapshot(image_snapshot, false);
325 
326  checkSnapshotExpired();
327  }
328 
329  // Load Image repo Targets metadata
330  {
331  std::string image_targets;
332  Role targets_role = Uptane::Role::Targets();
333  if (!storage.loadNonRoot(&image_targets, RepositoryType::Image(), targets_role)) {
334  throw Uptane::SecurityException(RepositoryType::IMAGE, "Could not load Image role");
335  }
336 
337  verifyTargets(image_targets, false);
338 
339  checkTargetsExpired();
340  }
341 }
342 
343 } // namespace Uptane
Hash
The Hash class The hash of a file or Uptane metadata.
Definition: types.h:159
Uptane::ExpiredMetadata
Definition: exceptions.h:74
Uptane::Exception
Definition: exceptions.h:10
Uptane::Role
TUF Roles.
Definition: tuf.h:61
Uptane
Base data types that are used in The Update Framework (TUF), part of Uptane.
Definition: packagemanagerinterface.h:18
INvStorage
Definition: invstorage.h:43
Uptane::SecurityException
Definition: exceptions.h:28
Uptane::VersionMismatch
Definition: exceptions.h:129