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 <map>
10 #include <ostream>
11 #include <set>
12 #include <unordered_map>
13 #include <vector>
14 
15 #include "crypto/crypto.h"
16 #include "libaktualizr/types.h"
17 #include "uptane/exceptions.h"
18 
19 namespace Uptane {
20 
22  private:
23  /** This must match the repo_type table in sqlstorage */
24  enum class Type { kUnknown = -1, kImage = 0, kDirector = 1 };
25 
26  public:
27  static const std::string IMAGE;
28  static const std::string DIRECTOR;
29 
30  RepositoryType() = default;
31  static constexpr int Director() { return static_cast<int>(Type::kDirector); }
32  static constexpr int Image() { return static_cast<int>(Type::kImage); }
33  RepositoryType(int type) { type_ = static_cast<RepositoryType::Type>(type); }
34  RepositoryType(const std::string &repo_type) {
35  if (repo_type == DIRECTOR) {
36  type_ = RepositoryType::Type::kDirector;
37  } else if (repo_type == IMAGE) {
38  type_ = RepositoryType::Type::kImage;
39  } else {
40  throw std::runtime_error(std::string("Incorrect repo type: ") + repo_type);
41  }
42  }
43  operator int() const { return static_cast<int>(type_); }
44  operator std::string() const { return toString(); }
45  Type type_;
46  std::string toString() const {
47  if (type_ == RepositoryType::Type::kDirector) {
48  return DIRECTOR;
49  } else if (type_ == RepositoryType::Type::kImage) {
50  return IMAGE;
51  } else {
52  return "";
53  }
54  }
55 };
56 
57 using KeyId = std::string;
58 /**
59  * TUF Roles
60  */
61 class Role {
62  public:
63  static const std::string ROOT;
64  static const std::string SNAPSHOT;
65  static const std::string TARGETS;
66  static const std::string TIMESTAMP;
67 
68  static Role Root() { return Role{RoleEnum::kRoot}; }
69  static Role Snapshot() { return Role{RoleEnum::kSnapshot}; }
70  static Role Targets() { return Role{RoleEnum::kTargets}; }
71  static Role Timestamp() { return Role{RoleEnum::kTimestamp}; }
72  static Role Delegation(const std::string &name) { return Role(name, true); }
73  static Role InvalidRole() { return Role{RoleEnum::kInvalidRole}; }
74  // Delegation is not included because this is only used for a metadata table
75  // that doesn't include delegations.
76  static std::vector<Role> Roles() { return {Root(), Snapshot(), Targets(), Timestamp()}; }
77  static bool IsReserved(const std::string &name) {
78  return (name == ROOT || name == TARGETS || name == SNAPSHOT || name == TIMESTAMP);
79  }
80 
81  explicit Role(const std::string &role_name, bool delegation = false);
82  std::string ToString() const;
83  int ToInt() const { return static_cast<int>(role_); }
84  bool IsDelegation() const { return role_ == RoleEnum::kDelegation; }
85  bool operator==(const Role &other) const { return name_ == other.name_; }
86  bool operator!=(const Role &other) const { return !(*this == other); }
87  bool operator<(const Role &other) const { return name_ < other.name_; }
88 
89  friend std::ostream &operator<<(std::ostream &os, const Role &role);
90 
91  private:
92  /** The four standard roles must match the meta_types table in sqlstorage.
93  * Delegations are special and handled differently. */
94  enum class RoleEnum { kRoot = 0, kSnapshot = 1, kTargets = 2, kTimestamp = 3, kDelegation = 4, kInvalidRole = -1 };
95 
96  explicit Role(RoleEnum role) : role_(role) {
97  if (role_ == RoleEnum::kRoot) {
98  name_ = ROOT;
99  } else if (role_ == RoleEnum::kSnapshot) {
100  name_ = SNAPSHOT;
101  } else if (role_ == RoleEnum::kTargets) {
102  name_ = TARGETS;
103  } else if (role_ == RoleEnum::kTimestamp) {
104  name_ = TIMESTAMP;
105  } else {
106  role_ = RoleEnum::kInvalidRole;
107  name_ = "invalidrole";
108  }
109  }
110 
111  RoleEnum role_;
112  std::string name_;
113 };
114 
115 std::ostream &operator<<(std::ostream &os, const Role &role);
116 
117 /**
118  * Metadata version numbers
119  */
120 class Version {
121  public:
122  Version() : version_(ANY_VERSION) {}
123  explicit Version(int v) : version_(v) {}
124  std::string RoleFileName(const Role &role) const;
125  int version() const { return version_; }
126  bool operator==(const Version &rhs) const { return version_ == rhs.version_; }
127  bool operator!=(const Version &rhs) const { return version_ != rhs.version_; }
128  bool operator<(const Version &rhs) const { return version_ < rhs.version_; }
129 
130  private:
131  static const int ANY_VERSION = -1;
132  int version_;
133  friend std::ostream &operator<<(std::ostream &os, const Version &v);
134 };
135 
136 std::ostream &operator<<(std::ostream &os, const Version &v);
137 
138 /* Metadata objects */
139 class MetaWithKeys;
140 class BaseMeta {
141  public:
142  BaseMeta() = default;
143  explicit BaseMeta(const Json::Value &json);
144  BaseMeta(RepositoryType repo, const Role &role, const Json::Value &json, const std::shared_ptr<MetaWithKeys> &signer);
145  int version() const { return version_; }
146  TimeStamp expiry() const { return expiry_; }
147  bool isExpired(const TimeStamp &now) const { return expiry_.IsExpiredAt(now); }
148  Json::Value original() const { return original_object_; }
149 
150  bool operator==(const BaseMeta &rhs) const { return version_ == rhs.version() && expiry_ == rhs.expiry(); }
151 
152  protected:
153  int version_ = {-1};
154  TimeStamp expiry_;
155  Json::Value original_object_;
156 
157  private:
158  void init(const Json::Value &json);
159 };
160 
161 class MetaWithKeys : public BaseMeta {
162  public:
163  enum class Policy { kRejectAll, kAcceptAll, kCheck };
164  /**
165  * An empty metadata object that could contain keys.
166  */
167  MetaWithKeys() { version_ = 0; }
168  /**
169  * A 'real' metadata object that can contain keys (Root or Targets with
170  * delegations) and that implements TUF signature validation.
171  * @param json - The contents of the 'signed' portion
172  */
173  MetaWithKeys(const Json::Value &json);
174  MetaWithKeys(RepositoryType repo, const Role &role, const Json::Value &json,
175  const std::shared_ptr<MetaWithKeys> &signer);
176 
177  virtual ~MetaWithKeys() = default;
178 
179  void ParseKeys(RepositoryType repo, const Json::Value &keys);
180  // role is the name of a role described in this object's metadata.
181  // meta_role is the name of this object's role.
182  void ParseRole(RepositoryType repo, const Json::ValueConstIterator &it, const Role &role,
183  const std::string &meta_role);
184 
185  /**
186  * Take a JSON blob that contains a signatures/signed component that is supposedly for a given role, and check that is
187  * suitably signed.
188  * If it is, it returns the contents of the 'signed' part.
189  *
190  * It performs the following checks:
191  * * "_type" matches the given role
192  * * "expires" is in the past (vs 'now')
193  * * The blob has valid signatures from enough keys to cross the threshold for this role
194  * @param repo - Repository type (only used to improve the error messages)
195  * @param role - The Uptane role of the signed metadata object
196  * @param signed_object
197  * @return
198  */
199  virtual void UnpackSignedObject(RepositoryType repo, const Role &role, const Json::Value &signed_object);
200 
201  bool operator==(const MetaWithKeys &rhs) const {
202  return version_ == rhs.version_ && expiry_ == rhs.expiry_ && keys_ == rhs.keys_ &&
203  keys_for_role_ == rhs.keys_for_role_ && thresholds_for_role_ == rhs.thresholds_for_role_;
204  }
205 
206  protected:
207  static const int64_t kMinSignatures = 1;
208  static const int64_t kMaxSignatures = 1000;
209 
210  std::map<KeyId, PublicKey> keys_;
211  std::set<std::pair<Role, KeyId>> keys_for_role_;
212  std::map<Role, int64_t> thresholds_for_role_;
213 };
214 
215 // Implemented in uptane/root.cc
216 class Root : public MetaWithKeys {
217  public:
218  /**
219  * An empty Root, that either accepts or rejects everything
220  */
221  explicit Root(Policy policy = Policy::kRejectAll) : policy_(policy) { version_ = 0; }
222  /**
223  * A 'real' Root that implements TUF signature validation
224  * @param repo - Repository type (only used to improve the error messages)
225  * @param json - The contents of the 'signed' portion
226  */
227  Root(RepositoryType repo, const Json::Value &json);
228  Root(RepositoryType repo, const Json::Value &json, Root &root);
229 
230  ~Root() override = default;
231 
232  /**
233  * Take a JSON blob that contains a signatures/signed component that is supposedly for a given role, and check that is
234  * suitably signed.
235  * If it is, it returns the contents of the 'signed' part.
236  *
237  * It performs the following checks:
238  * * "_type" matches the given role
239  * * "expires" is in the past (vs 'now')
240  * * The blob has valid signatures from enough keys to cross the threshold for this role
241  * @param repo - Repository type (only used to improve the error messages)
242  * @param role - The Uptane role of the signed metadata object
243  * @param signed_object
244  * @return
245  */
246  void UnpackSignedObject(RepositoryType repo, const Role &role, const Json::Value &signed_object) override;
247 
248  bool operator==(const Root &rhs) const {
249  return version_ == rhs.version_ && expiry_ == rhs.expiry_ && keys_ == rhs.keys_ &&
250  keys_for_role_ == rhs.keys_for_role_ && thresholds_for_role_ == rhs.thresholds_for_role_ &&
251  policy_ == rhs.policy_;
252  }
253 
254  private:
255  Policy policy_;
256 };
257 
258 static bool MatchTargetVector(const std::vector<Uptane::Target> &v1, const std::vector<Uptane::Target> &v2) {
259  if (v1.size() != v2.size()) {
260  return false;
261  }
262  for (size_t i = 0; i < v1.size(); ++i) {
263  if (!v1[i].MatchTarget(v2[i])) {
264  return false;
265  }
266  }
267  return true;
268 }
269 
270 // Also used for delegated targets.
271 class Targets : public MetaWithKeys {
272  public:
273  explicit Targets(const Json::Value &json);
274  Targets(RepositoryType repo, const Role &role, const Json::Value &json, const std::shared_ptr<MetaWithKeys> &signer);
275  Targets() = default;
276  ~Targets() override = default;
277 
278  bool operator==(const Targets &rhs) const {
279  return version_ == rhs.version() && expiry_ == rhs.expiry() && MatchTargetVector(targets, rhs.targets);
280  }
281 
282  const std::string &correlation_id() const { return correlation_id_; }
283 
284  void clear() {
285  targets.clear();
286  delegated_role_names_.clear();
287  paths_for_role_.clear();
288  terminating_role_.clear();
289  }
290 
291  std::vector<Uptane::Target> getTargets(const Uptane::EcuSerial &ecu_id,
292  const Uptane::HardwareIdentifier &hw_id) const {
293  std::vector<Uptane::Target> result;
294  for (auto it = targets.begin(); it != targets.end(); ++it) {
295  auto found_loc = std::find_if(it->ecus().begin(), it->ecus().end(),
296  [ecu_id, hw_id](const std::pair<EcuSerial, HardwareIdentifier> &val) {
297  return ((ecu_id == val.first) && (hw_id == val.second));
298  });
299 
300  if (found_loc != it->ecus().end()) {
301  result.push_back(*it);
302  }
303  }
304  return result;
305  }
306 
307  std::vector<Uptane::Target> targets;
308  std::vector<std::string> delegated_role_names_;
309  std::map<Role, std::vector<std::string>> paths_for_role_;
310  std::map<Role, bool> terminating_role_;
311 
312  private:
313  void init(const Json::Value &json);
314 
315  std::string name_;
316  std::string correlation_id_; // custom non-tuf
317 };
318 
319 class TimestampMeta : public BaseMeta {
320  public:
321  explicit TimestampMeta(const Json::Value &json);
322  TimestampMeta(RepositoryType repo, const Json::Value &json, const std::shared_ptr<MetaWithKeys> &signer);
323  TimestampMeta() = default;
324  std::vector<Hash> snapshot_hashes() const { return snapshot_hashes_; };
325  int64_t snapshot_size() const { return snapshot_size_; };
326  int snapshot_version() const { return snapshot_version_; };
327 
328  private:
329  void init(const Json::Value &json);
330 
331  std::vector<Hash> snapshot_hashes_;
332  int64_t snapshot_size_{0};
333  int snapshot_version_{-1};
334 };
335 
336 class Snapshot : public BaseMeta {
337  public:
338  explicit Snapshot(const Json::Value &json);
339  Snapshot(RepositoryType repo, const Json::Value &json, const std::shared_ptr<MetaWithKeys> &signer);
340  Snapshot() = default;
341  std::vector<Hash> role_hashes(const Uptane::Role &role) const;
342  int64_t role_size(const Uptane::Role &role) const;
343  int role_version(const Uptane::Role &role) const;
344  bool operator==(const Snapshot &rhs) const {
345  return version_ == rhs.version() && expiry_ == rhs.expiry() && role_size_ == rhs.role_size_ &&
346  role_version_ == rhs.role_version_ && role_hashes_ == rhs.role_hashes_;
347  }
348 
349  private:
350  void init(const Json::Value &json);
351  std::map<Uptane::Role, int64_t> role_size_;
352  std::map<Uptane::Role, int> role_version_;
353  std::map<Uptane::Role, std::vector<Hash>> role_hashes_;
354 };
355 
356 struct MetaPairHash {
357  std::size_t operator()(const std::pair<RepositoryType, Role> &pair) const {
358  return std::hash<std::string>()(pair.first.toString()) ^ std::hash<std::string>()(pair.second.ToString());
359  }
360 };
361 
362 std::string getMetaFromBundle(const MetaBundle &bundle, RepositoryType repo, const Role &role);
363 
364 int extractVersionUntrusted(const std::string &meta); // returns negative number if parsing fails
365 
366 } // namespace Uptane
367 
368 namespace std {
369 template <>
370 struct hash<Uptane::HardwareIdentifier> {
371  size_t operator()(const Uptane::HardwareIdentifier &hwid) const { return std::hash<std::string>()(hwid.hwid_); }
372 };
373 
374 template <>
375 struct hash<Uptane::EcuSerial> {
376  size_t operator()(const Uptane::EcuSerial &ecu_serial) const {
377  return std::hash<std::string>()(ecu_serial.ecu_serial_);
378  }
379 };
380 } // namespace std
381 
382 #endif // AKTUALIZR_UPTANE_TUF_H_
types.h
Uptane::Version
Metadata version numbers.
Definition: tuf.h:120
Uptane::MetaPairHash
Definition: tuf.h:356
Uptane::Root::Root
Root(Policy policy=Policy::kRejectAll)
An empty Root, that either accepts or rejects everything.
Definition: tuf.h:221
Uptane::HardwareIdentifier
Definition: types.h:315
Uptane::Snapshot
Definition: tuf.h:336
Uptane::MetaWithKeys::MetaWithKeys
MetaWithKeys()
An empty metadata object that could contain keys.
Definition: tuf.h:167
TimeStamp
Definition: types.h:188
Uptane::RepositoryType
Definition: tuf.h:21
Uptane::BaseMeta
Definition: tuf.h:140
Uptane::EcuSerial
Definition: types.h:346
Uptane::Targets
Definition: tuf.h:271
Uptane::MetaWithKeys
Definition: tuf.h:161
result
Results of libaktualizr API calls.
Definition: results.h:12
Uptane::Role
TUF Roles.
Definition: tuf.h:61
Uptane::Root
Definition: tuf.h:216
Uptane
Base data types that are used in The Update Framework (TUF), part of Uptane.
Definition: packagemanagerinterface.h:18
Uptane::TimestampMeta
Definition: tuf.h:319
Uptane::Root::UnpackSignedObject
void UnpackSignedObject(RepositoryType repo, const Role &role, const Json::Value &signed_object) override
Take a JSON blob that contains a signatures/signed component that is supposedly for a given role,...
Definition: root.cc:29
Delegation
Definition: repo.h:19
Uptane::MetaWithKeys::UnpackSignedObject
virtual void UnpackSignedObject(RepositoryType repo, const Role &role, const Json::Value &signed_object)
Take a JSON blob that contains a signatures/signed component that is supposedly for a given role,...
Definition: metawithkeys.cc:58