#ifndef _PX5G_OPENSSL_HPP #define _PX5G_OPENSSL_HPP // #define OPENSSL_API_COMPAT 0x10102000L #include #include #include #include #include #include #include #include #include static constexpr auto rsa_min_modulus_bits = 512; using EVP_PKEY_ptr = std::unique_ptr; using X509_NAME_ptr = std::unique_ptr; auto checkend(const std::string& crtpath, time_t seconds = 0, bool use_pem = true) -> bool; auto gen_eckey(int curve) -> EVP_PKEY_ptr; auto gen_rsakey(int keysize, BN_ULONG exponent = RSA_F4) -> EVP_PKEY_ptr; void write_key(const EVP_PKEY_ptr& pkey, const std::string& keypath = "", bool use_pem = true); auto subject2name(const std::string& subject) -> X509_NAME_ptr; void selfsigned(const EVP_PKEY_ptr& pkey, int days, const std::string& subject = "", const std::string& crtpath = "", bool use_pem = true); // ------------------------- implementation: ---------------------------------- inline auto print_error(const char* str, const size_t /*len*/, void* errmsg) -> int { *static_cast(errmsg) += str; return 0; } // wrapper for clang-tidy: inline auto _BIO_new_fp(FILE* stream, const bool use_pem, const bool close = false) -> BIO* { return BIO_new_fp(stream, // NOLINTNEXTLINE(hicpp-signed-bitwise) macros: (use_pem ? BIO_FP_TEXT : 0) | (close ? BIO_CLOSE : BIO_NOCLOSE)); } auto checkend(const std::string& crtpath, const time_t seconds, const bool use_pem) -> bool { BIO* bio = crtpath.empty() ? _BIO_new_fp(stdin, use_pem) : BIO_new_file(crtpath.c_str(), (use_pem ? "r" : "rb")); X509* x509 = nullptr; if (bio != nullptr) { x509 = use_pem ? PEM_read_bio_X509_AUX(bio, nullptr, nullptr, nullptr) : d2i_X509_bio(bio, nullptr); BIO_free(bio); } if (x509 == nullptr) { std::string errmsg{"checkend error: unable to load certificate\n"}; ERR_print_errors_cb(print_error, &errmsg); throw std::runtime_error(errmsg); } time_t checktime = time(nullptr) + seconds; auto cmp = X509_cmp_time(X509_get0_notAfter(x509), &checktime); X509_free(x509); return (cmp >= 0); } auto gen_eckey(const int curve) -> EVP_PKEY_ptr { EC_GROUP* group = curve != 0 ? EC_GROUP_new_by_curve_name(curve) : nullptr; if (group == nullptr) { std::string errmsg{"gen_eckey error: cannot build group for curve id "}; errmsg += std::to_string(curve) + "\n"; ERR_print_errors_cb(print_error, &errmsg); throw std::runtime_error(errmsg); } EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE); EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_UNCOMPRESSED); auto* eckey = EC_KEY_new(); if (eckey != nullptr) { if ((EC_KEY_set_group(eckey, group) == 0) || (EC_KEY_generate_key(eckey) == 0)) { EC_KEY_free(eckey); eckey = nullptr; } } EC_GROUP_free(group); if (eckey == nullptr) { std::string errmsg{"gen_eckey error: cannot build key with curve id "}; errmsg += std::to_string(curve) + "\n"; ERR_print_errors_cb(print_error, &errmsg); throw std::runtime_error(errmsg); } EVP_PKEY_ptr pkey{EVP_PKEY_new(), ::EVP_PKEY_free}; // EVP_PKEY_assign_EC_KEY is a macro casting eckey to char *: // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) if (!EVP_PKEY_assign_EC_KEY(pkey.get(), eckey)) { EC_KEY_free(eckey); std::string errmsg{"gen_eckey error: cannot assign EC key to EVP\n"}; ERR_print_errors_cb(print_error, &errmsg); throw std::runtime_error(errmsg); } return pkey; } auto gen_rsakey(const int keysize, const BN_ULONG exponent) -> EVP_PKEY_ptr { if (keysize < rsa_min_modulus_bits || keysize > OPENSSL_RSA_MAX_MODULUS_BITS) { std::string errmsg{"gen_rsakey error: RSA keysize ("}; errmsg += std::to_string(keysize) + ") out of range [512.."; errmsg += std::to_string(OPENSSL_RSA_MAX_MODULUS_BITS) + "]"; throw std::runtime_error(errmsg); } auto* bignum = BN_new(); if (bignum == nullptr) { std::string errmsg{"gen_rsakey error: cannot get big number struct\n"}; ERR_print_errors_cb(print_error, &errmsg); throw std::runtime_error(errmsg); } auto* rsa = RSA_new(); if (rsa != nullptr) { if ((BN_set_word(bignum, exponent) == 0) || (RSA_generate_key_ex(rsa, keysize, bignum, nullptr) == 0)) { RSA_free(rsa); rsa = nullptr; } } BN_free(bignum); if (rsa == nullptr) { std::string errmsg{"gen_rsakey error: cannot create RSA key with size"}; errmsg += std::to_string(keysize) + " and exponent "; errmsg += std::to_string(exponent) + "\n"; ERR_print_errors_cb(print_error, &errmsg); throw std::runtime_error(errmsg); } EVP_PKEY_ptr pkey{EVP_PKEY_new(), ::EVP_PKEY_free}; // EVP_PKEY_assign_RSA is a macro casting rsa to char *: // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) if (!EVP_PKEY_assign_RSA(pkey.get(), rsa)) { RSA_free(rsa); std::string errmsg{"gen_rsakey error: cannot assign RSA key to EVP\n"}; ERR_print_errors_cb(print_error, &errmsg); throw std::runtime_error(errmsg); } return pkey; } void write_key(const EVP_PKEY_ptr& pkey, const std::string& keypath, const bool use_pem) { BIO* bio = nullptr; if (keypath.empty()) { bio = _BIO_new_fp(stdout, use_pem); } else { // BIO_new_file(keypath.c_str(), (use_pem ? "w" : "wb") ); static constexpr auto mask = 0600; // auto fd = open(keypath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mask); // creat has no cloexec, alt. triggers cppcoreguidelines-pro-type-vararg // NOLINTNEXTLINE(android-cloexec-creat) auto fd = creat(keypath.c_str(), mask); // the same without va_args. if (fd >= 0) { auto* fp = fdopen(fd, (use_pem ? "w" : "wb")); if (fp != nullptr) { bio = _BIO_new_fp(fp, use_pem, true); if (bio == nullptr) { // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) fp owns fd: fclose(fp); } } else { close(fd); } } } if (bio == nullptr) { std::string errmsg{"write_key error: cannot open "}; errmsg += keypath.empty() ? "stdout" : keypath; errmsg += "\n"; ERR_print_errors_cb(print_error, &errmsg); throw std::runtime_error(errmsg); } int len = 0; auto* key = pkey.get(); switch (EVP_PKEY_base_id(key)) { // use same format as px5g: case EVP_PKEY_EC: len = use_pem ? PEM_write_bio_ECPrivateKey(bio, EVP_PKEY_get0_EC_KEY(key), nullptr, nullptr, 0, nullptr, nullptr) : i2d_ECPrivateKey_bio(bio, EVP_PKEY_get0_EC_KEY(key)); break; case EVP_PKEY_RSA: len = use_pem ? PEM_write_bio_RSAPrivateKey(bio, EVP_PKEY_get0_RSA(key), nullptr, nullptr, 0, nullptr, nullptr) : i2d_RSAPrivateKey_bio(bio, EVP_PKEY_get0_RSA(key)); break; default: len = use_pem ? PEM_write_bio_PrivateKey(bio, key, nullptr, nullptr, 0, nullptr, nullptr) : i2d_PrivateKey_bio(bio, key); } BIO_free_all(bio); if (len == 0) { std::string errmsg{"write_key error: cannot write EVP pkey to "}; errmsg += keypath.empty() ? "stdout" : keypath; errmsg += "\n"; ERR_print_errors_cb(print_error, &errmsg); throw std::runtime_error(errmsg); } } auto subject2name(const std::string& subject) -> X509_NAME_ptr { if (!subject.empty() && subject[0] != '/') { throw std::runtime_error("subject2name errror: not starting with /"); } X509_NAME_ptr name = {X509_NAME_new(), ::X509_NAME_free}; if (!name) { std::string errmsg{"subject2name error: cannot create X509 name \n"}; ERR_print_errors_cb(print_error, &errmsg); throw std::runtime_error(errmsg); } if (subject.empty()) { return name; } int prev = 1; std::string type{}; char chr = '='; for (int i = 0; subject[i] != 0;) { ++i; if (subject[i] == '\\' && subject[++i] == '\0') { throw std::runtime_error("subject2name errror: escape at the end"); } if (subject[i] != chr && subject[i] != '\0') { continue; } if (chr == '=') { type = subject.substr(prev, i - prev); chr = '/'; } else { auto nid = OBJ_txt2nid(type.c_str()); if (nid == NID_undef) { // skip unknown entries (silently?). } else { const auto* val = // X509_NAME_add_entry_by_NID wants it unsigned: // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) reinterpret_cast(&subject[prev]); int len = i - prev; if (X509_NAME_add_entry_by_NID( name.get(), nid, MBSTRING_ASC, // NOLINT(hicpp-signed-bitwise) is macro val, len, -1, 0) == 0) { std::string errmsg{"subject2name error: cannot add "}; errmsg += "/" + type + "=" + subject.substr(prev, len) + "\n"; ERR_print_errors_cb(print_error, &errmsg); throw std::runtime_error(errmsg); } } chr = '='; } prev = i + 1; } return name; } void selfsigned(const EVP_PKEY_ptr& pkey, const int days, const std::string& subject, const std::string& crtpath, const bool use_pem) { auto* x509 = X509_new(); if (x509 == nullptr) { std::string errmsg{"selfsigned error: cannot create X509 structure\n"}; ERR_print_errors_cb(print_error, &errmsg); throw std::runtime_error(errmsg); } auto freeX509_and_throw = [&x509](const std::string& what) { X509_free(x509); std::string errmsg{"selfsigned error: cannot set "}; errmsg += what + " in X509 certificate\n"; ERR_print_errors_cb(print_error, &errmsg); throw std::runtime_error(errmsg); }; if (X509_set_version(x509, 2) == 0) { freeX509_and_throw("version"); } if (X509_set_pubkey(x509, pkey.get()) == 0) { freeX509_and_throw("pubkey"); } if ((X509_gmtime_adj(X509_getm_notBefore(x509), 0) == nullptr) || (X509_time_adj_ex(X509_getm_notAfter(x509), days, 0, nullptr) == nullptr)) { freeX509_and_throw("times"); } X509_NAME_ptr name{nullptr, ::X509_NAME_free}; try { name = subject2name(subject); } catch (...) { X509_free(x509); throw; } if (X509_set_subject_name(x509, name.get()) == 0) { freeX509_and_throw("subject"); } if (X509_set_issuer_name(x509, name.get()) == 0) { freeX509_and_throw("issuer"); } auto* bignum = BN_new(); if (bignum == nullptr) { freeX509_and_throw("serial (creating big number struct)"); } static const auto BITS = 159; if (BN_rand(bignum, BITS, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY) == 0) { BN_free(bignum); freeX509_and_throw("serial (creating random number)"); } if (BN_to_ASN1_INTEGER(bignum, X509_get_serialNumber(x509)) == nullptr) { BN_free(bignum); freeX509_and_throw("random serial"); } BN_free(bignum); if (X509_sign(x509, pkey.get(), EVP_sha256()) == 0) { freeX509_and_throw("signing digest"); } BIO* bio = crtpath.empty() ? _BIO_new_fp(stdout, use_pem) : BIO_new_file(crtpath.c_str(), (use_pem ? "w" : "wb")); int len = 0; if (bio != nullptr) { len = use_pem ? PEM_write_bio_X509(bio, x509) : i2d_X509_bio(bio, x509); BIO_free_all(bio); } X509_free(x509); if (len == 0) { std::string errmsg{"selfsigned error: cannot write certificate to "}; errmsg += crtpath.empty() ? "stdout" : crtpath; errmsg += "\n"; ERR_print_errors_cb(print_error, &errmsg); throw std::runtime_error(errmsg); } } #endif