I have a client app that needs to authorize itself to a server using certificates. The communication is using SSL over TCP (not HTTP/HTTPS related).
I have this working when I use the private key file and set this on the IOHandler, etc.
I would like to avoid having to put the private key file on disk on the client side. Instead, I would like to provide the private key to Indy via memory in some way.
I have not been able to find a way to do this, so now I am wondering if this is at all possible?
Clarification:
My plan is to include the private key in encrypted form as a resource in the .exe
file, decrypt it in memory, and then feed it to Indy via a stream or buffer. So the question is, does Indy support this in some way?
At this time, Indy does not expose functionality to load certificates from memory. There is an open ticket for that feature request:
#150: Support loading OpenSSL certificate/key data from user-defined storage
However, OpenSSL does support it, and under D2009+ on Windows, Indy resorts to that functionality in order 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 the files into memory and uses the same parsing functions that OpenSSL itself uses. So what you are asking for is possible, just not straight forward.
Here is an example. These are Indy's IndySSL_CTX_use_PrivateKey_file_PKCS12()
and (UTF-16 based) IndySSL_CTX_use_PrivateKey_file()
wrapper functions, which TIdSSLContext.LoadKey()
calls to load the private key file specified in the TIdSSLOptions.KeyFile
property:
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;
As you can see, Indy loads the certificate file into a BIO
using BIO_new_mem_buf()
, extracts an EVP_PKEY
from it, and passes it to OpenSSL's SSL_CTX_use_PrivateKey()
function to load the key into the session's SSL_CTX
object (Indy does not support RSA/ASN1 private keys at this time).
So, you would have to adopt similar logic into your own code. Of course, you would have to make your code get invoked at the correct time, and for that you would need to modify Indy's source code to add your code to TIdSSLContext.LoadKey()
and then recompile Indy. At least until Indy exposes native access in a future release.