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 def test_ecdsa_keygen(self): 27 """Test ECDSA key generation.""" 28 with tempfile.TemporaryDirectory() as tempdir_name: 29 temp_root = Path(tempdir_name) 30 private_key_filename = (temp_root / 'test_key') 31 public_key_filename = (temp_root / 'test_key.pub') 32 33 keys.gen_ecdsa_keypair(private_key_filename) 34 35 self.assertTrue(private_key_filename.exists()) 36 self.assertTrue(public_key_filename.exists()) 37 public_key = keys.import_ecdsa_public_key( 38 public_key_filename.read_bytes()) 39 self.assertEqual(public_key.key.key_type, 40 KeyType.ECDSA_SHA2_NISTP256) 41 self.assertEqual(public_key.key.scheme, 42 KeyScheme.ECDSA_SHA2_NISTP256_SCHEME) 43 44 45class KeyIdTest(unittest.TestCase): 46 """Test Key ID generations """ 47 def test_256bit_length(self): 48 key_id = keys.gen_key_id( 49 Key(key_type=KeyType.ECDSA_SHA2_NISTP256, 50 scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME, 51 keyval=b'public_key bytes')) 52 self.assertEqual(len(key_id), 32) 53 54 def test_different_keyval(self): 55 key1 = Key(key_type=KeyType.ECDSA_SHA2_NISTP256, 56 scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME, 57 keyval=b'key 1 bytes') 58 key2 = Key(key_type=KeyType.ECDSA_SHA2_NISTP256, 59 scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME, 60 keyval=b'key 2 bytes') 61 62 key1_id, key2_id = keys.gen_key_id(key1), keys.gen_key_id(key2) 63 self.assertNotEqual(key1_id, key2_id) 64 65 def test_different_key_type(self): 66 key1 = Key(key_type=KeyType.RSA, 67 scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME, 68 keyval=b'key bytes') 69 key2 = Key(key_type=KeyType.ECDSA_SHA2_NISTP256, 70 scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME, 71 keyval=b'key bytes') 72 73 key1_id, key2_id = keys.gen_key_id(key1), keys.gen_key_id(key2) 74 self.assertNotEqual(key1_id, key2_id) 75 76 def test_different_scheme(self): 77 key1 = Key(key_type=KeyType.ECDSA_SHA2_NISTP256, 78 scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME, 79 keyval=b'key bytes') 80 key2 = Key(key_type=KeyType.ECDSA_SHA2_NISTP256, 81 scheme=KeyScheme.ED25519_SCHEME, 82 keyval=b'key bytes') 83 84 key1_id, key2_id = keys.gen_key_id(key1), keys.gen_key_id(key2) 85 self.assertNotEqual(key1_id, key2_id) 86 87 88class KeyImportTest(unittest.TestCase): 89 """Test key importing""" 90 def setUp(self): 91 # Generated with: 92 # $> openssl ecparam -name prime256v1 -genkey -noout -out priv.pem 93 # $> openssl ec -in priv.pem -pubout -out pub.pem 94 # $> cat pub.pem 95 self.valid_nistp256_pem_bytes = ( 96 b'-----BEGIN PUBLIC KEY-----\n' 97 b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKmK5mJwMV7eimA6MfFQL2q6KbZDr' 98 b'SnWwoeHvXB/aZBnwF422OLifuOuMjEUEHrNMmoekcua+ulHW41X3AgbvIw==\n' 99 b'-----END PUBLIC KEY-----\n') 100 101 # Generated with: 102 # $> openssl ecparam -name secp384r1 -genkey -noout -out priv.pem 103 # $> openssl ec -in priv.pem -pubout -out pub.pem 104 # $> cat pub.pem 105 self.valid_secp384r1_pem_bytes = ( 106 b'-----BEGIN PUBLIC KEY-----\n' 107 b'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE6xs+TEjb2/vIzs4AzSm2CSUWpJMCPAts' 108 b'e+gwvGwFrr2bXKHVLNCxr5/Va6rD0nDmB2NOiJwAXX1Z8CB5wqLLB31emCBFRb5i' 109 b'1LjZu8Bp3hrWOL7uvXer8uExnSfTKAoT\n' 110 b'-----END PUBLIC KEY-----\n') 111 112 # Replaces "MF" with "MM" 113 self.tampered_nistp256_pem_bytes = ( 114 b'-----BEGIN PUBLIC KEY-----\n' 115 b'MMkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKmK5mJwMV7eimA6MfFQL2q6KbZDr' 116 b'SnWwoeHvXB/aZBnwF422OLifuOuMjEUEHrNMmoekcua+ulHW41X3AgbvIw==\n' 117 b'-----END PUBLIC KEY-----\n') 118 119 self.rsa_2048_pem_bytes = ( 120 b'-----BEGIN PUBLIC KEY-----\n' 121 b'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsu0+ol90Ri2BQ5TE9ife' 122 b'6aAmAUMzvAD2b3cnWaTBGXKpi7O9PKnfKbMVf/nJcWsyw2Bj8uStx3oV98U6owLO' 123 b'vsQwyFKVgLZdrXo2qv0L6ljBfCLJxnDhjesEV/oG04dwdN7qyPwAZtpVBCrC7Qi8' 124 b'2rkTnzTQi/1slUxRjliDDhgEdqP7dHbCr7QXNIAA0HFRiOqYmHGD7HNKl67iYmAX' 125 b'd/Jv8GfZL/ykZstP6Ow1/ByP1ZKvrZvg2iXjC686hZXiMJLqmp0sIqLire82oW+8' 126 b'XFc1uyr1j20m+NI5Siy0G3RbfPXrVKyXIgAYPW12+a/BXR9SrqYJYcWwuOGbHZCM' 127 b'pwIDAQAB\n' 128 b'-----END PUBLIC KEY-----\n') 129 130 def test_valid_nistp256_key(self): 131 keys.import_ecdsa_public_key(self.valid_nistp256_pem_bytes) 132 133 def test_tampered_nistp256_key(self): 134 with self.assertRaises(ValueError): 135 keys.import_ecdsa_public_key(self.tampered_nistp256_pem_bytes) 136 137 def test_non_ec_key(self): 138 with self.assertRaises(TypeError): 139 keys.import_ecdsa_public_key(self.rsa_2048_pem_bytes) 140 141 def test_wrong_curve(self): 142 with self.assertRaises(TypeError): 143 keys.import_ecdsa_public_key(self.valid_secp384r1_pem_bytes) 144 145 146class SignatureVerificationTest(unittest.TestCase): 147 """ECDSA signing and verification test.""" 148 def setUp(self): 149 # Generated with: 150 # $> openssl ecparam -name prime256v1 -genkey -noout -out priv.pem 151 # $> openssl ec -in priv.pem -pubout -out pub.pem 152 # $> cat priv.pem pub.pem 153 self.private_key_pem = ( 154 b'-----BEGIN EC PRIVATE KEY-----\n' 155 b'MHcCAQEEIH9u1n4qAT59f7KRRl/ZB0Y/BUfS4blba+LONlF4s3ltoAoGCCqGSM49' 156 b'AwEHoUQDQgAEgKf3kY9Hi3hxIyqm2EkfqQvJkCijjlJSmEAJ1oAp0Godi5x2af+m' 157 b'cSNuBjpRcC8iW8x1/gizqyWlfAVrZV0XdA==\n' 158 b'-----END EC PRIVATE KEY-----\n') 159 self.public_key_pem = ( 160 b'-----BEGIN PUBLIC KEY-----\n' 161 b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgKf3kY9Hi3hxIyqm2EkfqQvJkCij' 162 b'jlJSmEAJ1oAp0Godi5x2af+mcSNuBjpRcC8iW8x1/gizqyWlfAVrZV0XdA==\n' 163 b'-----END PUBLIC KEY-----\n') 164 165 self.message = b'Hello Pigweed!' 166 self.tampered_message = b'Hell0 Pigweed!' 167 168 def test_good_signature(self): 169 sig = keys.create_ecdsa_signature(self.message, self.private_key_pem) 170 self.assertTrue( 171 keys.verify_ecdsa_signature( 172 sig.sig, self.message, 173 keys.import_ecdsa_public_key(self.public_key_pem).key)) 174 175 def test_tampered_message(self): 176 sig = keys.create_ecdsa_signature(self.message, self.private_key_pem) 177 self.assertFalse( 178 keys.verify_ecdsa_signature( 179 sig.sig, self.tampered_message, 180 keys.import_ecdsa_public_key(self.public_key_pem).key)) 181 182 def test_tampered_signature(self): 183 sig = keys.create_ecdsa_signature(self.message, self.private_key_pem) 184 tampered_sig = bytearray(sig.sig) 185 tampered_sig[0] ^= 1 186 self.assertFalse( 187 keys.verify_ecdsa_signature( 188 tampered_sig, self.message, 189 keys.import_ecdsa_public_key(self.public_key_pem).key)) 190 191 192if __name__ == '__main__': 193 unittest.main() 194