3 #include "crypto/crypto.h"
4 #include "logging/logging.h"
6 #include "campaign/campaign.h"
7 #include "director_repo.h"
8 #include "image_repo.h"
12 std::string correlation_id)
13 : repo_type_(repo_type), path_(std::move(path)), correlation_id_(std::move(correlation_id)) {
14 expiration_time_ = getExpirationTime(expires);
15 if (boost::filesystem::exists(path_)) {
16 if (boost::filesystem::directory_iterator(path_) != boost::filesystem::directory_iterator()) {
21 if (repo_type_ == Uptane::RepositoryType::Director()) {
22 repo_dir_ = path_ / DirectorRepo::dir;
23 }
else if (repo_type_ == Uptane::RepositoryType::Image()) {
24 repo_dir_ = path_ / ImageRepo::dir;
28 void Repo::addDelegationToSnapshot(Json::Value *snapshot,
const Uptane::Role &role) {
29 boost::filesystem::path repo_dir = repo_dir_;
30 if (role.IsDelegation()) {
31 repo_dir = repo_dir /
"delegations";
33 std::string role_file_name = role.ToString() +
".json";
35 Json::Value role_json = Utils::parseJSONFile(repo_dir / role_file_name)[
"signed"];
36 std::string signed_role = Utils::readFile(repo_dir / role_file_name);
38 (*snapshot)[
"meta"][role_file_name][
"version"] = role_json[
"version"].asUInt();
40 if (role_json[
"delegations"].isObject()) {
41 auto delegations_list = role_json[
"delegations"][
"roles"];
43 for (
auto it = delegations_list.begin(); it != delegations_list.end(); it++) {
44 addDelegationToSnapshot(snapshot,
Uptane::Role((*it)[
"name"].asString(),
true));
49 void Repo::updateRepo() {
50 const Json::Value old_snapshot = Utils::parseJSONFile(repo_dir_ /
"snapshot.json")[
"signed"];
52 snapshot[
"_type"] =
"Snapshot";
53 snapshot[
"expires"] = old_snapshot[
"expires"];
54 snapshot[
"version"] = (old_snapshot[
"version"].asUInt()) + 1;
56 const Json::Value root = Utils::parseJSONFile(repo_dir_ /
"root.json")[
"signed"];
57 snapshot[
"meta"][
"root.json"][
"version"] = root[
"version"].asUInt();
59 addDelegationToSnapshot(&snapshot, Uptane::Role::Targets());
61 const std::string signed_snapshot = Utils::jsonToCanonicalStr(signTuf(Uptane::Role::Snapshot(), snapshot));
62 Utils::writeFile(repo_dir_ /
"snapshot.json", signed_snapshot);
64 Json::Value timestamp = Utils::parseJSONFile(repo_dir_ /
"timestamp.json")[
"signed"];
65 timestamp[
"version"] = (timestamp[
"version"].asUInt()) + 1;
66 timestamp[
"meta"][
"snapshot.json"][
"hashes"][
"sha256"] =
67 boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha256digest(signed_snapshot)));
68 timestamp[
"meta"][
"snapshot.json"][
"hashes"][
"sha512"] =
69 boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha512digest(signed_snapshot)));
70 timestamp[
"meta"][
"snapshot.json"][
"length"] = static_cast<Json::UInt>(signed_snapshot.length());
71 timestamp[
"meta"][
"snapshot.json"][
"version"] = snapshot[
"version"].asUInt();
72 Utils::writeFile(repo_dir_ /
"timestamp.json",
73 Utils::jsonToCanonicalStr(signTuf(Uptane::Role::Timestamp(), timestamp)));
76 Json::Value Repo::signTuf(
const Uptane::Role &role,
const Json::Value &json) {
77 auto key = keys_[role];
79 Utils::toBase64(Crypto::Sign(key.public_key.Type(),
nullptr, key.private_key, Utils::jsonToCanonicalStr(json)));
80 Json::Value signature;
81 switch (key.public_key.Type()) {
82 case KeyType::kRSA2048:
83 case KeyType::kRSA3072:
84 case KeyType::kRSA4096:
85 signature[
"method"] =
"rsassa-pss";
87 case KeyType::kED25519:
88 signature[
"method"] =
"ed25519";
91 throw std::runtime_error(
"Unknown key type");
93 signature[
"sig"] = b64sig;
95 Json::Value signed_data;
96 signature[
"keyid"] = key.public_key.KeyId();
98 signed_data[
"signed"] = json;
99 signed_data[
"signatures"].append(signature);
103 std::string Repo::getExpirationTime(
const std::string &expires) {
104 if (expires.size() != 0) {
106 std::regex time_pattern(
"\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z");
107 if (!std::regex_match(expires, time_pattern)) {
108 throw std::runtime_error(
"Expiration date has wrong format\n date should be in ISO 8601 UTC format");
113 struct tm time_struct {};
115 gmtime_r(&raw_time, &time_struct);
116 time_struct.tm_year += 3;
118 strftime(formatted, 22,
"%Y-%m-%dT%H:%M:%SZ", &time_struct);
122 void Repo::generateKeyPair(KeyType key_type,
const Uptane::Role &key_name) {
123 boost::filesystem::path keys_dir = path_ / (
"keys/" + repo_type_.toString() +
"/" + key_name.ToString());
124 boost::filesystem::create_directories(keys_dir);
126 std::string public_key_string, private_key;
127 if (!Crypto::generateKeyPair(key_type, &public_key_string, &private_key)) {
128 throw std::runtime_error(
"Key generation failure");
130 PublicKey public_key(public_key_string, key_type);
132 std::stringstream key_str;
135 Utils::writeFile(keys_dir /
"private.key", private_key);
136 Utils::writeFile(keys_dir /
"public.key", public_key_string);
137 Utils::writeFile(keys_dir /
"key_type", key_str.str());
139 keys_[key_name] =
KeyPair(public_key, private_key);
142 void Repo::generateRepoKeys(KeyType key_type) {
143 generateKeyPair(key_type, Uptane::Role::Root());
144 generateKeyPair(key_type, Uptane::Role::Snapshot());
145 generateKeyPair(key_type, Uptane::Role::Targets());
146 generateKeyPair(key_type, Uptane::Role::Timestamp());
149 void Repo::generateRepo(KeyType key_type) {
150 generateRepoKeys(key_type);
152 boost::filesystem::create_directories(repo_dir_);
154 root[
"_type"] =
"Root";
155 root[
"expires"] = expiration_time_;
157 for (
auto const &keypair : keys_) {
158 root[
"keys"][keypair.second.public_key.KeyId()] = keypair.second.public_key.ToUptane();
162 role[
"threshold"] = 1;
164 role[
"keyids"].append(keys_[Uptane::Role::Root()].public_key.KeyId());
165 root[
"roles"][
"root"] = role;
167 role[
"keyids"].clear();
168 role[
"keyids"].append(keys_[Uptane::Role::Snapshot()].public_key.KeyId());
169 root[
"roles"][
"snapshot"] = role;
171 role[
"keyids"].clear();
172 role[
"keyids"].append(keys_[Uptane::Role::Targets()].public_key.KeyId());
173 root[
"roles"][
"targets"] = role;
175 role[
"keyids"].clear();
176 role[
"keyids"].append(keys_[Uptane::Role::Timestamp()].public_key.KeyId());
177 root[
"roles"][
"timestamp"] = role;
179 const std::string signed_root = Utils::jsonToCanonicalStr(signTuf(Uptane::Role::Root(), root));
180 Utils::writeFile(repo_dir_ /
"root.json", signed_root);
181 Utils::writeFile(repo_dir_ /
"1.root.json", signed_root);
184 targets[
"_type"] =
"Targets";
185 targets[
"expires"] = expiration_time_;
186 targets[
"version"] = 1;
187 targets[
"targets"] = Json::objectValue;
188 if (repo_type_ == Uptane::RepositoryType::Director() && correlation_id_ !=
"") {
189 targets[
"custom"][
"correlationId"] = correlation_id_;
191 const std::string signed_targets = Utils::jsonToCanonicalStr(signTuf(Uptane::Role::Targets(), targets));
192 Utils::writeFile(repo_dir_ /
"targets.json", signed_targets);
194 Json::Value snapshot;
195 snapshot[
"_type"] =
"Snapshot";
196 snapshot[
"expires"] = expiration_time_;
197 snapshot[
"version"] = 1;
198 snapshot[
"meta"][
"root.json"][
"hashes"][
"sha256"] =
199 boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha256digest(signed_root)));
200 snapshot[
"meta"][
"root.json"][
"hashes"][
"sha512"] =
201 boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha512digest(signed_root)));
202 snapshot[
"meta"][
"root.json"][
"length"] = static_cast<Json::UInt>(signed_root.length());
203 snapshot[
"meta"][
"root.json"][
"version"] = 1;
204 snapshot[
"meta"][
"targets.json"][
"version"] = 1;
205 std::string signed_snapshot = Utils::jsonToCanonicalStr(signTuf(Uptane::Role::Snapshot(), snapshot));
206 Utils::writeFile(repo_dir_ /
"snapshot.json", signed_snapshot);
208 Json::Value timestamp;
209 timestamp[
"_type"] =
"Timestamp";
210 timestamp[
"expires"] = expiration_time_;
211 timestamp[
"version"] = 1;
212 timestamp[
"meta"][
"snapshot.json"][
"hashes"][
"sha256"] =
213 boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha256digest(signed_snapshot)));
214 timestamp[
"meta"][
"snapshot.json"][
"hashes"][
"sha512"] =
215 boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha512digest(signed_snapshot)));
216 timestamp[
"meta"][
"snapshot.json"][
"length"] = static_cast<Json::UInt>(signed_snapshot.length());
217 timestamp[
"meta"][
"snapshot.json"][
"version"] = 1;
218 Utils::writeFile(repo_dir_ /
"timestamp.json",
219 Utils::jsonToCanonicalStr(signTuf(Uptane::Role::Timestamp(), timestamp)));
220 if (repo_type_ == Uptane::RepositoryType::Director()) {
221 Utils::writeFile(path_ / DirectorRepo::dir /
"manifest", std::string());
225 void Repo::generateCampaigns()
const {
226 std::vector<campaign::Campaign> campaigns;
228 auto &c = campaigns[0];
230 c.name =
"campaign1";
231 c.id =
"c2eb7e8d-8aa0-429d-883f-5ed8fdb2a493";
234 c.description =
"this is my message to show on the device";
235 c.estInstallationDuration = 10;
236 c.estPreparationDuration = 20;
239 campaign::Campaign::JsonFromCampaigns(campaigns, json);
241 Utils::writeFile(path_ /
"campaigns.json", Utils::jsonToCanonicalStr(json));
244 Json::Value Repo::getTarget(
const std::string &target_name) {
245 const Json::Value image_targets = Utils::parseJSONFile(repo_dir_ /
"targets.json")[
"signed"];
246 if (image_targets[
"targets"].isMember(target_name)) {
247 return image_targets[
"targets"][target_name];
248 }
else if (repo_type_ == Uptane::RepositoryType::Image()) {
249 if (!boost::filesystem::is_directory(repo_dir_ /
"delegations")) {
252 for (
auto &p : boost::filesystem::directory_iterator(repo_dir_ /
"delegations")) {
253 if (Uptane::Role::IsReserved(p.path().stem().string())) {
256 auto targets = Utils::parseJSONFile(p)[
"signed"];
257 if (targets[
"targets"].isMember(target_name)) {
258 return targets[
"targets"][target_name];
265 void Repo::readKeys() {
266 auto keys_path = path_ /
"keys" / repo_type_.toString();
267 for (
auto &p : boost::filesystem::directory_iterator(keys_path)) {
268 std::string public_key_string = Utils::readFile(p /
"public.key");
269 std::istringstream key_type_str(Utils::readFile(p /
"key_type"));
271 key_type_str >> key_type;
272 std::string private_key_string(Utils::readFile(p /
"private.key"));
273 auto name = p.path().filename().string();
274 keys_[
Uptane::Role(name, !Uptane::Role::IsReserved(name))] =
280 boost::filesystem::path meta_path = repo_dir_;
282 if (repo_type_ == Uptane::RepositoryType::Director() &&
283 (role == Uptane::Role::Timestamp() || role == Uptane::Role::Snapshot())) {
284 throw std::runtime_error(
"The " + role.ToString() +
" in the Director repo is not currently supported.");
287 if (role == Uptane::Role::Root()) {
288 meta_path /=
"root.json";
289 }
else if (role == Uptane::Role::Timestamp()) {
290 meta_path /=
"timestamp.json";
291 }
else if (role == Uptane::Role::Snapshot()) {
292 meta_path /=
"snapshot.json";
293 }
else if (role == Uptane::Role::Targets()) {
294 meta_path /=
"targets.json";
296 throw std::runtime_error(
"Refreshing custom role " + role.ToString() +
" is not currently supported.");
301 Json::Value meta_raw = Utils::parseJSONFile(meta_path)[
"signed"];
302 const unsigned version = meta_raw[
"version"].asUInt() + 1;
304 auto current_expire_time =
TimeStamp(meta_raw[
"expires"].asString());
306 if (current_expire_time.IsExpiredAt(TimeStamp::Now())) {
307 time_t new_expiration_time;
308 std::time(&new_expiration_time);
309 new_expiration_time += 60 * 60;
310 struct tm new_expiration_time_str {};
311 gmtime_r(&new_expiration_time, &new_expiration_time_str);
313 meta_raw[
"expires"] =
TimeStamp(new_expiration_time_str).ToString();
315 meta_raw[
"version"] = version;
316 const std::string signed_meta = Utils::jsonToCanonicalStr(signTuf(role, meta_raw));
317 Utils::writeFile(meta_path, signed_meta);
320 if (role == Uptane::Role::Root()) {
321 std::stringstream root_name;
322 root_name << version <<
".root.json";
323 Utils::writeFile(repo_dir_ / root_name.str(), signed_meta);
329 Delegation::Delegation(
const boost::filesystem::path &repo_path, std::string delegation_name)
330 : name(std::move(delegation_name)) {
331 if (Uptane::Role::IsReserved(name)) {
332 throw std::runtime_error(
"Delegation name " + name +
" is reserved.");
334 boost::filesystem::path delegation_path(((repo_path / ImageRepo::dir /
"delegations") / name).
string() +
".json");
335 boost::filesystem::path targets_path(repo_path / ImageRepo::dir /
"targets.json");
336 if (!boost::filesystem::exists(delegation_path) || !boost::filesystem::exists(targets_path)) {
337 throw std::runtime_error(std::string(
"delegation ") + delegation_path.string() +
" does not exist");
340 pattern = findPatternInTree(repo_path, name, Utils::parseJSONFile(targets_path)[
"signed"]);
342 if (pattern.empty()) {
343 throw std::runtime_error(
"Could not find delegation role in the delegation tree");
347 std::string Delegation::findPatternInTree(
const boost::filesystem::path &repo_path,
const std::string &name,
348 const Json::Value &targets_json) {
349 Json::Value delegations = targets_json[
"delegations"];
350 for (
const auto &role : delegations[
"roles"]) {
351 auto role_name = role[
"name"].asString();
352 if (role_name == name) {
353 auto pattern = role[
"paths"][0].asString();
354 if (pattern.back() ==
'/') {
355 pattern.append(
"**");
359 auto pattern = findPatternInTree(
361 Utils::parseJSONFile((repo_path / ImageRepo::dir /
"delegations") / (role_name +
".json"))[
"signed"]);
362 if (!pattern.empty()) {