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