xref: /aosp_15_r20/external/pigweed/pw_software_update/py/keys_test.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1# Copyright 2021 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Unit tests for pw_software_update/keys.py."""
15
16from pathlib import Path
17import tempfile
18import unittest
19
20from pw_software_update import keys
21from pw_software_update.tuf_pb2 import Key, KeyType, KeyScheme
22
23
24class KeyGenTest(unittest.TestCase):
25    """Test the generation of keys."""
26
27    def test_ecdsa_keygen(self):
28        """Test ECDSA key generation."""
29        with tempfile.TemporaryDirectory() as tempdir_name:
30            temp_root = Path(tempdir_name)
31            private_key_filename = temp_root / 'test_key'
32            public_key_filename = temp_root / 'test_key.pub'
33
34            keys.gen_ecdsa_keypair(private_key_filename)
35
36            self.assertTrue(private_key_filename.exists())
37            self.assertTrue(public_key_filename.exists())
38            public_key = keys.import_ecdsa_public_key(
39                public_key_filename.read_bytes()
40            )
41            self.assertEqual(
42                public_key.key.key_type, KeyType.ECDSA_SHA2_NISTP256
43            )
44            self.assertEqual(
45                public_key.key.scheme, KeyScheme.ECDSA_SHA2_NISTP256_SCHEME
46            )
47
48
49class KeyIdTest(unittest.TestCase):
50    """Test Key ID generations"""
51
52    def test_256bit_length(self):
53        key_id = keys.gen_key_id(
54            Key(
55                key_type=KeyType.ECDSA_SHA2_NISTP256,
56                scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
57                keyval=b'public_key bytes',
58            )
59        )
60        self.assertEqual(len(key_id), 32)
61
62    def test_different_keyval(self):
63        key1 = Key(
64            key_type=KeyType.ECDSA_SHA2_NISTP256,
65            scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
66            keyval=b'key 1 bytes',
67        )
68        key2 = Key(
69            key_type=KeyType.ECDSA_SHA2_NISTP256,
70            scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
71            keyval=b'key 2 bytes',
72        )
73
74        key1_id, key2_id = keys.gen_key_id(key1), keys.gen_key_id(key2)
75        self.assertNotEqual(key1_id, key2_id)
76
77    def test_different_key_type(self):
78        key1 = Key(
79            key_type=KeyType.RSA,
80            scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
81            keyval=b'key bytes',
82        )
83        key2 = Key(
84            key_type=KeyType.ECDSA_SHA2_NISTP256,
85            scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
86            keyval=b'key bytes',
87        )
88
89        key1_id, key2_id = keys.gen_key_id(key1), keys.gen_key_id(key2)
90        self.assertNotEqual(key1_id, key2_id)
91
92    def test_different_scheme(self):
93        key1 = Key(
94            key_type=KeyType.ECDSA_SHA2_NISTP256,
95            scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
96            keyval=b'key bytes',
97        )
98        key2 = Key(
99            key_type=KeyType.ECDSA_SHA2_NISTP256,
100            scheme=KeyScheme.ED25519_SCHEME,
101            keyval=b'key bytes',
102        )
103
104        key1_id, key2_id = keys.gen_key_id(key1), keys.gen_key_id(key2)
105        self.assertNotEqual(key1_id, key2_id)
106
107
108class KeyImportTest(unittest.TestCase):
109    """Test key importing"""
110
111    def setUp(self):
112        # Generated with:
113        # $> openssl ecparam -name prime256v1 -genkey -noout -out priv.pem
114        # $> openssl ec -in priv.pem -pubout -out pub.pem
115        # $> cat pub.pem
116        self.valid_nistp256_pem_bytes = (
117            b'-----BEGIN PUBLIC KEY-----\n'
118            b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKmK5mJwMV7eimA6MfFQL2q6KbZDr'
119            b'SnWwoeHvXB/aZBnwF422OLifuOuMjEUEHrNMmoekcua+ulHW41X3AgbvIw==\n'
120            b'-----END PUBLIC KEY-----\n'
121        )
122
123        # Generated with:
124        # $> openssl ecparam -name secp384r1 -genkey -noout -out priv.pem
125        # $> openssl ec -in priv.pem -pubout -out pub.pem
126        # $> cat pub.pem
127        self.valid_secp384r1_pem_bytes = (
128            b'-----BEGIN PUBLIC KEY-----\n'
129            b'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE6xs+TEjb2/vIzs4AzSm2CSUWpJMCPAts'
130            b'e+gwvGwFrr2bXKHVLNCxr5/Va6rD0nDmB2NOiJwAXX1Z8CB5wqLLB31emCBFRb5i'
131            b'1LjZu8Bp3hrWOL7uvXer8uExnSfTKAoT\n'
132            b'-----END PUBLIC KEY-----\n'
133        )
134
135        # Replaces "MF" with "MM"
136        self.tampered_nistp256_pem_bytes = (
137            b'-----BEGIN PUBLIC KEY-----\n'
138            b'MMkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKmK5mJwMV7eimA6MfFQL2q6KbZDr'
139            b'SnWwoeHvXB/aZBnwF422OLifuOuMjEUEHrNMmoekcua+ulHW41X3AgbvIw==\n'
140            b'-----END PUBLIC KEY-----\n'
141        )
142
143        self.rsa_2048_pem_bytes = (
144            b'-----BEGIN PUBLIC KEY-----\n'
145            b'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsu0+ol90Ri2BQ5TE9ife'
146            b'6aAmAUMzvAD2b3cnWaTBGXKpi7O9PKnfKbMVf/nJcWsyw2Bj8uStx3oV98U6owLO'
147            b'vsQwyFKVgLZdrXo2qv0L6ljBfCLJxnDhjesEV/oG04dwdN7qyPwAZtpVBCrC7Qi8'
148            b'2rkTnzTQi/1slUxRjliDDhgEdqP7dHbCr7QXNIAA0HFRiOqYmHGD7HNKl67iYmAX'
149            b'd/Jv8GfZL/ykZstP6Ow1/ByP1ZKvrZvg2iXjC686hZXiMJLqmp0sIqLire82oW+8'
150            b'XFc1uyr1j20m+NI5Siy0G3RbfPXrVKyXIgAYPW12+a/BXR9SrqYJYcWwuOGbHZCM'
151            b'pwIDAQAB\n'
152            b'-----END PUBLIC KEY-----\n'
153        )
154
155    def test_valid_nistp256_key(self):
156        keys.import_ecdsa_public_key(self.valid_nistp256_pem_bytes)
157
158    def test_tampered_nistp256_key(self):
159        with self.assertRaises(ValueError):
160            keys.import_ecdsa_public_key(self.tampered_nistp256_pem_bytes)
161
162    def test_non_ec_key(self):
163        with self.assertRaises(TypeError):
164            keys.import_ecdsa_public_key(self.rsa_2048_pem_bytes)
165
166    def test_wrong_curve(self):
167        with self.assertRaises(TypeError):
168            keys.import_ecdsa_public_key(self.valid_secp384r1_pem_bytes)
169
170
171class SignatureVerificationTest(unittest.TestCase):
172    """ECDSA signing and verification test."""
173
174    def setUp(self):
175        # Generated with:
176        # $> openssl ecparam -name prime256v1 -genkey -noout -out priv.pem
177        # $> openssl ec -in priv.pem -pubout -out pub.pem
178        # $> cat priv.pem pub.pem
179        self.private_key_pem = (
180            b'-----BEGIN EC PRIVATE KEY-----\n'
181            b'MHcCAQEEIH9u1n4qAT59f7KRRl/ZB0Y/BUfS4blba+LONlF4s3ltoAoGCCqGSM49'
182            b'AwEHoUQDQgAEgKf3kY9Hi3hxIyqm2EkfqQvJkCijjlJSmEAJ1oAp0Godi5x2af+m'
183            b'cSNuBjpRcC8iW8x1/gizqyWlfAVrZV0XdA==\n'
184            b'-----END EC PRIVATE KEY-----\n'
185        )
186        self.public_key_pem = (
187            b'-----BEGIN PUBLIC KEY-----\n'
188            b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgKf3kY9Hi3hxIyqm2EkfqQvJkCij'
189            b'jlJSmEAJ1oAp0Godi5x2af+mcSNuBjpRcC8iW8x1/gizqyWlfAVrZV0XdA==\n'
190            b'-----END PUBLIC KEY-----\n'
191        )
192
193        self.message = b'Hello Pigweed!'
194        self.tampered_message = b'Hell0 Pigweed!'
195
196    def test_good_signature(self):
197        sig = keys.create_ecdsa_signature(self.message, self.private_key_pem)
198        self.assertTrue(
199            keys.verify_ecdsa_signature(
200                sig.sig,
201                self.message,
202                keys.import_ecdsa_public_key(self.public_key_pem).key,
203            )
204        )
205
206    def test_tampered_message(self):
207        sig = keys.create_ecdsa_signature(self.message, self.private_key_pem)
208        self.assertFalse(
209            keys.verify_ecdsa_signature(
210                sig.sig,
211                self.tampered_message,
212                keys.import_ecdsa_public_key(self.public_key_pem).key,
213            )
214        )
215
216    def test_tampered_signature(self):
217        sig = keys.create_ecdsa_signature(self.message, self.private_key_pem)
218        tampered_sig = bytearray(sig.sig)
219        tampered_sig[0] ^= 1
220        self.assertFalse(
221            keys.verify_ecdsa_signature(
222                tampered_sig,
223                self.message,
224                keys.import_ecdsa_public_key(self.public_key_pem).key,
225            )
226        )
227
228
229if __name__ == '__main__':
230    unittest.main()
231