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