1import ./make-test-python.nix ({ ... }:
2
3let
4 oathSnakeoilSecret = "cdd4083ef8ff1fa9178c6d46bfb1a3";
5
6 # With HOTP mode the password is calculated based on a counter of
7 # how many passwords have been made. In this env, we'll always be on
8 # the 0th counter, so the password is static.
9 #
10 # Generated in nix-shell -p oathToolkit
11 # via: oathtool -v -d6 -w10 cdd4083ef8ff1fa9178c6d46bfb1a3
12 # and picking a the first 4:
13 oathSnakeOilPassword1 = "143349";
14 oathSnakeOilPassword2 = "801753";
15
16 alicePassword = "foobar";
17 # Generated via: mkpasswd -m sha-512 and passing in "foobar"
18 hashedAlicePassword = "$6$MsMrE1q.1HrCgTS$Vq2e/uILzYjSN836TobAyN9xh9oi7EmCmucnZID25qgPoibkw8qTCugiAPnn4eCGvn1A.7oEBFJaaGUaJsQQY.";
19
20in
21{
22 name = "pam-oath-login";
23
24 machine =
25 { ... }:
26 {
27 security.pam.oath = {
28 enable = true;
29 };
30
31 users.users.alice = {
32 isNormalUser = true;
33 name = "alice";
34 uid = 1000;
35 hashedPassword = hashedAlicePassword;
36 extraGroups = [ "wheel" ];
37 createHome = true;
38 home = "/home/alice";
39 };
40
41
42 systemd.services.setupOathSnakeoilFile = {
43 wantedBy = [ "default.target" ];
44 before = [ "default.target" ];
45 unitConfig = {
46 type = "oneshot";
47 RemainAfterExit = true;
48 };
49 script = ''
50 touch /etc/users.oath
51 chmod 600 /etc/users.oath
52 chown root /etc/users.oath
53 echo "HOTP/E/6 alice - ${oathSnakeoilSecret}" > /etc/users.oath
54 '';
55 };
56 };
57
58 testScript = ''
59 def switch_to_tty(tty_number):
60 machine.fail(f"pgrep -f 'agetty.*tty{tty_number}'")
61 machine.send_key(f"alt-f{tty_number}")
62 machine.wait_until_succeeds(f"[ $(fgconsole) = {tty_number} ]")
63 machine.wait_for_unit(f"getty@tty{tty_number}.service")
64 machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{tty_number}'")
65
66
67 def enter_user_alice(tty_number):
68 machine.wait_until_tty_matches(tty_number, "login: ")
69 machine.send_chars("alice\n")
70 machine.wait_until_tty_matches(tty_number, "login: alice")
71 machine.wait_until_succeeds("pgrep login")
72 machine.wait_until_tty_matches(tty_number, "One-time password")
73
74
75 machine.wait_for_unit("multi-user.target")
76 machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
77 machine.screenshot("postboot")
78
79 with subtest("Invalid password"):
80 switch_to_tty(2)
81 enter_user_alice(2)
82
83 machine.send_chars("${oathSnakeOilPassword1}\n")
84 machine.wait_until_tty_matches(2, "Password: ")
85 machine.send_chars("blorg\n")
86 machine.wait_until_tty_matches(2, "Login incorrect")
87
88 with subtest("Invalid oath token"):
89 switch_to_tty(3)
90 enter_user_alice(3)
91
92 machine.send_chars("000000\n")
93 machine.wait_until_tty_matches(3, "Login incorrect")
94 machine.wait_until_tty_matches(3, "login:")
95
96 with subtest("Happy path: Both passwords are mandatory to get us in"):
97 switch_to_tty(4)
98 enter_user_alice(4)
99
100 machine.send_chars("${oathSnakeOilPassword2}\n")
101 machine.wait_until_tty_matches(4, "Password: ")
102 machine.send_chars("${alicePassword}\n")
103
104 machine.wait_until_succeeds("pgrep -u alice bash")
105 machine.send_chars("touch done4\n")
106 machine.wait_for_file("/home/alice/done4")
107 '';
108})