Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
tuf.h
1 #ifndef AKTUALIZR_UPTANE_TUF_H_
2 #define AKTUALIZR_UPTANE_TUF_H_
3 
4 /**
5  * Base data types that are used in The Update Framework (TUF), part of Uptane.
6  */
7 
8 #include <functional>
9 #include <map>
10 #include <ostream>
11 #include <set>
12 #include <vector>
13 #include "uptane/exceptions.h"
14 
15 #include "crypto/crypto.h"
16 #include "utilities/types.h"
17 
18 namespace Uptane {
19 
21  private:
22  /** This must match the repo_type table in sqlstorage */
23  enum class Type { kUnknown = -1, kImage = 0, kDirector = 1 };
24 
25  public:
26  static const std::string IMAGE;
27  static const std::string DIRECTOR;
28 
29  RepositoryType() = default;
30  static constexpr int Director() { return static_cast<int>(Type::kDirector); }
31  static constexpr int Image() { return static_cast<int>(Type::kImage); }
32  RepositoryType(int type) { type_ = static_cast<RepositoryType::Type>(type); }
33  RepositoryType(const std::string &repo_type) {
34  if (repo_type == DIRECTOR) {
35  type_ = RepositoryType::Type::kDirector;
36  } else if (repo_type == IMAGE) {
37  type_ = RepositoryType::Type::kImage;
38  } else {
39  throw std::runtime_error(std::string("Incorrect repo type: ") + repo_type);
40  }
41  }
42  operator int() const { return static_cast<int>(type_); }
43  operator std::string() const { return toString(); }
44  Type type_;
45  std::string toString() const {
46  if (type_ == RepositoryType::Type::kDirector) {
47  return DIRECTOR;
48  } else if (type_ == RepositoryType::Type::kImage) {
49  return IMAGE;
50  } else {
51  return "";
52  }
53  }
54 };
55 
56 using KeyId = std::string;
57 /**
58  * TUF Roles
59  */
60 class Role {
61  public:
62  static const std::string ROOT;
63  static const std::string SNAPSHOT;
64  static const std::string TARGETS;
65  static const std::string TIMESTAMP;
66 
67  static Role Root() { return Role{RoleEnum::kRoot}; }
68  static Role Snapshot() { return Role{RoleEnum::kSnapshot}; }
69  static Role Targets() { return Role{RoleEnum::kTargets}; }
70  static Role Timestamp() { return Role{RoleEnum::kTimestamp}; }
71  static Role Delegation(const std::string &name) { return Role(name, true); }
72  static Role InvalidRole() { return Role{RoleEnum::kInvalidRole}; }
73  // Delegation is not included because this is only used for a metadata table
74  // that doesn't include delegations.
75  static std::vector<Role> Roles() { return {Root(), Snapshot(), Targets(), Timestamp()}; }
76  static bool IsReserved(const std::string &name) {
77  return (name == ROOT || name == TARGETS || name == SNAPSHOT || name == TIMESTAMP);
78  }
79 
80  explicit Role(const std::string &role_name, bool delegation = false);
81  std::string ToString() const;
82  int ToInt() const { return static_cast<int>(role_); }
83  bool IsDelegation() const { return role_ == RoleEnum::kDelegation; }
84  bool operator==(const Role &other) const { return name_ == other.name_; }
85  bool operator!=(const Role &other) const { return !(*this == other); }
86  bool operator<(const Role &other) const { return name_ < other.name_; }
87 
88  friend std::ostream &operator<<(std::ostream &os, const Role &role);
89 
90  private:
91  /** The four standard roles must match the meta_types table in sqlstorage.
92  * Delegations are special and handled differently. */
93  enum class RoleEnum { kRoot = 0, kSnapshot = 1, kTargets = 2, kTimestamp = 3, kDelegation = 4, kInvalidRole = -1 };
94 
95  explicit Role(RoleEnum role) : role_(role) {
96  if (role_ == RoleEnum::kRoot) {
97  name_ = ROOT;
98  } else if (role_ == RoleEnum::kSnapshot) {
99  name_ = SNAPSHOT;
100  } else if (role_ == RoleEnum::kTargets) {
101  name_ = TARGETS;
102  } else if (role_ == RoleEnum::kTimestamp) {
103  name_ = TIMESTAMP;
104  } else {
105  role_ = RoleEnum::kInvalidRole;
106  name_ = "invalidrole";
107  }
108  }
109 
110  RoleEnum role_;
111  std::string name_;
112 };
113 
114 std::ostream &operator<<(std::ostream &os, const Role &role);
115 
116 /**
117  * Metadata version numbers
118  */
119 class Version {
120  public:
121  Version() : version_(ANY_VERSION) {}
122  explicit Version(int v) : version_(v) {}
123  std::string RoleFileName(const Role &role) const;
124  int version() const { return version_; }
125  bool operator==(const Version &rhs) const { return version_ == rhs.version_; }
126  bool operator!=(const Version &rhs) const { return version_ != rhs.version_; }
127  bool operator<(const Version &rhs) const { return version_ < rhs.version_; }
128 
129  private:
130  static const int ANY_VERSION = -1;
131  int version_;
132  friend std::ostream &operator<<(std::ostream &os, const Version &v);
133 };
134 
136  InstalledImageInfo() : name{""} {}
137  InstalledImageInfo(std::string name_in, uint64_t len_in, std::string hash_in)
138  : name(std::move(name_in)), len(len_in), hash(std::move(hash_in)) {}
139  std::string name;
140  uint64_t len{0};
141  std::string hash;
142 };
143 
144 std::ostream &operator<<(std::ostream &os, const Version &v);
145 
147  public:
148  // https://github.com/advancedtelematic/ota-tuf/blob/master/libtuf/src/main/scala/com/advancedtelematic/libtuf/data/TufDataType.scala
149  static const int kMinLength = 0;
150  static const int kMaxLength = 200;
151 
152  static HardwareIdentifier Unknown() { return HardwareIdentifier("Unknown"); }
153  explicit HardwareIdentifier(const std::string &hwid) : hwid_(hwid) {
154  /* if (hwid.length() < kMinLength) {
155  throw std::out_of_range("Hardware Identifier too short");
156  } */
157  if (kMaxLength < hwid.length()) {
158  throw std::out_of_range("Hardware Identifier too long");
159  }
160  }
161 
162  std::string ToString() const { return hwid_; }
163 
164  bool operator==(const HardwareIdentifier &rhs) const { return hwid_ == rhs.hwid_; }
165  bool operator!=(const HardwareIdentifier &rhs) const { return !(*this == rhs); }
166 
167  bool operator<(const HardwareIdentifier &rhs) const { return hwid_ < rhs.hwid_; }
168  friend std::ostream &operator<<(std::ostream &os, const HardwareIdentifier &hwid);
169  friend struct std::hash<Uptane::HardwareIdentifier>;
170 
171  private:
172  std::string hwid_;
173 };
174 
175 std::ostream &operator<<(std::ostream &os, const HardwareIdentifier &hwid);
176 
177 class EcuSerial {
178  public:
179  // https://github.com/advancedtelematic/ota-tuf/blob/master/libtuf/src/main/scala/com/advancedtelematic/libtuf/data/TufDataType.scala
180  static const int kMinLength = 1;
181  static const int kMaxLength = 64;
182 
183  static EcuSerial Unknown() { return EcuSerial("Unknown"); }
184  explicit EcuSerial(const std::string &ecu_serial) : ecu_serial_(ecu_serial) {
185  if (ecu_serial.length() < kMinLength) {
186  throw std::out_of_range("Ecu serial identifier is too short");
187  }
188  if (kMaxLength < ecu_serial.length()) {
189  throw std::out_of_range("Ecu serial identifier is too long");
190  }
191  }
192 
193  std::string ToString() const { return ecu_serial_; }
194 
195  bool operator==(const EcuSerial &rhs) const { return ecu_serial_ == rhs.ecu_serial_; }
196  bool operator!=(const EcuSerial &rhs) const { return !(*this == rhs); }
197 
198  bool operator<(const EcuSerial &rhs) const { return ecu_serial_ < rhs.ecu_serial_; }
199  friend std::ostream &operator<<(std::ostream &os, const EcuSerial &ecu_serial);
200  friend struct std::hash<Uptane::EcuSerial>;
201 
202  private:
203  std::string ecu_serial_;
204 };
205 
206 std::ostream &operator<<(std::ostream &os, const EcuSerial &ecu_serial);
207 
208 using EcuMap = std::map<EcuSerial, HardwareIdentifier>;
209 
210 class Target {
211  public:
212  // From Uptane metadata
213  Target(std::string filename, const Json::Value &content);
214  // Internal use only. Only used for reading installed_versions list and by
215  // various tests.
216  Target(std::string filename, EcuMap ecus, std::vector<Hash> hashes, uint64_t length, std::string correlation_id = "");
217 
218  static Target Unknown();
219 
220  const EcuMap &ecus() const { return ecus_; }
221  std::string filename() const { return filename_; }
222  std::string sha256Hash() const;
223  std::string sha512Hash() const;
224  const std::vector<Hash> &hashes() const { return hashes_; };
225  const std::vector<HardwareIdentifier> &hardwareIds() const { return hwids_; };
226  std::string custom_version() const { return custom_["version"].asString(); }
227  Json::Value custom_data() const { return custom_; }
228  void updateCustom(Json::Value &custom) { custom_ = custom; };
229  std::string correlation_id() const { return correlation_id_; };
230  void setCorrelationId(std::string correlation_id) { correlation_id_ = std::move(correlation_id); };
231  uint64_t length() const { return length_; }
232  bool IsValid() const { return valid; }
233  std::string uri() const { return uri_; };
234  void setUri(std::string uri) { uri_ = std::move(uri); };
235  bool MatchHash(const Hash &hash) const;
236 
237  void InsertEcu(const std::pair<EcuSerial, HardwareIdentifier> &pair) { ecus_.insert(pair); }
238 
239  bool IsForEcu(const EcuSerial &ecuIdentifier) const {
240  return (std::find_if(ecus_.cbegin(), ecus_.cend(),
241  [&ecuIdentifier](const std::pair<EcuSerial, HardwareIdentifier> &pair) {
242  return pair.first == ecuIdentifier;
243  }) != ecus_.cend());
244  };
245 
246  /**
247  * Is this an OSTree target?
248  * OSTree targets need special treatment because the hash doesn't represent
249  * the contents of the update itself, instead it is the hash (name) of the
250  * root commit object.
251  */
252  bool IsOstree() const;
253  std::string type() const { return type_; }
254 
255  // Comparison is usually not meaningful. Use MatchTarget instead.
256  bool operator==(const Target &t2) = delete;
257  bool MatchTarget(const Target &t2) const;
258  Json::Value toDebugJson() const;
259  friend std::ostream &operator<<(std::ostream &os, const Target &t);
260  InstalledImageInfo getTargetImageInfo() const { return {filename(), length(), sha256Hash()}; }
261 
262  private:
263  bool valid{true};
264  std::string filename_;
265  std::string type_;
266  EcuMap ecus_; // Director only
267  std::vector<Hash> hashes_;
268  std::vector<HardwareIdentifier> hwids_; // Image repo only
269  Json::Value custom_;
270  uint64_t length_{0};
271  std::string correlation_id_;
272  std::string uri_;
273 
274  std::string hashString(Hash::Type type) const;
275 };
276 
277 std::ostream &operator<<(std::ostream &os, const Target &t);
278 
279 /* Metadata objects */
280 class MetaWithKeys;
281 class BaseMeta {
282  public:
283  BaseMeta() = default;
284  explicit BaseMeta(const Json::Value &json);
285  BaseMeta(RepositoryType repo, const Role &role, const Json::Value &json, const std::shared_ptr<MetaWithKeys> &signer);
286  int version() const { return version_; }
287  TimeStamp expiry() const { return expiry_; }
288  bool isExpired(const TimeStamp &now) const { return expiry_.IsExpiredAt(now); }
289  Json::Value original() const { return original_object_; }
290 
291  bool operator==(const BaseMeta &rhs) const { return version_ == rhs.version() && expiry_ == rhs.expiry(); }
292 
293  protected:
294  int version_ = {-1};
295  TimeStamp expiry_;
296  Json::Value original_object_;
297 
298  private:
299  void init(const Json::Value &json);
300 };
301 
302 class MetaWithKeys : public BaseMeta {
303  public:
304  enum class Policy { kRejectAll, kAcceptAll, kCheck };
305  /**
306  * An empty metadata object that could contain keys.
307  */
308  MetaWithKeys() { version_ = 0; }
309  /**
310  * A 'real' metadata object that can contain keys (Root or Targets with
311  * delegations) and that implements TUF signature validation.
312  * @param json - The contents of the 'signed' portion
313  */
314  MetaWithKeys(const Json::Value &json);
315  MetaWithKeys(RepositoryType repo, const Role &role, const Json::Value &json,
316  const std::shared_ptr<MetaWithKeys> &signer);
317 
318  virtual ~MetaWithKeys() = default;
319 
320  void ParseKeys(RepositoryType repo, const Json::Value &keys);
321  // role is the name of a role described in this object's metadata.
322  // meta_role is the name of this object's role.
323  void ParseRole(RepositoryType repo, const Json::ValueConstIterator &it, const Role &role,
324  const std::string &meta_role);
325 
326  /**
327  * Take a JSON blob that contains a signatures/signed component that is supposedly for a given role, and check that is
328  * suitably signed.
329  * If it is, it returns the contents of the 'signed' part.
330  *
331  * It performs the following checks:
332  * * "_type" matches the given role
333  * * "expires" is in the past (vs 'now')
334  * * The blob has valid signatures from enough keys to cross the threshold for this role
335  * @param repo - Repository type (only used to improve the error messages)
336  * @param role - The Uptane role of the signed metadata object
337  * @param signed_object
338  * @return
339  */
340  virtual void UnpackSignedObject(RepositoryType repo, const Role &role, const Json::Value &signed_object);
341 
342  bool operator==(const MetaWithKeys &rhs) const {
343  return version_ == rhs.version_ && expiry_ == rhs.expiry_ && keys_ == rhs.keys_ &&
344  keys_for_role_ == rhs.keys_for_role_ && thresholds_for_role_ == rhs.thresholds_for_role_;
345  }
346 
347  protected:
348  static const int64_t kMinSignatures = 1;
349  static const int64_t kMaxSignatures = 1000;
350 
351  std::map<KeyId, PublicKey> keys_;
352  std::set<std::pair<Role, KeyId>> keys_for_role_;
353  std::map<Role, int64_t> thresholds_for_role_;
354 };
355 
356 // Implemented in uptane/root.cc
357 class Root : public MetaWithKeys {
358  public:
359  /**
360  * An empty Root, that either accepts or rejects everything
361  */
362  explicit Root(Policy policy = Policy::kRejectAll) : policy_(policy) { version_ = 0; }
363  /**
364  * A 'real' Root that implements TUF signature validation
365  * @param repo - Repository type (only used to improve the error messages)
366  * @param json - The contents of the 'signed' portion
367  */
368  Root(RepositoryType repo, const Json::Value &json);
369  Root(RepositoryType repo, const Json::Value &json, Root &root);
370 
371  ~Root() override = default;
372 
373  /**
374  * Take a JSON blob that contains a signatures/signed component that is supposedly for a given role, and check that is
375  * suitably signed.
376  * If it is, it returns the contents of the 'signed' part.
377  *
378  * It performs the following checks:
379  * * "_type" matches the given role
380  * * "expires" is in the past (vs 'now')
381  * * The blob has valid signatures from enough keys to cross the threshold for this role
382  * @param repo - Repository type (only used to improve the error messages)
383  * @param role - The Uptane role of the signed metadata object
384  * @param signed_object
385  * @return
386  */
387  void UnpackSignedObject(RepositoryType repo, const Role &role, const Json::Value &signed_object) override;
388 
389  bool operator==(const Root &rhs) const {
390  return version_ == rhs.version_ && expiry_ == rhs.expiry_ && keys_ == rhs.keys_ &&
391  keys_for_role_ == rhs.keys_for_role_ && thresholds_for_role_ == rhs.thresholds_for_role_ &&
392  policy_ == rhs.policy_;
393  }
394 
395  private:
396  Policy policy_;
397 };
398 
399 static bool MatchTargetVector(const std::vector<Uptane::Target> &v1, const std::vector<Uptane::Target> &v2) {
400  if (v1.size() != v2.size()) {
401  return false;
402  }
403  for (size_t i = 0; i < v1.size(); ++i) {
404  if (!v1[i].MatchTarget(v2[i])) {
405  return false;
406  }
407  }
408  return true;
409 }
410 
411 // Also used for delegated targets.
412 class Targets : public MetaWithKeys {
413  public:
414  explicit Targets(const Json::Value &json);
415  Targets(RepositoryType repo, const Role &role, const Json::Value &json, const std::shared_ptr<MetaWithKeys> &signer);
416  Targets() = default;
417  ~Targets() override = default;
418 
419  bool operator==(const Targets &rhs) const {
420  return version_ == rhs.version() && expiry_ == rhs.expiry() && MatchTargetVector(targets, rhs.targets);
421  }
422 
423  const std::string &correlation_id() const { return correlation_id_; }
424 
425  void clear() {
426  targets.clear();
427  delegated_role_names_.clear();
428  paths_for_role_.clear();
429  terminating_role_.clear();
430  }
431 
432  std::vector<Uptane::Target> getTargets(const Uptane::EcuSerial &ecu_id,
433  const Uptane::HardwareIdentifier &hw_id) const {
434  std::vector<Uptane::Target> result;
435  for (auto it = targets.begin(); it != targets.end(); ++it) {
436  auto found_loc = std::find_if(it->ecus().begin(), it->ecus().end(),
437  [ecu_id, hw_id](const std::pair<EcuSerial, HardwareIdentifier> &val) {
438  return ((ecu_id == val.first) && (hw_id == val.second));
439  });
440 
441  if (found_loc != it->ecus().end()) {
442  result.push_back(*it);
443  }
444  }
445  return result;
446  }
447 
448  std::vector<Uptane::Target> targets;
449  std::vector<std::string> delegated_role_names_;
450  std::map<Role, std::vector<std::string>> paths_for_role_;
451  std::map<Role, bool> terminating_role_;
452 
453  private:
454  void init(const Json::Value &json);
455 
456  std::string name_;
457  std::string correlation_id_; // custom non-tuf
458 };
459 
460 class TimestampMeta : public BaseMeta {
461  public:
462  explicit TimestampMeta(const Json::Value &json);
463  TimestampMeta(RepositoryType repo, const Json::Value &json, const std::shared_ptr<MetaWithKeys> &signer);
464  TimestampMeta() = default;
465  std::vector<Hash> snapshot_hashes() const { return snapshot_hashes_; };
466  int64_t snapshot_size() const { return snapshot_size_; };
467  int snapshot_version() const { return snapshot_version_; };
468 
469  private:
470  void init(const Json::Value &json);
471 
472  std::vector<Hash> snapshot_hashes_;
473  int64_t snapshot_size_{0};
474  int snapshot_version_{-1};
475 };
476 
477 class Snapshot : public BaseMeta {
478  public:
479  explicit Snapshot(const Json::Value &json);
480  Snapshot(RepositoryType repo, const Json::Value &json, const std::shared_ptr<MetaWithKeys> &signer);
481  Snapshot() = default;
482  std::vector<Hash> role_hashes(const Uptane::Role &role) const;
483  int64_t role_size(const Uptane::Role &role) const;
484  int role_version(const Uptane::Role &role) const;
485  bool operator==(const Snapshot &rhs) const {
486  return version_ == rhs.version() && expiry_ == rhs.expiry() && role_size_ == rhs.role_size_ &&
487  role_version_ == rhs.role_version_ && role_hashes_ == rhs.role_hashes_;
488  }
489 
490  private:
491  void init(const Json::Value &json);
492  std::map<Uptane::Role, int64_t> role_size_;
493  std::map<Uptane::Role, int> role_version_;
494  std::map<Uptane::Role, std::vector<Hash>> role_hashes_;
495 };
496 
497 struct MetaPack {
498  Root director_root;
499  Targets director_targets;
500  Root image_root;
501  Targets image_targets;
502  TimestampMeta image_timestamp;
503  Snapshot image_snapshot;
504  bool isConsistent() const;
505 };
506 
507 struct RawMetaPack {
508  std::string director_root;
509  std::string director_targets;
510  std::string image_root;
511  std::string image_targets;
512  std::string image_timestamp;
513  std::string image_snapshot;
514 };
515 
516 int extractVersionUntrusted(const std::string &meta); // returns negative number if parsing fails
517 
518 } // namespace Uptane
519 
520 namespace std {
521 template <>
522 struct hash<Uptane::HardwareIdentifier> {
523  size_t operator()(const Uptane::HardwareIdentifier &hwid) const { return std::hash<std::string>()(hwid.hwid_); }
524 };
525 
526 template <>
527 struct hash<Uptane::EcuSerial> {
528  size_t operator()(const Uptane::EcuSerial &ecu_serial) const {
529  return std::hash<std::string>()(ecu_serial.ecu_serial_);
530  }
531 };
532 } // namespace std
533 
534 #endif // AKTUALIZR_UPTANE_TUF_H_
Metadata version numbers.
Definition: tuf.h:119
TUF Roles.
Definition: tuf.h:60
Base data types that are used in The Update Framework (TUF), part of Uptane.