162 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
		
		
			
		
	
	
			162 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| 
								 | 
							
								# 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)
							 | 
						||
| 
								 | 
							
								  '';
							 | 
						||
| 
								 | 
							
								})
							 |