Skip to content
This repository has been archived by the owner on May 21, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1743 from advancedtelematic/feat/OTA-5178/re-impo…
Browse files Browse the repository at this point in the history
…rt-client-cert

Allow re-importing the client TLS certifcate.
  • Loading branch information
pattivacek authored Aug 25, 2020
2 parents 7306cc5 + dcad65b commit 3d4d39e
Show file tree
Hide file tree
Showing 10 changed files with 402 additions and 279 deletions.
11 changes: 4 additions & 7 deletions src/cert_provider/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
set(CERT_PROVIDER_SRC main.cc)

# set the name of the executable
add_executable(aktualizr-cert-provider main.cc
add_executable(aktualizr-cert-provider ${CERT_PROVIDER_SRC}
$<TARGET_OBJECTS:bootstrap>
$<TARGET_OBJECTS:config>
$<TARGET_OBJECTS:crypto>
Expand All @@ -9,8 +11,6 @@ add_executable(aktualizr-cert-provider main.cc
$<TARGET_OBJECTS:primary_config>
$<TARGET_OBJECTS:utilities>)

set_source_files_properties(main.cc PROPERTIES COMPILE_FLAGS -Wno-deprecated-declarations)

target_link_libraries(aktualizr-cert-provider
${CMAKE_THREAD_LIBS_INIT}
${Boost_SYSTEM_LIBRARIES}
Expand All @@ -23,8 +23,6 @@ target_link_libraries(aktualizr-cert-provider
${sodium_LIBRARY_RELEASE}
)

aktualizr_source_file_checks(main.cc)

add_dependencies(build_tests aktualizr-cert-provider)

install(TARGETS aktualizr-cert-provider RUNTIME DESTINATION bin COMPONENT garage_deploy)
Expand All @@ -46,8 +44,7 @@ add_test(NAME aktualizr-cert-provider-option-version
COMMAND aktualizr-cert-provider --version)
set_tests_properties(aktualizr-cert-provider-option-version PROPERTIES PASS_REGULAR_EXPRESSION "Current aktualizr-cert-provider version is: ${AKTUALIZR_VERSION}")

aktualizr_source_file_checks(${AKTUALIZR_CERT_PROVIDER_SRC}
${AKTUALIZR_CERT_HEADERS}
aktualizr_source_file_checks(${CERT_PROVIDER_SRC}
cert_provider_shared_cred_test.cc
cert_provider_test.cc
cert_provider_test.h)
Expand Down
254 changes: 57 additions & 197 deletions src/cert_provider/main.cc
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
#include <cstdlib>
#include <iostream>
#include <random>
#include <sstream>
#include <string>
#include <utility>

#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
Expand Down Expand Up @@ -45,12 +43,12 @@ bpo::variables_map parseOptions(int argc, char** argv) {
("certificate-c", bpo::value<std::string>(), "value for C field in certificate subject name")
("certificate-st", bpo::value<std::string>(), "value for ST field in certificate subject name")
("certificate-o", bpo::value<std::string>(), "value for O field in certificate subject name")
("certificate-cn", bpo::value<std::string>(), "value for CN field in certificate subject name")
("certificate-cn", bpo::value<std::string>(), "value for CN field in certificate subject name (used for device ID)")
("target,t", bpo::value<std::string>(), "target device to scp credentials to (or [user@]host)")
("port,p", bpo::value<int>(), "target port")
("directory,d", bpo::value<boost::filesystem::path>(), "directory on target to write credentials to (conflicts with -config)")
("root-ca,r", "provide root CA")
("server-url,u", "provide server url file")
("directory,d", bpo::value<boost::filesystem::path>(), "directory on target to write credentials to (conflicts with --config)")
("root-ca,r", "provide root CA certificate")
("server-url,u", "provide server URL file")
("local,l", bpo::value<boost::filesystem::path>(), "local directory to write credentials to")
("config,g", bpo::value<std::vector<boost::filesystem::path> >()->composing(), "configuration file or directory from which to get file names")
("skip-checks,s", "skip strict host key checking for ssh/scp commands");
Expand Down Expand Up @@ -88,190 +86,6 @@ bpo::variables_map parseOptions(int argc, char** argv) {
return vm;
}

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define SSL_ERROR(description) \
{ \
std::cerr << (description) << ERR_error_string(ERR_get_error(), nullptr) << std::endl; \
return false; \
}
bool generateAndSign(const std::string& cacert_path, const std::string& capkey_path, std::string* pkey,
std::string* cert, const bpo::variables_map& commandline_map) {
int rsa_bits = 2048;
if (commandline_map.count("bits") != 0) {
rsa_bits = (commandline_map["bits"].as<int>());
if (rsa_bits < 31) { // sic!
std::cerr << "RSA key size can't be smaller than 31 bits" << std::endl;
return false;
}
}

int cert_days = 365;
if (commandline_map.count("days") != 0) {
cert_days = (commandline_map["days"].as<int>());
}

std::string newcert_c;
if (commandline_map.count("certificate-c") != 0) {
newcert_c = (commandline_map["certificate-c"].as<std::string>());
if (newcert_c.length() != 2) {
std::cerr << "Country code (--certificate-c) should be 2 characters long" << std::endl;
return false;
}
};

std::string newcert_st;
if (commandline_map.count("certificate-st") != 0) {
newcert_st = (commandline_map["certificate-st"].as<std::string>());
if (newcert_st.empty()) {
std::cerr << "State name (--certificate-st) can't be empty" << std::endl;
return false;
}
};

std::string newcert_o;
if (commandline_map.count("certificate-o") != 0) {
newcert_o = (commandline_map["certificate-o"].as<std::string>());
if (newcert_o.empty()) {
std::cerr << "Organization name (--certificate-o) can't be empty" << std::endl;
return false;
}
};

std::string newcert_cn;
if (commandline_map.count("certificate-cn") != 0) {
newcert_cn = (commandline_map["certificate-cn"].as<std::string>());
if (newcert_cn.empty()) {
std::cerr << "Common name (--certificate-cn) can't be empty" << std::endl;
return false;
}
} else {
newcert_cn = Utils::genPrettyName();
}

// read CA certificate
std::string cacert_contents = Utils::readFile(cacert_path);
StructGuard<BIO> bio_in_cacert(BIO_new_mem_buf(cacert_contents.c_str(), static_cast<int>(cacert_contents.size())),
BIO_free_all);
StructGuard<X509> ca_certificate(PEM_read_bio_X509(bio_in_cacert.get(), nullptr, nullptr, nullptr), X509_free);
if (ca_certificate.get() == nullptr) {
std::cerr << "Reading CA certificate failed.\n";
return false;
}

// read CA private key
std::string capkey_contents = Utils::readFile(capkey_path);
StructGuard<BIO> bio_in_capkey(BIO_new_mem_buf(capkey_contents.c_str(), static_cast<int>(capkey_contents.size())),
BIO_free_all);
StructGuard<EVP_PKEY> ca_privkey(PEM_read_bio_PrivateKey(bio_in_capkey.get(), nullptr, nullptr, nullptr),
EVP_PKEY_free);
if (ca_privkey.get() == nullptr) SSL_ERROR("PEM_read_bio_PrivateKey failed: ");

// create certificate
StructGuard<X509> certificate(X509_new(), X509_free);
if (certificate.get() == nullptr) SSL_ERROR("X509_new failed: ");

X509_set_version(certificate.get(), 2); // X509v3

{
std::random_device urandom;
std::uniform_int_distribution<> serial_dist(0, (1UL << 20) - 1);
ASN1_INTEGER_set(X509_get_serialNumber(certificate.get()), serial_dist(urandom));
}

// create and set certificate subject name
StructGuard<X509_NAME> subj(X509_NAME_new(), X509_NAME_free);
if (subj.get() == nullptr) SSL_ERROR("X509_NAME_new failed: ");

if (!newcert_c.empty()) {
if (X509_NAME_add_entry_by_txt(subj.get(), "C", MBSTRING_ASC,
reinterpret_cast<const unsigned char*>(newcert_c.c_str()), -1, -1, 0) == 0)
SSL_ERROR("X509_NAME_add_entry_by_txt failed: ");
}

if (!newcert_st.empty()) {
if (X509_NAME_add_entry_by_txt(subj.get(), "ST", MBSTRING_ASC,
reinterpret_cast<const unsigned char*>(newcert_st.c_str()), -1, -1, 0) == 0)
SSL_ERROR("X509_NAME_add_entry_by_txt failed: ");
}

if (!newcert_o.empty()) {
if (X509_NAME_add_entry_by_txt(subj.get(), "O", MBSTRING_ASC,
reinterpret_cast<const unsigned char*>(newcert_o.c_str()), -1, -1, 0) == 0)
SSL_ERROR("X509_NAME_add_entry_by_txt failed: ");
}

assert(!newcert_cn.empty());
if (X509_NAME_add_entry_by_txt(subj.get(), "CN", MBSTRING_ASC,
reinterpret_cast<const unsigned char*>(newcert_cn.c_str()), -1, -1, 0) == 0)
SSL_ERROR("X509_NAME_add_entry_by_txt failed: ");

if (X509_set_subject_name(certificate.get(), subj.get()) == 0) SSL_ERROR("X509_set_subject_name failed: ");

// set issuer name
X509_NAME* ca_subj = X509_get_subject_name(ca_certificate.get());
if (ca_subj == nullptr) SSL_ERROR("X509_get_subject_name failed: ");

if (X509_set_issuer_name(certificate.get(), ca_subj) == 0) SSL_ERROR("X509_set_issuer_name failed: ");

// create and set key

// freed by owner EVP_PKEY
RSA* certificate_rsa = RSA_generate_key(rsa_bits, RSA_F4, nullptr, nullptr);
if (certificate_rsa == nullptr) SSL_ERROR("RSA_generate_key failed: ");

StructGuard<EVP_PKEY> certificate_pkey(EVP_PKEY_new(), EVP_PKEY_free);
if (certificate_pkey.get() == nullptr) SSL_ERROR("EVP_PKEY_new failed: ");

if (!EVP_PKEY_assign_RSA(certificate_pkey.get(), certificate_rsa)) // NOLINT
SSL_ERROR("EVP_PKEY_assign_RSA failed: ");

if (X509_set_pubkey(certificate.get(), certificate_pkey.get()) == 0) SSL_ERROR("X509_set_pubkey failed: ");

// set validity period
if (X509_gmtime_adj(X509_get_notBefore(certificate.get()), 0) == nullptr) SSL_ERROR("X509_gmtime_adj failed: ");

if (X509_gmtime_adj(X509_get_notAfter(certificate.get()), 60L * 60L * 24L * cert_days) == nullptr)
SSL_ERROR("X509_gmtime_adj failed: ");

// sign
const EVP_MD* cert_digest = EVP_sha256();
if (X509_sign(certificate.get(), ca_privkey.get(), cert_digest) == 0) SSL_ERROR("X509_sign failed: ");

// serialize private key
char* privkey_buf;
StructGuard<BIO> privkey_file(BIO_new(BIO_s_mem()), BIO_vfree);
if (privkey_file == nullptr) {
std::cerr << "Error opening memstream" << std::endl;
return false;
}
int ret = PEM_write_bio_RSAPrivateKey(privkey_file.get(), certificate_rsa, nullptr, nullptr, 0, nullptr, nullptr);
if (ret == 0) {
std::cerr << "PEM_write_RSAPrivateKey" << std::endl;
return false;
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
auto privkey_len = BIO_get_mem_data(privkey_file.get(), &privkey_buf);
*pkey = std::string(privkey_buf, static_cast<size_t>(privkey_len));

// serialize certificate
char* cert_buf;
StructGuard<BIO> cert_file(BIO_new(BIO_s_mem()), BIO_vfree);
if (cert_file == nullptr) {
std::cerr << "Error opening memstream" << std::endl;
return false;
}
ret = PEM_write_bio_X509(cert_file.get(), certificate.get());
if (ret == 0) {
std::cerr << "PEM_write_X509" << std::endl;
return false;
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
auto cert_len = BIO_get_mem_data(cert_file.get(), &cert_buf);
*cert = std::string(cert_buf, static_cast<size_t>(cert_len));

return true;
}

class SSHRunner {
public:
SSHRunner(std::string target, const bool skip_checks, const int port = 22)
Expand Down Expand Up @@ -378,7 +192,7 @@ int main(int argc, char* argv[]) {

if (fleet_ca_path.empty() != fleet_ca_key_path.empty()) {
std::cerr << "fleet-ca and fleet-ca-key options should be used together" << std::endl;
return 1;
return EXIT_FAILURE;
}

if (!commandline_map["directory"].empty() && !commandline_map["config"].empty()) {
Expand Down Expand Up @@ -406,6 +220,18 @@ int main(int argc, char* argv[]) {
serverUrl = Bootstrap::readServerUrl(credentials_path);
}

std::string device_id;
if (commandline_map.count("certificate-cn") != 0) {
device_id = (commandline_map["certificate-cn"].as<std::string>());
if (device_id.empty()) {
std::cerr << "Common name (device ID, --certificate-cn) can't be empty" << std::endl;
return EXIT_FAILURE;
}
} else {
device_id = Utils::genPrettyName();
std::cout << "Random device ID is " << device_id << "\n";
}

boost::filesystem::path directory = "/var/sota/import";
utils::BasedPath pkey_file = utils::BasedPath("pkey.pem");
utils::BasedPath cert_file = utils::BasedPath("client.pem");
Expand Down Expand Up @@ -458,10 +284,6 @@ int main(int argc, char* argv[]) {
std::string ca;

if (fleet_ca_path.empty()) { // no fleet CA => provision with shared credentials

std::string device_id = Utils::genPrettyName();
std::cout << "Random device ID is " << device_id << "\n";

Bootstrap boot(credentials_path, "");
HttpClient http;
Json::Value data;
Expand Down Expand Up @@ -490,10 +312,48 @@ int main(int argc, char* argv[]) {
return EXIT_FAILURE;
}
} else { // fleet CA set => generate and sign a new certificate
if (!generateAndSign(fleet_ca_path.native(), fleet_ca_key_path.native(), &pkey, &cert, commandline_map)) {
return EXIT_FAILURE;
int rsa_bits = 2048;
if (commandline_map.count("bits") != 0) {
rsa_bits = (commandline_map["bits"].as<int>());
}

int cert_days = 365;
if (commandline_map.count("days") != 0) {
cert_days = (commandline_map["days"].as<int>());
}

std::string newcert_c;
if (commandline_map.count("certificate-c") != 0) {
newcert_c = (commandline_map["certificate-c"].as<std::string>());
if (newcert_c.length() != 2) {
std::cerr << "Country code (--certificate-c) should be 2 characters long" << std::endl;
return EXIT_FAILURE;
}
}

std::string newcert_st;
if (commandline_map.count("certificate-st") != 0) {
newcert_st = (commandline_map["certificate-st"].as<std::string>());
if (newcert_st.empty()) {
std::cerr << "State name (--certificate-st) can't be empty" << std::endl;
return EXIT_FAILURE;
}
}

std::string newcert_o;
if (commandline_map.count("certificate-o") != 0) {
newcert_o = (commandline_map["certificate-o"].as<std::string>());
if (newcert_o.empty()) {
std::cerr << "Organization name (--certificate-o) can't be empty" << std::endl;
return EXIT_FAILURE;
}
}

StructGuard<X509> certificate =
Crypto::generateCert(rsa_bits, cert_days, newcert_c, newcert_st, newcert_o, device_id);
Crypto::signCert(fleet_ca_path.native(), fleet_ca_key_path.native(), certificate.get());
Crypto::serializeCert(&pkey, &cert, certificate.get());

if (provide_ca) {
// Read server root CA from server_ca.pem in archive if found (to support
// community edition use case). Otherwise, default to the old version of
Expand Down
Loading

0 comments on commit 3d4d39e

Please sign in to comment.