Can Indy load SSL certificates from memory?
I have a client application that needs to authenticate to the server using certificates. Communication is SSL over TCP (not related to http / https). It works for me when I use the private key file and set it to an I / O handler etc.
I wish I would not make the private key file on disk on the client side. Instead, I would like to somehow expose the Indy secret key via memory.
I haven't been able to find a way to do this, so now I'm wondering if this is possible at all?
Clarification: My plan is to include the private key in the encrypted form as a ressource in the .exe file. Decrypt it in memory and then pass it to Indy via a stream or buffer. So the question really is that Indy supports this in some way.
source to share
At this time, Indy does not provide a function for loading certificates from memory. There is an open ticket for this function request:
Support for loading OpenSSL certificates / data from custom memory http://code.google.com/p/indyproject/issues/detail?id=196 http://indy.codeplex.com/workitem/21143
However, OpenSSL does support it, and under D2009 + on Windows Indy use this feature to load certificate files using UTF-16 filenames which OpenSSL does not support (on * Nix systems it does support UTF-8 filenames, though). Indy loads files into memory and uses the same parsing functions that OpenSSL uses. So what you are asking may just not be straight forward.
Here's an example. These are Indy IndySSL_CTX_use_PrivateKey_file_PKCS12()
and (based on UTF-16) functions IndySSL_CTX_use_PrivateKey_file()
that TIdSSLContext.LoadKey()
cause the download of the private key file specified in the property TIdSSLOptions.KeyFile
:
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 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 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;
As you can see, Indy loads the certificate file in BIO
with BIO_new_mem_buf()
, extracts from it EVP_PKEY
and passes it to the OpenSSL functions SSL_CTX_use_PrivateKey()
to load the key into the SSL_CTX
object session (Indy does not support RSA / ASN1 private keys at this time). Thus, you will have to adopt similar logic in your own code. Of course, you will need to get your code to run at the right time, and for that you will need to modify the Indy source code to add the code to TIdSSLContext.LoadKey()
and then recompile Indy. At least until Indy provides native access in a future release.
source to share
If the Indy code accepts a stream, you can easily load whatever you could load from the file from TMemoryStream
instead, but that doesn't solve the question of how you load the certificate TMemoryStream
in the first place.
What it all boils down to is that your program will have to download the certificate at some point, either from disk or from the network, before it can get it into memory. You can embed it as a resource in the EXE itself and load it using TResourceStream, but it is still considered to be on disk because it is part of the EXE. And downloading it over the network means you have a server somewhere serving your private key, so it's not a good idea for obvious reasons. In fact, there is no way to use your private key on disk; just make sure your server is configured in such a way that it cannot serve this file as a download.
source to share