Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
main.cc
1 #include <boost/program_options.hpp>
2 #include <fstream>
3 #include <iostream>
4 #include <istream>
5 #include <iterator>
6 #include <ostream>
7 #include <string>
8 
9 #include "logging/logging.h"
10 #include "uptane_repo.h"
11 #include "utilities/aktualizr_version.h"
12 
13 namespace po = boost::program_options;
14 
15 KeyType parseKeyType(const po::variables_map &vm) {
16  std::string key_type_arg = vm["keytype"].as<std::string>();
17  std::istringstream key_type_str{std::string("\"") + key_type_arg + "\""};
18  KeyType key_type;
19  key_type_str >> key_type;
20  return key_type;
21 }
22 
23 void check_info_options(const po::options_description &description, const po::variables_map &vm) {
24  if (vm.count("help") != 0 || (vm.count("command") == 0 && vm.count("version") == 0)) {
25  std::cout << description << '\n';
26  exit(EXIT_SUCCESS);
27  }
28  if (vm.count("version") != 0) {
29  std::cout << "Current uptane-generator version is: " << aktualizr_version() << "\n";
30  exit(EXIT_SUCCESS);
31  }
32 }
33 
34 int main(int argc, char **argv) {
35  po::options_description desc("uptane-generator command line options");
36  // clang-format off
37  desc.add_options()
38  ("help,h", "print usage")
39  ("version,v", "Current uptane-generator version")
40  ("command", po::value<std::string>(), "generate: \tgenerate a new repository\n"
41  "adddelegation: \tadd a delegated role to the Image repo metadata\n"
42  "revokedelegation: \tremove delegated role from the Image repo metadata and all signed targets of this role\n"
43  "image: \tadd a target to the Image repo metadata\n"
44  "addtarget: \tprepare Director Targets metadata for a given device\n"
45  "signtargets: \tsign the staged Director Targets metadata\n"
46  "emptytargets: \tclear the staged Director Targets metadata\n"
47  "oldtargets: \tfill the staged Director Targets metadata with what is currently signed\n"
48  "sign: \tsign arbitrary metadata with repo keys\n"
49  "addcampaigns: \tgenerate campaigns json\n"
50  "refresh: \trefresh a metadata object (bump the version)")
51  ("path", po::value<boost::filesystem::path>(), "path to the repository")
52  ("filename", po::value<boost::filesystem::path>(), "path to the image")
53  ("hwid", po::value<std::string>(), "target hardware identifier")
54  ("targetformat", po::value<std::string>(), "format of target for 'image' command")
55  ("targetcustom", po::value<boost::filesystem::path>(), "path to custom JSON for 'image' command")
56  ("serial", po::value<std::string>(), "target ECU serial")
57  ("expires", po::value<std::string>(), "expiration time")
58  ("keyname", po::value<std::string>(), "name of key's role")
59  ("repotype", po::value<std::string>(), "director|image")
60  ("correlationid", po::value<std::string>()->default_value(""), "correlation id")
61  ("keytype", po::value<std::string>()->default_value("RSA2048"), "Uptane key type")
62  ("targetname", po::value<std::string>(), "target's name (if different than filename)")
63  ("targetsha256", po::value<std::string>(), "target's SHA256 hash (for adding metadata without an actual file)")
64  ("targetsha512", po::value<std::string>(), "target's SHA512 hash (for adding metadata without an actual file)")
65  ("targetlength", po::value<uint64_t>(), "target's length (for adding metadata without an actual file)")
66  ("dname", po::value<std::string>(), "delegated role name")
67  ("dterm", po::bool_switch(), "if the created delegated role is terminating")
68  ("dparent", po::value<std::string>()->default_value("targets"), "delegated role parent name")
69  ("dpattern", po::value<std::string>(), "delegated file path pattern")
70  ("url", po::value<std::string>(), "custom download URL");
71 
72  // clang-format on
73 
74  po::positional_options_description positionalOptions;
75  positionalOptions.add("command", 1);
76  positionalOptions.add("path", 1);
77  positionalOptions.add("filename", 1);
78 
79  try {
80  logger_init();
81  logger_set_threshold(boost::log::trivial::info);
82  po::variables_map vm;
83  po::basic_parsed_options<char> parsed_options =
84  po::command_line_parser(argc, argv).options(desc).positional(positionalOptions).run();
85  po::store(parsed_options, vm);
86  check_info_options(desc, vm);
87  po::notify(vm);
88 
89  if (vm.count("command") != 0 && vm.count("path") != 0) {
90  std::string expiration_time;
91  if (vm.count("expires") != 0) {
92  expiration_time = vm["expires"].as<std::string>();
93  }
94  std::string correlation_id;
95  if (vm.count("correlationid") != 0) {
96  correlation_id = vm["correlationid"].as<std::string>();
97  }
98  std::string dname;
99  if (vm.count("dname") != 0) {
100  dname = vm["dname"].as<std::string>();
101  }
102  std::string command = vm["command"].as<std::string>();
103  boost::filesystem::path repo_dir = vm["path"].as<boost::filesystem::path>();
104  UptaneRepo repo(repo_dir, expiration_time, correlation_id);
105  if (command == "generate") {
106  KeyType key_type = parseKeyType(vm);
107  repo.generateRepo(key_type);
108  std::cout << "Uptane metadata repos generated at " << repo_dir << std::endl;
109  } else if (command == "image") {
110  if (vm.count("targetname") == 0 && vm.count("filename") == 0) {
111  std::cerr << "image command requires --targetname or --filename\n";
112  exit(EXIT_FAILURE);
113  }
114  if (vm.count("hwid") == 0) {
115  std::cerr << "image command requires --hwid\n";
116  exit(EXIT_FAILURE);
117  }
118  auto targetname = (vm.count("targetname") > 0) ? vm["targetname"].as<std::string>()
119  : vm["filename"].as<boost::filesystem::path>();
120  const std::string hwid = vm["hwid"].as<std::string>();
121 
122  Delegation delegation;
123  if (vm.count("dname") != 0) {
124  delegation = Delegation(repo_dir, dname);
125  if (!delegation.isMatched(targetname)) {
126  std::cerr << "Image path doesn't match delegation!\n";
127  exit(EXIT_FAILURE);
128  }
129  std::cout << "Added a target " << targetname << " to a delegated role " << dname << std::endl;
130  }
131  std::string url;
132  if (vm.count("url") != 0) {
133  url = vm["url"].as<std::string>();
134  }
135  if (vm.count("filename") > 0) {
136  repo.addImage(vm["filename"].as<boost::filesystem::path>(), targetname, hwid, url, delegation);
137  std::cout << "Added a target " << targetname << " to the Image repo metadata" << std::endl;
138  } else {
139  if ((vm.count("targetsha256") == 0 && vm.count("targetsha512") == 0) || vm.count("targetlength") == 0) {
140  std::cerr << "image command requires --targetsha256 or --targetsha512, and --targetlength when --filename "
141  "is not supplied.\n";
142  exit(EXIT_FAILURE);
143  }
144  std::unique_ptr<Hash> hash;
145  if (vm.count("targetsha256") > 0) {
146  hash = std_::make_unique<Hash>(Hash::Type::kSha256, vm["targetsha256"].as<std::string>());
147  } else {
148  hash = std_::make_unique<Hash>(Hash::Type::kSha512, vm["targetsha512"].as<std::string>());
149  }
150  Json::Value custom;
151  if (vm.count("targetcustom") > 0 && vm.count("targetformat") > 0) {
152  std::cerr << "--targetcustom and --targetformat cannot be used together";
153  exit(EXIT_FAILURE);
154  }
155  if (vm.count("targetcustom") > 0) {
156  std::ifstream custom_file(vm["targetcustom"].as<boost::filesystem::path>().c_str());
157  custom_file >> custom;
158  } else if (vm.count("targetformat") > 0) {
159  custom = Json::Value();
160  custom["targetFormat"] = vm["targetformat"].as<std::string>();
161  }
162  repo.addCustomImage(targetname.string(), *hash, vm["targetlength"].as<uint64_t>(), hwid, url, delegation,
163  custom);
164  std::cout << "Added a custom image target " << targetname.string() << std::endl;
165  }
166  } else if (command == "addtarget") {
167  if (vm.count("targetname") == 0 || vm.count("hwid") == 0 || vm.count("serial") == 0) {
168  std::cerr << "addtarget command requires --targetname, --hwid, and --serial\n";
169  exit(EXIT_FAILURE);
170  }
171  const std::string targetname = vm["targetname"].as<std::string>();
172  const std::string hwid = vm["hwid"].as<std::string>();
173  const std::string serial = vm["serial"].as<std::string>();
174  std::string url;
175  if (vm.count("url") != 0) {
176  url = vm["url"].as<std::string>();
177  }
178  repo.addTarget(targetname, hwid, serial, url, expiration_time);
179  std::cout << "Added target " << targetname << " to Director Targets metadata for ECU with serial " << serial
180  << " and hardware ID " << hwid << std::endl;
181  } else if (command == "adddelegation") {
182  if (vm.count("dname") == 0 || vm.count("dpattern") == 0) {
183  std::cerr << "adddelegation command requires --dname and --dpattern\n";
184  exit(EXIT_FAILURE);
185  }
186  std::string dparent = vm["dparent"].as<std::string>();
187  std::string dpattern = vm["dpattern"].as<std::string>();
188  KeyType key_type = parseKeyType(vm);
189  repo.addDelegation(Uptane::Role(dname, true), Uptane::Role(dparent, dparent != "targets"),
190  vm["dpattern"].as<std::string>(), vm["dterm"].as<bool>(), key_type);
191  std::cout << "Added a delegated role " << dname << " with dpattern " << dpattern
192  << " to the Image repo metadata" << std::endl;
193  } else if (command == "revokedelegation") {
194  if (vm.count("dname") == 0) {
195  std::cerr << "revokedelegation command requires --dname\n";
196  exit(EXIT_FAILURE);
197  }
198  repo.revokeDelegation(Uptane::Role(dname, true));
199  std::cout << "Revoked the delegation " << dname << std::endl;
200  } else if (command == "signtargets") {
201  repo.signTargets();
202  std::cout << "Signed the staged Director Targets metadata" << std::endl;
203  } else if (command == "emptytargets") {
204  repo.emptyTargets();
205  std::cout << "Cleared the staged Director Targets metadata" << std::endl;
206  } else if (command == "oldtargets") {
207  repo.oldTargets();
208  std::cout << "Populated the Director Targets metadata with the currently signed metadata" << std::endl;
209  } else if (command == "sign") {
210  if (vm.count("repotype") == 0 || vm.count("keyname") == 0) {
211  std::cerr << "sign command requires --repotype and --keyname\n";
212  exit(EXIT_FAILURE);
213  }
214  std::cin >> std::noskipws;
215  std::istream_iterator<char> it(std::cin);
216  std::istream_iterator<char> end;
217  std::string text_to_sign(it, end);
218 
219  Repo base_repo(Uptane::RepositoryType(vm["repotype"].as<std::string>()), repo_dir, expiration_time,
220  correlation_id);
221 
222  auto json_to_sign = Utils::parseJSON(text_to_sign);
223  if (json_to_sign.empty()) {
224  std::cerr << "Text to sign must be valid json\n";
225  exit(EXIT_FAILURE);
226  }
227 
228  auto json_signed = base_repo.signTuf(Uptane::Role(vm["keyname"].as<std::string>()), json_to_sign);
229  std::cout << Utils::jsonToCanonicalStr(json_signed);
230  } else if (command == "addcampaigns") {
231  repo.generateCampaigns();
232  std::cout << "Generated campaigns" << std::endl;
233  } else if (command == "refresh") {
234  if (vm.count("repotype") == 0 || vm.count("keyname") == 0) {
235  std::cerr << "refresh command requires --repotype and --keyname\n";
236  exit(EXIT_FAILURE);
237  }
238  repo.refresh(Uptane::RepositoryType(vm["repotype"].as<std::string>()),
239  Uptane::Role(vm["keyname"].as<std::string>()));
240  } else {
241  std::cout << desc << std::endl;
242  exit(EXIT_FAILURE);
243  }
244  } else {
245  std::cout << desc << std::endl;
246  exit(EXIT_FAILURE);
247  }
248 
249  exit(EXIT_SUCCESS);
250 
251  } catch (const po::error &o) {
252  std::cout << o.what() << std::endl;
253  std::cout << desc;
254  } catch (std::exception &ex) {
255  LOG_ERROR << "Exception: " << ex.what();
256  } catch (...) {
257  LOG_ERROR << "Unknown error";
258  }
259  exit(EXIT_FAILURE);
260 }
261 // vim: set tabstop=2 shiftwidth=2 expandtab:
Uptane::RepositoryType
Definition: tuf.h:21
Repo
Definition: repo.h:34
Uptane::Role
TUF Roles.
Definition: tuf.h:61
UptaneRepo
Definition: uptane_repo.h:7
Delegation
Definition: repo.h:19