Indy可以从内存加载SSL证书吗?

时间:2015-04-24 10:26:56

标签: delphi

我有一个客户端应用需要使用证书向服务器授权。通信是基于TCP的SSL(不是http / https相关)。 当我使用私钥文件并在IO处理程序等上设置它时,我有这个工作。

我想避免将私钥文件放在客户端的磁盘上。相反,我想以某种方式通过内存向Indy提供私钥。

我一直没能找到办法做到这一点,所以现在我想知道这是否可能呢?

澄清: 我的计划是将加密形式的私钥作为.exe文件中的资源包含在内。将其解密到内存中,然后通过流或缓冲区将其提供给Indy。所以问题是,如果Indy以某种方式支持这一点。

2 个答案:

答案 0 :(得分:2)

目前,Indy没有公开从内存加载证书的功能。该功能请求有一张开放票:

  

支持从用户定义的内存加载OpenSSL证书/密钥数据   http://code.google.com/p/indyproject/issues/detail?id=196   http://indy.codeplex.com/workitem/21143

然而,OpenSSL确实支持它,并且在Windows下的D2009 +上,由于OpenSSL不支持UTF-16文件名(在* Nix系统上,它支持UTF-8文件名),因此使用UTF-16文件名加载该功能,但是)。 Indy将文件加载到内存中,并使用OpenSSL自身使用的相同解析函数。所以你要求的是可能,而不是直接

这是一个例子。这些是Indy的File.WriteAllLines和(基于UTF-16)IndySSL_CTX_use_PrivateKey_file_PKCS12()包装函数,IndySSL_CTX_use_PrivateKey_file()调用它们来加载TIdSSLContext.LoadKey()属性中指定的私钥文件:

TIdSSLOptions.KeyFile

如您所见,Indy使用function TIdSSLContext.LoadKey: Boolean; begin if PosInStrArray(ExtractFileExt(KeyFile), ['.p12', '.pfx'], False) <> -1 then begin Result := IndySSL_CTX_use_PrivateKey_file_PKCS12(fContext, KeyFile) > 0; end else begin Result := IndySSL_CTX_use_PrivateKey_file(fContext, KeyFile, SSL_FILETYPE_PEM) > 0; end; if Result then begin Result := SSL_CTX_check_private_key(fContext) > 0; end; end; function IndySSL_CTX_use_PrivateKey_file_PKCS12(ctx: PSSL_CTX; const AFileName: String): TIdC_INT; var LM: TMemoryStream; B: PBIO; LKey: PEVP_PKEY; LCert: PX509; P12: PPKCS12; CertChain: PSTACK_OF_X509; LPassword: array of TIdAnsiChar; LPasswordPtr: PIdAnsiChar; begin Result := 0; LM := nil; try LM := TMemoryStream.Create; LM.LoadFromFile(AFileName); except // Surpress exception here since it's going to be called by the OpenSSL .DLL // Follow the OpenSSL .DLL Error conventions. SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_SYS_LIB); LM.Free; Exit; end; try B := BIO_new_mem_buf(LM.Memory, LM.Size); if not Assigned(B) then begin SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_BUF_LIB); Exit; end; try SetLength(LPassword, MAX_SSL_PASSWORD_LENGTH+1); LPassword[MAX_SSL_PASSWORD_LENGTH] := TIdAnsiChar(0); LPasswordPtr := PIdAnsiChar(LPassword); if Assigned(ctx^.default_passwd_callback) then begin ctx^.default_passwd_callback(LPasswordPtr, MAX_SSL_PASSWORD_LENGTH, 0, ctx^.default_passwd_callback_userdata); // TODO: check return value for failure end else begin // TODO: call PEM_def_callback(), like PEM_read_bio_X509() does // when default_passwd_callback is nil end; P12 := d2i_PKCS12_bio(B, nil); if not Assigned(P12) then begin SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_PKCS12_LIB); Exit; end; try CertChain := nil; if PKCS12_parse(P12, LPasswordPtr, LKey, LCert, @CertChain) <> 1 then begin SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_PKCS12_LIB); Exit; end; try Result := SSL_CTX_use_PrivateKey(ctx, LKey); finally sk_pop_free(CertChain, @X509_free); X509_free(LCert); EVP_PKEY_free(LKey); end; finally PKCS12_free(P12); end; finally BIO_free(B); end; finally FreeAndNil(LM); end; end; function IndySSL_CTX_use_PrivateKey_file(ctx: PSSL_CTX; const AFileName: String; AType: Integer): TIdC_INT; var LM: TMemoryStream; B: PBIO; LKey: PEVP_PKEY; j: TIdC_INT; begin Result := 0; LM := nil; try LM := TMemoryStream.Create; LM.LoadFromFile(AFileName); except // Surpress exception here since it's going to be called by the OpenSSL .DLL // Follow the OpenSSL .DLL Error conventions. SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_SYS_LIB); LM.Free; Exit; end; try B := BIO_new_mem_buf(LM.Memory, LM.Size); if not Assigned(B) then begin SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_BUF_LIB); Exit; end; try case AType of SSL_FILETYPE_PEM: begin j := ERR_R_PEM_LIB; LKey := PEM_read_bio_PrivateKey(B, nil, ctx^.default_passwd_callback, ctx^.default_passwd_callback_userdata); end; SSL_FILETYPE_ASN1: begin j := ERR_R_ASN1_LIB; LKey := d2i_PrivateKey_bio(B, nil); end; else SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, SSL_R_BAD_SSL_FILETYPE); Exit; end; if not Assigned(LKey) then begin SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, j); Exit; end; Result := SSL_CTX_use_PrivateKey(ctx, LKey); EVP_PKEY_free(LKey); finally BIO_free(B); end; finally FreeAndNil(LM); end; end; 将证书文件加载到BIO,从中提取BIO_new_mem_buf(),并将其传递给OpenSSL的EVP_PKEY函数以加载会话的SSL_CTX_use_PrivateKey()对象的密钥(Indy目前不支持RSA / ASN1私钥)。所以你必须在你自己的代码中采用类似的逻辑。当然,您必须在正确的时间调用代码,为此您需要修改Indy的源代码,将代码添加到SSL_CTX,然后重新编译Indy。至少在Indy在未来版本中公开本机访问之前。

答案 1 :(得分:1)

如果Indy代码接受流,您可以轻松地从TMemoryStream的文件中加载任何可以加载的内容,但这并不能解决您如何将证书加载到首先是TMemoryStream

所有这些都归结为您的程序必须在某些时候从光盘或网络加载证书,然后才能将其存储在内存中。您可以将它作为资源嵌入到EXE本身并使用TResourceStream加载它,但是它仍然计入光盘,因为它是EXE的一部分。通过网络加载它意味着你有一台服务器提供你的私钥,因为显而易见的原因,这不是一个好主意。在某处某处无法将你的私钥放在光盘上,真的没办法了;只需确保您的服务器的配置方式不能作为下载服务该文件。