Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how to request a certificat on a specific URL with TidHTTPServer #297

Closed
tothpaul opened this issue Apr 6, 2020 · 7 comments
Closed

how to request a certificat on a specific URL with TidHTTPServer #297

tothpaul opened this issue Apr 6, 2020 · 7 comments
Labels
Element: HTTP Issues related to HTTP handling, TIdHTTP, TIdHTTPServer, TIdHTTPProxyServer, etc Element: SSL/TLS Issues related to SSL/TLS handling, TIdSSLIOHandlerSocketBase and descendants Status: Completed Issue has been closed, no further work needed Type: Question Issue is asking a question, or requesting support/clarity

Comments

@tothpaul
Copy link

tothpaul commented Apr 6, 2020

Hello,

I'de like to implement something that do work under Apache but directly with TidHTTPServer

I have an HTTPS domain, and on a specific URL I need to request a client certificat, it's done like that under Apache:

# Secure location configurations
<Location /connect.php>
	SSLOptions +StdEnvVars +ExportCertData
	SSLVerifyClient require
	SSLVerifyDepth 4
</Location>

the purpose is to allow an optional connection with a card reader

french video about that
https://www.youtube.com/watch?v=1BSfWG1Cpyc&feature=youtu.be

BTW: what is the equivalent of PHP's $_SERVER['SSL_CLIENT_CERT']...

BTW2: is is possible to have a certificat per domain name ?

@rlebeau
Copy link
Member

rlebeau commented Apr 6, 2020

Typically, an SSL/TLS handshake is performed by the server only when a new TCP connection is made, before any HTTP request is received. That is how Apache implements per-server certificates. In Indy, this is done by assigning a TIdServerIOHandlerSSLOpenSSL to the TIdHTTPServer.IOHandler property, configuring a seerver certificate in the IOHandler, enabling the sslvrfPeer and sslvrfFailIfNoPeerCert flags in the SSLOptions.VerifyMode property if you want to require client certificates, and optionally assign an OnVerifyPeer event handler if you need to perform any custom verification of the client's certificate.

