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