8 #include <boost/filesystem.hpp> 9 #include <boost/program_options.hpp> 11 #include "json/json.h" 13 #include "bootstrap/bootstrap.h" 14 #include "config/config.h" 15 #include "crypto/crypto.h" 16 #include "http/httpclient.h" 17 #include "logging/logging.h" 18 #include "utilities/aktualizr_version.h" 19 #include "utilities/utils.h" 21 namespace bpo = boost::program_options;
23 void checkInfoOptions(
const bpo::options_description& description,
const bpo::variables_map& vm) {
24 if (vm.count(
"help") != 0) {
25 std::cout << description <<
'\n';
28 if (vm.count(
"version") != 0) {
29 std::cout <<
"Current aktualizr-cert-provider version is: " << aktualizr_version() <<
"\n";
34 bpo::variables_map parseOptions(
int argc,
char** argv) {
35 bpo::options_description description(
"aktualizr-cert-provider command line options");
37 description.add_options()
38 (
"help,h",
"print usage")
39 (
"version,v",
"Current aktualizr-cert-provider version")
40 (
"credentials,c", bpo::value<boost::filesystem::path>(),
"zipped credentials file")
41 (
"fleet-ca", bpo::value<boost::filesystem::path>(),
"path to fleet certificate authority certificate (for signing device certificates)")
42 (
"fleet-ca-key", bpo::value<boost::filesystem::path>(),
"path to the private key of fleet certificate authority")
43 (
"bits", bpo::value<int>(),
"size of RSA keys in bits")
44 (
"days", bpo::value<int>(),
"validity term for the certificate in days")
45 (
"certificate-c", bpo::value<std::string>(),
"value for C field in certificate subject name")
46 (
"certificate-st", bpo::value<std::string>(),
"value for ST field in certificate subject name")
47 (
"certificate-o", bpo::value<std::string>(),
"value for O field in certificate subject name")
48 (
"certificate-cn", bpo::value<std::string>(),
"value for CN field in certificate subject name")
49 (
"target,t", bpo::value<std::string>(),
"target device to scp credentials to (or [user@]host)")
50 (
"port,p", bpo::value<int>(),
"target port")
51 (
"directory,d", bpo::value<boost::filesystem::path>(),
"directory on target to write credentials to (conflicts with -config)")
52 (
"root-ca,r",
"provide root CA")
53 (
"server-url,u",
"provide server url file")
54 (
"local,l", bpo::value<boost::filesystem::path>(),
"local directory to write credentials to")
55 (
"config,g", bpo::value<std::vector<boost::filesystem::path> >()->composing(),
"configuration file or directory from which to get file names")
56 (
"skip-checks,s",
"skip strict host key checking for ssh/scp commands");
59 bpo::variables_map vm;
60 std::vector<std::string> unregistered_options;
62 bpo::basic_parsed_options<char> parsed_options =
63 bpo::command_line_parser(argc, argv).options(description).allow_unregistered().run();
64 bpo::store(parsed_options, vm);
65 checkInfoOptions(description, vm);
67 unregistered_options = bpo::collect_unrecognized(parsed_options.options, bpo::include_positional);
68 if (vm.count(
"help") == 0 && !unregistered_options.empty()) {
69 std::cout << description <<
"\n";
72 }
catch (
const bpo::required_option& ex) {
74 std::cout << ex.what() << std::endl << description;
76 }
catch (
const bpo::error& ex) {
77 checkInfoOptions(description, vm);
81 std::cout << ex.what() <<
'\n';
92 #define SSL_ERROR(description) \ 94 std::cerr << (description) << ERR_error_string(ERR_get_error(), nullptr) << std::endl; \ 97 bool generateAndSign(
const std::string& cacert_path,
const std::string& capkey_path, std::string* pkey,
98 std::string* cert,
const bpo::variables_map& commandline_map) {
100 if (commandline_map.count(
"bits") != 0) {
101 rsa_bits = (commandline_map[
"bits"].as<
int>());
103 std::cerr <<
"RSA key size can't be smaller than 31 bits" << std::endl;
109 if (commandline_map.count(
"days") != 0) {
110 cert_days = (commandline_map[
"days"].as<
int>());
113 std::string newcert_c;
114 if (commandline_map.count(
"certificate-c") != 0) {
115 newcert_c = (commandline_map[
"certificate-c"].as<std::string>());
116 if (newcert_c.length() != 2) {
117 std::cerr <<
"Country code (--certificate-c) should be 2 characters long" << std::endl;
122 std::string newcert_st;
123 if (commandline_map.count(
"certificate-st") != 0) {
124 newcert_st = (commandline_map[
"certificate-st"].as<std::string>());
125 if (newcert_st.empty()) {
126 std::cerr <<
"State name (--certificate-st) can't be empty" << std::endl;
131 std::string newcert_o;
132 if (commandline_map.count(
"certificate-o") != 0) {
133 newcert_o = (commandline_map[
"certificate-o"].as<std::string>());
134 if (newcert_o.empty()) {
135 std::cerr <<
"Organization name (--certificate-o) can't be empty" << std::endl;
140 std::string newcert_cn;
141 if (commandline_map.count(
"certificate-cn") != 0) {
142 newcert_cn = (commandline_map[
"certificate-cn"].as<std::string>());
143 if (newcert_cn.empty()) {
144 std::cerr <<
"Common name (--certificate-cn) can't be empty" << std::endl;
148 newcert_cn = Utils::genPrettyName();
152 std::string cacert_contents = Utils::readFile(cacert_path);
153 StructGuard<BIO> bio_in_cacert(BIO_new_mem_buf(cacert_contents.c_str(),
static_cast<int>(cacert_contents.size())),
155 StructGuard<X509> ca_certificate(PEM_read_bio_X509(bio_in_cacert.get(),
nullptr,
nullptr,
nullptr), X509_free);
156 if (ca_certificate.get() ==
nullptr) {
157 std::cerr <<
"Reading CA certificate failed.\n";
162 std::string capkey_contents = Utils::readFile(capkey_path);
163 StructGuard<BIO> bio_in_capkey(BIO_new_mem_buf(capkey_contents.c_str(),
static_cast<int>(capkey_contents.size())),
165 StructGuard<EVP_PKEY> ca_privkey(PEM_read_bio_PrivateKey(bio_in_capkey.get(),
nullptr,
nullptr,
nullptr),
167 if (ca_privkey.get() ==
nullptr) SSL_ERROR(
"PEM_read_bio_PrivateKey failed: ");
170 StructGuard<X509> certificate(X509_new(), X509_free);
171 if (certificate.get() ==
nullptr) SSL_ERROR(
"X509_new failed: ");
173 X509_set_version(certificate.get(), 2);
176 std::random_device urandom;
177 std::uniform_int_distribution<> serial_dist(0, (1UL << 20) - 1);
178 ASN1_INTEGER_set(X509_get_serialNumber(certificate.get()), serial_dist(urandom));
182 StructGuard<X509_NAME> subj(X509_NAME_new(), X509_NAME_free);
183 if (subj.get() ==
nullptr) SSL_ERROR(
"X509_NAME_new failed: ");
185 if (!newcert_c.empty()) {
186 if (X509_NAME_add_entry_by_txt(subj.get(),
"C", MBSTRING_ASC,
187 reinterpret_cast<const unsigned char*
>(newcert_c.c_str()), -1, -1, 0) == 0)
188 SSL_ERROR(
"X509_NAME_add_entry_by_txt failed: ");
191 if (!newcert_st.empty()) {
192 if (X509_NAME_add_entry_by_txt(subj.get(),
"ST", MBSTRING_ASC,
193 reinterpret_cast<const unsigned char*
>(newcert_st.c_str()), -1, -1, 0) == 0)
194 SSL_ERROR(
"X509_NAME_add_entry_by_txt failed: ");
197 if (!newcert_o.empty()) {
198 if (X509_NAME_add_entry_by_txt(subj.get(),
"O", MBSTRING_ASC,
199 reinterpret_cast<const unsigned char*
>(newcert_o.c_str()), -1, -1, 0) == 0)
200 SSL_ERROR(
"X509_NAME_add_entry_by_txt failed: ");
203 assert(!newcert_cn.empty());
204 if (X509_NAME_add_entry_by_txt(subj.get(),
"CN", MBSTRING_ASC,
205 reinterpret_cast<const unsigned char*
>(newcert_cn.c_str()), -1, -1, 0) == 0)
206 SSL_ERROR(
"X509_NAME_add_entry_by_txt failed: ");
208 if (X509_set_subject_name(certificate.get(), subj.get()) == 0) SSL_ERROR(
"X509_set_subject_name failed: ");
211 X509_NAME* ca_subj = X509_get_subject_name(ca_certificate.get());
212 if (ca_subj ==
nullptr) SSL_ERROR(
"X509_get_subject_name failed: ");
214 if (X509_set_issuer_name(certificate.get(), ca_subj) == 0) SSL_ERROR(
"X509_set_issuer_name failed: ");
219 RSA* certificate_rsa = RSA_generate_key(rsa_bits, RSA_F4,
nullptr,
nullptr);
220 if (certificate_rsa ==
nullptr) SSL_ERROR(
"RSA_generate_key failed: ");
222 StructGuard<EVP_PKEY> certificate_pkey(EVP_PKEY_new(), EVP_PKEY_free);
223 if (certificate_pkey.get() ==
nullptr) SSL_ERROR(
"EVP_PKEY_new failed: ");
225 if (!EVP_PKEY_assign_RSA(certificate_pkey.get(), certificate_rsa))
226 SSL_ERROR(
"EVP_PKEY_assign_RSA failed: ");
228 if (X509_set_pubkey(certificate.get(), certificate_pkey.get()) == 0) SSL_ERROR(
"X509_set_pubkey failed: ");
231 if (X509_gmtime_adj(X509_get_notBefore(certificate.get()), 0) ==
nullptr) SSL_ERROR(
"X509_gmtime_adj failed: ");
233 if (X509_gmtime_adj(X509_get_notAfter(certificate.get()), 60L * 60L * 24L * cert_days) ==
nullptr)
234 SSL_ERROR(
"X509_gmtime_adj failed: ");
237 const EVP_MD* cert_digest = EVP_sha256();
238 if (X509_sign(certificate.get(), ca_privkey.get(), cert_digest) == 0) SSL_ERROR(
"X509_sign failed: ");
242 StructGuard<BIO> privkey_file(BIO_new(BIO_s_mem()), BIO_vfree);
243 if (privkey_file ==
nullptr) {
244 std::cerr <<
"Error opening memstream" << std::endl;
247 int ret = PEM_write_bio_RSAPrivateKey(privkey_file.get(), certificate_rsa,
nullptr,
nullptr, 0,
nullptr,
nullptr);
249 std::cerr <<
"PEM_write_RSAPrivateKey" << std::endl;
253 auto privkey_len = BIO_get_mem_data(privkey_file.get(), &privkey_buf);
254 *pkey = std::string(privkey_buf, static_cast<size_t>(privkey_len));
258 StructGuard<BIO> cert_file(BIO_new(BIO_s_mem()), BIO_vfree);
259 if (cert_file ==
nullptr) {
260 std::cerr <<
"Error opening memstream" << std::endl;
263 ret = PEM_write_bio_X509(cert_file.get(), certificate.get());
265 std::cerr <<
"PEM_write_X509" << std::endl;
269 auto cert_len = BIO_get_mem_data(cert_file.get(), &cert_buf);
270 *cert = std::string(cert_buf, static_cast<size_t>(cert_len));
277 SSHRunner(std::string target,
const bool skip_checks,
const int port = 22)
278 : target_(std::move(target)), skip_checks_(skip_checks), port_(port) {}
280 void runCmd(
const std::string& cmd)
const {
281 std::ostringstream prefix;
285 prefix <<
"-p " << port_ <<
" ";
288 prefix <<
"-o StrictHostKeyChecking=no ";
290 prefix << target_ <<
" ";
292 std::string fullCmd = prefix.str() + cmd;
293 std::cout <<
"Running " << fullCmd << std::endl;
295 int ret = system(fullCmd.c_str());
297 throw std::runtime_error(
"Error running command on " + target_ +
": " + std::to_string(ret));
301 void transferFile(
const boost::filesystem::path& inFile,
const boost::filesystem::path& targetPath) {
303 runCmd(
"mkdir -p " + targetPath.parent_path().string());
305 std::ostringstream prefix;
309 prefix <<
"-P " << port_ <<
" ";
312 prefix <<
"-o StrictHostKeyChecking=no ";
315 std::string fullCmd = prefix.str() + inFile.string() +
" " + target_ +
":" + targetPath.string();
316 std::cout <<
"Running " << fullCmd << std::endl;
318 int ret = system(fullCmd.c_str());
320 throw std::runtime_error(
"Error copying file on " + target_ +
": " + std::to_string(ret));
330 void copyLocal(
const boost::filesystem::path& src,
const boost::filesystem::path& dest) {
331 boost::filesystem::path dest_dir = dest.parent_path();
332 if (boost::filesystem::exists(dest_dir)) {
333 boost::filesystem::remove(dest);
335 boost::filesystem::create_directories(dest_dir);
337 boost::filesystem::copy_file(src, dest);
340 int main(
int argc,
char* argv[]) {
341 int exit_code = EXIT_FAILURE;
344 logger_set_threshold(static_cast<boost::log::trivial::severity_level>(2));
347 bpo::variables_map commandline_map = parseOptions(argc, argv);
350 if (commandline_map.count(
"target") != 0) {
351 target = commandline_map[
"target"].as<std::string>();
354 if (commandline_map.count(
"port") != 0) {
355 port = (commandline_map[
"port"].as<
int>());
357 const bool provide_ca = commandline_map.count(
"root-ca") != 0;
358 const bool provide_url = commandline_map.count(
"server-url") != 0;
359 boost::filesystem::path local_dir;
360 if (commandline_map.count(
"local") != 0) {
361 local_dir = commandline_map[
"local"].as<boost::filesystem::path>();
363 std::vector<boost::filesystem::path> config_path;
364 if (commandline_map.count(
"config") != 0) {
365 config_path = commandline_map[
"config"].as<std::vector<boost::filesystem::path>>();
367 const bool skip_checks = commandline_map.count(
"skip-checks") != 0;
369 boost::filesystem::path fleet_ca_path =
"";
370 if (commandline_map.count(
"fleet-ca") != 0) {
371 fleet_ca_path = commandline_map[
"fleet-ca"].as<boost::filesystem::path>();
374 boost::filesystem::path fleet_ca_key_path =
"";
375 if (commandline_map.count(
"fleet-ca-key") != 0) {
376 fleet_ca_key_path = commandline_map[
"fleet-ca-key"].as<boost::filesystem::path>();
379 if (fleet_ca_path.empty() != fleet_ca_key_path.empty()) {
380 std::cerr <<
"fleet-ca and fleet-ca-key options should be used together" << std::endl;
384 if (!commandline_map[
"directory"].empty() && !commandline_map[
"config"].empty()) {
385 std::cerr <<
"Directory (--directory) and config (--config) options cannot be used together" << std::endl;
389 boost::filesystem::path credentials_path =
"";
390 if (commandline_map.count(
"credentials") != 0) {
391 credentials_path = commandline_map[
"credentials"].as<boost::filesystem::path>();
394 if (local_dir.empty() && target.empty()) {
395 std::cerr <<
"Please provide a local directory and/or target to output the generated files to" << std::endl;
399 std::string serverUrl;
400 if ((fleet_ca_path.empty() || provide_ca || provide_url) && credentials_path.empty()) {
402 <<
"Error: missing -c/--credentials parameters which is mandatory if the fleet CA is not specified or an " 403 "output of the root CA or a gateway URL is requested";
406 serverUrl = Bootstrap::readServerUrl(credentials_path);
409 boost::filesystem::path directory =
"/var/sota/import";
414 if (!config_path.empty()) {
415 Config config(config_path);
419 if (!config.import.base_path.empty()) {
420 directory = config.import.base_path;
421 }
else if (!config.storage.path.empty()) {
422 directory = config.storage.path;
425 if (!config.import.tls_pkey_path.empty()) {
426 pkey_file = config.import.tls_pkey_path;
428 pkey_file = config.storage.tls_pkey_path;
431 if (!config.import.tls_clientcert_path.empty()) {
432 cert_file = config.import.tls_clientcert_path;
434 cert_file = config.storage.tls_clientcert_path;
437 if (!config.import.tls_cacert_path.empty()) {
438 ca_file = config.import.tls_cacert_path;
440 ca_file = config.storage.tls_cacert_path;
444 url_file = config.tls.server_url_path;
448 if (!commandline_map[
"directory"].empty()) {
449 directory = commandline_map[
"directory"].as<boost::filesystem::path>();
452 TemporaryFile tmp_pkey_file(pkey_file.get(
"").filename().string());
453 TemporaryFile tmp_cert_file(cert_file.get(
"").filename().string());
454 TemporaryFile tmp_ca_file(ca_file.get(
"").filename().string());
455 TemporaryFile tmp_url_file(url_file.get(
"").filename().string());
461 if (fleet_ca_path.empty()) {
463 std::string device_id = Utils::genPrettyName();
464 std::cout <<
"Random device ID is " << device_id <<
"\n";
469 data[
"deviceId"] = device_id;
472 std::cout <<
"Provisioning against server...\n";
473 http.setCerts(boot.getCa(), CryptoSource::kFile, boot.getCert(), CryptoSource::kFile, boot.getPkey(),
474 CryptoSource::kFile);
475 HttpResponse response = http.post(serverUrl +
"/devices", data);
476 if (!response.isOk()) {
477 Json::Value resp_code = response.getJson()[
"code"];
478 if (resp_code.isString() && resp_code.asString() ==
"device_already_registered") {
479 std::cout <<
"Device ID" << device_id <<
"is occupied.\n";
482 std::cout <<
"Provisioning failed, response: " << response.body <<
"\n";
485 std::cout <<
"...success\n";
487 StructGuard<BIO> device_p12(BIO_new_mem_buf(response.body.c_str(),
static_cast<int>(response.body.size())),
489 if (!Crypto::parseP12(device_p12.get(),
"", &pkey, &cert, &ca)) {
490 std::cout <<
"Unable to parse p12 file received from server.\n";
494 if (!generateAndSign(fleet_ca_path.native(), fleet_ca_key_path.native(), &pkey, &cert, commandline_map)) {
502 ca = Bootstrap::readServerCa(credentials_path);
506 std::cout <<
"Server root CA read from autoprov_credentials.p12 in zipped archive.\n";
508 std::cout <<
"Server root CA read from server_ca.pem in zipped archive.\n";
513 tmp_pkey_file.PutContents(pkey);
514 tmp_cert_file.PutContents(cert);
516 tmp_ca_file.PutContents(ca);
519 tmp_url_file.PutContents(serverUrl);
522 if (!local_dir.empty()) {
523 auto pkey_file_path = local_dir / pkey_file.get(directory);
524 std::cout <<
"Writing the generated client private key to " << pkey_file_path <<
" ...\n";
525 copyLocal(tmp_pkey_file.PathString(), pkey_file_path);
527 auto cert_file_path = local_dir / cert_file.get(directory);
528 std::cout <<
"Writing the generated and signed client certificate to " << cert_file_path <<
" ...\n";
529 copyLocal(tmp_cert_file.PathString(), cert_file_path);
532 auto root_ca_file = local_dir / ca_file.get(directory);
533 std::cout <<
"Writing the server root CA to " << root_ca_file <<
" ...\n";
534 copyLocal(tmp_ca_file.PathString(), root_ca_file);
537 auto gtw_url_file = local_dir / url_file.get(
"");
538 std::cout <<
"Writing the gateway URL to " << gtw_url_file <<
" ...\n";
539 copyLocal(tmp_url_file.PathString(), gtw_url_file);
541 std::cout <<
"...success\n";
544 if (!target.empty()) {
545 std::cout <<
"Copying client certificate and keys to " << target <<
":" << directory;
547 std::cout <<
" on port " << port;
549 std::cout <<
" ...\n";
551 SSHRunner ssh{target, skip_checks, port};
554 ssh.transferFile(tmp_pkey_file.Path(), pkey_file.get(directory));
556 ssh.transferFile(tmp_cert_file.Path(), cert_file.get(directory));
559 ssh.transferFile(tmp_ca_file.Path(), ca_file.get(directory));
562 ssh.transferFile(tmp_url_file.Path(), url_file.get(directory));
565 std::cout <<
"...success\n";
566 }
catch (
const std::runtime_error& exc) {
567 std::cout << exc.what() << std::endl;
571 exit_code = EXIT_SUCCESS;
572 }
catch (
const std::exception& exc) {
573 LOG_ERROR <<
"Error: " << exc.what();
575 exit_code = EXIT_FAILURE;
Configuration object for an aktualizr instance running on a Primary ECU.
RAII Temporary file creation.