【问题标题】:Can Indy load SSL certificates from memory?Indy 可以从内存中加载 SSL 证书吗?
【发布时间】:2015-07-02 21:57:09
【问题描述】:

我有一个客户端应用程序需要使用证书将自己授权给服务器。通信使用基于 TCP 的 SSL(与 HTTP/HTTPS 无关)。

当我使用私钥文件并在 IOHandler 等上设置它时,我有这个工作。

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

我一直无法找到一种方法来做到这一点,所以现在我想知道这是否可能?

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

【问题讨论】:

    标签: delphi indy


    【解决方案1】:

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

    #150: Support loading OpenSSL certificate/key data from user-defined storage

    但是,OpenSSL 确实支持它,并且在 Windows 上的 D2009+ 下,Indy 使用该功能来使用 OpenSSL 不支持的 UTF-16 文件名加载证书文件(在 *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 object(Indy 目前不支持 RSA/ASN1 私钥)。

    因此,您必须在自己的代码中采用类似的逻辑。当然,您必须让您的代码在正确的时间被调用,为此您需要修改 Indy 的源代码,将您的代码添加到TIdSSLContext.LoadKey(),然后重新编译 Indy。至少在 Indy 在未来版本中公开原生访问之前。

    【讨论】:

      【解决方案2】:

      如果 Indy 代码将接受流,则您可以轻松地从 TMemoryStream 的文件中加载您可以加载的任何内容,但这并不能解决如何将证书加载到 TMemoryStream 中的问题第一名。

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

      【讨论】:

      • 谢谢。我已经按照这些思路进行了思考。我已对我的问题进行了澄清。
      猜你喜欢
      • 1970-01-01
      • 2020-01-13
      • 2021-04-04
      • 1970-01-01
      • 2012-03-20
      • 2012-11-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多