Zig TLS library, characteristics:
- TLS 1.2 and TLS 1.3 client
- TLS 1.3 server
- handles client authentication
- tested with many domains, handles badssl urls
- options to select client cipher sites to use, named groups, ...
- can configure Wireshark to show decrypted traffic
- same performance as standard library implementation
- can be used with standard library http client (with modified std lib copy)
Here is simple example of how to use library.
To upgrade existing tcp connection to the tls connection call tls.client
:
// Establish tcp connection
var tcp = try std.net.tcpConnectToHost(allocator, host, port);
defer tcp.close();
// Load system root certificates
var root_ca = try tls.CertBundle.fromSystem(allocator);
defer root_ca.deinit(allocator);
// Upgrade tcp connection to tls
var conn = try tls.client(tcp, .{
.host = host,
.root_ca = root_ca,
});
After that you can use conn
read/write methods as on plain tcp connection.
Second parameter in calling tls.client
are tls.ClientOptions they can be used to force subset of implemented ciphers, set client authentication parameters, allow self insecure signed certificates, collect handshake diagnostics, exchange session keys with Wireshark to view decrypted traffic.
To use just ciphers which are graded secure or recommended on https://ciphersuite.info:
var conn = try tls.client(tcp, .{
.host = host,
.root_ca = root_ca,
.cipher_suites = tls.cipher_suites.secure,
});
cipher_suites
can be used to force tls 1.3 only or tls 1.2 only ciphers. Or to reorder cipher preferences.
If server requires client authentication set auth
attribute in options. You need to prepare certificate key pair with client certificate(s) and client private key.
// Prepare client authentication key pair
var auth = try tls.CertKeyPair.load(allocator, cert_dir, "cert.pem", "key.pem");
defer auth.deinit(allocator);
var conn = try tls.client(tcp, .{
.host = host,
.root_ca = root_ca,
.auth = auth,
});
When client receives certificate request from server during handshake it will respond with client certificates message build from provided certificate bundle and client certificate verify message where verify data is signed with client private key.
If authentication is not provided client will respond with empty certificate message when server requests authentication (as specified in RFC).
Session keys can be written to file so that external programs can decrypt TLS connections. Wireshark can use these log files to decrypt packets. You can tell Wireshark where to find the key file via Edit→Preferences→Protocols→SSL→(Pre)-Master-Secret log filename.
Key logging is enabled by setting the environment variable SSLKEYLOGFILE to point to a file. And enabling key log callback in client options:
var conn = try tls.client(tcp, .{
.host = host,
.root_ca = root_ca,
.key_log_callback = tls.key_log.callback,
});
Library also has minimal, TLS 1.3 only server implementation. To upgrade tcp to tls connection:
// Load server certificate key pair
var auth = try tls.CertKeyPair.load(allocator, dir, "localhost_ec/cert.pem", "localhost_ec/key.pem");
defer auth.deinit(allocator);
// Tcp listener
const port = 9443;
const address = std.net.Address.initIp4([4]u8{ 127, 0, 0, 1 }, port);
var server = try address.listen(.{
.reuse_address = true,
});
// Tcp accept
const tcp = try server.accept();
defer tcp.stream.close();
// Upgrade tcp to tls
var conn = try tls.server(tcp.stream, .{ .auth = auth });
// use conn
Starting from cloudflare list of 10000 top domains, filtering those which can't be resolved got list of ~6k domains which are used to test establishing tls connection. If the connection fails test runs curl on the same domain, if curl can't connect it is count as error, if curl connect counts as fail. For each domain test reports tls handshake parameters (tls version, cipher suite used, named group and signature scheme).
$ zig-out/bin/top_sites
✔️ facebook.com tls_1_3 AES_128_GCM_SHA256 x25519 ecdsa_secp256r1_sha256
✔️ ebay.com tls_1_3 AES_128_GCM_SHA256 x25519 rsa_pss_rsae_sha256
✔️ live.com tls_1_3 AES_256_GCM_SHA384 secp384r1 rsa_pss_rsae_sha256
✔️ drive.google.com tls_1_3 AES_128_GCM_SHA256 x25519_kyber768d00 ecdsa_secp256r1_sha256
✔️ github.com tls_1_3 AES_128_GCM_SHA256 x25519 ecdsa_secp256r1_sha256
...
stats:
total: 6250
success: 6247
tls 1.2: 1530
tls 1.3: 4717
fail: 1
error: 2
skip: 0
When I found domain which fails I use http_get example to test wether it is transient error or point to something interesting. Now only transient errors are left in that domains group.
This example will connect to the domain, show response and tls statistic. You can change tls options to force tls version or specific cipher.
$ zig-out/bin/http_get google.com
HTTP/1.0 301 Moved Permanently
832 bytes read
google.com
tls version: tls_1_3
cipher: AES_128_GCM_SHA256
named group: x25519_kyber768d00
signature scheme: ecdsa_secp256r1_sha256
Uses urls from badssl.com to test client implementation.
$ zig-out/bin/badssl
Certificate Validation (High Risk)
If your browser connects to one of these sites, it could be very easy for an attacker to see and modify everything on web sites that you visit.
✅ expired.badssl.com error.CertificateExpired
✅ wrong.host.badssl.com error.CertificateHostMismatch
✅ self-signed.badssl.com error.CertificateIssuerNotFound
✅ untrusted-root.badssl.com error.CertificateIssuerNotFound
Interception Certificates (High Risk)
If your browser connects to one of these sites, it could be very easy for an attacker to see and modify everything on web sites that you visit. This may be due to interception software installed on your device.
✅ superfish.badssl.com error.CertificateIssuerNotFound
✅ edellroot.badssl.com error.CertificateIssuerNotFound
✅ dsdtestprovider.badssl.com error.CertificateIssuerNotFound
✅ preact-cli.badssl.com error.CertificateIssuerNotFound
✅ webpack-dev-server.badssl.com error.CertificateIssuerNotFound
Broken Cryptography (Medium Risk)
If your browser connects to one of these sites, an attacker with enough resources may be able to see and/or modify everything on web sites that you visit. This is because your browser supports connections settings that are outdated and known to have significant security flaws.
✅ rc4.badssl.com error.TlsAlertHandshakeFailure
✅ rc4-md5.badssl.com error.TlsAlertHandshakeFailure
✅ dh480.badssl.com error.TlsAlertHandshakeFailure
✅ dh512.badssl.com error.TlsAlertHandshakeFailure
✅ dh1024.badssl.com error.TlsAlertHandshakeFailure
✅ null.badssl.com error.TlsAlertHandshakeFailure
Legacy Cryptography (Moderate Risk)
If your browser connects to one of these sites, your web traffic is probably safe from attackers in the near future. However, your connections to some sites might not be using the strongest possible security. Your browser may use these settings in order to connect to some older sites.
✅ tls-v1-0.badssl.com error.TlsBadVersion
✅ tls-v1-1.badssl.com error.TlsBadVersion
🆗 cbc.badssl.com
✅ 3des.badssl.com error.TlsAlertHandshakeFailure
✅ dh2048.badssl.com error.TlsAlertHandshakeFailure
Domain Security Policies
These are special tests for some specific browsers. These tests may be able to tell whether your browser uses advanced domain security policy mechanisms (HSTS, HPKP, SCT) to detect illegitimate certificates.
🆗 revoked.badssl.com
🆗 pinning-test.badssl.com
✅ no-sct.badssl.com error.CertificateIssuerNotFound
Secure (Uncommon)
These settings are secure. However, they are less common and even if your browser doesn't support them you probably won't have issues with most sites.
🆗 1000-sans.badssl.com error.TlsUnsupportedFragmentedHandshakeMessage
🆗 10000-sans.badssl.com error.TlsUnsupportedFragmentedHandshakeMessage
🆗 sha384.badssl.com error.CertificateExpired
🆗 sha512.badssl.com error.CertificateExpired
🆗 rsa8192.badssl.com error.BufferOverflow
🆗 no-subject.badssl.com error.CertificateExpired
🆗 no-common-name.badssl.com error.CertificateExpired
🆗 incomplete-chain.badssl.com error.CertificateIssuerNotFound
Secure (Common)
These settings are secure and commonly used by sites. Your browser will need to support most of these in order to connect to sites securely.
✅ tls-v1-2.badssl.com
✅ sha256.badssl.com
✅ rsa2048.badssl.com
✅ ecc256.badssl.com
✅ ecc384.badssl.com
✅ mozilla-modern.badssl.com
Tries all supported ciphers on some domain.
$ zig-out/bin/all_ciphers cloudflare.com
✔️ AES_128_GCM_SHA256 cloudflare.com
✔️ AES_256_GCM_SHA384 cloudflare.com
✔️ CHACHA20_POLY1305_SHA256 cloudflare.com
✔️ ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 cloudflare.com
✔️ ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 cloudflare.com
✔️ ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 cloudflare.com
✔️ ECDHE_RSA_WITH_AES_128_GCM_SHA256 cloudflare.com
✔️ ECDHE_RSA_WITH_AES_256_GCM_SHA384 cloudflare.com
✔️ ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 cloudflare.com
✔️ ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 cloudflare.com
✔️ ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 cloudflare.com
✔️ ECDHE_ECDSA_WITH_AES_128_CBC_SHA cloudflare.com
✔️ ECDHE_RSA_WITH_AES_128_CBC_SHA256 cloudflare.com
✔️ ECDHE_RSA_WITH_AES_256_CBC_SHA384 cloudflare.com
✔️ ECDHE_RSA_WITH_AES_128_CBC_SHA cloudflare.com
✔️ RSA_WITH_AES_128_CBC_SHA256 cloudflare.com
✔️ RSA_WITH_AES_128_CBC_SHA cloudflare.com
Using cloudflare.com as example because it supports all implemented ciphers.
Create local development certificates and keys:
$ cd example && ./cert.sh && cd -
This uses minica tool. Go compiler and go install dir in the path are required.
Start server and connect to with client to the server.
$ zig build && zig-out/bin/server& ; sleep 1 && zig-out/bin/client ; kill %1
After we have certificates created in previous example, here we will start Go tls server which requires client authentication and connect to that server with various different rsa and ec certificates using both tls 1.2 and 1.3.
$ zig build ; cd example/go_tls_server; go run server.go & ; cd - ; sleep 1 && zig-out/bin/client_auth ; kill %1
Equivalent curl
is:
curl https://localhost:8443 --cacert example/cert/minica.pem --cert example/cert/client_rsa/cert.pem --key example/cert/client_rsa/key.pem
This library is only tls protocol implementation. Standard library has great
http client. We can replace standard library tls implementation with this one
and get http client with both tls 1.2 and 1.3 capability.
Here are
required changes, assuming that this library is available at
lib/std/crypt/tls23
path.
This script will checkout tls.zig library, an fork of the zig repository and
link tls.zig to the required path. After that we can point to that standard
library copy while building zig project with --zig-lib-dir
switch.
git clone https://github.com/ianic/tls.zig
git clone -b tls23 https://github.com/ianic/zig
ln -s $(pwd)/tls.zig/src zig/lib/std/crypto/tls23
cd tls.zig
zig build --zig-lib-dir ../zig/lib
zig-out/bin/std_top_sites
Starting local server which will stream a text file (src/main.zig
in this example) to the connected client:
$ zig build -Doptimize=ReleaseFast && zig-out/bin/server src/main.zig
Running 50 client request to that server by using this library and then by using standard library implementation and comparing them:
$ zig build -Doptimize=ReleaseFast && sudo ~/.local/bin/poop './zig-out/bin/client --cycles 50' 'zig-out/bin/client --cycles 50 --std'
Benchmark 1 (19 runs): ./zig-out/bin/client --cycles 50
measurement mean ± σ min … max outliers delta
wall_time 265ms ± 12.7ms 251ms … 294ms 0 ( 0%) 0%
peak_rss 828KB ± 131KB 524KB … 1.05MB 0 ( 0%) 0%
cpu_cycles 415M ± 10.2M 376M … 426M 1 ( 5%) 0%
instructions 1.62G ± 38.3M 1.48G … 1.64G 1 ( 5%) 0%
cache_references 375K ± 183K 65.9K … 563K 0 ( 0%) 0%
cache_misses 9.55K ± 2.21K 5.20K … 14.1K 0 ( 0%) 0%
branch_misses 187K ± 38.3K 124K … 228K 0 ( 0%) 0%
Benchmark 2 (19 runs): zig-out/bin/client --cycles 50 --std
measurement mean ± σ min … max outliers delta
wall_time 265ms ± 14.3ms 247ms … 296ms 0 ( 0%) + 0.2% ± 3.4%
peak_rss 786KB ± 0 786KB … 786KB 0 ( 0%) - 5.0% ± 7.4%
cpu_cycles 410M ± 11.7M 364M … 417M 1 ( 5%) - 1.1% ± 1.8%
instructions 1.61G ± 51.8M 1.40G … 1.64G 1 ( 5%) - 1.0% ± 1.9%
cache_references 340K ± 150K 49.9K … 487K 0 ( 0%) - 9.4% ± 29.5%
cache_misses 8.77K ± 1.97K 5.42K … 12.4K 0 ( 0%) - 8.2% ± 14.5%
branch_misses 189K ± 38.2K 113K … 232K 0 ( 0%) + 1.4% ± 13.6%
Tests are created using examples from The Illustrated TLS 1.2 Connection and The Illustrated TLS 1.3 Connection. Those are really useful in understanding what each byte means.