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

4
我有一个客户端应用程序,需要使用证书向服务器进行授权。通信是使用SSL over TCP(与HTTP / HTTPS无关)。
当我使用私钥文件并将其设置在IOHandler上时,它可以正常工作。
我想避免在客户端上将私钥文件放在磁盘上。相反,我希望以某种方式通过内存向Indy提供私钥。
我尚未找到一种方法来实现这一点,所以现在我想知道是否可能?
澄清: 我的计划是将加密形式的私钥作为资源包含在.exe文件中,在内存中解密,然后通过流或缓冲区将其馈送给Indy。所以问题是,Indy是否支持这种方式?
2个回答

2
此时,Indy不支持从内存中加载证书的功能。这个功能请求有一个开放的票据:#150: Support loading OpenSSL certificate/key data from user-defined storage。然而,OpenSSL支持它,在Windows的D2009+下,Indy使用该功能来加载使用UTF-16文件名的证书文件,而OpenSSL不支持(在*Nix系统上,它支持UTF-8文件名)。Indy将文件加载到内存中,并使用与OpenSSL本身使用的相同的解析函数。因此,您所要求的是“可能”的,只是不是“直接”的。
以下是示例。这些是Indy的IndySSL_CTX_use_PrivateKey_file_PKCS12()和(基于UTF-16)IndySSL_CTX_use_PrivateKey_file()包装函数,TIdSSLContext.LoadKey()调用以加载在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'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;

正如您所看到的,Indy使用BIO_new_mem_buf()将证书文件加载到BIO中,从中提取出一个EVP_PKEY,并将其传递给OpenSSL的SSL_CTX_use_PrivateKey()函数,以将密钥加载到会话的SSL_CTX对象中(目前Indy不支持RSA/ASN1私钥)。
因此,您需要采用类似的逻辑到您自己的代码中。当然,您需要在正确的时间调用您的代码,并且为此,您需要修改Indy的源代码以将您的代码添加到TIdSSLContext.LoadKey()中,然后重新编译Indy。直到Indy在未来发布中公开本地访问为止。

1

如果Indy代码可以接受一个流,那么你可以很容易地从TMemoryStream中加载任何你可以从文件中加载的东西,但这并不能解决如何将证书加载到TMemoryStream中的问题。

归根结底,你的程序在将证书加载到内存中之前必须在某个时候从磁盘或网络中加载它。你可以将其作为资源嵌入到EXE本身中,并使用TResourceStream加载它,但这仍然被视为存在于磁盘上,因为它是EXE的一部分。而从网络加载它意味着你有一个服务器在“提供你的私钥”,因此出于明显的原因,这不是一个好主意。实际上没有绕过将私钥存储在磁盘上的方法;只需确保你的服务器配置为无法将该文件作为下载提供即可。


谢谢。我已经在考虑这些方面了。我已经在我的问题中添加了澄清。 - Larsdk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接