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 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{""} {}
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{0};
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  void InsertEcu(const std::pair<EcuSerial, HardwareIdentifier> &pair) { ecus_.insert(pair); }
266 
267  bool IsForEcu(const EcuSerial &ecuIdentifier) const {
268  return (std::find_if(ecus_.cbegin(), ecus_.cend(),
269  [&ecuIdentifier](const std::pair<EcuSerial, HardwareIdentifier> &pair) {
270  return pair.first == ecuIdentifier;
271  }) != ecus_.cend());
272  };
273 
274  /**
275  * Is this an OSTree target?
276  * OSTree targets need special treatment because the hash doesn't represent
277  * the contents of the update itself, instead it is the hash (name) of the
278  * root commit object.
279  */
280  bool IsOstree() const;
281  std::string type() const { return type_; }
282 
283  // Comparison is usually not meaningful. Use MatchTarget instead.
284  bool operator==(const Target &t2) = delete;
285  bool MatchTarget(const Target &t2) const;
286  Json::Value toDebugJson() const;
287  friend std::ostream &operator<<(std::ostream &os, const Target &t);
288  InstalledImageInfo getTargetImageInfo() const { return {filename(), length(), sha256Hash()}; }
289 
290  private:
291  bool valid{true};
292  std::string filename_;
293  std::string type_;
294  EcuMap ecus_; // Director only
295  std::vector<Hash> hashes_;
296  std::vector<HardwareIdentifier> hwids_; // Image repo only
297  Json::Value custom_;
298  uint64_t length_{0};
299  std::string correlation_id_;
300  std::string uri_;
301 
302  std::string hashString(Hash::Type type) const;
303 };
304 
305 std::ostream &operator<<(std::ostream &os, const Target &t);
306 
307 /* Metadata objects */
308 class MetaWithKeys;
309 class BaseMeta {
310  public:
311  BaseMeta() = default;
312  explicit BaseMeta(const Json::Value &json);
313  BaseMeta(RepositoryType repo, const Role &role, const Json::Value &json, const std::shared_ptr<MetaWithKeys> &signer);
314  int version() const { return version_; }
315  TimeStamp expiry() const { return expiry_; }
316  bool isExpired(const TimeStamp &now) const { return expiry_.IsExpiredAt(now); }
317  Json::Value original() const { return original_object_; }
318 
319  bool operator==(const BaseMeta &rhs) const { return version_ == rhs.version() && expiry_ == rhs.expiry(); }
320 
321  protected:
322  int version_ = {-1};
323  TimeStamp expiry_;
324  Json::Value original_object_;
325 
326  private:
327  void init(const Json::Value &json);
328 };
329 
330 class MetaWithKeys : public BaseMeta {
331  public:
332  enum class Policy { kRejectAll, kAcceptAll, kCheck };
333  /**
334  * An empty metadata object that could contain keys.
335  */
336  MetaWithKeys() { version_ = 0; }
337  /**
338  * A 'real' metadata object that can contain keys (Root or Targets with
339  * delegations) and that implements TUF signature validation.
340  * @param json - The contents of the 'signed' portion
341  */
342  MetaWithKeys(const Json::Value &json);
343  MetaWithKeys(RepositoryType repo, const Role &role, const Json::Value &json,
344  const std::shared_ptr<MetaWithKeys> &signer);
345 
346  virtual ~MetaWithKeys() = default;
347 
348  void ParseKeys(RepositoryType repo, const Json::Value &keys);
349  // role is the name of a role described in this object's metadata.
350  // meta_role is the name of this object's role.
351  void ParseRole(RepositoryType repo, const Json::ValueConstIterator &it, const Role &role,
352  const std::string &meta_role);
353 
354  /**
355  * Take a JSON blob that contains a signatures/signed component that is supposedly for a given role, and check that is
356  * suitably signed.
357  * If it is, it returns the contents of the 'signed' part.
358  *
359  * It performs the following checks:
360  * * "_type" matches the given role
361  * * "expires" is in the past (vs 'now')
362  * * The blob has valid signatures from enough keys to cross the threshold for this role
363  * @param repo - Repository type (only used to improve the error messages)
364  * @param role - The Uptane role of the signed metadata object
365  * @param signed_object
366  * @return
367  */
368  virtual void UnpackSignedObject(RepositoryType repo, const Role &role, const Json::Value &signed_object);
369 
370  bool operator==(const MetaWithKeys &rhs) const {
371  return version_ == rhs.version_ && expiry_ == rhs.expiry_ && keys_ == rhs.keys_ &&
372  keys_for_role_ == rhs.keys_for_role_ && thresholds_for_role_ == rhs.thresholds_for_role_;
373  }
374 
375  protected:
376  static const int64_t kMinSignatures = 1;
377  static const int64_t kMaxSignatures = 1000;
378 
379  std::map<KeyId, PublicKey> keys_;
380  std::set<std::pair<Role, KeyId>> keys_for_role_;
381  std::map<Role, int64_t> thresholds_for_role_;
382 };
383 
384 // Implemented in uptane/root.cc
385 class Root : public MetaWithKeys {
386  public:
387  /**
388  * An empty Root, that either accepts or rejects everything
389  */
390  explicit Root(Policy policy = Policy::kRejectAll) : policy_(policy) { version_ = 0; }
391  /**
392  * A 'real' Root that implements TUF signature validation
393  * @param repo - Repository type (only used to improve the error messages)
394  * @param json - The contents of the 'signed' portion
395  */
396  Root(RepositoryType repo, const Json::Value &json);
397  Root(RepositoryType repo, const Json::Value &json, Root &root);
398 
399  ~Root() override = default;
400 
401  /**
402  * Take a JSON blob that contains a signatures/signed component that is supposedly for a given role, and check that is
403  * suitably signed.
404  * If it is, it returns the contents of the 'signed' part.
405  *
406  * It performs the following checks:
407  * * "_type" matches the given role
408  * * "expires" is in the past (vs 'now')
409  * * The blob has valid signatures from enough keys to cross the threshold for this role
410  * @param repo - Repository type (only used to improve the error messages)
411  * @param role - The Uptane role of the signed metadata object
412  * @param signed_object
413  * @return
414  */
415  void UnpackSignedObject(RepositoryType repo, const Role &role, const Json::Value &signed_object) override;
416 
417  bool operator==(const Root &rhs) const {
418  return version_ == rhs.version_ && expiry_ == rhs.expiry_ && keys_ == rhs.keys_ &&
419  keys_for_role_ == rhs.keys_for_role_ && thresholds_for_role_ == rhs.thresholds_for_role_ &&
420  policy_ == rhs.policy_;
421  }
422 
423  private:
424  Policy policy_;
425 };
426 
427 static bool MatchTargetVector(const std::vector<Uptane::Target> &v1, const std::vector<Uptane::Target> &v2) {
428  if (v1.size() != v2.size()) {
429  return false;
430  }
431  for (size_t i = 0; i < v1.size(); ++i) {
432  if (!v1[i].MatchTarget(v2[i])) {
433  return false;
434  }
435  }
436  return true;
437 }
438 
439 // Also used for delegated targets.
440 class Targets : public MetaWithKeys {
441  public:
442  explicit Targets(const Json::Value &json);
443  Targets(RepositoryType repo, const Role &role, const Json::Value &json, const std::shared_ptr<MetaWithKeys> &signer);
444  Targets() = default;
445  ~Targets() override = default;
446 
447  bool operator==(const Targets &rhs) const {
448  return version_ == rhs.version() && expiry_ == rhs.expiry() && MatchTargetVector(targets, rhs.targets);
449  }
450 
451  const std::string &correlation_id() const { return correlation_id_; }
452 
453  void clear() {
454  targets.clear();
455  delegated_role_names_.clear();
456  paths_for_role_.clear();
457  terminating_role_.clear();
458  }
459 
460  std::vector<Uptane::Target> getTargets(const Uptane::EcuSerial &ecu_id,
461  const Uptane::HardwareIdentifier &hw_id) const {
462  std::vector<Uptane::Target> result;
463  for (auto it = targets.begin(); it != targets.end(); ++it) {
464  auto found_loc = std::find_if(it->ecus().begin(), it->ecus().end(),
465  [ecu_id, hw_id](const std::pair<EcuSerial, HardwareIdentifier> &val) {
466  return ((ecu_id == val.first) && (hw_id == val.second));
467  });
468 
469  if (found_loc != it->ecus().end()) {
470  result.push_back(*it);
471  }
472  }
473  return result;
474  }
475 
476  std::vector<Uptane::Target> targets;
477  std::vector<std::string> delegated_role_names_;
478  std::map<Role, std::vector<std::string>> paths_for_role_;
479  std::map<Role, bool> terminating_role_;
480 
481  private:
482  void init(const Json::Value &json);
483 
484  std::string name_;
485  std::string correlation_id_; // custom non-tuf
486 };
487 
488 class TimestampMeta : public BaseMeta {
489  public:
490  explicit TimestampMeta(const Json::Value &json);
491  TimestampMeta(RepositoryType repo, const Json::Value &json, const std::shared_ptr<MetaWithKeys> &signer);
492  TimestampMeta() = default;
493  std::vector<Hash> snapshot_hashes() const { return snapshot_hashes_; };
494  int64_t snapshot_size() const { return snapshot_size_; };
495  int snapshot_version() const { return snapshot_version_; };
496 
497  private:
498  void init(const Json::Value &json);
499 
500  std::vector<Hash> snapshot_hashes_;
501  int64_t snapshot_size_{0};
502  int snapshot_version_{-1};
503 };
504 
505 class Snapshot : public BaseMeta {
506  public:
507  explicit Snapshot(const Json::Value &json);
508  Snapshot(RepositoryType repo, const Json::Value &json, const std::shared_ptr<MetaWithKeys> &signer);
509  Snapshot() = default;
510  std::vector<Hash> role_hashes(const Uptane::Role &role) const;
511  int64_t role_size(const Uptane::Role &role) const;
512  int role_version(const Uptane::Role &role) const;
513  bool operator==(const Snapshot &rhs) const {
514  return version_ == rhs.version() && expiry_ == rhs.expiry() && role_size_ == rhs.role_size_ &&
515  role_version_ == rhs.role_version_ && role_hashes_ == rhs.role_hashes_;
516  }
517 
518  private:
519  void init(const Json::Value &json);
520  std::map<Uptane::Role, int64_t> role_size_;
521  std::map<Uptane::Role, int> role_version_;
522  std::map<Uptane::Role, std::vector<Hash>> role_hashes_;
523 };
524 
525 struct MetaPack {
526  Root director_root;
527  Targets director_targets;
528  Root image_root;
529  Targets image_targets;
530  TimestampMeta image_timestamp;
531  Snapshot image_snapshot;
532  bool isConsistent() const;
533 };
534 
535 struct RawMetaPack {
536  std::string director_root;
537  std::string director_targets;
538  std::string image_root;
539  std::string image_targets;
540  std::string image_timestamp;
541  std::string image_snapshot;
542 };
543 
544 int extractVersionUntrusted(const std::string &meta); // returns negative number if parsing fails
545 
546 } // namespace Uptane
547 
548 namespace std {
549 template <>
550 struct hash<Uptane::HardwareIdentifier> {
551  size_t operator()(const Uptane::HardwareIdentifier &hwid) const { return std::hash<std::string>()(hwid.hwid_); }
552 };
553 
554 template <>
555 struct hash<Uptane::EcuSerial> {
556  size_t operator()(const Uptane::EcuSerial &ecu_serial) const {
557  return std::hash<std::string>()(ecu_serial.ecu_serial_);
558  }
559 };
560 } // namespace std
561 
562 #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:505
Uptane::RepositoryType
Definition: tuf.h:20
Uptane::InstalledImageInfo
Definition: tuf.h:132
Uptane::Targets
Definition: tuf.h:440
Uptane::Role
TUF Roles.
Definition: tuf.h:57
Uptane::Root
Definition: tuf.h:385
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