azure-protected-vm-secrets/Linux/OsslX509.cpp (393 lines of code) (raw):

// #include "X509.h" #include "OsslX509.h" #include "OsslError.h" #include <openssl/x509.h> #include <openssl/x509_vfy.h> #include <openssl/pem.h> #include <openssl/err.h> #include <openssl/evp.h> #include <stdio.h> #include "../JsonWebToken.h" #include <iostream> #include <vector> #include <string.h> #include <memory> #include "../LibraryLogger.h" #include "../ReturnCodes.h" #include "../DebugInfo.h" #include <openssl/conf.h> #include <openssl/x509v3.h> using namespace SecretsLogger; #define MY_ENCODING_TYPE (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING) #define TEMPBUF_SIZE 1024 #define SELFSIGNEDCERTNAME L"CN=SelfSignedCert" #define KEY_CONTAINER_NAME L"SelfSignedCertKeyContainer" #define DEBUG_SEPARATOR ", " std::string printBIO(BIO* bio) { char buffer[256]; int len; std::ostringstream ss; while ((len = BIO_read(bio, buffer, sizeof(buffer) - 1)) > 0) { buffer[len] = '\0'; ss << buffer; } BIO_reset(bio); return ss.str(); } // Format certificate subject and issuer details std::string printCertInfo(X509* cert, const std::string& label) { std::ostringstream ss; X509_NAME* subject = X509_get_subject_name(cert); X509_NAME* issuer = X509_get_issuer_name(cert); unsigned char md[EVP_MAX_MD_SIZE]; unsigned int n; if (X509_digest(cert, EVP_sha1(), md, &n)) { ss << "Thumbprint (SHA1): " << formatHexBuffer(md, n) << DEBUG_SEPARATOR; } char* subjectStr = X509_NAME_oneline(subject, nullptr, 0); char* issuerStr = X509_NAME_oneline(issuer, nullptr, 0); ss << label << " Certificate Details: "; ss << "Subject: " << subjectStr << DEBUG_SEPARATOR; ss << "Issuer: " << issuerStr << DEBUG_SEPARATOR; // Format validity period ASN1_TIME* not_before = X509_get_notBefore(cert); ASN1_TIME* not_after = X509_get_notAfter(cert); BIO* bio = BIO_new(BIO_s_mem()); ss << "Valid from: "; ASN1_TIME_print(bio, not_before); ss << printBIO(bio) << DEBUG_SEPARATOR; ss << "Valid until: "; ASN1_TIME_print(bio, not_after); ss << printBIO(bio) << DEBUG_SEPARATOR; BIO_free(bio); OPENSSL_free(subjectStr); OPENSSL_free(issuerStr); return ss.str(); } // Generate a new RSA key pair std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> generateKey(int bits = 2048) { EVP_PKEY* pkey = EVP_PKEY_new(); EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); if (!ctx || EVP_PKEY_keygen_init(ctx) <= 0) { EVP_PKEY_free(pkey); throw OsslError(ERR_get_error(), "Failed to initialize key generation"); } if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) <= 0) { EVP_PKEY_CTX_free(ctx); EVP_PKEY_free(pkey); throw OsslError(ERR_get_error(), "Failed to set key size"); } if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { EVP_PKEY_CTX_free(ctx); EVP_PKEY_free(pkey); throw OsslError(ERR_get_error(), "Failed to generate key pair"); } EVP_PKEY_CTX_free(ctx); return std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>(pkey, &EVP_PKEY_free); } // Create a certificate signing request (CSR) std::unique_ptr<X509_REQ, decltype(&X509_REQ_free)> generateCSR(EVP_PKEY* pkey, const std::string& commonName) { std::unique_ptr<X509_REQ, decltype(&X509_REQ_free)> req(X509_REQ_new(), &X509_REQ_free); std::unique_ptr<X509_NAME, decltype(&X509_NAME_free)> name(X509_NAME_new(), &X509_NAME_free); // Set the subject name fields if (!X509_NAME_add_entry_by_txt(name.get(), "C", MBSTRING_ASC, (const unsigned char*)"US", -1, -1, 0)) { throw OsslError(ERR_get_error(), "Failed to set country"); } if (!X509_NAME_add_entry_by_txt(name.get(), "ST", MBSTRING_ASC, (const unsigned char*)"State", -1, -1, 0)) { throw OsslError(ERR_get_error(), "Failed to set state"); } if (!X509_NAME_add_entry_by_txt(name.get(), "O", MBSTRING_ASC, (const unsigned char*)"Organization", -1, -1, 0)) { throw OsslError(ERR_get_error(), "Failed to set organization"); } if (!X509_NAME_add_entry_by_txt(name.get(), "CN", MBSTRING_ASC, (const unsigned char*)commonName.c_str(), -1, -1, 0)) { throw OsslError(ERR_get_error(), "Failed to set common name"); } if (!X509_REQ_set_subject_name(req.get(), name.get()) || !X509_REQ_set_pubkey(req.get(), pkey)) { throw OsslError(ERR_get_error(), "Failed to set subject name or public key"); } if (!X509_REQ_sign(req.get(), pkey, EVP_sha256())) { throw OsslError(ERR_get_error(), "Failed to sign CSR"); } return req; } // Add certificate extensions void addExtensions(X509* cert, X509* issuer, int is_ca, int pathlen = -1) { X509V3_CTX ctx; X509V3_set_ctx_nodb(&ctx); X509V3_set_ctx(&ctx, issuer, cert, NULL, NULL, 0); // Add Basic Constraints std::string bc; if (is_ca) { bc = "critical,CA:TRUE"; if (pathlen >= 0) { bc += ",pathlen:" + std::to_string(pathlen); } } else { bc = "critical,CA:FALSE"; } std::unique_ptr<X509_EXTENSION, decltype(&X509_EXTENSION_free)> ex( X509V3_EXT_conf_nid(NULL, &ctx, NID_basic_constraints, bc.c_str()), &X509_EXTENSION_free); if (!ex || X509_add_ext(cert, ex.get(), -1) != 1) { throw OsslError(ERR_get_error(), "Failed to add Basic Constraints extension"); } std::string ku = is_ca ? "critical,keyCertSign,cRLSign" : "critical,digitalSignature,keyEncipherment"; ex.reset(X509V3_EXT_conf_nid(NULL, &ctx, NID_key_usage, ku.c_str())); if (!ex || X509_add_ext(cert, ex.get(), -1) != 1) { throw OsslError(ERR_get_error(), "Failed to add Key Usage extension"); } ex.reset(X509V3_EXT_conf_nid(NULL, &ctx, NID_subject_key_identifier, "hash")); if (!ex || X509_add_ext(cert, ex.get(), -1) != 1) { throw OsslError(ERR_get_error(), "Failed to add Subject Key Identifier extension"); } if (issuer != NULL && issuer != cert) { ex.reset(X509V3_EXT_conf_nid(NULL, &ctx, NID_authority_key_identifier, "keyid:always")); if (!ex || X509_add_ext(cert, ex.get(), -1) != 1) { throw OsslError(ERR_get_error(), "Failed to add Authority Key Identifier extension"); } } } // Create and sign a certificate std::unique_ptr<X509, decltype(&X509_free)> signCertificate(X509_REQ* req, EVP_PKEY* issuerKey, X509* issuerCert, long serial, int days, int is_ca, int pathlen = -1) { std::unique_ptr<X509, decltype(&X509_free)> cert(X509_new(), &X509_free); // Set version to X509v3 if (X509_set_version(cert.get(), 2) != 1) { throw OsslError(ERR_get_error(), "Failed to set certificate version"); } // Set serial number if (ASN1_INTEGER_set(X509_get_serialNumber(cert.get()), serial) != 1) { throw OsslError(ERR_get_error(), "Failed to set serial number"); } // Set validity period if (X509_gmtime_adj(X509_get_notBefore(cert.get()), 0) == NULL) { throw OsslError(ERR_get_error(), "Failed to set notBefore time"); } if (X509_gmtime_adj(X509_get_notAfter(cert.get()), (long)60*60*24*days) == NULL) { throw OsslError(ERR_get_error(), "Failed to set notAfter time"); } // Set subject from CSR if (X509_set_subject_name(cert.get(), X509_REQ_get_subject_name(req)) != 1) { throw OsslError(ERR_get_error(), "Failed to set subject name"); } // Set issuer if (issuerCert == NULL) { // Self-signed: issuer = subject if (X509_set_issuer_name(cert.get(), X509_REQ_get_subject_name(req)) != 1) { throw OsslError(ERR_get_error(), "Failed to set issuer name for self-signed certificate"); } } else { if (X509_set_issuer_name(cert.get(), X509_get_subject_name(issuerCert)) != 1) { throw OsslError(ERR_get_error(), "Failed to set issuer name"); } } // Set public key from CSR EVP_PKEY* pubkey = X509_REQ_get0_pubkey(req); if (pubkey == NULL || X509_set_pubkey(cert.get(), pubkey) != 1) { throw OsslError(ERR_get_error(), "Failed to set public key"); } // Add extensions try { addExtensions(cert.get(), (issuerCert == NULL) ? cert.get() : issuerCert, is_ca, pathlen); } catch (const OsslError& e) { throw; } // Sign the certificate if (X509_sign(cert.get(), issuerKey, EVP_sha256()) == 0) { throw OsslError(ERR_get_error(), "Failed to sign certificate"); } return cert; } std::string getDerEncodedCertificate(X509* cert) { int len = i2d_X509(cert, nullptr); if (len < 0) { throw OsslError(ERR_get_error(), "Failed to get DER encoded length"); } std::vector<unsigned char> der(len); unsigned char* p = der.data(); len = i2d_X509(cert, &p); if (len < 0) { throw OsslError(ERR_get_error(), "Failed to encode certificate to DER"); } return encoders::base64_encode(der); } // Create a self-signed certificate std::unique_ptr<OsslX509> generateCertChain() { // Generate root CA std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> rootKey = generateKey(); std::unique_ptr<X509_REQ, decltype(&X509_REQ_free)> rootReq = generateCSR(rootKey.get(), "Root CA"); std::unique_ptr<X509, decltype(&X509_free)> rootCert = signCertificate(rootReq.get(), rootKey.get(), NULL, 1, 3650, 1, 1); LIBSECRETS_LOG( LogLevel::Info, "Certificate chain verification.", "Certificate chain verification with cert %s", printCertInfo(rootCert.get(), "Root").c_str()); // Generate intermediate CA std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> intermediateKey = generateKey(); std::unique_ptr<X509_REQ, decltype(&X509_REQ_free)> intermediateReq = generateCSR(intermediateKey.get(), "Intermediate CA"); std::unique_ptr<X509, decltype(&X509_free)> intermediateCert = signCertificate(intermediateReq.get(), rootKey.get(), rootCert.get(), 2, 1825, 1, 0); LIBSECRETS_LOG( LogLevel::Info, "Certificate chain verification.", "Certificate chain verification with cert %s", printCertInfo(intermediateCert.get(), "Inter CA").c_str()); // Generate leaf certificate std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> leafKey = generateKey(); std::unique_ptr<X509_REQ, decltype(&X509_REQ_free)> leafReq = generateCSR(leafKey.get(), "example.com"); std::unique_ptr<X509, decltype(&X509_free)> leafCert = signCertificate(leafReq.get(), intermediateKey.get(), intermediateCert.get(), 3, 365, 0); LIBSECRETS_LOG( LogLevel::Info, "Certificate chain verification.", "Certificate chain verification with cert %s", printCertInfo(leafCert.get(), "Leaf").c_str()); std::unique_ptr<OsslX509> retCertChain; try{ retCertChain = std::make_unique<OsslX509>((const char *)getDerEncodedCertificate(rootCert.get()).c_str()); retCertChain->LoadIntermediateCertificate((const char *)getDerEncodedCertificate(intermediateCert.get()).c_str()); retCertChain->LoadLeafCertificate((const char *)getDerEncodedCertificate(leafCert.get()).c_str()); retCertChain->SetLeafKey(leafKey.release()); } catch (const OsslError& e) { throw e; } return retCertChain; } OsslX509::OsslX509(const char *rootCert) : leaf_cert(nullptr, &X509_free), intermediate_certs(sk_X509_new_null()), leaf_key(nullptr, &EVP_PKEY_free) { // Linux-specific code for loading the root certificate using OpenSSL this->store = X509_STORE_new(); if (!this->store) { throw std::runtime_error("Failed to create X509_STORE"); } if (!intermediate_certs) { throw std::runtime_error("Failed to create stack of X509 certificates"); } auto root_cert = this->LoadCertificate(encoders::base64_decode(rootCert)); if (X509_STORE_add_cert(this->store, root_cert.get()) != 1) { throw std::runtime_error("Failed to add root certificate to store"); } LIBSECRETS_LOG( LogLevel::Info, "Certificate chain verification.", "Certificate chain verification with cert %s", printCertInfo(root_cert.get(), "Root").c_str()); X509_STORE_set_flags(this->store, X509_V_FLAG_X509_STRICT); } OsslX509::~OsslX509() { if (this->store != NULL) { X509_STORE_free(this->store); } if (this->intermediate_certs != NULL) { sk_X509_pop_free(this->intermediate_certs, &X509_free); } if (this->leaf_cert != NULL) { this->leaf_cert.reset(); } } void OsslX509::SetLeafKey(EVP_PKEY* key) { this->leaf_key.reset(key); } std::vector<unsigned char> OsslX509::SignData(const std::vector<unsigned char>& data) { std::vector<unsigned char> signature; std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> mdctx(EVP_MD_CTX_new(), &EVP_MD_CTX_free); if (!mdctx) { throw OsslError(ERR_get_error(), "Failed to create EVP_MD_CTX"); } if (EVP_DigestSignInit(mdctx.get(), nullptr, EVP_sha256(), nullptr, this->leaf_key.get()) != 1) { throw OsslError(ERR_get_error(), "Failed to initialize DigestSign"); } if (EVP_DigestSignUpdate(mdctx.get(), data.data(), data.size()) != 1) { throw OsslError(ERR_get_error(), "Failed to update DigestSign"); } size_t siglen = 0; if (EVP_DigestSignFinal(mdctx.get(), nullptr, &siglen) != 1) { throw OsslError(ERR_get_error(), "Failed to finalize DigestSign (get length)"); } signature.resize(siglen); if (EVP_DigestSignFinal(mdctx.get(), signature.data(), &siglen) != 1) { throw OsslError(ERR_get_error(), "Failed to finalize DigestSign"); } return signature; } bool OsslX509::VerifySignature(std::vector<unsigned char> const&signedData, std::vector<unsigned char> const&signature) { // Linux-specific code for verifying a signature using OpenSSL EVP_PKEY* pubkey = X509_get_pubkey(this->leaf_cert.get()); if (!pubkey) { throw OsslError(ERR_get_error(), "Failed to get public key from leaf certificate"); } std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> pubkey_ptr(pubkey, &EVP_PKEY_free); auto ctx = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(EVP_MD_CTX_new(), EVP_MD_CTX_free); if (!ctx) { throw OsslError(ERR_get_error(), "Failed to create EVP_MD_CTX"); } if (EVP_DigestVerifyInit(ctx.get(), nullptr, EVP_sha256(), nullptr, pubkey) != 1) { throw OsslError(ERR_get_error(), "Failed to initialize digest verify"); } if (EVP_DigestVerifyUpdate(ctx.get(), signedData.data(), signedData.size()) != 1) { throw OsslError(ERR_get_error(), "Failed to update digest verify"); } int result = EVP_DigestVerifyFinal(ctx.get(), signature.data(), signature.size()); if (result == 1) { LIBSECRETS_LOG( LogLevel::Debug, "Signature verified successfully.", "" ); return true; } else { LIBSECRETS_LOG( LogLevel::Debug, "Signature failed verification.", "" ); return false; } } std::unique_ptr<X509, decltype(&X509_free)> OsslX509::LoadCertificate(const std::vector<unsigned char>& cert_buffer) { const unsigned char* p = cert_buffer.data(); X509* cert = d2i_X509(nullptr, &p, cert_buffer.size()); if (!cert) { throw std::runtime_error("Failed to load certificate from buffer"); } return std::unique_ptr<X509, decltype(&X509_free)>(cert, &X509_free); } void OsslX509::LoadLeafCertificate(const char* cert) { auto leaf_cert_buffer = encoders::base64_decode(cert); this->leaf_cert = this->LoadCertificate(leaf_cert_buffer); LIBSECRETS_LOG( LogLevel::Info, "Certificate chain verification.", "Certificate chain verification with cert %s", printCertInfo(leaf_cert.get(), "Leaf").c_str()); } void OsslX509::LoadIntermediateCertificate(const char* cert) { auto inter_cert = this->LoadCertificate(encoders::base64_decode(cert)); LIBSECRETS_LOG( LogLevel::Info, "Certificate chain verification.", "Certificate chain verification with cert %s", printCertInfo(inter_cert.get(), "Intermediate CA").c_str()); X509* inter_cert_ptr = inter_cert.release(); if (!sk_X509_push(this->intermediate_certs, inter_cert_ptr)) { throw std::runtime_error("Failed to push intermediate certificate onto stack"); } } bool OsslX509::VerifyCertChain() { bool ret = false; auto ctx = std::unique_ptr<X509_STORE_CTX, decltype(&X509_STORE_CTX_free)>(X509_STORE_CTX_new(), &X509_STORE_CTX_free); if (!ctx) { throw std::runtime_error("Failed to create X509_STORE_CTX"); } if (X509_STORE_CTX_init(ctx.get(), this->store, this->leaf_cert.get(), this->intermediate_certs) != 1) { throw std::runtime_error("Failed to initialize X509_STORE_CTX"); } X509_STORE_CTX_set_flags(ctx.get(), X509_V_FLAG_CHECK_SS_SIGNATURE); int result = X509_verify_cert(ctx.get()); if (result == 1) { LIBSECRETS_LOG( LogLevel::Debug, "Certificate chain verified successfully.", ""); ret = true; } else { int error = X509_STORE_CTX_get_error(ctx.get()); int depth = X509_STORE_CTX_get_error_depth(ctx.get()); X509* cert = X509_STORE_CTX_get_current_cert(ctx.get()); std::string certInfo = printCertInfo(cert, "Failed"); LIBSECRETS_LOG( LogLevel::Error, "Certificate chain failed to verify.", "Certificate chain verification failed with error code %s at depth %d for certificate %s", X509_verify_cert_error_string(error), depth, certInfo.c_str()); ret = false; } return ret; }