Delphi mutual authentication
Asked Answered
I

2

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

Instar answered 23/3, 2018 at 18:57 Comment(19)
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.Corena
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.Instar
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?Consumerism
@TarunLalwani Yes.Instar
Does this help? codeguru.com/cpp/i-n/internet/generalinternet/article.php/c3367/…Consumerism
@TarunLalwani InternetErrorDlg is unknown in delphi 7Instar
As per this thread it should exists? I am not a Deplhi programmer so may not know how the API stuff works in Deplhi. See this thread also, #9861809Consumerism
@TarunLalwani It doesnt exists. Maybe a difference in Delphi versions. I use Delphi7.Instar
Let us continue this discussion in chat.Consumerism
Might be better to use WinHttp instead of WinInet. See the accepted answer for this question: stackoverflow.com/questions/6725348Germanism
@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 workBuoyancy
@loki I can give you a svntl.Instar
@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 github.com/Zeus64/alcinoe) : svn.code.sf.net/p/alcinoe/code/demos/ALWinInetHTTPClient/win32/… or winhttp version: svn.code.sf.net/p/alcinoe/code/demos/ALWinHTTPClient/win32/…Buoyancy
173.212.219.42Instar
Using client aut certificates is a bit of negotiation between the client and the server (see 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: mobile.codeguru.com/cpp/i-n/internet/generalinternet/…Stereochemistry
So you want to display that dialog, or specify the certificate in code?Firecrest
@Firecrest 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.Instar
Aha, so actually act like Internet Explorer would be.Firecrest
@Firecrest You are right, exactly like the IE.Instar
V
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;
Vestpocket answered 20/4, 2018 at 21:33 Comment(9)
dwLastError is 12005 (ERROR_INTERNET_INVALID_URL). The expected ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED is 12044. Its a different error-code!Instar
Oh, your code points out that the servername is a url (see your InternetConnect statement).Instar
Is not an url what are you trying to connect to?Vestpocket
Yes, but I am not allowed to put a URL in, the parameter must be the servername, not the url. MSDN sais it must be lpszServerName not PChar(strURL).Instar
Anyway, it is easy to adjust my sample code to your requirements with some little changes, the purpouse is the way to show the Internet dialog to pick a valid cert.Vestpocket
And I am pretty sure your answer is the correct one, let be do a short test, then I give your answer the bounty.Instar
Thanks, Peter, glad to help.Vestpocket
The code is updated to use a servername and resource, give it a look.Vestpocket
Code tested successfully compleatly.Instar
B
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.
      }
    }
  }
Buoyancy answered 1/4, 2018 at 14:49 Comment(5)
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.Firecrest
@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 (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 bigBuoyancy
Sorry then. I'm not downvoting. I would upvote if the code was Delphi :)Firecrest
@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 callBuoyancy
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 ":=" :)Buoyancy

© 2022 - 2025 — McMap. All rights reserved.