7 #include "crypto/crypto.h"
8 #include "director_repo.h"
9 #include "image_repo.h"
10 #include "libaktualizr/campaign.h"
11 #include "logging/logging.h"
14 std::string correlation_id)
15 : repo_type_(repo_type), path_(std::move(path)), correlation_id_(std::move(correlation_id)) {
16 expiration_time_ = getExpirationTime(expires);
17 if (boost::filesystem::exists(path_)) {
18 if (boost::filesystem::directory_iterator(path_) != boost::filesystem::directory_iterator()) {
23 if (repo_type_ == Uptane::RepositoryType::Director()) {
24 repo_dir_ = path_ / DirectorRepo::dir;
25 }
else if (repo_type_ == Uptane::RepositoryType::Image()) {
26 repo_dir_ = path_ / ImageRepo::dir;
30 void Repo::addDelegationToSnapshot(Json::Value *snapshot,
const Uptane::Role &role) {
31 boost::filesystem::path repo_dir = repo_dir_;
32 if (role.IsDelegation()) {
33 repo_dir = repo_dir /
"delegations";
35 std::string role_file_name = role.ToString() +
".json";
37 Json::Value role_json = Utils::parseJSONFile(repo_dir / role_file_name)[
"signed"];
38 std::string signed_role = Utils::readFile(repo_dir / role_file_name);
40 (*snapshot)[
"meta"][role_file_name][
"version"] = role_json[
"version"].asUInt();
42 if (role_json[
"delegations"].isObject()) {
43 auto delegations_list = role_json[
"delegations"][
"roles"];
45 for (
auto it = delegations_list.begin(); it != delegations_list.end(); it++) {
46 addDelegationToSnapshot(snapshot,
Uptane::Role((*it)[
"name"].asString(),
true));
51 void Repo::updateRepo() {
52 const Json::Value old_snapshot = Utils::parseJSONFile(repo_dir_ /
"snapshot.json")[
"signed"];
54 snapshot[
"_type"] =
"Snapshot";
55 snapshot[
"expires"] = old_snapshot[
"expires"];
56 snapshot[
"version"] = (old_snapshot[
"version"].asUInt()) + 1;
58 const Json::Value root = Utils::parseJSONFile(repo_dir_ /
"root.json")[
"signed"];
59 snapshot[
"meta"][
"root.json"][
"version"] = root[
"version"].asUInt();
61 addDelegationToSnapshot(&snapshot, Uptane::Role::Targets());
63 const std::string signed_snapshot = Utils::jsonToCanonicalStr(signTuf(Uptane::Role::Snapshot(), snapshot));
64 Utils::writeFile(repo_dir_ /
"snapshot.json", signed_snapshot);
66 Json::Value timestamp = Utils::parseJSONFile(repo_dir_ /
"timestamp.json")[
"signed"];
67 timestamp[
"version"] = (timestamp[
"version"].asUInt()) + 1;
68 timestamp[
"meta"][
"snapshot.json"][
"hashes"][
"sha256"] =
69 boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha256digest(signed_snapshot)));
70 timestamp[
"meta"][
"snapshot.json"][
"hashes"][
"sha512"] =
71 boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha512digest(signed_snapshot)));
72 timestamp[
"meta"][
"snapshot.json"][
"length"] =
static_cast<Json::UInt
>(signed_snapshot.length());
73 timestamp[
"meta"][
"snapshot.json"][
"version"] = snapshot[
"version"].asUInt();
74 Utils::writeFile(repo_dir_ /
"timestamp.json",
75 Utils::jsonToCanonicalStr(signTuf(Uptane::Role::Timestamp(), timestamp)));
78 Json::Value Repo::signTuf(
const Uptane::Role &role,
const Json::Value &json) {
79 auto key = keys_[role];
81 Utils::toBase64(Crypto::Sign(key.public_key.Type(),
nullptr, key.private_key, Utils::jsonToCanonicalStr(json)));
82 Json::Value signature;
83 switch (key.public_key.Type()) {
84 case KeyType::kRSA2048:
85 case KeyType::kRSA3072:
86 case KeyType::kRSA4096:
87 signature[
"method"] =
"rsassa-pss";
89 case KeyType::kED25519:
90 signature[
"method"] =
"ed25519";
93 throw std::runtime_error(
"Unknown key type");
95 signature[
"sig"] = b64sig;
97 Json::Value signed_data;
98 signature[
"keyid"] = key.public_key.KeyId();
100 signed_data[
"signed"] = json;
101 signed_data[
"signatures"].append(signature);
105 std::string Repo::getExpirationTime(
const std::string &expires) {
106 if (!expires.empty()) {
108 std::regex time_pattern(
"\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z");
109 if (!std::regex_match(expires, time_pattern)) {
110 throw std::runtime_error(
"Expiration date has wrong format\n date should be in ISO 8601 UTC format");
115 struct tm time_struct {};
117 gmtime_r(&raw_time, &time_struct);
118 time_struct.tm_year += 3;
119 std::array<char, 22> formatted{};
120 strftime(formatted.data(), formatted.size(),
"%Y-%m-%dT%H:%M:%SZ", &time_struct);
121 return formatted.data();
124 void Repo::generateKeyPair(KeyType key_type,
const Uptane::Role &key_name) {
125 boost::filesystem::path keys_dir = path_ / (
"keys/" + repo_type_.toString() +
"/" + key_name.ToString());
126 boost::filesystem::create_directories(keys_dir);
128 std::string public_key_string;
129 std::string private_key;
130 if (!Crypto::generateKeyPair(key_type, &public_key_string, &private_key)) {
131 throw std::runtime_error(
"Key generation failure");
133 PublicKey public_key(public_key_string, key_type);
135 std::stringstream key_str;
138 Utils::writeFile(keys_dir /
"private.key", private_key);
139 Utils::writeFile(keys_dir /
"public.key", public_key_string);
140 Utils::writeFile(keys_dir /
"key_type", key_str.str());
142 keys_[key_name] =
KeyPair(public_key, private_key);
145 void Repo::generateRepoKeys(KeyType key_type) {
146 generateKeyPair(key_type, Uptane::Role::Root());
147 generateKeyPair(key_type, Uptane::Role::Snapshot());
148 generateKeyPair(key_type, Uptane::Role::Targets());
149 generateKeyPair(key_type, Uptane::Role::Timestamp());
152 void Repo::generateRepo(KeyType key_type) {
153 generateRepoKeys(key_type);
155 boost::filesystem::create_directories(repo_dir_);
157 root[
"_type"] =
"Root";
158 root[
"expires"] = expiration_time_;
160 for (
auto const &keypair : keys_) {
161 root[
"keys"][keypair.second.public_key.KeyId()] = keypair.second.public_key.ToUptane();
165 role[
"threshold"] = 1;
167 role[
"keyids"].append(keys_[Uptane::Role::Root()].public_key.KeyId());
168 root[
"roles"][
"root"] = role;
170 role[
"keyids"].clear();
171 role[
"keyids"].append(keys_[Uptane::Role::Snapshot()].public_key.KeyId());
172 root[
"roles"][
"snapshot"] = role;
174 role[
"keyids"].clear();
175 role[
"keyids"].append(keys_[Uptane::Role::Targets()].public_key.KeyId());
176 root[
"roles"][
"targets"] = role;
178 role[
"keyids"].clear();
179 role[
"keyids"].append(keys_[Uptane::Role::Timestamp()].public_key.KeyId());
180 root[
"roles"][
"timestamp"] = role;
182 const std::string signed_root = Utils::jsonToCanonicalStr(signTuf(Uptane::Role::Root(), root));
183 Utils::writeFile(repo_dir_ /
"root.json", signed_root);
184 Utils::writeFile(repo_dir_ /
"1.root.json", signed_root);
187 targets[
"_type"] =
"Targets";
188 targets[
"expires"] = expiration_time_;
189 targets[
"version"] = 1;
190 targets[
"targets"] = Json::objectValue;
191 if (repo_type_ == Uptane::RepositoryType::Director() && !correlation_id_.empty()) {
192 targets[
"custom"][
"correlationId"] = correlation_id_;
194 const std::string signed_targets = Utils::jsonToCanonicalStr(signTuf(Uptane::Role::Targets(), targets));
195 Utils::writeFile(repo_dir_ /
"targets.json", signed_targets);
197 Json::Value snapshot;
198 snapshot[
"_type"] =
"Snapshot";
199 snapshot[
"expires"] = expiration_time_;
200 snapshot[
"version"] = 1;
201 snapshot[
"meta"][
"root.json"][
"hashes"][
"sha256"] =
202 boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha256digest(signed_root)));
203 snapshot[
"meta"][
"root.json"][
"hashes"][
"sha512"] =
204 boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha512digest(signed_root)));
205 snapshot[
"meta"][
"root.json"][
"length"] =
static_cast<Json::UInt
>(signed_root.length());
206 snapshot[
"meta"][
"root.json"][
"version"] = 1;
207 snapshot[
"meta"][
"targets.json"][
"version"] = 1;
208 std::string signed_snapshot = Utils::jsonToCanonicalStr(signTuf(Uptane::Role::Snapshot(), snapshot));
209 Utils::writeFile(repo_dir_ /
"snapshot.json", signed_snapshot);
211 Json::Value timestamp;
212 timestamp[
"_type"] =
"Timestamp";
213 timestamp[
"expires"] = expiration_time_;
214 timestamp[
"version"] = 1;
215 timestamp[
"meta"][
"snapshot.json"][
"hashes"][
"sha256"] =
216 boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha256digest(signed_snapshot)));
217 timestamp[
"meta"][
"snapshot.json"][
"hashes"][
"sha512"] =
218 boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha512digest(signed_snapshot)));
219 timestamp[
"meta"][
"snapshot.json"][
"length"] =
static_cast<Json::UInt
>(signed_snapshot.length());
220 timestamp[
"meta"][
"snapshot.json"][
"version"] = 1;
221 Utils::writeFile(repo_dir_ /
"timestamp.json",
222 Utils::jsonToCanonicalStr(signTuf(Uptane::Role::Timestamp(), timestamp)));
223 if (repo_type_ == Uptane::RepositoryType::Director()) {
224 Utils::writeFile(path_ / DirectorRepo::dir /
"manifest", std::string());
228 void Repo::generateCampaigns()
const {
229 std::vector<campaign::Campaign> campaigns;
231 auto &c = campaigns[0];
233 c.name =
"campaign1";
234 c.id =
"c2eb7e8d-8aa0-429d-883f-5ed8fdb2a493";
237 c.description =
"this is my message to show on the device";
238 c.estInstallationDuration = 10;
239 c.estPreparationDuration = 20;
242 campaign::Campaign::JsonFromCampaigns(campaigns, json);
244 Utils::writeFile(path_ /
"campaigns.json", Utils::jsonToCanonicalStr(json));
247 Json::Value Repo::getTarget(
const std::string &target_name) {
248 const Json::Value image_targets = Utils::parseJSONFile(repo_dir_ /
"targets.json")[
"signed"];
249 if (image_targets[
"targets"].isMember(target_name)) {
250 return image_targets[
"targets"][target_name];
251 }
else if (repo_type_ == Uptane::RepositoryType::Image()) {
252 if (!boost::filesystem::is_directory(repo_dir_ /
"delegations")) {
255 for (
auto &p : boost::filesystem::directory_iterator(repo_dir_ /
"delegations")) {
256 if (Uptane::Role::IsReserved(p.path().stem().string())) {
259 auto targets = Utils::parseJSONFile(p)[
"signed"];
260 if (targets[
"targets"].isMember(target_name)) {
261 return targets[
"targets"][target_name];
268 void Repo::readKeys() {
269 auto keys_path = path_ /
"keys" / repo_type_.toString();
270 for (
auto &p : boost::filesystem::directory_iterator(keys_path)) {
271 std::string public_key_string = Utils::readFile(p /
"public.key");
272 std::istringstream key_type_str(Utils::readFile(p /
"key_type"));
274 key_type_str >> key_type;
275 std::string private_key_string(Utils::readFile(p /
"private.key"));
276 auto name = p.path().filename().string();
277 keys_[
Uptane::Role(name, !Uptane::Role::IsReserved(name))] =
283 boost::filesystem::path meta_path = repo_dir_;
285 if (repo_type_ == Uptane::RepositoryType::Director() &&
286 (role == Uptane::Role::Timestamp() || role == Uptane::Role::Snapshot())) {
287 throw std::runtime_error(
"The " + role.ToString() +
" in the Director repo is not currently supported.");
290 if (role == Uptane::Role::Root()) {
291 meta_path /=
"root.json";
292 }
else if (role == Uptane::Role::Timestamp()) {
293 meta_path /=
"timestamp.json";
294 }
else if (role == Uptane::Role::Snapshot()) {
295 meta_path /=
"snapshot.json";
296 }
else if (role == Uptane::Role::Targets()) {
297 meta_path /=
"targets.json";
299 throw std::runtime_error(
"Refreshing custom role " + role.ToString() +
" is not currently supported.");
304 Json::Value meta_raw = Utils::parseJSONFile(meta_path)[
"signed"];
305 const unsigned version = meta_raw[
"version"].asUInt() + 1;
307 auto current_expire_time =
TimeStamp(meta_raw[
"expires"].asString());
309 if (current_expire_time.IsExpiredAt(TimeStamp::Now())) {
310 time_t new_expiration_time;
311 std::time(&new_expiration_time);
312 new_expiration_time += 60 * 60;
313 struct tm new_expiration_time_str {};
314 gmtime_r(&new_expiration_time, &new_expiration_time_str);
316 meta_raw[
"expires"] =
TimeStamp(new_expiration_time_str).ToString();
318 meta_raw[
"version"] = version;
319 const std::string signed_meta = Utils::jsonToCanonicalStr(signTuf(role, meta_raw));
320 Utils::writeFile(meta_path, signed_meta);
323 if (role == Uptane::Role::Root()) {
324 std::stringstream root_name;
325 root_name << version <<
".root.json";
326 Utils::writeFile(repo_dir_ / root_name.str(), signed_meta);
332 Delegation::Delegation(
const boost::filesystem::path &repo_path, std::string delegation_name)
333 : name(std::move(delegation_name)) {
334 if (Uptane::Role::IsReserved(name)) {
335 throw std::runtime_error(
"Delegation name " + name +
" is reserved.");
337 boost::filesystem::path delegation_path(((repo_path / ImageRepo::dir /
"delegations") / name).
string() +
".json");
338 boost::filesystem::path targets_path(repo_path / ImageRepo::dir /
"targets.json");
339 if (!boost::filesystem::exists(delegation_path) || !boost::filesystem::exists(targets_path)) {
340 throw std::runtime_error(std::string(
"delegation ") + delegation_path.string() +
" does not exist");
343 pattern = findPatternInTree(repo_path, name, Utils::parseJSONFile(targets_path)[
"signed"]);
345 if (pattern.empty()) {
346 throw std::runtime_error(
"Could not find delegation role in the delegation tree");
350 std::string Delegation::findPatternInTree(
const boost::filesystem::path &repo_path,
const std::string &name,
351 const Json::Value &targets_json) {
352 Json::Value delegations = targets_json[
"delegations"];
353 for (
const auto &role : delegations[
"roles"]) {
354 auto role_name = role[
"name"].asString();
355 if (role_name == name) {
356 auto pattern = role[
"paths"][0].asString();
357 if (pattern.back() ==
'/') {
358 pattern.append(
"**");
362 auto pattern = findPatternInTree(
364 Utils::parseJSONFile((repo_path / ImageRepo::dir /
"delegations") / (role_name +
".json"))[
"signed"]);
365 if (!pattern.empty()) {