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