Using per-domain certificates is not implemented at this time, but is being worked on (#160).

To implement per-URL certificates, Apache forces an SSL/TLS renegotiation once an HTTP request has been parsed to know which URL is being requested. To perform that with Indy's use of OpenSSL, you will need to call OpenSSL's SSL_renegotiate() and SSL_do_handshake() functions directly (see How to perform a rehandshake (renegotiation) with OpenSSL API?). However, Indy does not currently import those functions, so you will have to import them manually (see the GetSSLLibHandle() and GetCryptLibHandle() functions in the IdSSLOpenSSLHeaders units, and the recently added LoadLibFunction() function in the IdGlobal unit). You can get the OpenSSL connection handle in any of the server's OnCommand... events by accessing the client's TIdSSLSocket.fSSL data member. It is declared as protected, so you will have to use an accessor class to reach it, eg:

uses
  ..., IdSSLOpenSSLHeaders, IdSSLOpenSSL;

type
  TIdSSLSocketAccess = class(TIdSSLSocket)
  end;

procedure TMyForm.IdHTTPServer1CommandGet(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  SSLio: TIdSSLIOHandlerSocketOpenSSL;
  SSLconn: PSSL;
  err: Integer;
begin
  if (ARequestInfo needs a specific certificate) then
  begin
    SSLio := TIdSSLIOHandlerSocketOpenSSL(AContext.Connection.IOHandler);
    SSLconn := TIdSSLSocketAccess(SSLio.SSLSocket).fSSL;

    // configure SSLconn for new certificate as needed ...

    err := SSL_renegotiate(SSLconn);
    if err <= 0 then
      EIdOSSLDataBindingError.RaiseException(SSLconn, err, 'SSL_renegotiate failed');

    err := SSL_do_handshake(SSLconn);
    if err <= 0 then
      EIdOSSLDataBindingError.RaiseException(SSLconn, err, 'SSL_do_handshake failed');

    ssl.state := SSL_ST_ACCEPT;

    err := SSL_do_handshake(SSLconn);
    if err <= 0 then
      EIdOSSLDataBindingError.RaiseException(SSLconn, err, 'SSL_do_handshake failed');

    // not sure if this is really needed...
    AResponseInfo.CloseConnection := true; // so an HTTP keep-alive does not keep using the new certificate after the response is sent...
  end;

  // populate AResponseInfo as needed ...
end;

@tothpaul
Copy link
Author

tothpaul commented Apr 7, 2020

great answer as usual ;)

I've just added this to configure the SSLconn (with a SSLOptions.VerifyMode = [])
SSL_set_verify(SSLconn, SSL_VERIFY_PEER or SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nil);

Do you know how I can invalidate the negociation in a /logout URL ? actually I have to close the navigator to clear the session.

@tothpaul
Copy link
Author

tothpaul commented Apr 7, 2020

Yes ! I've found the answer
https://stackoverflow.com/a/10273994/7519535

type
  verify_callback = function(preverify_ok: Integer; ctx: PX509_STORE_CTX): Integer; cdecl;

var
  SSL_set_verify: procedure(SSL: PSSL; mode: Integer; callback :verify_callback); cdecl;
  SSL_renegotiate: function(SSL: PSSL): Integer; cdecl;
  SSL_do_handshake: function(SSL: PSSL): Integer; cdecl;

function refuse_callback(preverify_ok: Integer; ctx: PX509_STORE_CTX): Integer; cdecl;
begin
  Result  := 0; // 1 = success, 0 = failure
end;

function LoadSSL(const Name: PChar): Pointer;
begin
  Result := LoadLibFunction(GetSSLLibHandle, Name);
  if Result = nil then
  begin
    log(name + ' not found !');
    Halt(EXIT_FAILURE);
  end;
end;
...
  IdSSLOpenSSLHeaders.Load;

  SSL_set_verify := LoadSSL('SSL_set_verify');
  SSL_renegotiate := LoadSSL('SSL_renegotiate');
  SSL_do_handshake := LoadSSL('SSL_do_handshake');

...
    // configure SSLconn for new certificate as needed ...
    if ARequestInfo.QueryParams = 'logout' then
    begin
      SSL_set_verify(SSLconn, SSL_VERIFY_PEER or SSL_VERIFY_FAIL_IF_NO_PEER_CERT, refuse_callback);
    end else begin
      SSL_set_verify(SSLconn, SSL_VERIFY_PEER or SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nil);
    end;

@tothpaul tothpaul closed this as completed Apr 7, 2020
@tothpaul
Copy link
Author

Hello,

After a while I'm back on this project, Unbuntu is now in version 20.04 and Delphi is Sydney, I use this repository source code for Indy...and my code do not work anymore...

GetSSLLibHandle returns 0 (because LoadSSLLibrary returns IdNilHandle)
and LoadLibFunction returns nil

it doesn't work either when defining USE_BASEUNIX...what is the pb ?

Thanks

@tothpaul tothpaul reopened this Oct 20, 2020
@rlebeau
Copy link
Member

rlebeau commented Oct 20, 2020

Which platform are you targeting? When LoadSSLLibrary() fails, what does WhichFailedToLoad() report? When using TIdSSLIOHandlerSocketOpenSSL, make sure you are using OpenSSL 1.0.2u or earlier, not OpenSSL 1.1.x (what does OpenSSLVersion() report?). On modern 'Nix systems, you may need to call IdOpenSSLSetLoadSymLinksFirst(False) or IdOpenSSLSetCanLoadSymLinks(False) at program startup to avoid Indy loading unversioned symlinks that now point to OpenSSL 1.1.x, If you want to use OpenSSL 1.1.x, use the IOHandler code from this pull request instead.

@tothpaul
Copy link
Author

Ubuntu 20.04 64bits (just updated from 18.04)
WichFailedToLoad = "libcrypto.so."
OpenSSLVersion =""

openssl version returns "OpenSSL 1.1.1f 31 Mar 2020"

hum...after a apt-get isntall libssl-dev I do not have the error above anymore, but a EIdOSSLCouldNotLoadSSLLibray exception with
'SSL_load_error_strings,SSL_library_init,SSLeay_version,SSLeay,CRYPTO_lock,CRYPTO_num_locks,CRYPTO_set_locking_callback,CRYPTO_set_id_callback,ERR_free_strings,EVP_MD_CTX_init,EVP_MD_CTX_cleanup,EVP_CIPHER_CTX_flags,HMAC_CTX_init,HMAC_CTX_cleanup,CRYPTO_mem_leaks,CRYPTO_set_mem_debug_functions,OpenSSL_add_all_algorithms,OpenSSL_add_all_ciphers,OpenSSL_add_all_digests,EVP_cleanup,sk_num,sk_new,sk_new_null,sk_free,sk_push,sk_dup,sk_find,sk_value'

it is expected for 1.1.1 if I understand well

@rlebeau
Copy link
Member

rlebeau commented Oct 20, 2020

As I said, TIdSSLIOHandlerSocketOpenSSL does not support OpenSSL 1.1.x, hence the load errors. Yes, there are significant API changes from 1.0.2 to 1.1.x, which is why all of those library functions are missing. So either install 1.0.2 (and call IdOpenSSLSetLibPath() if needed), or else use the new 1.1.x-enabled IOHandler that I linked to earlier.

@rlebeau rlebeau added Element: SSL/TLS Issues related to SSL/TLS handling, TIdSSLIOHandlerSocketBase and descendants Element: HTTP Issues related to HTTP handling, TIdHTTP, TIdHTTPServer, TIdHTTPProxyServer, etc Status: Completed Issue has been closed, no further work needed Type: Question Issue is asking a question, or requesting support/clarity labels Apr 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Element: HTTP Issues related to HTTP handling, TIdHTTP, TIdHTTPServer, TIdHTTPProxyServer, etc Element: SSL/TLS Issues related to SSL/TLS handling, TIdSSLIOHandlerSocketBase and descendants Status: Completed Issue has been closed, no further work needed Type: Question Issue is asking a question, or requesting support/clarity
Projects
None yet
Development

No branches or pull requests

2 participants