Add tests for some device & address variables
[tinc] / test / integration / cmd_keys.py
1 #!/usr/bin/env python3
2 # pylint: disable=import-outside-toplevel
3
4 """Test key management commands."""
5
6 import os
7
8 from testlib import check, util
9 from testlib.log import log
10 from testlib.const import RUN_ACCESS_CHECKS
11 from testlib.feature import Feature
12 from testlib.proc import Tinc
13 from testlib.test import Test
14
15
16 TEST_DATA = b"foo bar baz"
17
18
19 def try_rsa_keys(priv_path: str, pub_path: str) -> None:
20     """Check that RSA key pair works."""
21
22     try:
23         import cryptography  # type: ignore
24         from cryptography.hazmat.primitives import hashes, serialization  # type: ignore
25         from cryptography.hazmat.primitives.asymmetric import padding  # type: ignore
26     except ImportError:
27         log.info("cryptography module missing or broken, skipping key checks")
28         return
29
30     version = cryptography.__version__.split(".", maxsplit=2)
31     if not (int(version[0]) >= 3 and int(version[1]) >= 3):
32         log.info("cryptography module is too old, skipping key check")
33         return
34
35     log.info("loading keys from (%s, %s)", priv_path, pub_path)
36     with open(priv_path, "rb") as priv, open(pub_path, "rb") as pub:
37         key_pair = (
38             serialization.load_pem_private_key(priv.read(), password=None),
39             serialization.load_pem_public_key(pub.read()),
40         )
41
42     s_pad = padding.PSS(
43         mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH
44     )
45     s_hash = hashes.SHA256()
46
47     log.info("signing sample data %s", TEST_DATA)
48     signature = key_pair[0].sign(TEST_DATA, s_pad, s_hash)
49
50     log.info("verifying signature %s", signature)
51     key_pair[1].verify(signature, TEST_DATA, s_pad, s_hash)
52
53
54 def test_rsa(foo: Tinc) -> None:
55     """Test command 'generate-rsa-keys'."""
56
57     for key_size in "foobar", "512", "16384":
58         log.info("generate %s-bit RSA key", key_size)
59         _, err = foo.cmd("generate-rsa-keys", key_size, code=1)
60         check.is_in("Invalid key size", err)
61
62     log.info("generate RSA key with too many arguments")
63     _, err = foo.cmd("generate-rsa-keys", "2048", "4096", code=1)
64     check.is_in("Too many arguments", err)
65
66     rsa_priv = foo.sub("rsa_key.priv")
67     rsa_pub = foo.sub(f"hosts/{foo}")
68
69     for key_size in "1024", "1025":
70         log.info("generate %s-bit RSA key", key_size)
71         _, err = foo.cmd("generate-rsa-keys", key_size)
72         check.is_in("Generating 1024 bits", err)
73         check.is_in("generating a weak", err)
74         check.is_in("found and disabled", err)
75         try_rsa_keys(rsa_priv, rsa_pub)
76
77     for key_size in "2048", "2049":
78         log.info("generate %s-bit RSA key", key_size)
79         os.remove(rsa_priv)
80         _, err = foo.cmd("generate-rsa-keys", key_size)
81         check.is_in("Generating 2048 bits", err)
82         check.file_exists(rsa_priv)
83         try_rsa_keys(rsa_priv, rsa_pub)
84
85     log.info("check that key is present")
86     key = util.read_text(rsa_priv)
87     check.has_prefix(key, "-----BEGIN RSA PRIVATE KEY-----")
88
89     if RUN_ACCESS_CHECKS:
90         log.info("remove access to private key")
91         os.chmod(rsa_priv, 0)
92         _, err = foo.cmd("generate-rsa-keys", "1024", code=1)
93         check.is_in("Error opening file", err)
94
95
96 def test_rsa_nolegacy(foo: Tinc) -> None:
97     """Test command 'generate-rsa-keys' on a nolegacy build."""
98
99     log.info("generate RSA key with nolegacy tinc")
100     _, err = foo.cmd("generate-rsa-keys", code=1)
101     check.is_in("Unknown command", err)
102
103
104 def test_eddsa(foo: Tinc) -> None:
105     """Test command 'generate-ed25519-keys'."""
106
107     log.info("generate EC key with too many arguments")
108     _, err = foo.cmd("generate-ed25519-keys", "2048", code=1)
109     check.is_in("Too many arguments", err)
110
111     log.info("generate and replace EC key")
112     _, err = foo.cmd("generate-ed25519-keys")
113     check.is_in("found and disabled", err)
114
115     log.info("remove EC key files")
116     ec_priv = foo.sub("ed25519_key.priv")
117     ec_pub = foo.sub(f"hosts/{foo}")
118     os.remove(ec_priv)
119     os.remove(ec_pub)
120
121     log.info("create new EC key files")
122     foo.cmd("generate-ed25519-keys")
123     check.has_prefix(util.read_text(ec_priv), "-----BEGIN ED25519 PRIVATE KEY-----")
124     check.has_prefix(util.read_text(ec_pub), "Ed25519PublicKey")
125
126     if RUN_ACCESS_CHECKS:
127         log.info("remove access to EC private key file")
128         os.chmod(ec_priv, 0)
129         _, err = foo.cmd("generate-ed25519-keys", code=1)
130         check.is_in("Error opening file", err)
131
132
133 def run_tests(foo: Tinc) -> None:
134     """Run tests."""
135
136     test_eddsa(foo)
137
138     if Feature.LEGACY_PROTOCOL in foo.features:
139         test_rsa(foo)
140     else:
141         test_rsa_nolegacy(foo)
142
143
144 with Test("run tests") as context:
145     run_tests(context.node(init=True))