使用OpenSSL生成X509 CSR失败:本地乱码+Godbolt崩溃
问题原因与解决方案
核心问题分析
1. 段错误(Godbolt返回错误码139)的根源
代码存在未初始化野指针:
unsigned char *request = request;
这行将未初始化的指针自赋值,i2d_X509_REQ尝试向野指针指向的内存写入数据,直接触发段错误(SIGSEGV),这就是Godbolt平台崩溃的直接原因。
2. 本地输出乱码的原因
i2d_X509_REQ生成的是DER格式二进制数据,并非可读的PEM文本格式,直接输出到控制台自然显示乱码。
3. 内存泄漏风险
i2d_X509_REQ动态分配的内存未被释放,注释中的CRYPTO_free(request)无法编译,是因为缺少<openssl/crypto.h>头文件。
4. 不安全的签名算法
使用MD5作为签名算法存在严重安全漏洞,不符合现代安全标准,建议替换为SHA256等强哈希算法。
修复后的完整代码
#include <memory> #include <stdexcept> #include <string> #include <iostream> #include <sstream> #include <openssl/aes.h> #include <openssl/bn.h> #include <openssl/pem.h> #include <openssl/rsa.h> #include <openssl/crypto.h> // 用于CRYPTO_free namespace { std::string make_csr(); } // namespace int main(int, char **) { try { std::cout << ::make_csr() << std::endl; return 0; } catch (const std::exception &e) { std::cerr << "Failed with error: " << e.what() << std::endl; return -1; } } namespace ssl_utils { template <typename T> void free(T *); template <typename T> T *make(); template <typename T> class raii_obj { private: struct _free { void operator()(T *ptr) const { ssl_utils::free(ptr); } }; public: using type = T; using ptr = std::unique_ptr<type, _free>; static ptr make() { return ptr(ssl_utils::make<type>()); } }; using raii_bignum = raii_obj<BIGNUM>; using raii_evp_pkey = raii_obj<EVP_PKEY>; using raii_x509_req = raii_obj<X509_REQ>; } // namespace ssl_utils namespace { std::string make_csr() { constexpr size_t privateKeyLength{2048}; auto pBignum = ssl_utils::raii_bignum::make(); auto pKey = ssl_utils::raii_evp_pkey::make(); RSA *rsa = RSA_new(); if (!(static_cast<bool>(pBignum) && // static_cast<bool>(rsa) && // BN_set_word(pBignum.get(), RSA_F4) && RSA_generate_key_ex(rsa, // privateKeyLength, // pBignum.get(), // nullptr) && EVP_PKEY_assign_RSA(pKey.get(), rsa))) { RSA_free(rsa); // 初始化失败时手动释放 throw std::runtime_error("Failed to generate RSA key!"); } auto pReq = ssl_utils::raii_x509_req::make(); X509_REQ_set_version(pReq.get(), X509_REQ_VERSION_1); // 使用宏明确版本 X509_REQ_set_pubkey(pReq.get(), pKey.get()); X509_NAME *const pName = X509_REQ_get_subject_name(pReq.get()); // 添加邮箱字段并检查错误 if (!X509_NAME_add_entry_by_txt( pName, LN_pkcs9_emailAddress, MBSTRING_UTF8, reinterpret_cast<const unsigned char *>("dummy.mail@gmail.com"), -1, -1, 0)) { throw std::runtime_error("Failed to add subject name entry!"); } // 使用SHA256替代MD5作为签名算法 if (!X509_REQ_sign(pReq.get(), pKey.get(), EVP_sha256()) || !X509_REQ_verify(pReq.get(), pKey.get())) throw std::runtime_error("CSR signing failed!"); // 修复野指针:初始化为nullptr,让i2d_X509_REQ自动分配内存 unsigned char *request = nullptr; const std::string::size_type reqSize = i2d_X509_REQ(pReq.get(), &request); if (reqSize == 0 || !request) { throw std::runtime_error("Failed to convert CSR to DER format!"); } // 将DER格式转换为PEM格式,解决乱码问题 std::stringstream pemStream; BIO *bio = BIO_new_fp(pemStream.rdbuf(), BIO_NOCLOSE); if (!bio || !PEM_write_bio_X509_REQ(bio, pReq.get())) { CRYPTO_free(request); BIO_free(bio); throw std::runtime_error("Failed to convert CSR to PEM format!"); } // 释放动态分配的资源 CRYPTO_free(request); BIO_free(bio); return pemStream.str(); } } namespace ssl_utils { template <> BIGNUM *make() { return BN_new(); } template <> void free(BIGNUM *ptr) { BN_free(ptr); } template <> EVP_PKEY *make() { return EVP_PKEY_new(); } template <> void free(EVP_PKEY *ptr) { EVP_PKEY_free(ptr); } template <> X509_REQ *make() { return X509_REQ_new(); } template <> void free(X509_REQ *ptr) { X509_REQ_free(ptr); } } // namespace ssl_utils
关键修改点说明
- 修复野指针:将
unsigned char *request = request;改为unsigned char *request = nullptr;,确保i2d_X509_REQ能正确分配内存。 - 转换为PEM格式:通过
BIO和PEM_write_bio_X509_REQ将DER二进制数据转换为可读的PEM文本,解决控制台乱码问题。 - 内存泄漏修复:添加
<openssl/crypto.h>头文件,调用CRYPTO_free释放i2d_X509_REQ分配的内存,同时正确释放BIO对象。 - 安全升级:将签名算法从MD5替换为SHA256,符合现代安全规范。
- 错误处理增强:为
X509_NAME_add_entry_by_txt、i2d_X509_REQ等操作添加错误检查,同时在RSA初始化失败时手动释放资源,避免内存泄漏。 - 代码可读性优化:使用
X509_REQ_VERSION_1宏替代直接写0L,明确CSR版本。
内容的提问来源于stack exchange,提问作者Sergey Kolesnik
相关产品推荐
相关产品推荐

