18

I use the WinINet library to connect to a website.

Using the Internet Explorer (Win10) it works and shows me the message to select the certificate to use.

This is the delphi code I call:

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;

If I call the method, I get this message:

enter image description here

Buuut, using the Edge-Browser I have to specify a certificate, and it works just great.

enter image description here

Question

How to specify the certificate?

Edit (new informations):

If I change the code to

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

It shows: enter image description here

Grim
  • 1,938
  • 10
  • 56
  • 123
  • 1
    Unfortunately, the error message in your screen-shot is uninformative because it only has the text you coded. It seems there's an issue translating the error code into a German message. If you provide the error code returned by `GetLastError` that might be more informative. – Disillusioned Mar 23 '18 at 23:39
  • The ErrorCode missing the translation is the error-code 317 (aka `ERROR_MR_MID_NOT_FOUND`) having the description *The system cannot find message text for message number 0x%1 in the message file for %2.* – Grim Mar 24 '18 at 08:27
  • Just to be on same page. The code you posted is a client side code? And it works in IE 10 and IE Edge as of now, you want it to be working through the code? – Tarun Lalwani Mar 26 '18 at 09:08
  • @TarunLalwani Yes. – Grim Mar 26 '18 at 10:14
  • Does this help? https://www.codeguru.com/cpp/i-n/internet/generalinternet/article.php/c3367/Selecting-a-client-certificate.htm – Tarun Lalwani Mar 26 '18 at 10:15
  • @TarunLalwani InternetErrorDlg is unknown in delphi 7 – Grim Mar 26 '18 at 10:20
  • As per this [thread](https://social.msdn.microsoft.com/Forums/sqlserver/en-US/32362e37-4e09-442b-aaae-0989b56a7daa/interneterrordlg-windows-10-build-10547-behaviour-changed?forum=windowssdk) it should exists? I am not a Deplhi programmer so may not know how the API stuff works in Deplhi. See this thread also, https://stackoverflow.com/questions/9861309/wininet-ssl-client-authenticate-oddness – Tarun Lalwani Mar 26 '18 at 10:24
  • @TarunLalwani It doesnt exists. Maybe a difference in Delphi versions. I use Delphi7. – Grim Mar 26 '18 at 11:13
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/167556/discussion-between-tarun-lalwani-and-peter-rader). – Tarun Lalwani Mar 26 '18 at 11:17
  • Might be better to use WinHttp instead of WinInet. See the accepted answer for this question: https://stackoverflow.com/questions/6725348/ – Brian Mar 26 '18 at 13:45
  • @PeterRader : can you gave me an url on with I can try to do a test ? i want to try some settings to see if it's will work – zeus Mar 28 '18 at 08:39
  • @loki I can give you a svntl. – Grim Mar 28 '18 at 11:50
  • @PeterRader I just need an url, I already made some tool with wininet / winHttp and want to check if they will work with your url (if yes then you will have the solution inspecting the code). you can also try the tool (taken from https://github.com/Zeus64/alcinoe) : https://svn.code.sf.net/p/alcinoe/code/demos/ALWinInetHTTPClient/win32/ALWinInetHTTPClientDemo.exe or winhttp version: https://svn.code.sf.net/p/alcinoe/code/demos/ALWinHTTPClient/win32/ALWinHTTPClientDemo.exe – zeus Mar 28 '18 at 15:32
  • https://173.212.219.42/ – Grim Mar 28 '18 at 21:04
  • Using client aut certificates is a bit of negotiation between the client and the server (see http://www.jscape.com/blog/client-certificate-authentication). It means that if the client code is not able to follow this process, you either need to replace that component or go deeper, and handle it yourself. The WinInet by itself does not provide that higher approach, even less with InternetOpenUrl as far as I know. But you can handle at lower level: https://mobile.codeguru.com/cpp/i-n/internet/generalinternet/article.php/c3367/Selecting-a-client-certificate.htm – ZorgoZ Apr 01 '18 at 09:35
  • So you want to display that dialog, or specify the certificate in code? – Victoria Apr 10 '18 at 09:51
  • @Victoria I do not like to specify the certificate in code. I like the OS to ask the user everything necessary to establish the connection, that means that the OS should decide if a dialog or even a certificate selection is required. – Grim Apr 10 '18 at 13:19
  • Aha, so actually act like Internet Explorer would be. – Victoria Apr 10 '18 at 14:29
  • @Victoria You are right, exactly like the IE. – Grim Apr 10 '18 at 17:05

2 Answers2

4

Consider the use of InternetErrorDlg

Code example:

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;
Grim
  • 1,938
  • 10
  • 56
  • 123
vzamanillo
  • 9,905
  • 1
  • 36
  • 56
2

Using WinHTTP (You can do the same with WinInetHTTP) you can set the certificate like this via 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();

So that easy with ActiveX but it's not really what you want (i gave you the example as illustration). So with the windows API, WinHTTP enables you to select and send a certificate from a local certificate store. The following code example shows how to open a certificate store and locate a certificate based on subject name after the ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED error has been returned.

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.
      }
    }
  }
zeus
  • 12,173
  • 9
  • 63
  • 184
  • 1
    This should have been about WinINet. not WinHTTP, nor some made up WinInetHTTP. And it's Delphi, not C++ Sometimes is quite dangerous set up bounty too high. People will post anything to get just a half of it. – Victoria Apr 10 '18 at 12:24
  • 1
    @victoria: sometime it's just dangerous to share anything, someone will always like argue ;) I answer to this question not for the bounty (was not even aware they was a bounty before you point it to me and serious who care about their reputation on SO ?) but because I wrote 2 wrappers that use WinHttp and WininetHttp (https://github.com/Zeus64/alcinoe), so i know about it. Their implementation are quite very similar and I found this sample that describe very well how to do on winhttp and with a very little research can be translated to wininethttp. Cant post this sample in comment too big – zeus Apr 10 '18 at 12:42
  • 1
    Sorry then. I'm not downvoting. I would upvote if the code was Delphi :) – Victoria Apr 10 '18 at 12:46
  • @victoria seriously, you want i convert the "{" by "begin" and the "}" by "end" ? the sample don't use anything particular connected to c++, it's just api call – zeus Apr 10 '18 at 12:48
  • api are translated in delphi in the unit Soap.Win.CertHelper.pas and delphi sample can be found in System.Net.HttpClient.Win.pas (so with the word "begin", the word "end" and ":=" :) – zeus Apr 10 '18 at 12:52