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