Add tests for sign/verify commands
[tinc] / test / integration / cmd_sign_verify.py
1 #!/usr/bin/env python3
2
3 """Test sign/verify commands."""
4
5 import os
6 import tempfile
7
8 from testlib import util, cmd, check
9 from testlib.proc import Tinc
10 from testlib.test import Test
11
12 PRIV_KEY = """
13 -----BEGIN ED25519 PRIVATE KEY-----
14 4Q8bJqfN60s0tOiZdAhAWLgB9+o947cta2WMXmQIz8mCdBdcphzhp23Wt2vUzfQ6
15 XHt9+5IqidIw/lLXG61Nbc6IZ+4Fy1XOO1uJ6j4hqIKjdSytD2Vb7MPlNJfPdCDu
16 -----END ED25519 PRIVATE KEY-----
17 """
18
19 HOST = """
20 Ed25519PublicKey = nOSmPehc9ljTtbi+IeoKiyYnkc7gd12OzTZTy3TnwgL
21 Port = 17879
22 """
23
24 # Do not replace \n or this will break on Windows if cloned with native line endings
25 SIGNED_BYTES = """Signature = foo 1653397516 \
26 T8Bjg7dc7IjsCrZQC/20qLRsWPlrbthnjyDHQM0BMLoTeAHbLt0fxP5CbTy7Cifgg7P0K179GeahBFsnaIr4MA\n\
27 fake testing data\n\
28 hello there\n\
29 """.encode(
30     "utf-8"
31 )
32
33 RAW_DATA = tempfile.mktemp()
34
35 with open(RAW_DATA, "wb") as raw_file:
36     raw_file.write(util.random_string(64).encode("utf-8"))
37
38
39 def init(ctx: Test) -> Tinc:
40     """Initialize a node."""
41
42     foo = ctx.node()
43     stdin = f"init {foo}"
44     foo.cmd(stdin=stdin)
45     return foo
46
47
48 def test_sign_errors(foo: Tinc) -> None:
49     """Test `sign` error conditions."""
50
51     _, err = foo.cmd("sign", "foo", "bar", code=1)
52     check.is_in("Too many arguments", err)
53
54     _, err = foo.cmd("sign", "/nonexistent", code=1)
55     check.is_in("Could not open", err)
56
57     os.truncate(foo.sub("ed25519_key.priv"), 0)
58     _, err = foo.cmd("sign", RAW_DATA, code=1)
59     check.is_in("Could not read private key from", err)
60
61     os.remove(foo.sub("ed25519_key.priv"))
62     _, err = foo.cmd("sign", RAW_DATA, code=1)
63     check.is_in("Could not open", err)
64
65
66 def test_verify(foo: Tinc) -> None:
67     """Test `verify` of data known to work."""
68
69     signed_file = tempfile.mktemp()
70     with open(signed_file, "wb") as f:
71         f.write(SIGNED_BYTES)
72
73     foo.name = "foo"
74     util.write_text(foo.sub("tinc.conf"), f"Name = {foo}")
75     util.write_text(foo.sub(f"hosts/{foo}"), HOST)
76     util.write_text(foo.sub("ed25519_key.priv"), PRIV_KEY)
77
78     for name in ".", foo.name:
79         foo.cmd("verify", name, stdin=SIGNED_BYTES)
80         foo.cmd("verify", name, signed_file)
81
82     if os.name != "nt":
83         foo.cmd("verify", "*", stdin=SIGNED_BYTES)
84         foo.cmd("verify", "*", signed_file)
85
86     os.remove(signed_file)
87
88
89 def test_verify_errors(foo: Tinc) -> None:
90     """Test `verify` error conditions."""
91
92     _, err = foo.cmd("verify", code=1)
93     check.is_in("Not enough arguments", err)
94
95     _, err = foo.cmd("verify", foo.name, "bar", "baz", code=1)
96     check.is_in("Too many arguments", err)
97
98     _, err = foo.cmd("verify", "foo@", code=1)
99     check.is_in("Invalid node name", err)
100
101     _, err = foo.cmd("verify", foo.name, "/nonexistent", code=1)
102     check.is_in("Could not open", err)
103
104     _, err = foo.cmd("verify", foo.name, stdin="", code=1)
105     check.is_in("Invalid input", err)
106
107     _, err = foo.cmd("verify", foo.name, stdin="Signature = foo bar baz", code=1)
108     check.is_in("Invalid input", err)
109
110     sig = (
111         "Signature = dog "
112         "1653395565 "
113         "D25ACFD89jaV9+6g9TNMDTDxH8JGd3wLMv/YNMwXbrj9Bos9q6IW/tuFPxGxYNQ6qAc93XFzkH5u7Gw+Z86GDA\n"
114     )
115     _, err = foo.cmd("verify", foo.name, stdin=sig, code=1)
116     check.is_in(f"Signature is not made by {foo}", err)
117
118     sig = (
119         f"Signature = {foo} "
120         "1653395565 "
121         "D25ACFD89jaV9+6g9TNMDTDxH8JGd3wLMv/YNMwXbrj9Bos9q6IW/tuFPxGxYNQ6qAc93XFzkH5u7Gw+Z86GDA\n"
122     )
123     _, err = foo.cmd("verify", foo.name, stdin=sig, code=1)
124     check.is_in("Invalid signature", err)
125
126     util.write_text(foo.sub(f"hosts/{foo}"), "foobar")
127     _, err = foo.cmd("verify", foo.name, stdin=sig, code=1)
128     check.is_in("Could not read public key from", err)
129
130
131 def test_sign_verify(foo: Tinc, bar: Tinc) -> None:
132     """Test `sign` and pass its result to `verify`."""
133
134     signed, _ = foo.cmd("sign", RAW_DATA, stdin=b"")
135     assert isinstance(signed, bytes)
136
137     signed_file = tempfile.mktemp()
138     with open(signed_file, "wb") as f:
139         f.write(signed)
140
141     for name in ".", foo.name:
142         foo.cmd("verify", name, signed_file)
143         foo.cmd("verify", name, stdin=signed)
144
145     if os.name != "nt":
146         foo.cmd("verify", "*", signed_file)
147         foo.cmd("verify", "*", stdin=signed)
148
149     os.remove(signed_file)
150
151     cmd.exchange(foo, bar)
152
153     if os.name != "nt":
154         signed, _ = foo.cmd("sign", RAW_DATA)
155         bar.cmd("verify", "*", stdin=signed)
156
157     signed, _ = bar.cmd("sign", RAW_DATA)
158     foo.cmd("verify", bar.name, stdin=signed)
159
160
161 with Test("test errors in `sign`") as context:
162     test_sign_errors(init(context))
163
164 with Test("test errors in `verify`") as context:
165     test_verify_errors(init(context))
166
167 with Test("test successful `verify`") as context:
168     test_verify(init(context))
169
170 with Test("test `sign` and `verify`") as context:
171     test_sign_verify(init(context), init(context))