【问题标题】:Delphi mutual authenticationDelphi相互认证
【发布时间】:2018-09-02 13:04:26
【问题描述】:

我使用 WinINet 库连接到网站。

使用 Internet Explorer (Win10) 可以正常工作并向我显示选择要使用的证书的消息。

这是我调用的delphi代码:

FUNCTION TRAD.lastOrganization(): Integer;
VAR
  js:TlkJSONobject;
  ws: TlkJSONstring;
  url, resp: String;
  count,statusCodeLen, bodyCodeLen: Cardinal;
  header,tmp: String;
  buffer, body: String;
  statusCode: ARRAY [0 .. 1024] OF Char;
  bodyCode: ARRAY [0 .. 1024] OF Char;
  UrlHandle: HINTERNET;
BEGIN
  buffer := '00000000000000000000';
  url := contextUrl + '/rest/organization/count';
  UrlHandle := InternetOpenUrl(NetHandle, PChar(url), nil, 0, INTERNET_FLAG_RELOAD, 0);
  IF NOT ASSIGNED(UrlHandle) THEN
    SHOWMESSAGE('Unable to read the amount of Organization using the URL ' + url + ': ' +  SysErrorMessage(GetLastError));
  statusCodeLen := Length(statusCode);
  bodyCodeLen := Length(bodyCode);
  count := 0;
  IF HttpQueryInfo(UrlHandle, HTTP_QUERY_STATUS_CODE, @statusCode[0], statusCodeLen, count) THEN
  BEGIN
    buffer := statusCode;
    IF buffer <> '200' THEN
    BEGIN
      ShowMessage('While read amount of Organization I got a status code ' + buffer + ' but 200 was expected.');
      EXIT;
    END;
  END;

  count := 0;
  body := '';
  REPEAT
    FillChar(bodyCode, bodyCodeLen, 0);
    IF NOT InternetReadFile(UrlHandle, @bodyCode[0], bodyCodeLen, count) THEN
    BEGIN
      ShowMessage('Problem on reading from response stream while read the amount of Organization using the URL ' + url + '.');
      EXIT;
    END;
    IF count > 0 THEN
    BEGIN
      tmp := bodyCode;
      body := body + LeftStr(tmp, count);
    END;
  UNTIL count = 0;

  InternetCloseHandle(UrlHandle);
  Result := strtoint(body);
END;

如果我调用该方法,我会收到以下消息:

Buuut,使用 Edge-Browser 我必须指定一个证书,而且效果很好。

问题

如何指定证书?

编辑(新信息):

如果我将代码更改为

FUNCTION TRAD.lastOrganization(): Integer;
VAR
  js:TlkJSONobject;
  ws: TlkJSONstring;
  url, resp: String;
  count,statusCodeLen, bodyCodeLen: Cardinal;
  header,tmp: String;
  buffer, body: String;
  statusCode: ARRAY [0 .. 1024] OF Char;
  bodyCode: ARRAY [0 .. 1024] OF Char;
  UrlHandle: HINTERNET;
BEGIN
  buffer := '00000000000000000000';
  url := contextUrl + '/rest/organization/count';
  UrlHandle := InternetOpenUrl(NetHandle, PChar(url), nil, 0, INTERNET_FLAG_RELOAD, 0);
  IF NOT ASSIGNED(UrlHandle) THEN
    raiseLastOSError();

显示:

【问题讨论】:

  • 不幸的是,您的屏幕截图中的错误消息没有提供信息,因为它只有您编码的文本。将错误代码转换为德语消息似乎存在问题。如果您提供GetLastError 返回的错误代码,可能会提供更多信息。
  • 缺少翻译的错误代码是错误代码 317 (aka ERROR_MR_MID_NOT_FOUND),其描述为 系统在 %2 的消息文件中找不到消息编号 0x%1 的消息文本.
  • 只是为了在同一页面上。您发布的代码是客户端代码?现在它在 IE 10 和 IE Edge 中工作,你希望它通过代码工作吗?
  • @TarunLalwani 是的。

标签: delphi microsoft-edge cryptoapi mutual-authentication


【解决方案1】:

使用 WinHTTP(你可以用 WinInetHTTP 做同样的事情)你可以通过 ActiveX 像这样设置证书:

// Instantiate a WinHttpRequest object.
var HttpReq = new ActiveXObject("WinHttp.WinHttpRequest.5.1");

// Open an HTTP connection.
HttpReq.Open("GET", "https://www.fabrikam.com/", false);

// Select a client certificate.
HttpReq.SetClientCertificate(
            "LOCAL_MACHINE\\Personal\\My Middle-Tier Certificate");

// Send the HTTP Request.
HttpReq.Send();

使用 ActiveX 很容易,但这并不是你真正想要的(我给你的例子是为了说明)。因此,使用 Windows API,WinHTTP 使您能够从本地证书存储中选择和发送证书。以下代码示例显示了在返回 ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED 错误后如何打开证书存储并根据使用者名称定位证书。

