加载自签名证书到Indy Web服务器时遇‘Could not load certificate’错误
解决Indy加载自签名SSL证书时的ASN1编码错误
出现EIdOSSLLoadingCertError错误(提示error:0D07209B:asn1 encoding routines:ASN1_get_object:too long),核心原因是证书生成过程中ASN1结构无效,以及代码中的参数不匹配问题。以下是具体修复步骤:
1. 修正证书生成函数的参数不匹配问题
你的GenerateSelfSignedCertificate函数定义仅接受1个参数,但调用时传入了2个文件名,这会导致编译或运行时文件生成混乱。修改函数定义为:
procedure GenerateSelfSignedCertificate(const ACertFileName, AKeyFileName: string);
2. 修复时间到ASN1_TIME的转换逻辑
ASN1_TIME_set需要Unix时间戳(time_t类型),而非Delphi的TDateTime,这是导致ASN1编码错误的关键。替换DateToASN1_TIME函数:
function DateToASN1_TIME(const ADate: TDateTime): PASN1_TIME; var UnixTime: LongInt; begin UnixTime := DateTimeToUnix(ADate); Result := ASN1_TIME_set(nil, UnixTime); if Result = nil then raise Exception.Create('日期转ASN1_TIME失败'); end;
3. 替换废弃的RSA密钥生成函数
RSA_generate_key是OpenSSL旧版函数,改用更安全的RSA_generate_key_ex:
// 替换原密钥生成代码 var RSA: PRSA; begin RSA := RSA_new; if RSA = nil then raise Exception.Create('创建RSA对象失败'); try if not RSA_generate_key_ex(RSA, 2048, RSA_F4, nil, 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; except RSA_free(RSA); raise; end;
4. 规范证书与私钥的文件后缀
私钥文件建议用.key后缀(.crt通常用于证书),避免混淆。同时确保保存时使用正确的传入文件名:
// 保存证书 BioCert := BIO_new_file(PAnsiChar(AnsiString(ACertFileName)), 'w+'); try if PEM_write_bio_X509(BioCert, Cert) = 0 then raise Exception.Create('写入证书文件失败'); finally BIO_free(BioCert); end; // 保存私钥 BioKey := BIO_new_file(PAnsiChar(AnsiString(AKeyFileName)), 'w+'); try if PEM_write_bio_PrivateKey(BioKey, PrivateKey, nil, nil, 0, nil, nil) = 0 then raise Exception.Create('写入私钥文件失败'); finally BIO_free(BioKey); end;
5. 更新服务器SSL配置的文件路径
如果私钥后缀改为.key,同步更新FormCreate中的配置:
FIOHandler.SSLOptions.CertFile := '.\CertificatTest.pem'; FIOHandler.SSLOptions.KeyFile := '.\CheiePrivata.key';
完整修正后的证书生成函数
整合所有修复后的代码片段:
procedure TForm2.Label1DblClick(Sender: TObject); procedure GenerateSelfSignedCertificate(const ACertFileName, AKeyFileName: string); procedure GenerateRandomSerialNumber(SerialNumber: PASN1_INTEGER); var RandomValue: Int64; I: Integer; begin 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('设置序列号失败'); end; function DateToASN1_TIME(const ADate: TDateTime): PASN1_TIME; var UnixTime: LongInt; begin UnixTime := DateTimeToUnix(ADate); Result := ASN1_TIME_set(nil, UnixTime); if Result = nil then raise Exception.Create('日期转ASN1_TIME失败'); end; var Cert: PX509; PrivateKey: PEVP_PKEY; SerialNumber: PASN1_INTEGER; Name: PX509_NAME; BioCert, BioKey: PBIO; NotBefore, NotAfter: TDateTime; RSA: PRSA; begin Cert := X509_new; if Cert = nil then raise Exception.Create('创建X509证书失败'); try PrivateKey := EVP_PKEY_new; if PrivateKey = nil then raise Exception.Create('创建EVP_PKEY失败'); try RSA := RSA_new; if RSA = nil then raise Exception.Create('创建RSA对象失败'); try if not RSA_generate_key_ex(RSA, 2048, RSA_F4, nil, 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; except RSA_free(RSA); raise; end; X509_set_version(Cert, 2); SerialNumber := ASN1_INTEGER_new; if SerialNumber = nil then raise Exception.Create('创建ASN1_INTEGER失败'); try GenerateRandomSerialNumber(SerialNumber); if X509_set_serialNumber(Cert, SerialNumber) = 0 then raise Exception.Create('设置证书序列号失败'); finally ASN1_INTEGER_free(SerialNumber); end; NotBefore := Now - 1; NotAfter := NotBefore + 364; if X509_set_notBefore(Cert, DateToASN1_TIME(NotBefore)) = 0 then raise Exception.Create('设置证书生效日期失败'); if X509_set_notAfter(Cert, DateToASN1_TIME(NotAfter)) = 0 then raise Exception.Create('设置证书过期日期失败'); if X509_set_pubkey(Cert, PrivateKey) = 0 then raise Exception.Create('设置证书公钥失败'); Name := X509_get_subject_name(Cert); if X509_NAME_add_entry_by_txt(Name, 'CN', MBSTRING_ASC, PAnsiChar('localhost'), -1, -1, 0) = 0 then raise Exception.Create('添加CN字段到证书主体失败'); if X509_set_issuer_name(Cert, Name) = 0 then raise Exception.Create('设置证书颁发者名称失败'); if X509_sign(Cert, PrivateKey, EVP_sha256) = 0 then raise Exception.Create('签名证书失败'); BioCert := BIO_new_file(PAnsiChar(AnsiString(ACertFileName)), 'w+'); if BioCert = nil then raise Exception.Create('打开证书文件失败'); try if PEM_write_bio_X509(BioCert, Cert) = 0 then raise Exception.Create('写入证书文件失败'); finally BIO_free(BioCert); end; BioKey := BIO_new_file(PAnsiChar(AnsiString(AKeyFileName)), 'w+'); if BioKey = nil then raise Exception.Create('打开私钥文件失败'); try if PEM_write_bio_PrivateKey(BioKey, PrivateKey, nil, nil, 0, nil, nil) = 0 then raise Exception.Create('写入私钥文件失败'); finally BIO_free(BioKey); end; finally EVP_PKEY_free(PrivateKey); end; finally X509_free(Cert); end; end; begin GenerateSelfSignedCertificate('.\CertificatTest.pem', '.\CheiePrivata.key'); ShowMessage('证书和私钥生成成功!'); end;
验证步骤
- 重新编译程序,双击标签生成新的证书和私钥。
- 启动服务器,确认不再抛出ASN1相关的加载错误。
- 测试OAuth2回调是否能正常通过HTTPS连接到本地服务器。
内容的提问来源于stack exchange,提问作者Sorin
相关产品推荐
相关产品推荐

