Delphi双向认证

18

我使用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;

如果我调用这个方法,我会得到这个信息:

enter image description here

但是,使用Edge浏览器,我需要指定一个证书,然后就可以正常工作了。

enter image description here

问题

如何指定证书?

编辑(新信息):

如果我将代码更改为

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();

它显示:

enter image description here

1
很遗憾,你截图中的错误信息并不具有信息量,因为它只包含了你编写的文本。看起来在将错误代码转换成德语消息时出现了问题。如果你提供由 GetLastError 返回的错误代码,那可能会更有信息量。 - Disillusioned
ErrorCode 缺少翻译的是错误代码317(又名 ERROR_MR_MID_NOT_FOUND),其描述为系统无法在消息文件 %2 中找到消息号 0x%1 的消息文本。 - Grim
只是为了确保大家在同一个页面上。您发布的代码是客户端代码吗?目前它可以在IE 10和IE Edge中运行,您想通过代码使其工作? - Tarun Lalwani
@TarunLalwani InternetErrorDlg在Delphi 7中未知。 - Grim
啊哈,实际上就像Internet Explorer一样操作。 - Victoria
显示剩余14条评论
2个回答

4
请考虑使用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。这是一个不同的错误代码! - Grim
哦,你的代码指出服务器名称是一个URL(请查看你的InternetConnect语句)。 - Grim
我非常确定你的答案是正确的,让我们做一个简短的测试,然后我会给你的答案奖励。 - Grim
代码已更新以使用服务器名称和资源,请查看。 - vzamanillo
代码已经完全测试成功。 - Grim
显示剩余4条评论

2

使用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.
      }
    }
  }

1
这本该是关于WinINet的,而不是WinHTTP或一些虚构的WinInetHTTP。还有,这是Delphi,不是C++。有时候设定太高的赏金会很危险。人们会发布任何东西,只为了得到其中一半。 - Victoria
1
@victoria:有时候分享任何东西都是危险的,总会有人想争辩;) 我回答这个问题不是为了赏金(甚至在你指出之前我都不知道有赏金,而且谁会在SO上关心他们的声誉呢?),而是因为我写了2个使用WinHttp和WininetHttp的包装器(https://github.com/Zeus64/alcinoe),所以我对此很熟悉。它们的实现非常相似,我找到了一个很好地描述如何在winhttp上做的示例,并可以通过一些研究来转换为wininethttp。不能在评论中发布此示例,因为太大了。 - zeus
1
抱歉,我没有给你投反对票。如果这段代码是Delphi的话,我会给你点赞的 :) - Victoria
@victoria,你真的想让我把“{”转换成“begin”,把“}”转换成“end”吗?这个示例并没有使用与C++有关的特殊内容,它只是API调用。 - zeus
API已经在Delphi中的单元Soap.Win.CertHelper.pas中进行了翻译,而Delphi示例可以在System.Net.HttpClient.Win.pas中找到(因此包括单词“begin”,单词“end”和“:=” :))。 - zeus

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