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