Synopse Open Source - Tag - TLSmORMot MVC / SOA / ORM and friends2024-02-02T17:08:25+00:00urn:md5:cc547126eb580a9adbec2349d7c65274DotclearNative TLS Support for mORMot 2 REST or WebSockets Serversurn:md5:0d13d921c925b27dacea2bb1bda2c2852022-07-09T11:08:00+01:002022-07-10T06:39:19+01:00Arnaud BouchezmORMot FrameworkAsymmetricFreePascalHTTPhttp.sysHTTPSLinuxmORMotmORMot2OpenSourceOpenSSLperformancePublicKeyRESTSChannelTLSWebSockets<p>Since the beginning, we delegated the TLS encryption support to a reverse proxy server, mainly <a href="https://nginx.org">Nginx</a>. Under Windows, you could setup the http.sys HTTPS layer as usual, as a native - even a bit complicated - solution.<br />
Nginx has several advantages, the first being a proven and efficient technology, with plenty of documentation and configuration tips. It interfaces nicely with Let's Encrypt, and is very good for any regular website, using static content and PHP. This very blog and the <a href="https://synopse.info">Synopse</a> web site is hosted via Ngnix on a small Linux server.</p>
<p><img src="https://blog.synopse.info?post/public/blog/TlsServer.png" alt="" /></p>
<p>But in mORMot 2, we introduced a new set of <a href="https://blog.synopse.info/?post/2022/05/21/New-Async-HTTP/WebSocket-Server-on-mORMot-2">asynchronous web server classes</a>. So stability and performance are not a problem any more. Some benchmarks even consider this server to <a href="https://synopse.info/forum/viewtopic.php?pid=36546#p36546">be faster than nginx</a> (the stability issue mentioned in this post has been fixed in-between).<br />
We just introduced TLS support of our socket-based servers, both the blocking and asynchronous classes. It would use OpenSSL if available, or the SChannel API layer of Windows. Serving HTTPS or WSS with a self-signed certificate is just a matter of a single parameter now, and performance seems pretty good, especially with OpenSSL.</p> <h4>From HTTP to HTTPS</h4>
<p>Here is how you publish a <code>TRestServer</code> instance over HTTP, on port 8888, and with 16 threads for the thread pool:</p>
<pre>
Server := TRestHttpServer.Create([RestServer], '8888', 16);
</pre>
<p>Note the new constructor, easier to use than before, if you just want the default asynchronous server.</p>
<p>And to publish over HTTPS, on the very same port, with a self-signed certificate:</p>
<pre>
Server := TRestHttpServer.Create([RestServer], '8888', 16, secTLSSelfSigned);
</pre>
<p>For a <em>mORMot</em> client, you should also specify that you expect TLS support, and ignore the fact that this self-signed certificate is unknown by the system:</p>
<pre>
Client := TRestHttpClientSocket.Create('127.0.0.1', '8888', Model, {https=}true);
Client.IgnoreTlsCertificateErrors := true;
</pre>
<p>Then, at least with OpenSSL, you could serve TLS 1.3 content from now on, with a safe cipher negotiation by default (which could be tuned if needed).<br />
Nice and easy!</p>
<h4>Give Me the Keys</h4>
<p>And if you generated your own public/private keys pair, you could specify it:</p>
<pre>
Server := TRestHttpServer.Create([RestServer], '8888', 16, secTLS,
HTTPSERVER_DEFAULT_OPTIONS, 'mycert.pem', 'mypriv.pem', 'privpassw');
</pre>
<p>And don't forget to keep your private key... private. :)</p>
<h4>Keys Rule</h4>
<p>Where do those certificates come from? Do I need to read endless and complex OpenSSL command line samples, and mess with files and passwords?<br />
Our framework make it easy. You can now use the new <em>mORMot 2</em> high-level cryptography interfaces to generate the keys you want in simple pascal code.</p>
<p>Here is how the framework generates the self-signed server certificate on OpenSSL, or use a pre-computed one for SChannel:</p>
<pre>
procedure InitNetTlsContextSelfSignedServer(var TLS: TNetTlsContext;
Algo: TCryptAsymAlgo);
var
cert: ICryptCert;
certfile, keyfile: TFileName;
keypass: RawUtf8;
begin
certfile := TemporaryFileName;
if CryptCertAlgoOpenSsl[Algo] = nil then
begin
FileFromString(PrivKeyCertPfx, certfile); // use pre-computed key
keypass := 'pass';
end
else
begin
keyfile := TemporaryFileName;
keypass := CardinalToHexLower(Random32);
cert := CryptCertAlgoOpenSsl[Algo].
Generate(CU_TLS_SERVER, '127.0.0.1', {authority=}nil, 3650);
cert.SaveToFile(certfile, cccCertOnly, '', ccfPem);
cert.SaveToFile(keyfile, cccPrivateKeyOnly, keypass, ccfPem);
//writeln(BinToSource('PRIVKEY_PFX', '',
// cert.Save(cccCertWithPrivateKey, 'pass', ccfBinary)));
end;
InitNetTlsContext(TLS, {server=}true, certfile, keyfile, keypass);
end;
</pre>
<p>As you can see, the <code>ICryptCert</code> interface is very simple to use, and hide all the complexity of X509 and OpenSSL. We provided <code>nil</code> as authority, but you could specify a <code>ICryptCert</code> instance to sign your certificate, if needed.<br />
Under comments in the above source, you can see how to export the keys pair as PKCS#12 certificate, ready to be used for SChannel.</p>
<h4>TLS Everywhere</h4>
<p>Offering TLS as part of your software solution could be a game-changer for serious business, even over a corporate network, with self-signed certificates. It would help your IT and management people trust your <em>mORMot</em> / pascal solution in an heterogeneous and complex mesh of services. Modern object pascal is still on track for the next decades! :)</p>
<p>Feedback is <a href="https://synopse.info/forum/viewtopic.php?id=6291">welcome on our forum</a>, as usual. :)</p>OpenSSL 1.1.1 Support for mORMot 2urn:md5:4cf412e6fbfe63673ee7f189fd8a3f9c2021-02-22T09:06:00+00:002021-02-22T10:40:23+00:00Arnaud BouchezmORMot FrameworkAESAES-CTRAES-GCMAES-NiasmblogCACertificatesDelphiECCECDHECIESECSDAed25519FreePascalJWTLinuxMaxOSXmORMot2OpenSSLperformancePublicKeyRSATLS <h3>Why OpenSSL?</h3>
<p><img src="https://blog.synopse.info?post/public/blog/OpenSSL_logo.png" alt="" /></p>
<p><a href="https://www.openssl.org/">OpenSSL</a> is the reference library for cryptography and secure TLS/HTTPS communication. It is part of most Linux/BSD systems, and covers a lot of use cases and algorithms. Even if it had some vulnerabilities in the past, it has been audited and validated for business use. Some algorithms have been deeply optimized, with very tuned assembly code.</p>
<p><em>mORMot 2</em> can now access all OpenSSL 1.1.1 features, on all supported targets.<br />
OpenSSL 1.1.1 is the latest stable branch, with LTS support, and nice new additions like TLS 1.3 and ed25519.</p>
<p>On Windows, you can use our internal assembly code for encryption, hashing and randomness (e.g. AES or SHA2/3), and we provide <a href="https://docs.microsoft.com/en-us/windows-server/security/tls/tls-ssl-schannel-ssp-overview">SChannel</a> support since <em>mORMot 1</em>, which allows to create a TLS/HTTPS layer without shipping any external library - using OpenSSL <code>.dll</code> files can be a real PITA on Windows, since it is very likely that several versions do exist on your systems, even in your path! We just <a href="https://synopse.info/forum/viewtopic.php?pid=34389#p34389">discovered and fixed some problems with SChannel</a>, which seems now stable and working fine even on latest Windows revisions.</p>
<p>Of course, if you really need to use OpenSSL on Windows, you can, and our little mORMot will try to do its best to find the right <code>.dll</code> for you. Windows' SChannel is behind OpenSSL in terms of ciphers support, and also about performance - especially if your Windows version is old or not updated. But it is part of the system, so you can rely on it if you expect basic TLS support.</p>
<p>On Linux / BSD / POSIX, OpenSSL 1.1.1 is the reference cryptographic library. It is already installed on any recent system.<br />
On MacOS and Android, you can use static linking to your application.</p>
<h3>OpenSSL 1.1.1 in mORMot 2</h3>
<p>Our OpenSSL integration in <em>mORMot 2</em> has several key features:</p>
<ul>
<li>Split into two units: <code>mormot.lib.openssl11.pas</code> for the raw API, and <code>mormot.core.crypto.openssl.pas</code> for integration with the other <em>mORMot 2</em> features;</li>
<li><code>mormot.lib.openssl11.pas</code> leverages OpenSSL 1.1.1, which is the latest stable version, and the current TLS branch - when OpenSSL 3 will be released, the API should remain compatible;</li>
<li><code>mormot.lib.openssl11.pas</code> by default loads dynamically OpenSSL 1.1.1 libraries, so even if your system won't have OpenSSL installed, your application will still start, with explicit error messages when you would try to use OpenSSL features;</li>
<li><code>mormot.lib.openssl11.pas</code> has a "full-API" conditional, which is used as reference when adding new features, and could be defined in your projects if you need specific OpenSSL features;</li>
<li><code>mormot.lib.openssl11.pas</code> publishes a TLS layer, recognized by our <code>mormot.net.sock.pas</code> unit, just like SChannel on Windows, to enabled TLS/HTTPS client connections;</li>
<li><code>mormot.core.crypto.openssl.pas</code> exposes the OpenSSL PRNG, which may be used for regulatory purposes, if our own PRNG doesn't meet your requirements;</li>
<li><code>mormot.core.crypto.openssl.pas</code> exposes the main (and safest) AES ciphers, and Hashing/Signing offered by OpenSSL, in a <code>mormot.core.crypto.pas</code> compatible way;</li>
<li><code>mormot.core.crypto.openssl.pas</code> defines some JWT classes, with full coverage of <strong>all</strong> the algorithms defined by the official <a href="https://jwt.io">JWT website</a> (it is the only Delphi library defining all of them);</li>
<li><code>mormot.core.crypto.openssl.pas</code> replaces the <code>mormot.core.crypto.pas</code> and <code>mormot.core.ecc256r1.pas</code> functions with the OpenSSL version, if they are faster;</li>
<li>We (re)validated our <code>mormot.core.crypto.pas</code> and <code>mormot.core.ecc256r1.pas</code> implementation against OpenSSL as reference, and also enhanced the test coverage (with more reference vectors for instance) for <em>mORMot 2</em>.</li>
</ul>
<p>In a nutshell, OpenSSL is slower than our <code>mormot.core.crypto.pas</code> assembly on x86_64 for most AES process (ciphers and PRNG) - but AES-GCM. But OpenSSL is much faster than <code>mormot.core.ecc256r1.pas</code> for ECC public key cryptography, so is automatically selected, which is a good idea e.g. on a Linux server environment.</p>
<h3>AES-PRNG, AES-CTR and AES-GCM</h3>
<p>See <a href="https://blog.synopse.info/?post/2021/02/13/Fastest-AES-PRNG%2C-AES-CTR-and-AES-GCM-Delphi-implementation">this article</a> for actual performance numbers of the AES encryption, and how our internal <code>mormot.core.crypto.pas</code> assembly is faster than OpenSSL in most cases.<br />
Our x86_64 tuned assembly, with full AES-NI and PCLMUL support does wonders.</p>
<h3>Public Key Cryptography and JWT</h3>
<p>The <code>mormot.core.crypto.openssl.pas</code> unit covers all possible JWT algorithms, with very interesting results, as shown by our regression tests:</p>
<pre>
mormot.core.crypto.pas algorithms:
1000 HS256 in 2.16ms i.e. 461,041/s, aver. 2us
1000 HS384 in 2.19ms i.e. 456,204/s, aver. 2us
1000 HS512 in 2.18ms i.e. 457,456/s, aver. 2us
1000 S3224 in 1.91ms i.e. 521,376/s, aver. 1us
1000 S3256 in 1.89ms i.e. 526,870/s, aver. 1us
1000 S3384 in 1.92ms i.e. 518,941/s, aver. 1us
1000 S3512 in 1.94ms i.e. 513,874/s, aver. 1us
1000 S3S128 in 1.92ms i.e. 520,833/s, aver. 1us
1000 S3S256 in 1.94ms i.e. 513,610/s, aver. 1us
100 ES256 in 14.65ms i.e. 6,823/s, aver. 146us
mormot.core.crypto.openssl.pas algorithms:
100 RS256 in 5.35ms i.e. 18,674/s, aver. 53us
100 RS384 in 5.16ms i.e. 19,368/s, aver. 51us
100 RS512 in 5.16ms i.e. 19,357/s, aver. 51us
100 PS256 in 5.56ms i.e. 17,985/s, aver. 55us
100 PS384 in 5.52ms i.e. 18,102/s, aver. 55us
100 PS512 in 5.65ms i.e. 17,677/s, aver. 56us
100 ES256 in 14.38ms i.e. 6,951/s, aver. 143us
100 ES384 in 123.79ms i.e. 807/s, aver. 1.23ms
100 ES512 in 94.50ms i.e. 1,058/s, aver. 945us
100 ES256K in 63.09ms i.e. 1,584/s, aver. 630us
100 EdDSA in 18.83ms i.e. 5,310/s, aver. 188us
</pre>
<p>The timing above are to verify a JWT signature.<br />
We can see that the ES256 has very tuned assembly code, and that the other ECC algorithms (ES384/ES512/ES256K) use standard coding, which are much slower. EdDSA is the ed25519 public cryptography algorithm, which is efficient on non-Intel/AMD platforms too, e.g. on ARM.</p>
<p>Raw asymmetric / public key cryptography using OpenSSL is therefore consistent with the expectations:</p>
<pre>
3 RSA 2048 Generation in 184.43ms i.e. 16/s, aver. 61.47ms
30 RSA 2048 Sign in 48.66ms i.e. 616/s, aver. 1.62ms
30 RSA 2048 Verify in 1.53ms i.e. 19,505/s, aver. 51us
3 RSA-PSS 2048 Generation in 205.60ms i.e. 14/s, aver. 68.53ms
30 RSA-PSS 2048 Sign in 47.32ms i.e. 633/s, aver. 1.57ms
30 RSA-PSS 2048 Verify in 1.57ms i.e. 19,047/s, aver. 52us
100 prime256v1 Generation in 8.26ms i.e. 12,096/s, aver. 82us
100 prime256v1 Sign in 7.02ms i.e. 14,226/s, aver. 70us
100 prime256v1 Verify in 13.45ms i.e. 7,433/s, aver. 134us
100 ed25519 Generation in 7.33ms i.e. 13,642/s, aver. 73us
100 ed25519 Sign in 13.67ms i.e. 7,310/s, aver. 136us
100 ed25519 Verify in 18.44ms i.e. 5,422/s, aver. 184us
</pre>
<p>RSA generation is really slow in comparison to other options (but verification is fast). In practice, prime256v1 (ES256) is to be privileged - especially since we offer our own pure standalone version if OpenSSL is not available.<br />
Note that any other algorithm exposed by OpenSSL are available - you are not restricted to the list above.</p>
<p>We introduced an easy way to generate public/private key pairs in a single function call, which is mandatory for any automated server work. No need to try the obfuscated <code>openssl ecparam -name prime256v1 -genkey -noout -out key.pem && openssl ec -in key.pem -pubout -out public.pem</code> command line switches: just call <code>OpenSslGenerateKeys()</code> or <code>TJwtAbstractOsl.GenerateKeys</code> and you are ready to go!</p>
<h3>TLS Layer</h3>
<p>We integrated the OpenSSL TLS layer to our <code>mormot.net.sock.pas</code> unit, with a powerful and easy way of using it:</p>
<pre>
with THttpClientSocket.Create do
try
TLS.WithPeerInfo := true;
TLS.IgnoreCertificateErrors := true;
TLS.CipherList := 'ECDHE-RSA-AES256-GCM-SHA384';
OpenBind('synopse.info', '443', {bind=}false, {tls=}true);
writeln(TLS.PeerInfo);
writeln(TLS.CipherName);
writeln(Get('/forum/', 1000), ' len=', ContentLength);
writeln(Get('/fossil/wiki/Synopse+OpenSource', 1000));
finally
Free;
end;
</pre>
<p>The <code>TCrtSocket.TLS</code> new field can be used to set the private and public keys or root certificates, if needed. Its <code>PeerInfo</code> return the whole certificate of the server side, and <code>CipherName</code> returns the current cipher used, e.g. <code>'ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD'</code>.</p>
<p>The very same code can be used with the SChannel version of <code>mormot.net.sock.windows.inc</code> - but with less features by now.</p>
<h3>OpenSource ForEver</h3>
<p>The source code of the units is available for review:</p>
<ul>
<li><a href="https://github.com/synopse/mORMot2/blob/master/src/lib/mormot.lib.openssl11.pas">/src/lib/mormot.lib.openssl11.pas</a>;</li>
<li><a href="https://github.com/synopse/mORMot2/blob/master/src/core/mormot.core.crypto.openssl.pas">/src/core/mormot.core.crypto.openssl.pas</a>.</li>
</ul>
<p>It works for FPC and Delphi, in the usual targets supported by <em>mORMot 2</em>.</p>
<p>Interfacing OpenSSL is really tricky sometimes, due to the opaque API names, lack of documentation, and weak/deprecated samples on the net.<br />
We tried to leverage our integration, so that using OpenSSL would be seamless for the <em>mORMot 2</em> users: you just use the mORMot classes, and don't have to care about which engine (mORMot's or OpenSSL's) is actually running them.</p>
<p>And feedback is <a href="https://synopse.info/forum/viewtopic.php?pid=34388#p34388">welcome on our forum</a>, as usual!</p>