about summary refs log tree commit diff
path: root/nixpkgs/nixos/tests/custom-ca.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/tests/custom-ca.nix')
-rw-r--r--nixpkgs/nixos/tests/custom-ca.nix195
1 files changed, 195 insertions, 0 deletions
diff --git a/nixpkgs/nixos/tests/custom-ca.nix b/nixpkgs/nixos/tests/custom-ca.nix
new file mode 100644
index 000000000000..0fcdf81022d7
--- /dev/null
+++ b/nixpkgs/nixos/tests/custom-ca.nix
@@ -0,0 +1,195 @@
+# Checks that `security.pki` options are working in curl and the main browser
+# engines: Gecko (via Firefox), Chromium, QtWebEngine (via qutebrowser) 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.
+
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  inherit (pkgs) lib;
+
+  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";
+    };
+
+  webserverConfig =
+    { 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 = ''
+            add_header Content-Type text/plain;
+            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 = ''
+            add_header Content-Type text/plain;
+            return 200 'It does not work!';
+          '';
+        };
+    };
+
+  curlTest = makeTest {
+    name = "custom-ca-curl";
+    meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+    nodes.machine = { ... }: webserverConfig;
+    testScript = ''
+        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")
+    '';
+  };
+
+  mkBrowserTest = browser: testParams: makeTest {
+    name = "custom-ca-${browser}";
+    meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+    enableOCR = true;
+
+    nodes.machine = { pkgs, ... }:
+      { imports =
+          [ ./common/user-account.nix
+            ./common/x11.nix
+            webserverConfig
+          ];
+
+        # chromium-based browsers refuse to run as root
+        test-support.displayManager.auto.user = "alice";
+
+        # machine often runs out of memory with less
+        virtualisation.memorySize = 1024;
+
+        environment.systemPackages = [ pkgs.xdotool pkgs.${browser} ];
+      };
+
+    testScript = ''
+      from typing import Tuple
+      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()
+      machine.wait_for_x()
+
+      command = "${browser} ${testParams.args or ""}"
+      with subtest("Good certificate is trusted in ${browser}"):
+          execute_as(
+              "alice", f"{command} https://good.example.com >&2 &"
+          )
+          wait_for_window_as("alice", "${browser}")
+          machine.sleep(4)
+          execute_as("alice", "xdotool key ctrl+r")  # reload to be safe
+          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"{command} https://bad.example.com >&2 &")
+          machine.wait_for_text("${testParams.error}")
+          machine.screenshot("bad${browser}")
+    '';
+  };
+
+in
+
+{
+  curl = curlTest;
+} // pkgs.lib.mapAttrs mkBrowserTest {
+  firefox = { error = "Security Risk"; };
+  chromium = { error = "not private"; };
+  qutebrowser = { args = "-T"; error = "Certificate error"; };
+  midori = { args = "-p"; error = "Security"; };
+}