Aktualizr
C++ SOTA Client
root.cc
1 
2 #include "logging/logging.h"
3 #include "uptane/exceptions.h"
4 #include "uptane/tuf.h"
5 
6 using Uptane::Root;
7 
8 Root::Root(RepositoryType repo, const Json::Value &json, Root &root) : Root(repo, json) {
9  root.UnpackSignedObject(repo, json);
10  this->UnpackSignedObject(repo, json);
11 }
12 
13 Root::Root(RepositoryType repo, const Json::Value &json) : policy_(Policy::kCheck) {
14  if (!json.isObject() || !json.isMember("signed")) {
15  throw InvalidMetadata("", "", "invalid metadata json");
16  }
17 
18  version_ = json["signed"]["version"].asInt();
19 
20  expiry_ = Uptane::TimeStamp(json["signed"]["expires"].asString());
21  original_object_ = json;
22 
23  if (!json.isObject() || !json["signed"].isMember("keys") || !json["signed"].isMember("roles")) {
24  throw InvalidMetadata(RepoString(repo), "root", "missing keys/roles field");
25  }
26 
27  Json::Value keys = json["signed"]["keys"];
28  for (Json::ValueIterator it = keys.begin(); it != keys.end(); ++it) {
29  std::string key_type = boost::algorithm::to_lower_copy((*it)["keytype"].asString());
30  if (key_type != "rsa" && key_type != "ed25519") {
31  throw SecurityException(RepoString(repo), "Unsupported key type: " + (*it)["keytype"].asString());
32  }
33  KeyId keyid = it.key().asString();
34  PublicKey key(*it);
35  keys_[keyid] = key;
36  }
37 
38  Json::Value roles = json["signed"]["roles"];
39  for (Json::ValueIterator it = roles.begin(); it != roles.end(); it++) {
40  std::string role_name = it.key().asString();
41  Role role = Role(role_name);
42  if (role == Role::InvalidRole()) {
43  LOG_WARNING << "Invalid role in root.json";
44  LOG_TRACE << "Role name:" << role_name;
45  LOG_TRACE << "root.json is:" << json;
46  continue;
47  }
48  // Threshold
49  int64_t requiredThreshold = (*it)["threshold"].asInt64();
50  if (requiredThreshold < kMinSignatures) {
51  // static_cast<int64_t> is to stop << taking a reference to kMinSignatures
52  // http://www.stroustrup.com/bs_faq2.html#in-class
53  // this occurs in Boost 1.62 (and possibly other versions)
54  LOG_DEBUG << "Failing with threshold for role " << role << " too small: " << requiredThreshold << " < "
55  << static_cast<int64_t>(kMinSignatures);
56  throw IllegalThreshold(RepoString(repo), "The role " + role_name + " had an illegal signature threshold.");
57  }
58  if (kMaxSignatures < requiredThreshold) {
59  // static_cast<int> is to stop << taking a reference to kMaxSignatures
60  // http://www.stroustrup.com/bs_faq2.html#in-class
61  // this occurs in Boost 1.62 (and possibly other versions)
62  LOG_DEBUG << "Failing with threshold for role " << role << " too large: " << static_cast<int>(kMaxSignatures)
63  << " < " << requiredThreshold;
64  throw IllegalThreshold(RepoString(repo), "root.json contains a role that requires too many signatures");
65  }
66  thresholds_for_role_[role] = requiredThreshold;
67 
68  // KeyIds
69  Json::Value keyids = (*it)["keyids"];
70  for (Json::ValueIterator itk = keyids.begin(); itk != keyids.end(); ++itk) {
71  keys_for_role_.insert(std::make_pair(role, (*itk).asString()));
72  }
73  }
74 }
75 
76 void Uptane::Root::UnpackSignedObject(RepositoryType repo, const Json::Value &signed_object) {
77  std::string repository = RepoString(repo);
78 
79  Uptane::Role role(signed_object["signed"]["_type"].asString());
80  if (policy_ == Policy::kAcceptAll) {
81  return;
82  }
83  if (policy_ == Policy::kRejectAll) {
84  throw SecurityException(repository, "Root policy is Policy::kRejectAll");
85  }
86  assert(policy_ == Policy::kCheck);
87 
88  std::string canonical = Json::FastWriter().write(signed_object["signed"]);
89  Json::Value signatures = signed_object["signatures"];
90  int valid_signatures = 0;
91 
92  std::set<std::string> used_keyids;
93  for (Json::ValueIterator sig = signatures.begin(); sig != signatures.end(); ++sig) {
94  std::string keyid = (*sig)["keyid"].asString();
95  if (used_keyids.count(keyid) != 0) {
96  throw NonUniqueSignatures(repository, role.ToString());
97  }
98  used_keyids.insert(keyid);
99 
100  std::string method((*sig)["method"].asString());
101  std::transform(method.begin(), method.end(), method.begin(), ::tolower);
102 
103  if (method != "rsassa-pss" && method != "rsassa-pss-sha256" && method != "ed25519") {
104  throw SecurityException(repository, std::string("Unsupported sign method: ") + (*sig)["method"].asString());
105  }
106 
107  if (keys_.count(keyid) == 0u) {
108  LOG_DEBUG << "Signed by unknown KeyId: " << keyid << ". Skipping.";
109  continue;
110  }
111 
112  if (keys_for_role_.count(std::make_pair(role, keyid)) == 0u) {
113  LOG_WARNING << "KeyId " << keyid << " is not valid to sign for this role (" << role.ToString() << ").";
114  continue;
115  }
116  std::string signature = (*sig)["sig"].asString();
117  if (keys_[keyid].VerifySignature(signature, canonical)) {
118  valid_signatures++;
119  } else {
120  LOG_WARNING << "Signature was present but invalid: " << signature << " with KeyId: " << keyid;
121  }
122  }
123  int64_t threshold = thresholds_for_role_[role];
124  if (threshold < kMinSignatures || kMaxSignatures < threshold) {
125  throw IllegalThreshold(repository, "Invalid signature threshold");
126  }
127  // One signature and it is bad: throw bad key ID.
128  // Multiple signatures but not enough good ones to pass threshold: throw unmet threshold.
129  if (signatures.size() == 1 && valid_signatures == 0) {
130  throw BadKeyId(repository);
131  }
132  if (valid_signatures < threshold) {
133  throw UnmetThreshold(repository, role.ToString());
134  }
135 
136  Uptane::Role actual_role(Uptane::Role(signed_object["signed"]["_type"].asString()));
137  if (role != actual_role) {
138  LOG_ERROR << "Object was signed for a different role";
139  LOG_TRACE << " role:" << role;
140  LOG_TRACE << " actual_role:" << actual_role;
141  throw SecurityException(repository, "Object was signed for a different role");
142  }
143 }
Root(Policy policy=Policy::kRejectAll)
An empty Root, that either accepts or rejects everything.
Definition: tuf.h:277
TUF Roles.
Definition: tuf.h:25
RepositoryType
This must match the repo_type table in sqlstorage.
Definition: tuf.h:18
Only check for updates.
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