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

调用Indy中X509_new函数时触发访问违例问题求助

Delphi生成自签名X509证书时X509_new触发访问违例问题

问题详情

使用Delphi编写代码生成自签名X509证书,执行Cert := X509_new;语句时触发访问违例错误:

Access violation at address ...

完整代码

procedure TForm2.Label1DblClick(Sender: TObject);
  procedure GenerateSelfSignedCertificate(const AFileName: string);
    procedure GenerateRandomSerialNumber(SerialNumber: PASN1_INTEGER);
    var
      RandomValue: Int64;
      I: Integer;
    begin
      // 生成64位随机数
      RandomValue := 0;
      for I := 1 to 8 do
      begin
        RandomValue := (RandomValue shl 8) or Random(256);
      end;

      // 确保数字为正数
      RandomValue := RandomValue and $7FFFFFFFFFFFFFFF;

      // 设置序列号
      if ASN1_INTEGER_set(SerialNumber, RandomValue) = 0 then
        raise Exception.Create('Error setting serial number');
    end;

    function DateToASN1_TIME(const ADate: TDateTime): PASN1_TIME;
    var
      AStruct: TSystemTime;
      ASN1_Time: PASN1_TIME;
    begin
      DateTimeToSystemTime(ADate, AStruct);
      ASN1_Time := ASN1_TIME_set(nil, SystemTimeToDateTime(AStruct));
      Result := ASN1_Time;
    end;

  var
    Cert: PX509;
    PrivateKey: PEVP_PKEY;
    SerialNumber: PASN1_INTEGER;
    Name: PX509_NAME;
    Bio: PBIO;
    NotBefore, NotAfter: TDateTime;
  begin
    // 初始化OpenSSL
    IdSSLOpenSSL.LoadOpenSSLLibrary;

    // 创建新证书
    Cert := X509_new; // 错误触发点:Access Vioaltion at address 00000...
    try 
      // 创建RSA私钥
      PrivateKey := EVP_PKEY_new;
      try
        if EVP_PKEY_assign(PrivateKey, EVP_PKEY_RSA,
          PByte(RSA_generate_key(2048, RSA_F4, nil, nil))) = 0 then
          raise Exception.Create('Error generating private key');

        // 设置证书版本(X509 v3)
        X509_set_version(Cert, 2);

        // 生成随机序列号
        SerialNumber := ASN1_INTEGER_new;
        if SerialNumber = nil then
          raise Exception.Create('Error creating ASN1_INTEGER');

        try
          GenerateRandomSerialNumber(SerialNumber);
          if X509_set_serialNumber(Cert, SerialNumber) = 0 then
            raise Exception.Create
              ('Error setting serial number for certificate');
        finally
          ASN1_INTEGER_free(SerialNumber);
        end;

        // 设置证书有效期
        NotBefore := Now - 1; // 从昨天开始生效
        NotAfter := NotBefore + 365; // 有效期1年
        X509_set_notBefore(Cert, DateToASN1_TIME(NotBefore));
        X509_set_notAfter(Cert, DateToASN1_TIME(NotAfter));

        // 设置公钥
        X509_set_pubkey(Cert, PrivateKey);

        // 设置颁发者和主体名称(使用localhost)
        Name := X509_get_subject_name(Cert);
        X509_NAME_add_entry_by_txt(Name, 'CN', MBSTRING_ASC,
          PAnsiChar('localhost'), -1, -1, 0);
        X509_set_issuer_name(Cert, Name);

        // 签名证书
        if X509_sign(Cert, PrivateKey, EVP_sha256) = 0 then
          raise Exception.Create('Error signing the certificate');

        // 将证书和私钥保存到PEM文件
        Bio := BIO_new_file(PAnsiChar(AnsiString(AFileName)), 'w+');
        try
          PEM_write_bio_X509(Bio, Cert);
          PEM_write_bio_PrivateKey(Bio, PrivateKey, nil, nil, 0, nil, nil);
        finally
          BIO_free(Bio);
        end;
      finally
        EVP_PKEY_free(PrivateKey);
      end;
    finally
      X509_free(Cert);
    end;
  end;

begin
  GenerateSelfSignedCertificate('.\CertificatTest.pem');
  ShowMessage('Certificate successfully generated!');
end;

X509_new函数定义

该函数在Indy的IdSSLOpenSSLHeaders单元中定义为动态绑定函数:

{$EXTERNALSYM X509_new}
X509_new : function: PX509 cdecl = nil;

问题原因及修复方案

核心原因

仅调用IdSSLOpenSSL.LoadOpenSSLLibrary仅完成了OpenSSL库文件的加载,但未完成动态函数指针的绑定,导致X509_new指向空地址,触发访问违例。

分步修复

  1. 补全OpenSSL初始化流程
    在加载库文件后,必须调用LoadOpenSSLFunctions完成所有OpenSSL函数的绑定:

    // 初始化OpenSSL
    if not IdSSLOpenSSL.LoadOpenSSLLibrary then
      raise Exception.Create('无法加载OpenSSL库');
    IdSSLOpenSSL.LoadOpenSSLFunctions;
    
  2. 确保版本与位数兼容

    • 确认OpenSSL库版本与Indy框架匹配(如Indy 10适配OpenSSL 1.1.x或3.x);
    • 项目编译位数(32/64位)必须与OpenSSL库的位数完全一致。
  3. 优化代码兼容性

    • 废弃函数替换:RSA_generate_key在OpenSSL 1.1.0+已被废弃,改用RSA_generate_key_ex:
      var
        RSA: PRSA;
        Bignum: PBIGNUM;
      begin
        RSA := RSA_new;
        Bignum := BN_new;
        try
          BN_set_word(Bignum, RSA_F4);
          if not RSA_generate_key_ex(RSA, 2048, Bignum, nil) then
            raise Exception.Create('生成RSA密钥失败');
          if EVP_PKEY_assign_RSA(PrivateKey, RSA) = 0 then
          begin
            RSA_free(RSA);
            raise Exception.Create('绑定RSA密钥到EVP_PKEY失败');
          end;
        finally
          BN_free(Bignum);
        end;
      
    • 简化日期转换函数:
      function DateToASN1_TIME(const ADate: TDateTime): PASN1_TIME;
      var
        UnixTime: time_t;
      begin
        UnixTime := DateTimeToUnix(ADate);
        Result := ASN1_TIME_set(nil, UnixTime);
      end;
      

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 13:10:55