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