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