2021-01-10 17:28:29 +00:00
|
|
|
# Checks that `security.pki` options are working in curl and the main browser
|
|
|
|
# engines: Gecko (via Firefox), Chromium, QtWebEngine (Falkon) and WebKitGTK
|
|
|
|
# (via Midori). The test checks that certificates issued by a custom trusted
|
|
|
|
# CA are accepted but those from an unknown CA are rejected.
|
|
|
|
|
|
|
|
import ./make-test-python.nix ({ pkgs, lib, ... }:
|
|
|
|
|
|
|
|
let
|
|
|
|
makeCert = { caName, domain }: pkgs.runCommand "example-cert"
|
|
|
|
{ buildInputs = [ pkgs.gnutls ]; }
|
|
|
|
''
|
|
|
|
mkdir $out
|
|
|
|
|
|
|
|
# CA cert template
|
|
|
|
cat >ca.template <<EOF
|
|
|
|
organization = "${caName}"
|
|
|
|
cn = "${caName}"
|
|
|
|
expiration_days = 365
|
|
|
|
ca
|
|
|
|
cert_signing_key
|
|
|
|
crl_signing_key
|
|
|
|
EOF
|
|
|
|
|
|
|
|
# server cert template
|
|
|
|
cat >server.template <<EOF
|
|
|
|
organization = "An example company"
|
|
|
|
cn = "${domain}"
|
|
|
|
expiration_days = 30
|
|
|
|
dns_name = "${domain}"
|
|
|
|
encryption_key
|
|
|
|
signing_key
|
|
|
|
EOF
|
|
|
|
|
|
|
|
# generate CA keypair
|
|
|
|
certtool \
|
|
|
|
--generate-privkey \
|
|
|
|
--key-type rsa \
|
|
|
|
--sec-param High \
|
|
|
|
--outfile $out/ca.key
|
|
|
|
certtool \
|
|
|
|
--generate-self-signed \
|
|
|
|
--load-privkey $out/ca.key \
|
|
|
|
--template ca.template \
|
|
|
|
--outfile $out/ca.crt
|
|
|
|
|
|
|
|
# generate server keypair
|
|
|
|
certtool \
|
|
|
|
--generate-privkey \
|
|
|
|
--key-type rsa \
|
|
|
|
--sec-param High \
|
|
|
|
--outfile $out/server.key
|
|
|
|
certtool \
|
|
|
|
--generate-certificate \
|
|
|
|
--load-privkey $out/server.key \
|
|
|
|
--load-ca-privkey $out/ca.key \
|
|
|
|
--load-ca-certificate $out/ca.crt \
|
|
|
|
--template server.template \
|
|
|
|
--outfile $out/server.crt
|
|
|
|
'';
|
|
|
|
|
|
|
|
example-good-cert = makeCert
|
|
|
|
{ caName = "Example good CA";
|
|
|
|
domain = "good.example.com";
|
|
|
|
};
|
|
|
|
|
|
|
|
example-bad-cert = makeCert
|
|
|
|
{ caName = "Unknown CA";
|
|
|
|
domain = "bad.example.com";
|
|
|
|
};
|
|
|
|
|
|
|
|
in
|
|
|
|
|
|
|
|
{
|
|
|
|
name = "custom-ca";
|
|
|
|
meta.maintainers = with lib.maintainers; [ rnhmjoj ];
|
|
|
|
|
|
|
|
enableOCR = true;
|
|
|
|
|
2022-03-20 23:15:30 +00:00
|
|
|
nodes.machine = { pkgs, ... }:
|
2021-01-10 17:28:29 +00:00
|
|
|
{ imports = [ ./common/user-account.nix ./common/x11.nix ];
|
|
|
|
|
|
|
|
# chromium-based browsers refuse to run as root
|
|
|
|
test-support.displayManager.auto.user = "alice";
|
|
|
|
|
|
|
|
# browsers may hang with the default memory
|
2021-11-18 21:19:46 +00:00
|
|
|
virtualisation.memorySize = 600;
|
2021-01-10 17:28:29 +00:00
|
|
|
|
|
|
|
networking.hosts."127.0.0.1" = [ "good.example.com" "bad.example.com" ];
|
|
|
|
security.pki.certificateFiles = [ "${example-good-cert}/ca.crt" ];
|
|
|
|
|
|
|
|
services.nginx.enable = true;
|
|
|
|
services.nginx.virtualHosts."good.example.com" =
|
|
|
|
{ onlySSL = true;
|
|
|
|
sslCertificate = "${example-good-cert}/server.crt";
|
|
|
|
sslCertificateKey = "${example-good-cert}/server.key";
|
2021-05-02 21:38:56 +00:00
|
|
|
locations."/".extraConfig = ''
|
|
|
|
add_header Content-Type text/plain;
|
|
|
|
return 200 'It works!';
|
|
|
|
'';
|
2021-01-10 17:28:29 +00:00
|
|
|
};
|
|
|
|
services.nginx.virtualHosts."bad.example.com" =
|
|
|
|
{ onlySSL = true;
|
|
|
|
sslCertificate = "${example-bad-cert}/server.crt";
|
|
|
|
sslCertificateKey = "${example-bad-cert}/server.key";
|
2021-05-02 21:38:56 +00:00
|
|
|
locations."/".extraConfig = ''
|
|
|
|
add_header Content-Type text/plain;
|
|
|
|
return 200 'It does not work!';
|
|
|
|
'';
|
2021-01-10 17:28:29 +00:00
|
|
|
};
|
|
|
|
|
2021-06-08 22:32:53 +00:00
|
|
|
environment.systemPackages = with pkgs; [
|
|
|
|
xdotool
|
2021-06-13 01:23:18 +00:00
|
|
|
firefox
|
2021-06-08 22:32:53 +00:00
|
|
|
chromium
|
2021-09-29 14:35:08 +00:00
|
|
|
qutebrowser
|
2021-06-08 22:32:53 +00:00
|
|
|
midori
|
|
|
|
];
|
2021-01-10 17:28:29 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
testScript = ''
|
2021-05-08 21:02:08 +00:00
|
|
|
from typing import Tuple
|
2021-01-10 17:28:29 +00:00
|
|
|
def execute_as(user: str, cmd: str) -> Tuple[int, str]:
|
|
|
|
"""
|
|
|
|
Run a shell command as a specific user.
|
|
|
|
"""
|
|
|
|
return machine.execute(f"sudo -u {user} {cmd}")
|
|
|
|
|
|
|
|
|
|
|
|
def wait_for_window_as(user: str, cls: str) -> None:
|
|
|
|
"""
|
|
|
|
Wait until a X11 window of a given user appears.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def window_is_visible(last_try: bool) -> bool:
|
|
|
|
ret, stdout = execute_as(user, f"xdotool search --onlyvisible --class {cls}")
|
|
|
|
if last_try:
|
|
|
|
machine.log(f"Last chance to match {cls} on the window list")
|
|
|
|
return ret == 0
|
|
|
|
|
|
|
|
with machine.nested("Waiting for a window to appear"):
|
|
|
|
retry(window_is_visible)
|
|
|
|
|
|
|
|
|
|
|
|
machine.start()
|
|
|
|
|
|
|
|
with subtest("Good certificate is trusted in curl"):
|
|
|
|
machine.wait_for_unit("nginx")
|
|
|
|
machine.wait_for_open_port(443)
|
|
|
|
machine.succeed("curl -fv https://good.example.com")
|
|
|
|
|
|
|
|
with subtest("Unknown CA is untrusted in curl"):
|
|
|
|
machine.fail("curl -fv https://bad.example.com")
|
|
|
|
|
2021-09-29 14:35:08 +00:00
|
|
|
browsers = {
|
2021-06-13 01:23:18 +00:00
|
|
|
"firefox": "Security Risk",
|
2021-09-29 14:35:08 +00:00
|
|
|
"chromium": "not private",
|
|
|
|
"qutebrowser -T": "Certificate error",
|
|
|
|
"midori": "Security"
|
|
|
|
}
|
2021-01-10 17:28:29 +00:00
|
|
|
|
|
|
|
machine.wait_for_x()
|
2021-09-29 14:35:08 +00:00
|
|
|
for command, error in browsers.items():
|
|
|
|
browser = command.split()[0]
|
2021-01-10 17:28:29 +00:00
|
|
|
with subtest("Good certificate is trusted in " + browser):
|
|
|
|
execute_as(
|
2021-11-18 21:19:46 +00:00
|
|
|
"alice", f"{command} https://good.example.com >&2 &"
|
2021-01-10 17:28:29 +00:00
|
|
|
)
|
|
|
|
wait_for_window_as("alice", browser)
|
|
|
|
machine.wait_for_text("It works!")
|
|
|
|
machine.screenshot("good" + browser)
|
|
|
|
execute_as("alice", "xdotool key ctrl+w") # close tab
|
|
|
|
|
|
|
|
with subtest("Unknown CA is untrusted in " + browser):
|
2021-11-18 21:19:46 +00:00
|
|
|
execute_as("alice", f"{command} https://bad.example.com >&2 &")
|
2021-01-10 17:28:29 +00:00
|
|
|
machine.wait_for_text(error)
|
|
|
|
machine.screenshot("bad" + browser)
|
2021-11-18 21:19:46 +00:00
|
|
|
machine.succeed("pkill -f " + browser)
|
2021-01-10 17:28:29 +00:00
|
|
|
'';
|
|
|
|
})
|