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