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