if( !WinHttpReceiveResponse( hRequest, NULL ) )
  {
    if( GetLastError( ) == ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED )
    {
      //MY is the store the certificate is in.
      hMyStore = CertOpenSystemStore( 0, TEXT("MY") );
      if( hMyStore )
      {
        pCertContext = CertFindCertificateInStore( hMyStore,
             X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
             0,
             CERT_FIND_SUBJECT_STR,
             (LPVOID) szCertName, //Subject string in the certificate.
             NULL );
        if( pCertContext )
        {
          WinHttpSetOption( hRequest, 
                            WINHTTP_OPTION_CLIENT_CERT_CONTEXT,
                            (LPVOID) pCertContext, 
                            sizeof(CERT_CONTEXT) );
          CertFreeCertificateContext( pCertContext );
        }
        CertCloseStore( hMyStore, 0 );

        // NOTE: Application should now resend the request.
      }
    }
  }

【讨论】:

  • 这应该是关于 WinINet 的。不是WinHTTP,也不是一些人编造的WinInetHTTP。它是 Delphi,而不是 C++ 有时将赏金设置得太高是很危险的。人们会发布任何内容来获得其中的一半。
  • @victoria:有时分享任何东西都是危险的,总有人喜欢争论;)我回答这个问题不是为了赏金(在你指向我之前甚至不知道他们是赏金和认真的谁关心他们在 SO 上的声誉?)但是因为我写了 2 个使用 WinHttp 和 WininetHttp (github.com/Zeus64/alcinoe) 的包装器,所以我知道它。它们的实现非常相似,我发现这个示例很好地描述了如何在 winhttp 上进行操作,并且只需很少的研究就可以将其翻译为 wininethttp。无法在评论中发布此示例
  • 那么对不起。我不反对。如果代码是 Delphi,我会投票赞成 :)
  • @victoria 说真的,你想让我把“{”转换成“开始”,把“}”转换成“结束”吗?该示例不使用任何与 c++ 相关的特定内容,它只是 api 调用
  • api 是在delphi 中翻译的单元Soap.Win.CertHelper.pas 和delphi 示例可以在System.Net.HttpClient.Win.pas 中找到(所以用“开始”这个词,这个词"end" 和 ":=" :)
【解决方案2】:

考虑使用InternetErrorDlg

代码示例:

function WebSiteConnect(const UserAgent: string; const Server: string; const Resource: string;): string;
var
  hInet: HINTERNET;
  hConn: HINTERNET;
  hReq:  HINTERNET;
  dwLastError:DWORD;

  nilptr:Pointer;
  dwRetVal:DWORD;

  bLoop: boolean;
  port:Integer;
begin
  hInet := InternetOpen(PChar(UserAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
  if hInet = nil then exit;
  hConn := InternetConnect(hInet, PChar(Server), INTERNET_DEFAULT_HTTPS_PORT, nil, nil, INTERNET_SERVICE_HTTP, 0, 0);
  if hConn = nil then
  begin
    InternetCloseHandle(hInet);
    exit;
  end;
  hReq := HttpOpenRequest(hConn, 'GET', PChar(Resource), 'HTTP/1.0', nil, nil, INTERNET_FLAG_SECURE, 0);
  if hReq = nil then
  Begin
    InternetCloseHandle(hConn);
    InternetCloseHandle(hInet);
    exit;
  end;

  bLoop := true;
  while bLoop do
  begin
    if HttpSendRequest(hReq, nil, 0, nil, 0) then
      dwLastError := ERROR_SUCCESS
    else
      dwLastError:= GetLastError();

    if dwLastError = ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED then
    begin
      dwRetVal:= InternetErrorDlg(application.handle, hReq, dwLastError,
      FLAGS_ERROR_UI_FILTER_FOR_ERRORS or
      FLAGS_ERROR_UI_FLAGS_GENERATE_DATA or
      FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS,
      nilptr );

      if dwRetVal = ERROR_INTERNET_FORCE_RETRY then
        continue
      else  // CANCEL button
      begin
        InternetCloseHandle(hReq);
        InternetCloseHandle(hConn);
        InternetCloseHandle(hInet);
        exit;
      end;
    end
    else
      bLoop := false;
  end;
  Result:= ...
end;

【讨论】:

  • dwLastError 为 12005 (ERROR_INTERNET_INVALID_URL)。预期的 ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED 是 12044。它是一个不同的错误代码!
  • 哦,您的代码指出服务器名称是一个 url(请参阅您的 InternetConnect 声明)。
  • 不是 url 你想连接什么?
  • 是的,但是我不能放网址,参数必须是服务器名,而不是网址。 MSDN 说它必须是 lpszServerName 而不是 PChar(strURL)
  • 无论如何,只要稍作改动,就可以很容易地根据您的要求调整我的示例代码,目的是显示 Internet 对话框以选择有效证书。
猜你喜欢
  • 2012-11-19
  • 2012-09-12
  • 2014-03-03
  • 1970-01-01
  • 1970-01-01
  • 2013-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多