2 Test suite for curl-impersonate
5 Uses the test suite from the curl-impersonate source repo which:
7 1. Performs requests with libcurl and captures the TLS client-hello
8 packets with tcpdump to compare against known-good signatures
9 2. Spins up an nghttpd2 server to test client HTTP/2 headers against
12 See https://github.com/lwthiker/curl-impersonate/tree/main/tests/signatures
16 - We need to have our own web server running because the tests expect to be able
17 to hit domains like wikipedia.org and the sandbox has no internet
18 - We need to be able to do (verifying) TLS handshakes without internet access.
19 We do that by creating a trusted CA and issuing a cert that includes
20 all of the test domains as subject-alternative names and then spoofs the
21 hostnames in /etc/hosts.
24 import ./make-test-python.nix ({ pkgs, lib, ... }: let
25 # Update with domains in TestImpersonate.TEST_URLS if needed from:
26 # https://github.com/lwthiker/curl-impersonate/blob/main/tests/test_impersonate.py
37 # Configure CA with X.509 v3 extensions that would be trusted by curl
38 ca-cert-conf = pkgs.writeText "curl-impersonate-ca.cnf" ''
39 basicConstraints = critical, CA:TRUE
40 subjectKeyIdentifier = hash
41 authorityKeyIdentifier = keyid:always, issuer:always
42 keyUsage = critical, cRLSign, digitalSignature, keyCertSign
45 # Configure leaf certificate with X.509 v3 extensions that would be trusted
46 # by curl and set subject-alternative names for test domains
47 tls-cert-conf = pkgs.writeText "curl-impersonate-tls.cnf" ''
48 basicConstraints = critical, CA:FALSE
49 subjectKeyIdentifier = hash
50 authorityKeyIdentifier = keyid:always, issuer:always
51 keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement
52 extendedKeyUsage = critical, serverAuth
53 subjectAltName = @alt_names
56 ${lib.concatStringsSep "\n" (lib.imap0 (idx: domain: "DNS.${toString idx} = ${domain}") domains)}
58 in pkgs.runCommand "curl-impersonate-test-certs" {
59 nativeBuildInputs = [ pkgs.openssl ];
61 # create CA certificate and key
62 openssl req -newkey rsa:4096 -keyout ca-key.pem -out ca-csr.pem -nodes -subj '/CN=curl-impersonate-ca.nixos.test'
63 openssl x509 -req -sha512 -in ca-csr.pem -key ca-key.pem -out ca.pem -extfile ${ca-cert-conf} -days 36500
64 openssl x509 -in ca.pem -text
66 # create server certificate and key
67 openssl req -newkey rsa:4096 -keyout key.pem -out csr.pem -nodes -subj '/CN=curl-impersonate.nixos.test'
68 openssl x509 -req -sha512 -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile ${tls-cert-conf} -days 36500
69 openssl x509 -in cert.pem -text
71 # output CA cert and server cert and key
73 cp key.pem cert.pem ca.pem $out
77 curl-impersonate-test = let
78 # Build miniature libcurl client used by test driver
79 minicurl = pkgs.runCommandCC "minicurl" {
80 buildInputs = [ pkgs.curl ];
83 $CC -Wall -Werror -o $out/bin/minicurl ${pkgs.curl-impersonate.src}/tests/minicurl.c `curl-config --libs`
85 in pkgs.writeShellScript "curl-impersonate-test" ''
88 # Test driver requirements
89 export PATH="${with pkgs; lib.makeBinPath [
92 python3Packages.pytest
96 export PYTHONPATH="${with pkgs.python3Packages; makePythonPath [
103 # Prepare test root prefix
104 mkdir -p usr/{bin,lib}
105 cp -rs ${pkgs.curl-impersonate}/* ${minicurl}/* usr/
107 cp -r ${pkgs.curl-impersonate.src}/tests ./
111 pytest . --install-dir ../usr --capture-interface eth1
114 name = "curl-impersonate";
116 meta = with lib.maintainers; {
121 web = { nodes, pkgs, lib, config, ... }: {
122 networking.firewall.allowedTCPPorts = [ 80 443 ];
127 virtualHosts."curl-impersonate.nixos.test" = {
130 sslCertificate = "${tls-certs}/cert.pem";
131 sslCertificateKey = "${tls-certs}/key.pem";
137 curl = { nodes, pkgs, lib, config, ... }: {
138 networking.extraHosts = lib.concatStringsSep "\n" (map (domain: "${nodes.web.networking.primaryIPAddress} ${domain}") domains);
140 security.pki.certificateFiles = [ "${tls-certs}/ca.pem" ];
144 testScript = { nodes, ... }: ''
147 with subtest("Wait for network"):
148 web.systemctl("start network-online.target")
149 curl.systemctl("start network-online.target")
150 web.wait_for_unit("network-online.target")
151 curl.wait_for_unit("network-online.target")
153 with subtest("Wait for web server"):
154 web.wait_for_unit("nginx.service")
155 web.wait_for_open_port(443)
157 with subtest("Run curl-impersonate tests"):
158 curl.succeed("${curl-impersonate-test}")