Reputation: 1
I'm writing a test Delphi 10.4 Win32 application that uses TIdHTTPServer
and TIdServerIOHandlerSSLOpenSSL
from Indy 10.6.2.0 installed by Delphi 10.4, using a port on localhost for the purposes of communicating between a C# (.NET 5) client application and this Win32 Delphi VCL (server) application.
I know very little about TLS, SSL, and certificates but have been learning as I attempt to build this; from the examples I have seen, I had expected this to be simple.
The "general" page for the .pfx
certificate properties says "Enable all purposes for this certificate".
I started partly by reviewing examples and also Remy Lebeau's postings here:
https://stackoverflow.com/a/34422109/17371832
https://stackoverflow.com/a/63082235/17371832
Using Indy 10 IdHTTP with TLS 1.2
I have provided it with OpenSSL 1.0.2.17 DLLs:
libeay32.dll
ssleay32.dll
These DLLs load successfully.
The problem is that clients can't connect to it. .NET says "The SSL connection could not be established" (more about the error inside Indy later).
WireShark shows "Client Hello" (TLS 1.2 with about 20 cipher types) but no "Server Hello", alerts, or errors are sent. In fact, only one packet marked as TLS 1.2 is sent (when the client said "Client Hello").
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:(and many others)
I had tried providing the full list (as formatted above) to the TIdHTTPServer
's CipherList
property, but got an error (I think it was "SetCipherList failed").
If the cipher list is left empty, it seems to internally use "AES:ALL:!aNULL:!eNULL:+RC4:@STRENGTH"
. I left it that way.
Here are the components involved:
object srv: TIdHTTPServer
Bindings = <
item
IP = '127.0.0.1'
Port = 5500
end>
DefaultPort = 5500
IOHandler = iohSSL
MaxConnections = 25
AutoStartSession = True
KeepAlive = True
SessionState = True
OnCreatePostStream = srvCreatePostStream
OnDoneWithPostStream = srvDoneWithPostStream
OnCommandGet = srvCommandGet
Left = 183
Top = 118
end
object iohSSL: TIdServerIOHandlerSSLOpenSSL
SSLOptions.CertFile = 'C:\Users\Mike\AppData\Local\Somewhere\cert.pfx'
SSLOptions.KeyFile = 'C:\Users\Mike\AppData\Local\Somewhere\cert.pfx'
SSLOptions.Method = sslvTLSv1_2
SSLOptions.SSLVersions = [sslvTLSv1_2]
SSLOptions.Mode = sslmServer
OnGetPasswordEx = iohSSLGetPasswordEx
Left = 256
Top = 120
end
The code isn't interesting:
procedure TdmCloud.DataModuleCreate(Sender: TObject);
begin
iohSSL.SSLOptions.CertFile:='.\cert.pfx';
//iohSSL.SSLOptions.RootCertFile:='.\root2.pem';
//iohSSL.SSLOptions.KeyFile:='.\key.pem';
srv.Active:=true;
end;
procedure TdmCloud.iohSSLGetPasswordEx(ASender: TObject; var VPassword: string;
const AIsWrite: Boolean);
begin
VPassword := 'password';
end;
procedure TdmCloud.srvCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
begin
AResponseInfo.ResponseNo := HTTP_OK;
AResponseInfo.ContentType := 'text/html';
AResponseInfo.ContentText := '<html><head><title>Title</title></head><body>HELLO</body></html>';
end;
procedure TdmCloud.srvCreatePostStream(AContext: TIdContext;
AHeaders: TIdHeaderList; var VPostStream: TStream);
begin
VPostStream := TMemoryStream.Create;
end;
procedure TdmCloud.srvDoneWithPostStream(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; var VCanFree: Boolean);
begin
VCanFree := true;
end;
I have seen in the debugger that the certificate file is loaded successfully with the password provided by the event handler. The listener thread runs ok until it receives a request.
TIdHTTPServer
's OnCommandGet
event doesn't fire.
I've seen in the debugger that InternalReadLn()
returns a string that looks encrypted; it's definitely binary. Anyway, below, I get "error in parsing command"; judging from the data it's looking at, I'm not surprised.
var
i: integer;
s, LInputLine, LRawHTTPCommand, LCmd, LContentType, LAuthType: String;
LURI: TIdURI;
LContinueProcessing, LCloseConnection: Boolean;
LConn: TIdTCPConnection;
LEncoding: IIdTextEncoding;
begin
LContinueProcessing := True;
Result := False;
LCloseConnection := not KeepAlive;
try
try
LConn := AContext.Connection;
repeat
// InternalReadLn( ) returns a string that looks encrypted; it's definitely binary
LInputLine := InternalReadLn(LConn.IOHandler);
i := RPos(' ', LInputLine, -1); {Do not Localize}
if i = 0 then begin // true
raise EIdHTTPErrorParsingCommand.Create(RSHTTPErrorParsingCommand);
end;
As it happens, InternalReadLn()
calls TIdServerIOHandlerSSLOpenSSL
's Readln()
method, which has provided this binary data.
Remy Lebeau said this about the error in an online posting:
The error is actually coming from several lines further down:
i := RPos(' ', LInputLine, -1); {Do not Localize} if i = 0 then begin raise EIdHTTPErrorParsingCommand.Create(RSHTTPErrorParsingCommand); end;
Which means your client is sending a malformed HTTP request. The first line in EVERY request must end with an HTTP version number, ie:
GET /login.html HTTP/1.0
Hence the call to RPos(). The error means there is no space character anywhere in the line.
Although it could certainly be a malformed request, I'm told that the application's requests have been tested and found to be valid/working.
I've tried a browser directed to https://localhost:5500/ with similar results.
That brings me back to the conclusion that TLS negotiation didn't start at "Client hello". I have no idea why. I don't know where that process is handled in Indy.
Here's a typical, working TLS negotiation: https://tls.ulfheim.net/
Key areas of suspicion for me; I've made some effort to explore each possibility:
CertFile
, RootCertFile
, KeyFile
, and CipherList
do I need to provide for TLS 1.2 to work? (and how do I choose what to use?)Unhelpfully, all of documentation links at https://www.indyproject.org/documentation/ are broken! My Delphi install didn't include any Indy help either.
Help being absent, I'm uncertain which to provide of RootCertFile
, CertFile
, and KeyFile
. I've tried the .pfx
which was accepted for CertFile
and KeyFile
but later saw https://stackoverflow.com/a/15416234/17371832 so I followed the instructions and was able to make a .pem
file (root file was not accepted by Indy). This provided no improvement.
Is Indy dying? Is there something else that's better? Haven't looked into that at all.
Why won't it connect?
Upvotes: 0
Views: 1642
Reputation: 597941
The standard TCP port for HTTPS is 443, but you are running your TIdHTTPServer
on port 5500 instead. As such, you must use the TIdHTTPServer.OnQuerySSLPort
event to explicitly tell TIdHTTPServer
that you want to use SSL/TLS on that port, eg:
procedure TdmCloud.srvQuerySSLPort(APort: TIdPort; var VUseSSL: Boolean);
begin
VUseSSL := APort = 5500;
end;
This event allows HTTP and HTTPS to co-exist on a single server (via multiple TIdHTTPServer.Bindings
items) even when using non-standard ports. Without this event handler assigned, this is why your server is not handling the client's SSL/TLS handshake correctly.
Upvotes: 1