You are not logged in.
Pages: 1
Hello, ab!
I need TLS support for THttpServer (mORMot2).
There is some support for it in TCrtSocket: parameters in TNetTlsContext and encryption layer abstraction in INetTls. But only for client connections. So, I started trying to make it for server.
At first step I changed some code in mormot.net.sock.pas for Windows, i.e. add some Cert* functions and change call from InitializeSecurityContext to AcceptSecurityContext. Now my THttpServer serves HTTPS (tested in Chrome and Firefox).
At next step I try to work on TLS for Linux and OpenSSL.
But for server TLS we need some state, shared for all accepted connections, like certificate (CredHandle or SSL_CTX). So we need to do some refactoring.
I think we can change TNetTlsContext to interface like INetTlsContext. Then implement it for client and server. For server, we store shared state in underlying object. All accepted sockets will get a reference to parent socket INetTlsContext.
Sure, INetTls will be implemented differently for client and server.
Client code may looks like this:
with THttpClientSocket.Create do
try
TLS := NewTlsContext();
TLS.WithPeerInfo := true;
// Or maybe:
// TLS := NewTlsContext({WithPeerInfo=}true, {IgnoreCertificateErrors=}true);
OpenBind('synopse.info', '443', {bind=}false, {tls=}true);
writeln(Secure.PeerInfo);
writeln(Secure.CipherName);
writeln(Get('/forum/', 1000), ' len=', ContentLength);
finally
Free;
end;
Server side:
fHttpServer := THttpServer.Create(...);
fHttpServer.WaitStarted;
fHttpServer.Sock.TLS := NewServerTlsContext('mycert.pfx');
Maybe you can suggest other solutions.
Offline
You are right, TLS is only implemented on client side yet.
The idea is to put a nginx as reverse proxy in front of the mORMot server. It scales very well, is very audited so safer, and can be used also for static content publishing, and also for automatic certificates renewal (it is very easy to use Let's Encrypt with ngxin for instance).
But of course, native TLS on server side could be a nice addition.
Could you share the source code to the point where you are currently?
Offline
The idea is to put a nginx as reverse proxy in front of the mORMot server.
For those who are not so experienced with servers, I would like to mention the Caddy Server. With this simple configuration (more needs not be written) in the caddyfile you get a fast and secure reverse proxy server.
my-domain.com {
reverse_proxy localhost:35530
}
With best regards
Thomas
Offline
Could you share the source code to the point where you are currently?
https://gist.github.com/achechulin/fb78 … aa58377ef5
I started from http-server-raw example, and modified mORMot 2 code in destructive way - change TLS client code to work in server mode.
First, THttpAsyncServer changed to THttpServer.
Then, TCrtSocket.OpenBind changed to always call DoTlsHandshake for accepted socket.
Third, TSChannelClient.AfterConnection load server certificate (mycert.pfx) on first use, and then call AcquireCredentialsHandle with that certificate. In HandshakeLoop InitializeSecurityContext changed to AcceptSecurityContext.
I use Let's Encrypt sertificate, converted to PFX:
openssl pkcs12 -inkey privkey.pem -in cert.pem -export -out mycert.pfx
Offline
About perfomance.
First call to AcceptSecurityContext takes about 1 ms, then about 2 ms we wait client answer, and second call to AcceptSecurityContext takes 1 ms.
Summary 4 ms for establishing TLS connection.
Some numbers, when testing with Apache benchmarking tool.
> abs.exe -n 10000 -c 100 https://test:8888/echo
Server Software: mORMot2
Server Hostname: test
Server Port: 8888
SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES256-GCM-SHA384,2048,256
Server Temp Key: ECDH P-384 384 bits
TLS Server Name: test
Document Path: /echo
Document Length: 53 bytes
Concurrency Level: 100
Time taken for tests: 49.170 seconds
Complete requests: 10000
Failed requests: 0
Keep-Alive requests: 0
Total transferred: 1560000 bytes
HTML transferred: 530000 bytes
Requests per second: 203.38 [#/sec] (mean)
Time per request: 491.698 [ms] (mean)
Time per request: 4.917 [ms] (mean, across all concurrent requests)
Transfer rate: 30.98 [Kbytes/sec] received
For comparision, numbers for server without TLS:
Requests per second: 3488.43 [#/sec] (mean)
Time per request: 28.666 [ms] (mean)
Time per request: 0.287 [ms] (mean, across all concurrent requests)
Transfer rate: 531.44 [Kbytes/sec] received
And IIS:
Server Software: Microsoft-IIS/10.0
Server Hostname: test
Server Port: 443
SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES256-GCM-SHA384,2048,256
Server Temp Key: ECDH P-384 384 bits
TLS Server Name: test
Document Path: /iisstart.htm
Document Length: 696 bytes
Concurrency Level: 100
Time taken for tests: 30.434 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 9390000 bytes
HTML transferred: 6960000 bytes
Requests per second: 328.58 [#/sec] (mean)
Time per request: 304.343 [ms] (mean)
Time per request: 3.043 [ms] (mean, across all concurrent requests)
Transfer rate: 301.30 [Kbytes/sec] received
Offline
On Windows, why not use the http.sys server, which supports TLS out of the box, and has similar performance to IIS because it is based on http.sys (so seems faster than the socket layer because the kernel module handle TLS)?
Only because it is easier to setup?
Offline
On Windows, why not use the http.sys server, which supports TLS out of the box, and has similar performance to IIS (so is faster than the socket layer)?
Only because it is easier to setup?
First, I would like to have one codebase for Windows and Linux. And, ideally, certificate renewal infrastructure/code.
And in THttpServer I use feature called "stream": when processing special URL, I move socket from working thread to array of stream subscribers, and convert socket to async. Later, when data become availabe, I send it to all stream subscribers (subscriber is array of async sockets). Not sure yet if this will work with TLS.
Now I try Linux, but in my Ubuntu 22.04 installed OpenSSL 3, and there is some functions were renamed (e.g. EVP_PKEY_size -> EVP_PKEY_get_size).
Offline
Of course, a built-in feature in mORMot HTTPS server to be able to auto-renew Lets's Encrypt certificates, like the caddy server, could be a nice feature.
But our server is only HTTP/1.1 whereas today, modern browsers prefer HTTP/2.0 or even HTTP/3.0 to leverage the connection.
With caddy or nginx, it is easy to offer HTTP/2.0 transmission. Sadly, this HTTP/2.0 is so complex that we would never implement it natively in pascal code. We could use an external library, but then using a reverse proxy sounds like a better approach.
So I am not sure a mORMot server, even with HTTPS embedded, could be very useful on production, with no proxy on front end.
Offline
I agree with you, that if we create web server on the Internet, available for a large number of users, we need proxy on front end.
But there are other use cases. For example, we automate some work, and need web-interface for initial configuration and monitoring. In that case we have limited number of users, no need in high perfomance, but secure access to the administration interface is required.
I think we can add TLS support for THttpServer at little cost. There is more problems with certificate management, but we'll deal with that later.
What we need:
1. Shared context.
2. Divide TNetTlsContext to shared and per-connection properties. For example, CertificateFile -> shared, PeerInfo -> per-connection.
3. For accepted socket set shared context and then do handshake.
May be implemented:
1. Change record TNetTlsContext to class or interface. Optionally with auto creation in property getter, for better backward compatibility.
3. Move CipherName, Peer*, LastError from TNetTlsContext to INetTls. Move TNetTlsContext.Enabled to TCrtSocket.TlsEnabled.
3. Change TCrtSocket.OpenBind to set TLS context for accepted socket and do handshake.
Offline
Perhaps just adding this method to INetTls could be enough:
/// method called for each new connection on server side
// - should make the proper server-side TLS handshake and create a session
// - should raise an exception on error
// - TNetTlsContext should have been copied from the server properties
procedure AfterAccept(Socket: TNetSocket; const Address: TNetAddr;
var Context: TNetTlsContext);
Then using regular Receive/Send as expected.
TNetTlsContext is specific to a TLS session, so we could just have one with the certificates information on the bind socket, which will be copied to each TCrtSocket instance before calling AfterAccept().
It seems easier than putting properties into INetTls, which would break existing code.
Offline
Perhaps just adding this method to INetTls could be enough: AfterAccept
Yes, this method is needed.
But we also need some shared state for storing TLS options and certificate keys. Minimum, for schannel it is TCredHandle, for openssl it is SSL_CTX.
Simple solution: add opaque pointer in TNetTlsContext for it.
Offline
We already have those TLS options and certificates in TNetTlsContext, copied from the bind socket.
And connection details are already passed to the callbacks as opaque TLS/Peer pointers.
Note that I have found proper ACME v2 support in unit OverbyteIcsSslX509Certs of ICS.
So we may have some reference code in pascal to interface with Let's Encrypt.
Offline
Sorry, I was not clear enough.
We need some TLS-library specific context.
For example, for schannel we open system certificate store or create new store in memory and load certificate from file. Then we obtain certificate handle, and then create credentials handle from those handles.
And for OpenSSL we allocate SSL_CTX structure, set options, load certificate and corresponding private key.
We may put TLS-library specific context in the connection specific object (TSChannelClient) or in the server-specific object - opaque pointer in TNetTlsContext, that initialized at server start and copied from bind socket to accept socket.
So, let's start with first option.
Next, I will measure server performance, and we make decision on further development.
Last edited by Chaa (2022-05-23 09:48:03)
Offline
ab, can I use mormot.lib.sspi in mormot.net.sock.windows.inc?
And why we load secur32.dll dynamically? It's available since Windows 95 and Windows NT 4 with installed Directory Service Client, and always available on Windows 2000.
Offline
Yesterday, I prepared moving all SSPI/SChannel code to mormot.lib.sspi.
And removed mormot.core.unicode dependency on mormot.lib.sspi.
Offline
Yesterday, I prepared moving all SSPI/SChannel code to mormot.lib.sspi.
Great, thanks!
Offline
For infomation, patch to use OpenSSL 3.0 (installed on Ubuntu 22.04):
https://gist.github.com/achechulin/adbb … 1810dc78c6
Offline
Now I try Linux, but in my Ubuntu 22.04 installed OpenSSL 3, and there is some functions were renamed (e.g. EVP_PKEY_size -> EVP_PKEY_get_size).
I have made mormot.lib.openssl compatible with both OpenSSL 3 and 1.1.1.
It should now adapt at runtime to the version it finds.
Please check https://github.com/synopse/mORMot2/commit/2d9c3f61
and https://github.com/synopse/mORMot2/commit/fb9f662c
Offline
Initial TLS support:
https://github.com/synopse/mORMot2/pull/92
Next, I want to fix compilation in case OPENSSLSTATIC or OPENSSLFULLAPI defined.
Offline
Thanks for merge.
But I see commit in history:
https://github.com/synopse/mORMot2/comm … 057af03443
May be it's wrong? Git is quite complex.
Offline
Sounds like if non blocking sockets are not so difficult to implement: https://github.com/yedf2/openssl-exampl … ssl-svr.cc
I will use it as reference to add TLS support to our async servers.
Offline
There is one problem with OpenSSL and Linux.
While OpenSSL write to a socket where the other end is closed already, a SIGPIPE will be generated, and process terminated.
It's because OpenSSL use "send" without MSG_NOSIGNAL.
CURL authors suggest changes to default BIO:
https://github.com/openssl/openssl/pull/17734
I see two option:
1. Add fpSignal(SIGPIPE, SIG_IGN) and wait for OpenSSL changes.
2. Add custom BIO with MSG_NOSIGNAL, like https://github.com/alanxz/rabbitmq-c/pull/402
Offline
Hello, I had a similar issue (the SIGPIPE signal on socket send) with a small project I was working on.
Happily, I could solve it using a fpsignal(SIGPIPE, @handlesignal) before the main loop, where handlesignal is an "empty" procedure (just with an exit instruction).
By the way, this TLS support for THttpServer is a nice feature, especially for small projects where the server only has a few dozens of concurrent clients. Hopefully will be merged in a near future.
Regards
Offline
Please try https://github.com/synopse/mORMot2/commit/be163535
which should speed up the server connections in a noticeable way.
In the meanwhile, I have disabled SIG_PIPE when OpenSSL TLS is used on POSIX.
https://github.com/synopse/mORMot2/commit/f9ca0c95
Offline
Some little patches.
For mormot.net.server.pas, in THttpServerSocketGeneric.WaitStarted, because for WIndows CertificateFile is enough:
@ -1620,7 +1620,7 @@ begin
until false;
// now the server socket has been bound, and is ready to accept connections
if (hsoEnableTls in fOptions) and
- (PrivateKeyFile <> '') and
+ (CertificateFile <> '') and
not fSock.TLS.Enabled then
begin
StringToUtf8(CertificateFile, fSock.TLS.CertificateFile);
For mormot.net.sock.windows.inc, in TSChannelNetTls.AfterBind, for proper error when .pfx can not be read:
@ -1117,6 +1117,8 @@ begin
// openssl pkcs12 -inkey privkey.pem -in cert.pem -export -out mycert.pfx
fAcceptCertStore := PFXImportCertStore(@blob, nil,
PKCS12_INCLUDE_EXTENDED_PROPERTIES);
+ if fAcceptCertStore = nil then
+ ESChannelRaiseLastError(SEC_E_CERT_UNKNOWN);
end;
// find first certificate in store
fAcceptCert := CertFindCertificateInStore(fAcceptCertStore, 0, 0,
For mormot.net.async.pas, in TPollAsyncSockets.ProcessRead, because working thread terminates after exception in TLS code:
@ -1353,7 +1353,13 @@ begin
not (fFirstRead in connection.fFlags) then
begin
include(connection.fFlags, fFirstRead);
- fOnFirstRead(connection); // e.g. TAsyncServer.OnFirstReadDoTls
+ try
+ fOnFirstRead(connection); // e.g. TAsyncServer.OnFirstReadDoTls
+ except
+ // TLS error -> abort
+ UnlockAndCloseConnection(false, connection, 'ProcessRead OnFirstRead');
+ exit;
+ end;
end;
repeat
if fRead.Terminated or
Offline
And there is problem in THttpServerSocketGeneric.WaitStarted - for THttpAsyncServer fSock is nil. We need to use Async.Server instead.
Or maybe in THttpAsyncServer set fSock := Async.Server.
Offline
I made some work and fixes on it yesterday, but forgot to commit.
Please check https://github.com/synopse/mORMot2/commit/535977bf
And your two fixes as https://github.com/synopse/mORMot2/commit/4f9f5517
The useHttpSocket seems fine on Linux with OpenSSL.
With very good performance: only slow down from 100MB/s to 70MB/s for a single HTTPS client in HTTP/1.1 mode. Of course, much slower in HTTP/1.0 but it is as expected due to the handshake.
On my Debian system, it even used TLS 1.3 out of the box. Nice
For the client, I used https://github.com/synopse/mORMot2/commit/893a0066
But the async server has some troubles working...
If you can't find the issue, I will investigate on Monday.
Offline
After latest changes (added TOpenSslNetTls.SetupCtx), browser asks client certificate when connecting via HTTPS:
https://drive.google.com/file/d/1SoyGOv … Rk0ZDmH1t/
So, we need to change IgnoreCertificateErrors to True to avoid client certificate request.
But this can be done only in THttpServerSocketGeneric.WaitStarted.
May be add new option to THttpServerOptions, like hsoTlsClientCert (or some other name, hsoTlsVerifyPeer?), and in WaitStarted:
// now the server socket has been bound, and is ready to accept connections
if (hsoEnableTls in fOptions) and
(CertificateFile <> '') and
(fSock <> nil) and // may be nil at first
not fSock.TLS.Enabled then
begin
...
fSock.TLS.IgnoreCertificateErrors := not (hsoTlsClientCert in fOptions);
InitializeTlsAfterBind; // validate TLS certificate(s) now
...
end;
Offline
You are right.
I guess my latest commits should fix that, and enhance TLS support on the server side.
See e.g. https://github.com/synopse/mORMot2/commit/2f060007
and https://github.com/synopse/mORMot2/commit/a546a602
and https://github.com/synopse/mORMot2/commit/51ac4dd6
Offline
Some tips I found in source:
1. https://github.com/synopse/mORMot2/blob … r.pas#L716
Inherited Create called twice.
2. https://github.com/synopse/mORMot2/blob … .pas#L4259
IsRemoteIPBanned always return True.
3. https://github.com/synopse/mORMot2/blob … .inc#L1208
RedirectRtlCall do something strange. Maybe some defines missing. It's redirect one function in mormot.core.rtti to another in mormot.core.rtti.
It's crashed with AV on my x86_64 linux.
Offline
@Chaa
Nice findings! Thanks!
About 1 and 2 - please check https://github.com/synopse/mORMot2/commit/94f486b7
But I can't reproduce point 3 - I use x86_64 linux as my main system since years and don't have any issue with it.
Look at the code: RedirectRtlCall is not the same as PatchCode. It does not redirect one function in mormot.core.rtti, it parses the binary one function in mormot.core.rtti to extract a low-level RTL call, which is redirected.
Which FPC revision do you use?
Perhaps FPC_X64MM conditional is not coherent between your mormot package and your project...
Edit: please try https://github.com/synopse/mORMot2/comm … bb597b0649
I have tried to remove all direct connection to mormot.core.fpcx64mm which was pretty confusing for sure.
Offline
Perhaps FPC_X64MM conditional is not coherent between your mormot package and your project.
I do not use package and do not use mormot.core.fpcx64mm.
I use FPC 3.2.3-554-g8b21bf1cce, and may be it's too old.
Look at the code: RedirectRtlCall is not the same as PatchCode. It does not redirect one function in mormot.core.rtti, it parses the binary one function in mormot.core.rtti to extract a low-level RTL call, which is redirected.
In my case RedirectRtlCall calls RedirectCode($FFFFFFFFF14BF09F, $0000000000D659E0) - function address invalid - negative, and then AV.
With latest version (2.0.3676) there is no changes.
Generated ASM for _fpc_setstring_ansistr:
https://drive.google.com/file/d/1GqN080 … sp=sharing
Offline
1. Please try to use packages on FPC.
This is the easiest way of using mORMot 2 for sure, and consistent with what other users do.
2. It is pretty weird.
My FPC compiler generates https://gist.github.com/synopse/5c97a80 … 21ea967676
They did change the function definition for sure: it was a out it is a var in 3.2.3...
So your version is not too old... it is too new than mine.
So I have made a full refactoring of the RTL redirection, so that it uses internal linking names, and no asm parsing trick, which are unreliable.
Now any such issue would appear at linking time.
Please try https://github.com/synopse/mORMot2/commit/ce9c4152
Offline
Thanks!
Now it works fine.
Offline
@Chaa
For FPC 3.2.3, perhaps https://github.com/synopse/mORMot2/commit/80fd1d0f is also needed.
Offline
I created the basic ACME client:
https://gist.github.com/achechulin/7aa9 … e5f940b3bb
I think it's needed a critical review.
And may be some parts, like JSON Web Signature (JWS), can be moved to JWT support unit.
BN_bn2bin can be moved to BIGNUM, GetEccPubKey and GetRsaPubKey to EVP_PKEY, CreateCsr to mormot.crypt.openssl and so on.
Also, I not fully understood ECC coordinate compression details, so GetEccPubKey and DerToEccSign may be not optimal in some ways.
Note that I have found proper ACME v2 support in unit OverbyteIcsSslX509Certs of ICS.
So we may have some reference code in pascal to interface with Let's Encrypt.
Overbyte code is too complicated, but answered some questions about ECC, thanks!
Offline
Notes:
1. The next logical step seems to integrate TChallengeConsumer.DoChallenges in our THttpServer instance.
2. Instead of using unfinished mormot.crypt.x509.pas we could just use DerToEcc() from mormot.crypt.ecc.pas which does the parsing for us.
Offline
2. Instead of using unfinished mormot.crypt.x509.pas we could just use DerToEcc() from mormot.crypt.ecc.pas which does the parsing for us.
Yes, it works (now only ES256).
But I can adopt it to use DerParse for 32, 48 or 66 bytes of signature (ECC_SIZE).
P.S.
It's not so simple, because for ES512 there is 2-byte length value in DER:
https://gist.github.com/achechulin/3212 … b0595bfc24
Last edited by Chaa (2022-09-21 10:06:23)
Offline
Please check https://github.com/synopse/mORMot2/commit/8ef89510
It is just a review of your code, with more integration to other units, as you suggested.
Now I will integrate it into TRestHttpServer, because it has already hosts support.
Offline
It is just a review of your code, with more integration to other units, as you suggested.
It's a very good review!
Some small fixes:
https://github.com/synopse/mORMot2/pull/119
Offline
Pages: 1