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.

+3


source to share


2 answers


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.

+2


source


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.

+1


source







All Articles