at 25.11-pre 6.2 kB view raw
1# Checks that `security.pki` options are working in curl and the main browser 2# engines: Gecko (via Firefox), Chromium, QtWebEngine (via qutebrowser) and 3# WebKitGTK (via Midori). The test checks that certificates issued by a custom 4# trusted CA are accepted but those from an unknown CA are rejected. 5 6{ 7 system ? builtins.currentSystem, 8 config ? { }, 9 pkgs ? import ../.. { inherit system config; }, 10}: 11 12with import ../lib/testing-python.nix { inherit system pkgs; }; 13 14let 15 inherit (pkgs) lib; 16 17 makeCert = 18 { caName, domain }: 19 pkgs.runCommand "example-cert" { buildInputs = [ pkgs.gnutls ]; } '' 20 mkdir $out 21 22 # CA cert template 23 cat >ca.template <<EOF 24 organization = "${caName}" 25 cn = "${caName}" 26 expiration_days = 365 27 ca 28 cert_signing_key 29 crl_signing_key 30 EOF 31 32 # server cert template 33 cat >server.template <<EOF 34 organization = "An example company" 35 cn = "${domain}" 36 expiration_days = 30 37 dns_name = "${domain}" 38 encryption_key 39 signing_key 40 EOF 41 42 # generate CA keypair 43 certtool \ 44 --generate-privkey \ 45 --key-type rsa \ 46 --sec-param High \ 47 --outfile $out/ca.key 48 certtool \ 49 --generate-self-signed \ 50 --load-privkey $out/ca.key \ 51 --template ca.template \ 52 --outfile $out/ca.crt 53 54 # generate server keypair 55 certtool \ 56 --generate-privkey \ 57 --key-type rsa \ 58 --sec-param High \ 59 --outfile $out/server.key 60 certtool \ 61 --generate-certificate \ 62 --load-privkey $out/server.key \ 63 --load-ca-privkey $out/ca.key \ 64 --load-ca-certificate $out/ca.crt \ 65 --template server.template \ 66 --outfile $out/server.crt 67 ''; 68 69 example-good-cert = makeCert { 70 caName = "Example good CA"; 71 domain = "good.example.com"; 72 }; 73 74 example-bad-cert = makeCert { 75 caName = "Unknown CA"; 76 domain = "bad.example.com"; 77 }; 78 79 webserverConfig = { 80 networking.hosts."127.0.0.1" = [ 81 "good.example.com" 82 "bad.example.com" 83 ]; 84 security.pki.certificateFiles = [ "${example-good-cert}/ca.crt" ]; 85 86 services.nginx.enable = true; 87 services.nginx.virtualHosts."good.example.com" = { 88 onlySSL = true; 89 sslCertificate = "${example-good-cert}/server.crt"; 90 sslCertificateKey = "${example-good-cert}/server.key"; 91 locations."/".extraConfig = '' 92 add_header Content-Type text/plain; 93 return 200 'It works!'; 94 ''; 95 }; 96 services.nginx.virtualHosts."bad.example.com" = { 97 onlySSL = true; 98 sslCertificate = "${example-bad-cert}/server.crt"; 99 sslCertificateKey = "${example-bad-cert}/server.key"; 100 locations."/".extraConfig = '' 101 add_header Content-Type text/plain; 102 return 200 'It does not work!'; 103 ''; 104 }; 105 }; 106 107 curlTest = makeTest { 108 name = "custom-ca-curl"; 109 meta.maintainers = with lib.maintainers; [ rnhmjoj ]; 110 nodes.machine = { ... }: webserverConfig; 111 testScript = '' 112 with subtest("Good certificate is trusted in curl"): 113 machine.wait_for_unit("nginx") 114 machine.wait_for_open_port(443) 115 machine.succeed("curl -fv https://good.example.com") 116 117 with subtest("Unknown CA is untrusted in curl"): 118 machine.fail("curl -fv https://bad.example.com") 119 ''; 120 }; 121 122 mkBrowserTest = 123 browser: testParams: 124 makeTest { 125 name = "custom-ca-${browser}"; 126 meta.maintainers = with lib.maintainers; [ rnhmjoj ]; 127 128 enableOCR = true; 129 130 nodes.machine = 131 { pkgs, ... }: 132 { 133 imports = [ 134 ./common/user-account.nix 135 ./common/x11.nix 136 webserverConfig 137 ]; 138 139 # chromium-based browsers refuse to run as root 140 test-support.displayManager.auto.user = "alice"; 141 142 # machine often runs out of memory with less 143 virtualisation.memorySize = 1024; 144 145 environment.systemPackages = [ 146 pkgs.xdotool 147 pkgs.${browser} 148 ]; 149 }; 150 151 testScript = '' 152 from typing import Tuple 153 def execute_as(user: str, cmd: str) -> Tuple[int, str]: 154 """ 155 Run a shell command as a specific user. 156 """ 157 return machine.execute(f"sudo -u {user} {cmd}") 158 159 160 def wait_for_window_as(user: str, cls: str) -> None: 161 """ 162 Wait until a X11 window of a given user appears. 163 """ 164 165 def window_is_visible(last_try: bool) -> bool: 166 ret, stdout = execute_as(user, f"xdotool search --onlyvisible --class {cls}") 167 if last_try: 168 machine.log(f"Last chance to match {cls} on the window list") 169 return ret == 0 170 171 with machine.nested("Waiting for a window to appear"): 172 retry(window_is_visible) 173 174 175 machine.start() 176 machine.wait_for_x() 177 178 command = "${browser} ${testParams.args or ""}" 179 with subtest("Good certificate is trusted in ${browser}"): 180 execute_as( 181 "alice", f"{command} https://good.example.com >&2 &" 182 ) 183 wait_for_window_as("alice", "${browser}") 184 machine.sleep(4) 185 execute_as("alice", "xdotool key ctrl+r") # reload to be safe 186 machine.wait_for_text("It works!") 187 machine.screenshot("good${browser}") 188 execute_as("alice", "xdotool key ctrl+w") # close tab 189 190 with subtest("Unknown CA is untrusted in ${browser}"): 191 execute_as("alice", f"{command} https://bad.example.com >&2 &") 192 machine.wait_for_text("${testParams.error}") 193 machine.screenshot("bad${browser}") 194 ''; 195 }; 196 197in 198 199{ 200 curl = curlTest; 201} 202// pkgs.lib.mapAttrs mkBrowserTest { 203 firefox = { 204 error = "Security Risk"; 205 }; 206 chromium = { 207 error = "not private"; 208 }; 209 qutebrowser = { 210 args = "-T"; 211 error = "Certificate error"; 212 }; 213 midori = { 214 args = "-p"; 215 error = "Security"; 216 }; 217}