Aktualizr
C++ SOTA Client
tuf.cc
1 #include "uptane/tuf.h"
2 
3 #include <ctime>
4 #include <ostream>
5 #include <sstream>
6 
7 #include <boost/algorithm/hex.hpp>
8 #include <boost/algorithm/string/case_conv.hpp>
9 #include <utility>
10 
11 #include "crypto/crypto.h"
12 #include "logging/logging.h"
13 #include "utilities/exceptions.h"
14 
15 using Uptane::Hash;
16 using Uptane::Root;
17 using Uptane::Target;
18 using Uptane::TimeStamp;
19 using Uptane::Version;
20 using Uptane::MetaPack;
21 
22 std::ostream &Uptane::operator<<(std::ostream &os, const Version &v) {
23  if (v.version_ == Version::ANY_VERSION) {
24  os << "vANY";
25  } else {
26  os << "v" << v.version_;
27  }
28  return os;
29 }
30 
31 TimeStamp TimeStamp::Now() {
32  time_t raw_time;
33  struct tm time_struct {};
34  time(&raw_time);
35  gmtime_r(&raw_time, &time_struct);
36  char formatted[22];
37  strftime(formatted, 22, "%Y-%m-%dT%H:%M:%SZ", &time_struct);
38  return TimeStamp(formatted);
39 }
40 
41 TimeStamp::TimeStamp(std::string rfc3339) {
42  if (rfc3339.length() != 20 || rfc3339[19] != 'Z') {
43  throw Uptane::InvalidMetadata("", "", "Invalid timestamp");
44  }
45  time_ = rfc3339;
46 }
47 
48 bool TimeStamp::IsValid() const { return time_.length() != 0; }
49 
50 bool TimeStamp::IsExpiredAt(const TimeStamp &now) const {
51  if (!IsValid()) {
52  return true;
53  }
54  if (!now.IsValid()) {
55  return true;
56  }
57  return *this < now;
58 }
59 
60 bool TimeStamp::operator<(const TimeStamp &other) const { return IsValid() && other.IsValid() && time_ < other.time_; }
61 
62 bool TimeStamp::operator>(const TimeStamp &other) const { return (other < *this); }
63 
64 std::ostream &Uptane::operator<<(std::ostream &os, const TimeStamp &t) {
65  os << t.time_;
66  return os;
67 }
68 
69 std::ostream &Uptane::operator<<(std::ostream &os, const HardwareIdentifier &hwid) {
70  os << hwid.hwid_;
71  return os;
72 }
73 
74 std::ostream &Uptane::operator<<(std::ostream &os, const EcuSerial &ecu_serial) {
75  os << ecu_serial.ecu_serial_;
76  return os;
77 }
78 
79 Hash::Hash(const std::string &type, const std::string &hash) : hash_(boost::algorithm::to_upper_copy(hash)) {
80  if (type == "sha512") {
81  type_ = Hash::Type::kSha512;
82  } else if (type == "sha256") {
83  type_ = Hash::Type::kSha256;
84  } else {
85  type_ = Hash::Type::kUnknownAlgorithm;
86  }
87 }
88 
89 Hash::Hash(Type type, const std::string &hash) : type_(type), hash_(boost::algorithm::to_upper_copy(hash)) {}
90 
91 bool Hash::operator==(const Hash &other) const { return type_ == other.type_ && hash_ == other.hash_; }
92 
93 std::string Hash::TypeString() const {
94  switch (type_) {
95  case Type::kSha256:
96  return "sha256";
97  case Type::kSha512:
98  return "sha512";
99  default:
100  return "unknown";
101  }
102 }
103 
104 Hash::Type Hash::type() const { return type_; }
105 
106 std::ostream &Uptane::operator<<(std::ostream &os, const Hash &h) {
107  os << "Hash: " << h.hash_;
108  return os;
109 }
110 
111 Target::Target(std::string filename, const Json::Value &content) : filename_(std::move(filename)) {
112  if (content.isMember("custom")) {
113  Json::Value custom = content["custom"];
114 
115  Json::Value ecus = custom["ecuIdentifiers"];
116  for (Json::ValueIterator i = ecus.begin(); i != ecus.end(); ++i) {
117  ecus_.insert({EcuSerial(i.key().asString()), HardwareIdentifier((*i)["hardwareId"].asString())});
118  }
119 
120  if (custom.isMember("targetFormat")) {
121  type_ = custom["targetFormat"].asString();
122  }
123  }
124 
125  length_ = content["length"].asInt64();
126 
127  Json::Value hashes = content["hashes"];
128  for (Json::ValueIterator i = hashes.begin(); i != hashes.end(); ++i) {
129  Hash h(i.key().asString(), (*i).asString());
130  if (h.HaveAlgorithm()) {
131  hashes_.push_back(h);
132  }
133  }
134  // sort hashes so that higher priority hash algorithm goes first
135  std::sort(hashes_.begin(), hashes_.end(), [](const Hash &l, const Hash &r) { return l.type() < r.type(); });
136 }
137 
138 bool Target::MatchWith(const Hash &hash) const {
139  return (std::find(hashes_.begin(), hashes_.end(), hash) != hashes_.end());
140 }
141 
142 std::string Target::sha256Hash() const {
143  std::vector<Uptane::Hash>::const_iterator it;
144  for (it = hashes_.begin(); it != hashes_.end(); it++) {
145  if (it->type() == Hash::Type::kSha256) {
146  return boost::algorithm::to_lower_copy(it->HashString());
147  }
148  }
149  return std::string("");
150 }
151 
152 bool Target::IsOstree() const {
153  if (type_ == "OSTREE") {
154  // Modern servers explicitly specify the type of the target
155  return true;
156  } else if (type_.empty() && length() == 0) {
157  // Older servers don't specify the type of the target. Assume that it is
158  // an OSTree target if the length is zero.
159  return true;
160  } else {
161  // If type is explicitly not OSTREE or the length is non-zero, then this
162  // is a firmware blob.
163  return false;
164  }
165 }
166 
167 Json::Value Target::toDebugJson() const {
168  Json::Value res;
169  for (auto it = ecus_.begin(); it != ecus_.cend(); ++it) {
170  res["custom"]["ecuIdentifiers"][it->first.ToString()]["hardwareId"] = it->second.ToString();
171  }
172  res["custom"]["targetFormat"] = type_;
173 
174  for (auto it = hashes_.cbegin(); it != hashes_.cend(); ++it) {
175  res["hashes"][it->TypeString()] = it->HashString();
176  }
177  res["length"] = Json::Value(static_cast<Json::Value::Int64>(length_));
178  return res;
179 }
180 
181 std::ostream &Uptane::operator<<(std::ostream &os, const Target &t) {
182  os << "Target(" << t.filename_;
183  os << " ecu_identifiers: (";
184 
185  for (auto it = t.ecus_.begin(); it != t.ecus_.end(); ++it) {
186  os << it->first;
187  }
188  os << ")"
189  << " length:" << t.length();
190  os << " hashes: (";
191  for (auto it = t.hashes_.begin(); it != t.hashes_.end(); ++it) {
192  os << *it << ", ";
193  }
194  os << "))";
195 
196  return os;
197 }
198 
199 void Uptane::BaseMeta::init(const Json::Value &json) {
200  if (!json.isObject() || !json.isMember("signed")) {
201  LOG_ERROR << "BM FAILURE";
202  throw Uptane::InvalidMetadata("", "", "invalid metadata json");
203  }
204 
205  version_ = json["signed"]["version"].asInt();
206  expiry_ = Uptane::TimeStamp(json["signed"]["expires"].asString());
207  original_object_ = json;
208 }
209 Uptane::BaseMeta::BaseMeta(const Json::Value &json) { init(json); }
210 
211 Uptane::BaseMeta::BaseMeta(RepositoryType repo, const Json::Value &json, Root &root) {
212  if (!json.isObject() || !json.isMember("signed")) {
213  throw Uptane::InvalidMetadata("", "", "invalid metadata json");
214  }
215 
216  root.UnpackSignedObject(repo, json);
217 
218  init(json);
219 }
220 
221 void Uptane::Targets::init(const Json::Value &json) {
222  if (!json.isObject() || json["signed"]["_type"] != "Targets") {
223  throw Uptane::InvalidMetadata("", "targets", "invalid targets.json");
224  }
225 
226  Json::Value target_list = json["signed"]["targets"];
227  for (Json::ValueIterator t_it = target_list.begin(); t_it != target_list.end(); t_it++) {
228  Target t(t_it.key().asString(), *t_it);
229  targets.push_back(t);
230  }
231 }
232 
233 Uptane::Targets::Targets(const Json::Value &json) : BaseMeta(json) { init(json); }
234 
235 Uptane::Targets::Targets(RepositoryType repo, const Json::Value &json, Root &root) : BaseMeta(repo, json, root) {
236  init(json);
237 }
238 
239 void Uptane::TimestampMeta::init(const Json::Value &json) {
240  Json::Value hashes_list = json["signed"]["meta"]["snapshot.json"]["hashes"];
241  Json::Value meta_size = json["signed"]["meta"]["snapshot.json"]["length"];
242  Json::Value meta_version = json["signed"]["meta"]["snapshot.json"]["version"];
243  if (!json.isObject() || json["signed"]["_type"] != "Timestamp" || !hashes_list.isObject() ||
244  !meta_size.isIntegral() || !meta_version.isIntegral()) {
245  throw Uptane::InvalidMetadata("", "timestamp", "invalid timestamp.json");
246  }
247 
248  for (Json::ValueIterator it = hashes_list.begin(); it != hashes_list.end(); ++it) {
249  Hash h(it.key().asString(), (*it).asString());
250  snapshot_hashes_.push_back(h);
251  }
252  snapshot_size_ = meta_size.asInt();
253  snapshot_version_ = meta_version.asInt();
254 }
255 
256 Uptane::TimestampMeta::TimestampMeta(const Json::Value &json) : BaseMeta(json) { init(json); }
257 
258 Uptane::TimestampMeta::TimestampMeta(RepositoryType repo, const Json::Value &json, Root &root)
259  : BaseMeta(repo, json, root) {
260  init(json);
261 }
262 
263 void Uptane::Snapshot::init(const Json::Value &json) {
264  Json::Value hashes_list = json["signed"]["meta"]["targets.json"]["hashes"];
265  Json::Value meta_size = json["signed"]["meta"]["targets.json"]["length"];
266  Json::Value meta_version = json["signed"]["meta"]["targets.json"]["version"];
267 
268  if (!json.isObject() || json["signed"]["_type"] != "Snapshot" || !meta_version.isIntegral()) {
269  throw Uptane::InvalidMetadata("", "snapshot", "invalid snapshot.json");
270  }
271 
272  if (hashes_list.isObject()) {
273  for (Json::ValueIterator it = hashes_list.begin(); it != hashes_list.end(); ++it) {
274  Hash h(it.key().asString(), (*it).asString());
275  targets_hashes_.push_back(h);
276  }
277  }
278 
279  if (meta_size.isIntegral()) {
280  targets_size_ = meta_size.asInt();
281  } else {
282  targets_size_ = -1;
283  }
284  targets_version_ = meta_version.asInt();
285 }
286 
287 Uptane::Snapshot::Snapshot(const Json::Value &json) : BaseMeta(json) { init(json); }
288 
289 Uptane::Snapshot::Snapshot(RepositoryType repo, const Json::Value &json, Root &root) : BaseMeta(repo, json, root) {
290  init(json);
291 }
292 
293 bool MetaPack::isConsistent() const {
294  TimeStamp now(TimeStamp::Now());
295  try {
296  if (director_root.original() != Json::nullValue) {
297  Uptane::Root original_root(director_root);
298  Uptane::Root new_root(RepositoryType::Director, director_root.original(), new_root);
299  if (director_targets.original() != Json::nullValue) {
300  Uptane::Targets(RepositoryType::Director, director_targets.original(), original_root);
301  }
302  }
303  } catch (const std::logic_error &exc) {
304  LOG_WARNING << "Inconsistent metadata: " << exc.what();
305  return false;
306  }
307  return true;
308 }
309 
310 std::string Uptane::RepoString(RepositoryType repo) {
311  switch (repo) {
312  case RepositoryType::Director:
313  return "director";
314  case RepositoryType::Images:
315  return "images";
316  default:
317  return "";
318  }
319 }
320 
321 int Uptane::extractVersionUntrusted(const std::string &meta) {
322  auto version_json = Utils::parseJSON(meta)["signed"]["version"];
323  if (!version_json.isIntegral()) {
324  return -1;
325  } else {
326  return version_json.asInt();
327  }
328 }
Definition: common.h:20
Metadata version numbers.
Definition: tuf.h:57
TimeStamp()
An invalid TimeStamp.
Definition: tuf.h:76
STL namespace.
bool IsOstree() const
Is this an OSTree target? OSTree targets need special treatment because the hash doesn&#39;t represent th...
Definition: tuf.cc:152
RepositoryType
This must match the repo_type table in sqlstorage.
Definition: tuf.h:18
void UnpackSignedObject(RepositoryType repo, const Json::Value &signed_object)
Take a JSON blob that contains a signatures/signed component that is supposedly for a given role...
Definition: root.cc:76
The hash of a file or TUF metadata.
Definition: tuf.h:158