at 25.11-pre 6.3 kB view raw
1/* 2 Test suite for curl-impersonate 3 4 Abstract: 5 Uses the test suite from the curl-impersonate source repo which: 6 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 10 known-good headers 11 12 See https://github.com/lwthiker/curl-impersonate/tree/main/tests/signatures 13 for details. 14 15 Notes: 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. 22*/ 23 24import ./make-test-python.nix ( 25 { pkgs, lib, ... }: 26 let 27 # Update with domains in TestImpersonate.TEST_URLS if needed from: 28 # https://github.com/lwthiker/curl-impersonate/blob/main/tests/test_impersonate.py 29 domains = [ 30 "www.wikimedia.org" 31 "www.wikipedia.org" 32 "www.mozilla.org" 33 "www.apache.org" 34 "www.kernel.org" 35 "git-scm.com" 36 ]; 37 38 tls-certs = 39 let 40 # Configure CA with X.509 v3 extensions that would be trusted by curl 41 ca-cert-conf = pkgs.writeText "curl-impersonate-ca.cnf" '' 42 basicConstraints = critical, CA:TRUE 43 subjectKeyIdentifier = hash 44 authorityKeyIdentifier = keyid:always, issuer:always 45 keyUsage = critical, cRLSign, digitalSignature, keyCertSign 46 ''; 47 48 # Configure leaf certificate with X.509 v3 extensions that would be trusted 49 # by curl and set subject-alternative names for test domains 50 tls-cert-conf = pkgs.writeText "curl-impersonate-tls.cnf" '' 51 basicConstraints = critical, CA:FALSE 52 subjectKeyIdentifier = hash 53 authorityKeyIdentifier = keyid:always, issuer:always 54 keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement 55 extendedKeyUsage = critical, serverAuth 56 subjectAltName = @alt_names 57 58 [alt_names] 59 ${lib.concatStringsSep "\n" (lib.imap0 (idx: domain: "DNS.${toString idx} = ${domain}") domains)} 60 ''; 61 in 62 pkgs.runCommand "curl-impersonate-test-certs" 63 { 64 nativeBuildInputs = [ pkgs.openssl ]; 65 } 66 '' 67 # create CA certificate and key 68 openssl req -newkey rsa:4096 -keyout ca-key.pem -out ca-csr.pem -nodes -subj '/CN=curl-impersonate-ca.nixos.test' 69 openssl x509 -req -sha512 -in ca-csr.pem -key ca-key.pem -out ca.pem -extfile ${ca-cert-conf} -days 36500 70 openssl x509 -in ca.pem -text 71 72 # create server certificate and key 73 openssl req -newkey rsa:4096 -keyout key.pem -out csr.pem -nodes -subj '/CN=curl-impersonate.nixos.test' 74 openssl x509 -req -sha512 -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile ${tls-cert-conf} -days 36500 75 openssl x509 -in cert.pem -text 76 77 # output CA cert and server cert and key 78 mkdir -p $out 79 cp key.pem cert.pem ca.pem $out 80 ''; 81 82 # Test script 83 curl-impersonate-test = 84 let 85 # Build miniature libcurl client used by test driver 86 minicurl = 87 pkgs.runCommandCC "minicurl" 88 { 89 buildInputs = [ pkgs.curl ]; 90 } 91 '' 92 mkdir -p $out/bin 93 $CC -Wall -Werror -o $out/bin/minicurl ${pkgs.curl-impersonate.src}/tests/minicurl.c `curl-config --libs` 94 ''; 95 in 96 pkgs.writeShellScript "curl-impersonate-test" '' 97 set -euxo pipefail 98 99 # Test driver requirements 100 export PATH="${ 101 with pkgs; 102 lib.makeBinPath [ 103 bash 104 coreutils 105 python3Packages.pytest 106 nghttp2 107 tcpdump 108 ] 109 }" 110 export PYTHONPATH="${ 111 with pkgs.python3Packages; 112 makePythonPath [ 113 pyyaml 114 pytest-asyncio 115 dpkt 116 ts1-signatures 117 ] 118 }" 119 120 # Prepare test root prefix 121 mkdir -p usr/{bin,lib} 122 cp -rs ${pkgs.curl-impersonate}/* ${minicurl}/* usr/ 123 124 cp -r ${pkgs.curl-impersonate.src}/tests ./ 125 126 # Run tests 127 cd tests 128 pytest . --install-dir ../usr --capture-interface eth1 129 ''; 130 in 131 { 132 name = "curl-impersonate"; 133 134 meta = with lib.maintainers; { 135 maintainers = [ ]; 136 }; 137 138 nodes = { 139 web = 140 { 141 nodes, 142 pkgs, 143 lib, 144 config, 145 ... 146 }: 147 { 148 networking.firewall.allowedTCPPorts = [ 149 80 150 443 151 ]; 152 153 services = { 154 nginx = { 155 enable = true; 156 virtualHosts."curl-impersonate.nixos.test" = { 157 default = true; 158 addSSL = true; 159 sslCertificate = "${tls-certs}/cert.pem"; 160 sslCertificateKey = "${tls-certs}/key.pem"; 161 }; 162 }; 163 }; 164 }; 165 166 curl = 167 { 168 nodes, 169 pkgs, 170 lib, 171 config, 172 ... 173 }: 174 { 175 networking.extraHosts = lib.concatStringsSep "\n" ( 176 map (domain: "${nodes.web.networking.primaryIPAddress} ${domain}") domains 177 ); 178 179 security.pki.certificateFiles = [ "${tls-certs}/ca.pem" ]; 180 }; 181 }; 182 183 testScript = 184 { nodes, ... }: 185 '' 186 start_all() 187 188 with subtest("Wait for network"): 189 web.systemctl("start network-online.target") 190 curl.systemctl("start network-online.target") 191 web.wait_for_unit("network-online.target") 192 curl.wait_for_unit("network-online.target") 193 194 with subtest("Wait for web server"): 195 web.wait_for_unit("nginx.service") 196 web.wait_for_open_port(443) 197 198 with subtest("Run curl-impersonate tests"): 199 curl.succeed("${curl-impersonate-test}") 200 ''; 201 } 202)