From cc8a4227c1bf3638a7997cd5772b2d08ef2d903b Mon Sep 17 00:00:00 2001 From: rnhmjoj <rnhmjoj@inventati.org> Date: Sun, 10 Jan 2021 18:28:29 +0100 Subject: [PATCH] nixos/tests/custom-ca: init This is a NixOS test for the security.pki options. --- nixos/tests/all-tests.nix | 1 + nixos/tests/custom-ca.nix | 161 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 nixos/tests/custom-ca.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 523d3c051e04..c7b21432b58c 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -81,6 +81,7 @@ in corerad = handleTest ./corerad.nix {}; couchdb = handleTest ./couchdb.nix {}; cri-o = handleTestOn ["x86_64-linux"] ./cri-o.nix {}; + custom-ca = handleTest ./custom-ca.nix {}; deluge = handleTest ./deluge.nix {}; dhparams = handleTest ./dhparams.nix {}; dnscrypt-proxy2 = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy2.nix {}; diff --git a/nixos/tests/custom-ca.nix b/nixos/tests/custom-ca.nix new file mode 100644 index 000000000000..67f7b3ff1f16 --- /dev/null +++ b/nixos/tests/custom-ca.nix @@ -0,0 +1,161 @@ +# 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; + + machine = { pkgs, ... }: + { 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 + virtualisation.memorySize = "500"; + + 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"; + locations."/".extraConfig = "return 200 'It works!';"; + }; + services.nginx.virtualHosts."bad.example.com" = + { onlySSL = true; + sslCertificate = "${example-bad-cert}/server.crt"; + sslCertificateKey = "${example-bad-cert}/server.key"; + locations."/".extraConfig = "return 200 'It does not work!';"; + }; + + environment.systemPackages = with pkgs; + [ xdotool firefox chromium falkon midori ]; + }; + + testScript = '' + 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") + + browsers = ["firefox", "chromium", "falkon", "midori"] + errors = ["Security Risk", "not private", "Certificate Error", "Security"] + + machine.wait_for_x() + for browser, error in zip(browsers, errors): + with subtest("Good certificate is trusted in " + browser): + execute_as( + "alice", f"env P11_KIT_DEBUG=trust {browser} https://good.example.com & >&2" + ) + 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): + execute_as("alice", f"{browser} https://bad.example.com & >&2") + machine.wait_for_text(error) + machine.screenshot("bad" + browser) + machine.succeed("pkill " + browser) + ''; +})