| # Copyright 2021 The Pigweed Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| # use this file except in compliance with the License. You may obtain a copy of |
| # the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations under |
| # the License. |
| """Unit tests for pw_software_update/keys.py.""" |
| |
| from pathlib import Path |
| import tempfile |
| import unittest |
| |
| from pw_software_update import keys |
| from pw_software_update.tuf_pb2 import Key, KeyType, KeyScheme |
| |
| |
| class KeyGenTest(unittest.TestCase): |
| """Test the generation of keys.""" |
| def test_ecdsa_keygen(self): |
| """Test ECDSA key generation.""" |
| with tempfile.TemporaryDirectory() as tempdir_name: |
| temp_root = Path(tempdir_name) |
| private_key_filename = (temp_root / 'test_key') |
| public_key_filename = (temp_root / 'test_key.pub') |
| |
| keys.gen_ecdsa_keypair(private_key_filename) |
| |
| self.assertTrue(private_key_filename.exists()) |
| self.assertTrue(public_key_filename.exists()) |
| public_key = keys.import_ecdsa_public_key( |
| public_key_filename.read_bytes()) |
| self.assertEqual(public_key.key.key_type, |
| KeyType.ECDSA_SHA2_NISTP256) |
| self.assertEqual(public_key.key.scheme, |
| KeyScheme.ECDSA_SHA2_NISTP256_SCHEME) |
| |
| |
| class KeyIdTest(unittest.TestCase): |
| """Test Key ID generations """ |
| def test_256bit_length(self): |
| key_id = keys.gen_key_id( |
| Key(key_type=KeyType.ECDSA_SHA2_NISTP256, |
| scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME, |
| keyval=b'public_key bytes')) |
| self.assertEqual(len(key_id), 32) |
| |
| def test_different_keyval(self): |
| key1 = Key(key_type=KeyType.ECDSA_SHA2_NISTP256, |
| scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME, |
| keyval=b'key 1 bytes') |
| key2 = Key(key_type=KeyType.ECDSA_SHA2_NISTP256, |
| scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME, |
| keyval=b'key 2 bytes') |
| |
| key1_id, key2_id = keys.gen_key_id(key1), keys.gen_key_id(key2) |
| self.assertNotEqual(key1_id, key2_id) |
| |
| def test_different_key_type(self): |
| key1 = Key(key_type=KeyType.RSA, |
| scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME, |
| keyval=b'key bytes') |
| key2 = Key(key_type=KeyType.ECDSA_SHA2_NISTP256, |
| scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME, |
| keyval=b'key bytes') |
| |
| key1_id, key2_id = keys.gen_key_id(key1), keys.gen_key_id(key2) |
| self.assertNotEqual(key1_id, key2_id) |
| |
| def test_different_scheme(self): |
| key1 = Key(key_type=KeyType.ECDSA_SHA2_NISTP256, |
| scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME, |
| keyval=b'key bytes') |
| key2 = Key(key_type=KeyType.ECDSA_SHA2_NISTP256, |
| scheme=KeyScheme.ED25519_SCHEME, |
| keyval=b'key bytes') |
| |
| key1_id, key2_id = keys.gen_key_id(key1), keys.gen_key_id(key2) |
| self.assertNotEqual(key1_id, key2_id) |
| |
| |
| class KeyImportTest(unittest.TestCase): |
| """Test key importing""" |
| def setUp(self): |
| # Generated with: |
| # $> openssl ecparam -name prime256v1 -genkey -noout -out priv.pem |
| # $> openssl ec -in priv.pem -pubout -out pub.pem |
| # $> cat pub.pem |
| self.valid_nistp256_pem_bytes = ( |
| b'-----BEGIN PUBLIC KEY-----\n' |
| b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKmK5mJwMV7eimA6MfFQL2q6KbZDr' |
| b'SnWwoeHvXB/aZBnwF422OLifuOuMjEUEHrNMmoekcua+ulHW41X3AgbvIw==\n' |
| b'-----END PUBLIC KEY-----\n') |
| |
| # Generated with: |
| # $> openssl ecparam -name secp384r1 -genkey -noout -out priv.pem |
| # $> openssl ec -in priv.pem -pubout -out pub.pem |
| # $> cat pub.pem |
| self.valid_secp384r1_pem_bytes = ( |
| b'-----BEGIN PUBLIC KEY-----\n' |
| b'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE6xs+TEjb2/vIzs4AzSm2CSUWpJMCPAts' |
| b'e+gwvGwFrr2bXKHVLNCxr5/Va6rD0nDmB2NOiJwAXX1Z8CB5wqLLB31emCBFRb5i' |
| b'1LjZu8Bp3hrWOL7uvXer8uExnSfTKAoT\n' |
| b'-----END PUBLIC KEY-----\n') |
| |
| # Replaces "MF" with "MM" |
| self.tampered_nistp256_pem_bytes = ( |
| b'-----BEGIN PUBLIC KEY-----\n' |
| b'MMkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKmK5mJwMV7eimA6MfFQL2q6KbZDr' |
| b'SnWwoeHvXB/aZBnwF422OLifuOuMjEUEHrNMmoekcua+ulHW41X3AgbvIw==\n' |
| b'-----END PUBLIC KEY-----\n') |
| |
| self.rsa_2048_pem_bytes = ( |
| b'-----BEGIN PUBLIC KEY-----\n' |
| b'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsu0+ol90Ri2BQ5TE9ife' |
| b'6aAmAUMzvAD2b3cnWaTBGXKpi7O9PKnfKbMVf/nJcWsyw2Bj8uStx3oV98U6owLO' |
| b'vsQwyFKVgLZdrXo2qv0L6ljBfCLJxnDhjesEV/oG04dwdN7qyPwAZtpVBCrC7Qi8' |
| b'2rkTnzTQi/1slUxRjliDDhgEdqP7dHbCr7QXNIAA0HFRiOqYmHGD7HNKl67iYmAX' |
| b'd/Jv8GfZL/ykZstP6Ow1/ByP1ZKvrZvg2iXjC686hZXiMJLqmp0sIqLire82oW+8' |
| b'XFc1uyr1j20m+NI5Siy0G3RbfPXrVKyXIgAYPW12+a/BXR9SrqYJYcWwuOGbHZCM' |
| b'pwIDAQAB\n' |
| b'-----END PUBLIC KEY-----\n') |
| |
| def test_valid_nistp256_key(self): |
| keys.import_ecdsa_public_key(self.valid_nistp256_pem_bytes) |
| |
| def test_tampered_nistp256_key(self): |
| with self.assertRaises(ValueError): |
| keys.import_ecdsa_public_key(self.tampered_nistp256_pem_bytes) |
| |
| def test_non_ec_key(self): |
| with self.assertRaises(TypeError): |
| keys.import_ecdsa_public_key(self.rsa_2048_pem_bytes) |
| |
| def test_wrong_curve(self): |
| with self.assertRaises(TypeError): |
| keys.import_ecdsa_public_key(self.valid_secp384r1_pem_bytes) |
| |
| |
| class SignatureVerificationTest(unittest.TestCase): |
| """ECDSA signing and verification test.""" |
| def setUp(self): |
| # Generated with: |
| # $> openssl ecparam -name prime256v1 -genkey -noout -out priv.pem |
| # $> openssl ec -in priv.pem -pubout -out pub.pem |
| # $> cat priv.pem pub.pem |
| self.private_key_pem = ( |
| b'-----BEGIN EC PRIVATE KEY-----\n' |
| b'MHcCAQEEIH9u1n4qAT59f7KRRl/ZB0Y/BUfS4blba+LONlF4s3ltoAoGCCqGSM49' |
| b'AwEHoUQDQgAEgKf3kY9Hi3hxIyqm2EkfqQvJkCijjlJSmEAJ1oAp0Godi5x2af+m' |
| b'cSNuBjpRcC8iW8x1/gizqyWlfAVrZV0XdA==\n' |
| b'-----END EC PRIVATE KEY-----\n') |
| self.public_key_pem = ( |
| b'-----BEGIN PUBLIC KEY-----\n' |
| b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgKf3kY9Hi3hxIyqm2EkfqQvJkCij' |
| b'jlJSmEAJ1oAp0Godi5x2af+mcSNuBjpRcC8iW8x1/gizqyWlfAVrZV0XdA==\n' |
| b'-----END PUBLIC KEY-----\n') |
| |
| self.message = b'Hello Pigweed!' |
| self.tampered_message = b'Hell0 Pigweed!' |
| |
| def test_good_signature(self): |
| sig = keys.create_ecdsa_signature(self.message, self.private_key_pem) |
| self.assertTrue( |
| keys.verify_ecdsa_signature( |
| sig.sig, self.message, |
| keys.import_ecdsa_public_key(self.public_key_pem).key)) |
| |
| def test_tampered_message(self): |
| sig = keys.create_ecdsa_signature(self.message, self.private_key_pem) |
| self.assertFalse( |
| keys.verify_ecdsa_signature( |
| sig.sig, self.tampered_message, |
| keys.import_ecdsa_public_key(self.public_key_pem).key)) |
| |
| def test_tampered_signature(self): |
| sig = keys.create_ecdsa_signature(self.message, self.private_key_pem) |
| tampered_sig = bytearray(sig.sig) |
| tampered_sig[0] ^= 1 |
| self.assertFalse( |
| keys.verify_ecdsa_signature( |
| tampered_sig, self.message, |
| keys.import_ecdsa_public_key(self.public_key_pem).key)) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |