You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

关键修改点说明

  1. 修复野指针:将unsigned char *request = request;改为unsigned char *request = nullptr;,确保i2d_X509_REQ能正确分配内存。
  2. 转换为PEM格式:通过BIO和PEM_write_bio_X509_REQ将DER二进制数据转换为可读的PEM文本,解决控制台乱码问题。
  3. 内存泄漏修复:添加<openssl/crypto.h>头文件,调用CRYPTO_free释放i2d_X509_REQ分配的内存,同时正确释放BIO对象。
  4. 安全升级:将签名算法从MD5替换为SHA256,符合现代安全规范。
  5. 错误处理增强:为X509_NAME_add_entry_by_txt、i2d_X509_REQ等操作添加错误检查,同时在RSA初始化失败时手动释放资源,避免内存泄漏。
  6. 代码可读性优化:使用X509_REQ_VERSION_1宏替代直接写0L,明确CSR版本。

内容的提问来源于stack exchange,提问作者Sergey Kolesnik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 11:30:54