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.
6 { system ? builtins.currentSystem,
8 pkgs ? import ../.. { inherit system config; }
11 with import ../lib/testing-python.nix { inherit system pkgs; };
16 makeCert = { caName, domain }: pkgs.runCommand "example-cert"
17 { buildInputs = [ pkgs.gnutls ]; }
22 cat >ca.template <<EOF
23 organization = "${caName}"
31 # server cert template
32 cat >server.template <<EOF
33 organization = "An example company"
36 dns_name = "${domain}"
48 --generate-self-signed \
49 --load-privkey $out/ca.key \
50 --template ca.template \
53 # generate server keypair
58 --outfile $out/server.key
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
68 example-good-cert = makeCert
69 { caName = "Example good CA";
70 domain = "good.example.com";
73 example-bad-cert = makeCert
74 { caName = "Unknown CA";
75 domain = "bad.example.com";
79 { networking.hosts."127.0.0.1" = [ "good.example.com" "bad.example.com" ];
80 security.pki.certificateFiles = [ "${example-good-cert}/ca.crt" ];
82 services.nginx.enable = true;
83 services.nginx.virtualHosts."good.example.com" =
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!';
92 services.nginx.virtualHosts."bad.example.com" =
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!';
103 curlTest = makeTest {
104 name = "custom-ca-curl";
105 meta.maintainers = with lib.maintainers; [ rnhmjoj ];
106 nodes.machine = { ... }: webserverConfig;
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")
113 with subtest("Unknown CA is untrusted in curl"):
114 machine.fail("curl -fv https://bad.example.com")
118 mkBrowserTest = browser: testParams: makeTest {
119 name = "custom-ca-${browser}";
120 meta.maintainers = with lib.maintainers; [ rnhmjoj ];
124 nodes.machine = { pkgs, ... }:
126 [ ./common/user-account.nix
131 # chromium-based browsers refuse to run as root
132 test-support.displayManager.auto.user = "alice";
134 # machine often runs out of memory with less
135 virtualisation.memorySize = 1024;
137 environment.systemPackages = [ pkgs.xdotool pkgs.${browser} ];
141 from typing import Tuple
142 def execute_as(user: str, cmd: str) -> Tuple[int, str]:
144 Run a shell command as a specific user.
146 return machine.execute(f"sudo -u {user} {cmd}")
149 def wait_for_window_as(user: str, cls: str) -> None:
151 Wait until a X11 window of a given user appears.
154 def window_is_visible(last_try: bool) -> bool:
155 ret, stdout = execute_as(user, f"xdotool search --onlyvisible --class {cls}")
157 machine.log(f"Last chance to match {cls} on the window list")
160 with machine.nested("Waiting for a window to appear"):
161 retry(window_is_visible)
167 command = "${browser} ${testParams.args or ""}"
168 with subtest("Good certificate is trusted in ${browser}"):
170 "alice", f"{command} https://good.example.com >&2 &"
172 wait_for_window_as("alice", "${browser}")
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
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}")
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"; };