6 #include <boost/filesystem.hpp>
7 #include <boost/program_options.hpp>
11 #include "bootstrap/bootstrap.h"
12 #include "crypto/crypto.h"
13 #include "http/httpclient.h"
14 #include "libaktualizr/config.h"
15 #include "logging/logging.h"
16 #include "utilities/aktualizr_version.h"
17 #include "utilities/utils.h"
19 namespace bpo = boost::program_options;
21 void checkInfoOptions(
const bpo::options_description& description,
const bpo::variables_map& vm) {
22 if (vm.count(
"help") != 0) {
23 std::cout << description <<
'\n';
26 if (vm.count(
"version") != 0) {
27 std::cout <<
"Current aktualizr-cert-provider version is: " << aktualizr_version() <<
"\n";
32 bpo::variables_map parseOptions(
int argc,
char** argv) {
33 bpo::options_description description(
"aktualizr-cert-provider command line options");
35 description.add_options()
36 (
"help,h",
"print usage")
37 (
"version,v",
"Current aktualizr-cert-provider version")
38 (
"credentials,c", bpo::value<boost::filesystem::path>(),
"zipped credentials file")
39 (
"fleet-ca", bpo::value<boost::filesystem::path>(),
"path to fleet certificate authority certificate (for signing device certificates)")
40 (
"fleet-ca-key", bpo::value<boost::filesystem::path>(),
"path to the private key of fleet certificate authority")
41 (
"bits", bpo::value<int>(),
"size of RSA keys in bits")
42 (
"days", bpo::value<int>(),
"validity term for the certificate in days")
43 (
"certificate-c", bpo::value<std::string>(),
"value for C field in certificate subject name")
44 (
"certificate-st", bpo::value<std::string>(),
"value for ST field in certificate subject name")
45 (
"certificate-o", bpo::value<std::string>(),
"value for O field in certificate subject name")
46 (
"certificate-cn", bpo::value<std::string>(),
"value for CN field in certificate subject name (used for device ID)")
47 (
"target,t", bpo::value<std::string>(),
"target device to scp credentials to (or [user@]host)")
48 (
"port,p", bpo::value<int>(),
"target port")
49 (
"directory,d", bpo::value<boost::filesystem::path>(),
"directory on target to write credentials to (conflicts with --config)")
50 (
"root-ca,r",
"provide root CA certificate")
51 (
"server-url,u",
"provide server URL file")
52 (
"local,l", bpo::value<boost::filesystem::path>(),
"local directory to write credentials to")
53 (
"config,g", bpo::value<std::vector<boost::filesystem::path> >()->composing(),
"configuration file or directory from which to get file names")
54 (
"skip-checks,s",
"skip strict host key checking for ssh/scp commands");
57 bpo::variables_map vm;
58 std::vector<std::string> unregistered_options;
60 bpo::basic_parsed_options<char> parsed_options =
61 bpo::command_line_parser(argc, argv).options(description).allow_unregistered().run();
62 bpo::store(parsed_options, vm);
63 checkInfoOptions(description, vm);
65 unregistered_options = bpo::collect_unrecognized(parsed_options.options, bpo::include_positional);
66 if (vm.count(
"help") == 0 && !unregistered_options.empty()) {
67 std::cout << description <<
"\n";
70 }
catch (
const bpo::required_option& ex) {
72 std::cout << ex.what() << std::endl << description;
74 }
catch (
const bpo::error& ex) {
75 checkInfoOptions(description, vm);
79 std::cout << ex.what() <<
'\n';
91 SSHRunner(std::string target,
const bool skip_checks,
const int port = 22)
92 : target_(std::move(target)), skip_checks_(skip_checks), port_(port) {}
94 void runCmd(
const std::string& cmd)
const {
95 std::ostringstream prefix;
99 prefix <<
"-p " << port_ <<
" ";
102 prefix <<
"-o StrictHostKeyChecking=no ";
104 prefix << target_ <<
" ";
106 std::string fullCmd = prefix.str() + cmd;
107 std::cout <<
"Running " << fullCmd << std::endl;
109 int ret = system(fullCmd.c_str());
111 throw std::runtime_error(
"Error running command on " + target_ +
": " + std::to_string(ret));
115 void transferFile(
const boost::filesystem::path& inFile,
const boost::filesystem::path& targetPath) {
117 runCmd(
"mkdir -p " + targetPath.parent_path().string());
119 std::ostringstream prefix;
123 prefix <<
"-P " << port_ <<
" ";
126 prefix <<
"-o StrictHostKeyChecking=no ";
129 std::string fullCmd = prefix.str() + inFile.string() +
" " + target_ +
":" + targetPath.string();
130 std::cout <<
"Running " << fullCmd << std::endl;
132 int ret = system(fullCmd.c_str());
134 throw std::runtime_error(
"Error copying file on " + target_ +
": " + std::to_string(ret));
144 void copyLocal(
const boost::filesystem::path& src,
const boost::filesystem::path& dest) {
145 boost::filesystem::path dest_dir = dest.parent_path();
146 if (boost::filesystem::exists(dest_dir)) {
147 boost::filesystem::remove(dest);
149 boost::filesystem::create_directories(dest_dir);
151 boost::filesystem::copy_file(src, dest);
154 int main(
int argc,
char* argv[]) {
155 int exit_code = EXIT_FAILURE;
158 logger_set_threshold(
static_cast<boost::log::trivial::severity_level
>(2));
161 bpo::variables_map commandline_map = parseOptions(argc, argv);
164 if (commandline_map.count(
"target") != 0) {
165 target = commandline_map[
"target"].as<std::string>();
168 if (commandline_map.count(
"port") != 0) {
169 port = (commandline_map[
"port"].as<
int>());
171 const bool provide_ca = commandline_map.count(
"root-ca") != 0;
172 const bool provide_url = commandline_map.count(
"server-url") != 0;
173 boost::filesystem::path local_dir;
174 if (commandline_map.count(
"local") != 0) {
175 local_dir = commandline_map[
"local"].as<boost::filesystem::path>();
177 std::vector<boost::filesystem::path> config_path;
178 if (commandline_map.count(
"config") != 0) {
179 config_path = commandline_map[
"config"].as<std::vector<boost::filesystem::path>>();
181 const bool skip_checks = commandline_map.count(
"skip-checks") != 0;
183 boost::filesystem::path fleet_ca_path =
"";
184 if (commandline_map.count(
"fleet-ca") != 0) {
185 fleet_ca_path = commandline_map[
"fleet-ca"].as<boost::filesystem::path>();
188 boost::filesystem::path fleet_ca_key_path =
"";
189 if (commandline_map.count(
"fleet-ca-key") != 0) {
190 fleet_ca_key_path = commandline_map[
"fleet-ca-key"].as<boost::filesystem::path>();
193 if (fleet_ca_path.empty() != fleet_ca_key_path.empty()) {
194 std::cerr <<
"fleet-ca and fleet-ca-key options should be used together" << std::endl;
198 if (!commandline_map[
"directory"].empty() && !commandline_map[
"config"].empty()) {
199 std::cerr <<
"Directory (--directory) and config (--config) options cannot be used together" << std::endl;
203 boost::filesystem::path credentials_path =
"";
204 if (commandline_map.count(
"credentials") != 0) {
205 credentials_path = commandline_map[
"credentials"].as<boost::filesystem::path>();
208 if (local_dir.empty() && target.empty()) {
209 std::cerr <<
"Please provide a local directory and/or target to output the generated files to" << std::endl;
213 std::string serverUrl;
214 if ((fleet_ca_path.empty() || provide_ca || provide_url) && credentials_path.empty()) {
216 <<
"Error: missing -c/--credentials parameters which is mandatory if the fleet CA is not specified or an "
217 "output of the root CA or a gateway URL is requested";
220 serverUrl = Bootstrap::readServerUrl(credentials_path);
223 std::string device_id;
224 if (commandline_map.count(
"certificate-cn") != 0) {
225 device_id = (commandline_map[
"certificate-cn"].as<std::string>());
226 if (device_id.empty()) {
227 std::cerr <<
"Common name (device ID, --certificate-cn) can't be empty" << std::endl;
231 device_id = Utils::genPrettyName();
232 std::cout <<
"Random device ID is " << device_id <<
"\n";
235 boost::filesystem::path directory =
"/var/sota/import";
240 if (!config_path.empty()) {
241 Config config(config_path);
244 if (!config.import.base_path.empty()) {
245 directory = config.import.base_path;
246 }
else if (!config.storage.path.empty()) {
247 directory = config.storage.path;
250 if (!config.import.tls_pkey_path.empty()) {
251 pkey_file = config.import.tls_pkey_path;
253 pkey_file = config.storage.tls_pkey_path;
256 if (!config.import.tls_clientcert_path.empty()) {
257 cert_file = config.import.tls_clientcert_path;
259 cert_file = config.storage.tls_clientcert_path;
262 if (!config.import.tls_cacert_path.empty()) {
263 ca_file = config.import.tls_cacert_path;
265 ca_file = config.storage.tls_cacert_path;
268 if (provide_url && !config.tls.server_url_path.empty()) {
269 url_file = config.tls.server_url_path;
273 if (!commandline_map[
"directory"].empty()) {
274 directory = commandline_map[
"directory"].as<boost::filesystem::path>();
277 TemporaryFile tmp_pkey_file(pkey_file.get(
"").filename().string());
278 TemporaryFile tmp_cert_file(cert_file.get(
"").filename().string());
279 TemporaryFile tmp_ca_file(ca_file.get(
"").filename().string());
280 TemporaryFile tmp_url_file(url_file.get(
"").filename().string());
286 if (fleet_ca_path.empty()) {
290 data[
"deviceId"] = device_id;
293 std::cout <<
"Provisioning against server...\n";
294 http.setCerts(boot.getCa(), CryptoSource::kFile, boot.getCert(), CryptoSource::kFile, boot.getPkey(),
295 CryptoSource::kFile);
297 if (!response.isOk()) {
298 Json::Value resp_code = response.getJson()[
"code"];
299 if (resp_code.isString() && resp_code.asString() ==
"device_already_registered") {
300 std::cout <<
"Device ID" << device_id <<
"is occupied.\n";
303 std::cout <<
"Provisioning failed, response: " << response.body <<
"\n";
306 std::cout <<
"...success\n";
308 StructGuard<BIO> device_p12(BIO_new_mem_buf(response.body.c_str(),
static_cast<int>(response.body.size())),
310 if (!Crypto::parseP12(device_p12.get(),
"", &pkey, &cert, &ca)) {
311 std::cout <<
"Unable to parse p12 file received from server.\n";
316 if (commandline_map.count(
"bits") != 0) {
317 rsa_bits = (commandline_map[
"bits"].as<
int>());
321 if (commandline_map.count(
"days") != 0) {
322 cert_days = (commandline_map[
"days"].as<
int>());
325 std::string newcert_c;
326 if (commandline_map.count(
"certificate-c") != 0) {
327 newcert_c = (commandline_map[
"certificate-c"].as<std::string>());
328 if (newcert_c.length() != 2) {
329 std::cerr <<
"Country code (--certificate-c) should be 2 characters long" << std::endl;
334 std::string newcert_st;
335 if (commandline_map.count(
"certificate-st") != 0) {
336 newcert_st = (commandline_map[
"certificate-st"].as<std::string>());
337 if (newcert_st.empty()) {
338 std::cerr <<
"State name (--certificate-st) can't be empty" << std::endl;
343 std::string newcert_o;
344 if (commandline_map.count(
"certificate-o") != 0) {
345 newcert_o = (commandline_map[
"certificate-o"].as<std::string>());
346 if (newcert_o.empty()) {
347 std::cerr <<
"Organization name (--certificate-o) can't be empty" << std::endl;
352 StructGuard<X509> certificate =
353 Crypto::generateCert(rsa_bits, cert_days, newcert_c, newcert_st, newcert_o, device_id);
354 Crypto::signCert(fleet_ca_path.native(), fleet_ca_key_path.native(), certificate.get());
355 Crypto::serializeCert(&pkey, &cert, certificate.get());
361 ca = Bootstrap::readServerCa(credentials_path);
365 std::cout <<
"Server root CA read from autoprov_credentials.p12 in zipped archive.\n";
367 std::cout <<
"Server root CA read from server_ca.pem in zipped archive.\n";
372 tmp_pkey_file.PutContents(pkey);
373 tmp_cert_file.PutContents(cert);
375 tmp_ca_file.PutContents(ca);
378 tmp_url_file.PutContents(serverUrl);
381 if (!local_dir.empty()) {
382 auto pkey_file_path = local_dir / pkey_file.get(directory);
383 std::cout <<
"Writing the generated client private key to " << pkey_file_path <<
" ...\n";
384 copyLocal(tmp_pkey_file.PathString(), pkey_file_path);
386 auto cert_file_path = local_dir / cert_file.get(directory);
387 std::cout <<
"Writing the generated and signed client certificate to " << cert_file_path <<
" ...\n";
388 copyLocal(tmp_cert_file.PathString(), cert_file_path);
391 auto root_ca_file = local_dir / ca_file.get(directory);
392 std::cout <<
"Writing the server root CA to " << root_ca_file <<
" ...\n";
393 copyLocal(tmp_ca_file.PathString(), root_ca_file);
396 auto gtw_url_file = local_dir / url_file.get(directory);
397 std::cout <<
"Writing the gateway URL to " << gtw_url_file <<
" ...\n";
398 copyLocal(tmp_url_file.PathString(), gtw_url_file);
400 std::cout <<
"...success\n";
403 if (!target.empty()) {
404 std::cout <<
"Copying client certificate and keys to " << target <<
":" << directory;
406 std::cout <<
" on port " << port;
408 std::cout <<
" ...\n";
410 SSHRunner ssh{target, skip_checks, port};
413 ssh.transferFile(tmp_pkey_file.Path(), pkey_file.get(directory));
415 ssh.transferFile(tmp_cert_file.Path(), cert_file.get(directory));
418 ssh.transferFile(tmp_ca_file.Path(), ca_file.get(directory));
421 ssh.transferFile(tmp_url_file.Path(), url_file.get(directory));
424 std::cout <<
"...success\n";
425 }
catch (
const std::runtime_error& exc) {
426 std::cout << exc.what() << std::endl;
430 exit_code = EXIT_SUCCESS;
431 }
catch (
const std::exception& exc) {
432 LOG_ERROR <<
"Error: " << exc.what();
434 exit_code = EXIT_FAILURE;