Aktualizr
C++ SOTA Client
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 <ostream>
10 #include <set>
11 #include "uptane/exceptions.h"
12 
13 #include "crypto/crypto.h"
14 #include "utilities/types.h"
15 
16 namespace Uptane {
17 
18 /** This must match the repo_type table in sqlstorage */
19 enum class RepositoryType { Unknown = -1, Images = 0, Director = 1 };
20 std::string RepoString(RepositoryType repo);
21 
22 using KeyId = std::string;
23 /**
24  * TUF Roles
25  */
26 class Role {
27  public:
28  static Role Root() { return Role{RoleEnum::kRoot}; }
29  static Role Snapshot() { return Role{RoleEnum::kSnapshot}; }
30  static Role Targets() { return Role{RoleEnum::kTargets}; }
31  static Role Timestamp() { return Role{RoleEnum::kTimestamp}; }
32  static Role InvalidRole() { return Role{RoleEnum::kInvalidRole}; }
33  static std::vector<Role> Roles() { return {Root(), Snapshot(), Targets(), Timestamp()}; }
34 
35  explicit Role(const std::string & /*role_name*/);
36  std::string ToString() const;
37  int ToInt() const { return static_cast<int>(role_); }
38  bool operator==(const Role &other) const { return role_ == other.role_; }
39  bool operator!=(const Role &other) const { return !(*this == other); }
40  bool operator<(const Role &other) const { return role_ < other.role_; }
41 
42  friend std::ostream &operator<<(std::ostream &os, const Role &t);
43 
44  private:
45  /** This must match the meta_types table in sqlstorage */
46  enum class RoleEnum { kRoot = 0, kSnapshot = 1, kTargets = 2, kTimestamp = 3, kInvalidRole = -1 };
47 
48  explicit Role(RoleEnum role) : role_(role) {}
49 
50  RoleEnum role_;
51 };
52 
53 std::ostream &operator<<(std::ostream &os, const Role &t);
54 
55 /**
56  * Metadata version numbers
57  */
58 class Version {
59  public:
60  Version() : version_(ANY_VERSION) {}
61  explicit Version(int v) : version_(v) {}
62  std::string RoleFileName(Role role) const;
63  int version() { return version_; }
64 
65  private:
66  static const int ANY_VERSION = -1;
67  int version_;
68  friend std::ostream &operator<<(std::ostream &os, const Version &v);
69 };
70 
71 std::ostream &operator<<(std::ostream &os, const Version &v);
72 
74  public:
75  // https://github.com/advancedtelematic/ota-tuf/blob/master/libtuf/src/main/scala/com/advancedtelematic/libtuf/data/TufDataType.scala
76  static const int kMinLength = 0;
77  static const int kMaxLength = 200;
78 
79  static HardwareIdentifier Unknown() { return HardwareIdentifier("Unknown"); }
80  explicit HardwareIdentifier(const std::string &hwid) : hwid_(hwid) {
81  /* if (hwid.length() < kMinLength) {
82  throw std::out_of_range("Hardware Identifier too short");
83  } */
84  if (kMaxLength < hwid.length()) {
85  throw std::out_of_range("Hardware Identifier too long");
86  }
87  }
88 
89  std::string ToString() const { return hwid_; }
90 
91  bool operator==(const HardwareIdentifier &rhs) const { return hwid_ == rhs.hwid_; }
92  bool operator!=(const HardwareIdentifier &rhs) const { return !(*this == rhs); }
93 
94  bool operator<(const HardwareIdentifier &rhs) const { return hwid_ < rhs.hwid_; }
95  friend std::ostream &operator<<(std::ostream &os, const HardwareIdentifier &hwid);
96  friend struct std::hash<Uptane::HardwareIdentifier>;
97 
98  private:
99  std::string hwid_;
100 };
101 
102 std::ostream &operator<<(std::ostream &os, const HardwareIdentifier &hwid);
103 
104 class EcuSerial {
105  public:
106  // https://github.com/advancedtelematic/ota-tuf/blob/master/libtuf/src/main/scala/com/advancedtelematic/libtuf/data/TufDataType.scala
107  static const int kMinLength = 1;
108  static const int kMaxLength = 64;
109 
110  static EcuSerial Unknown() { return EcuSerial("Unknown"); }
111  explicit EcuSerial(const std::string &ecu_serial) : ecu_serial_(ecu_serial) {
112  if (ecu_serial.length() < kMinLength) {
113  throw std::out_of_range("Ecu serial identifier is too short");
114  }
115  if (kMaxLength < ecu_serial.length()) {
116  throw std::out_of_range("Ecu serial identifier is too long");
117  }
118  }
119 
120  std::string ToString() const { return ecu_serial_; }
121 
122  bool operator==(const EcuSerial &rhs) const { return ecu_serial_ == rhs.ecu_serial_; }
123  bool operator!=(const EcuSerial &rhs) const { return !(*this == rhs); }
124 
125  bool operator<(const EcuSerial &rhs) const { return ecu_serial_ < rhs.ecu_serial_; }
126  friend std::ostream &operator<<(std::ostream &os, const EcuSerial &ecu_serial);
127  friend struct std::hash<Uptane::EcuSerial>;
128 
129  private:
130  std::string ecu_serial_;
131 };
132 
133 std::ostream &operator<<(std::ostream &os, const EcuSerial &ecu_serial);
134 
135 /**
136  * The hash of a file or TUF metadata. File hashes/checksums in TUF include the length of the object, in order to
137  * defeat infinite download attacks.
138  */
139 class Hash {
140  public:
141  // order corresponds algorithm priority
142  enum class Type { kSha256, kSha512, kUnknownAlgorithm };
143 
144  Hash(const std::string &type, const std::string &hash);
145  Hash(Type type, const std::string &hash);
146 
147  bool HaveAlgorithm() const { return type_ != Type::kUnknownAlgorithm; }
148  bool operator==(const Hash &other) const;
149  bool operator!=(const Hash &other) const { return !operator==(other); }
150  std::string TypeString() const;
151  Type type() const;
152  std::string HashString() const { return hash_; }
153  friend std::ostream &operator<<(std::ostream &os, const Hash &h);
154 
155  private:
156  Type type_;
157  std::string hash_;
158 };
159 
160 std::ostream &operator<<(std::ostream &os, const Hash &h);
161 
162 class Target {
163  public:
164  Target(std::string filename, const Json::Value &content);
165 
166  const std::map<EcuSerial, HardwareIdentifier> &ecus() const { return ecus_; }
167  std::string filename() const { return filename_; }
168  std::string sha256Hash() const;
169  std::vector<Hash> hashes() const { return hashes_; };
170 
171  bool MatchWith(const Hash &hash) const;
172 
173  int64_t length() const { return length_; }
174 
175  bool IsForSecondary(const EcuSerial &ecuIdentifier) const {
176  return (std::find_if(ecus_.cbegin(), ecus_.cend(), [&ecuIdentifier](std::pair<EcuSerial, HardwareIdentifier> pair) {
177  return pair.first == ecuIdentifier;
178  }) != ecus_.cend());
179  };
180 
181  /**
182  * Is this an OSTree target?
183  * OSTree targets need special treatment because the hash doesn't represent
184  * the contents of the update itself, instead it is the hash (name) of the
185  * root commit object.
186  */
187  bool IsOstree() const;
188 
189  bool operator==(const Target &t2) const {
190  // if (type_ != t2.type_) return false; // Director doesn't include targetFormat
191  if (filename_ != t2.filename_) {
192  return false;
193  }
194  if (length_ != t2.length_) {
195  return false;
196  }
197 
198  // requirements:
199  // - all hashes of the same type should match
200  // - at least one pair of hashes should match
201  bool oneMatchingHash = false;
202  for (const Hash &hash : hashes_) {
203  for (const Hash &hash2 : t2.hashes_) {
204  if (hash.type() == hash2.type() && !(hash == hash2)) {
205  return false;
206  }
207  if (hash == hash2) {
208  oneMatchingHash = true;
209  }
210  }
211  }
212  return oneMatchingHash;
213  }
214 
215  Json::Value toDebugJson() const;
216  friend std::ostream &operator<<(std::ostream &os, const Target &t);
217 
218  private:
219  std::string filename_;
220  std::string type_;
221  std::map<EcuSerial, HardwareIdentifier> ecus_;
222  std::vector<Hash> hashes_;
223  int64_t length_{0};
224 };
225 
226 std::ostream &operator<<(std::ostream &os, const Target &t);
227 
228 /* Metadata objects */
229 class Root;
230 class BaseMeta {
231  public:
232  BaseMeta() = default;
233  explicit BaseMeta(const Json::Value &json);
234  BaseMeta(RepositoryType repo, const Json::Value &json, Root &root);
235  int version() const { return version_; }
236  TimeStamp expiry() const { return expiry_; }
237  bool isExpired(const TimeStamp &now) const { return expiry_.IsExpiredAt(now); }
238  Json::Value original() const { return original_object_; }
239 
240  bool operator==(const BaseMeta &rhs) const { return version_ == rhs.version() && expiry_ == rhs.expiry(); }
241 
242  protected:
243  int version_ = {-1};
244  TimeStamp expiry_;
245  Json::Value original_object_;
246 
247  private:
248  void init(const Json::Value &json);
249 };
250 
251 // Implemented in uptane/root.cc
252 class Root : public BaseMeta {
253  public:
254  enum class Policy { kRejectAll, kAcceptAll, kCheck };
255  /**
256  * An empty Root, that either accepts or rejects everything
257  */
258  explicit Root(Policy policy = Policy::kRejectAll) : policy_(policy) { version_ = 0; }
259  /**
260  * A 'real' root that implements TUF signature validation
261  * @param repo - Repository type (only used to improve the error messages)
262  * @param json - The contents of the 'signed' portion
263  */
264  Root(RepositoryType repo, const Json::Value &json);
265  Root(RepositoryType repo, const Json::Value &json, Root &root);
266 
267  /**
268  * Take a JSON blob that contains a signatures/signed component that is supposedly for a given role, and check that is
269  * suitably signed.
270  * If it is, it returns the contents of the 'signed' part.
271  *
272  * It performs the following checks:
273  * * "_type" matches the given role
274  * * "expires" is in the past (vs 'now')
275  * * The blob has valid signatures from enough keys to cross the threshold for this role
276  * @param repo - Repository type (only used to improve the error messages)
277  * @param signed_object
278  * @return
279  */
280  void UnpackSignedObject(RepositoryType repo, const Json::Value &signed_object);
281  bool operator==(const Root &rhs) const {
282  return version_ == rhs.version_ && expiry_ == rhs.expiry_ && keys_ == rhs.keys_ &&
283  keys_for_role_ == rhs.keys_for_role_ && thresholds_for_role_ == rhs.thresholds_for_role_ &&
284  policy_ == rhs.policy_;
285  }
286 
287  private:
288  static const int64_t kMinSignatures = 1;
289  static const int64_t kMaxSignatures = 1000;
290 
291  Policy policy_;
292  std::map<KeyId, PublicKey> keys_;
293  std::set<std::pair<Role, KeyId> > keys_for_role_;
294  std::map<Role, int64_t> thresholds_for_role_;
295 };
296 
297 class Targets : public BaseMeta {
298  public:
299  explicit Targets(const Json::Value &json);
300  Targets(RepositoryType repo, const Json::Value &json, Root &root);
301  Targets() = default;
302 
303  std::vector<Uptane::Target> targets;
304  bool operator==(const Targets &rhs) const {
305  return version_ == rhs.version() && expiry_ == rhs.expiry() && targets == rhs.targets;
306  }
307  const std::string &correlation_id() const { return correlation_id_; }
308 
309  private:
310  void init(const Json::Value &json);
311 
312  std::string correlation_id_; // custom non-tuf
313 };
314 
315 class TimestampMeta : public BaseMeta {
316  public:
317  explicit TimestampMeta(const Json::Value &json);
318  TimestampMeta(RepositoryType repo, const Json::Value &json, Root &root);
319  TimestampMeta() = default;
320  std::vector<Hash> snapshot_hashes() const { return snapshot_hashes_; };
321  int64_t snapshot_size() const { return snapshot_size_; };
322  int snapshot_version() const { return snapshot_version_; };
323 
324  private:
325  void init(const Json::Value &json);
326 
327  std::vector<Hash> snapshot_hashes_;
328  int64_t snapshot_size_{0};
329  int snapshot_version_{-1};
330 };
331 
332 class Snapshot : public BaseMeta {
333  public:
334  explicit Snapshot(const Json::Value &json);
335  Snapshot(RepositoryType repo, const Json::Value &json, Root &root);
336  Snapshot() = default;
337  std::vector<Hash> targets_hashes() const { return targets_hashes_; };
338  int64_t targets_size() const { return targets_size_; };
339  int targets_version() const { return targets_version_; };
340  bool operator==(const Snapshot &rhs) const {
341  return version_ == rhs.version() && expiry_ == rhs.expiry() && targets_size_ == rhs.targets_size_ &&
342  targets_version_ == rhs.targets_version_ && targets_hashes_ == rhs.targets_hashes_;
343  }
344 
345  private:
346  void init(const Json::Value &json);
347  int64_t targets_size_{0};
348  int targets_version_{-1};
349  std::vector<Hash> targets_hashes_;
350 };
351 
352 struct MetaPack {
353  Root director_root;
354  Targets director_targets;
355  Root image_root;
356  Targets image_targets;
357  TimestampMeta image_timestamp;
358  Snapshot image_snapshot;
359  bool isConsistent() const;
360 };
361 
362 struct RawMetaPack {
363  std::string director_root;
364  std::string director_targets;
365  std::string image_root;
366  std::string image_targets;
367  std::string image_timestamp;
368  std::string image_snapshot;
369 };
370 
371 int extractVersionUntrusted(const std::string &meta); // returns negative number if parsing fails
372 
373 } // namespace Uptane
374 
375 namespace std {
376 template <>
377 struct hash<Uptane::HardwareIdentifier> {
378  size_t operator()(const Uptane::HardwareIdentifier &hwid) const { return std::hash<std::string>()(hwid.hwid_); }
379 };
380 
381 template <>
382 struct hash<Uptane::EcuSerial> {
383  size_t operator()(const Uptane::EcuSerial &ecu_serial) const {
384  return std::hash<std::string>()(ecu_serial.ecu_serial_);
385  }
386 };
387 } // namespace std
388 
389 #endif // AKTUALIZR_UPTANE_TUF_H_
Metadata version numbers.
Definition: tuf.h:58
TUF Roles.
Definition: tuf.h:26
RepositoryType
This must match the repo_type table in sqlstorage.
Definition: tuf.h:19
Base data types that are used in The Update Framework (TUF), part of UPTANE.