Skip to content

Commit

Permalink
espsecure: Add support and tests for using verify_signature with bina…
Browse files Browse the repository at this point in the history
…ry public key file

Previously, keyfile could only be in PEM format

Closes #357
  • Loading branch information
projectgus committed Dec 20, 2018
1 parent 955ee9b commit d978421
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 24 deletions.
20 changes: 13 additions & 7 deletions espsecure.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,16 @@ def sign_data(args):
def verify_signature(args):
""" Verify a previously signed binary image, using the ECDSA public key """
key_data = args.keyfile.read()
try:
vk = ecdsa.VerifyingKey.from_pem(key_data)
except ecdsa.der.UnexpectedDER:
if b"-BEGIN EC PRIVATE KEY" in key_data:
sk = ecdsa.SigningKey.from_pem(key_data)
vk = sk.get_verifying_key()
elif b"-BEGIN PUBLIC KEY" in key_data:
vk = ecdsa.VerifyingKey.from_pem(key_data)
elif len(key_data) == 64:
vk = ecdsa.VerifyingKey.from_string(key_data,
curve=ecdsa.NIST256p)
else:
raise esptool.FatalError("Verification key does not appear to be an EC key in PEM format or binary EC public key data. Unsupported")

if vk.curve != ecdsa.NIST256p:
raise esptool.FatalError("Public key uses incorrect curve. ESP32 Secure Boot only supports NIST256p (openssl calls this curve 'prime256v1")
Expand Down Expand Up @@ -363,19 +368,20 @@ def main():

p = subparsers.add_parser('verify_signature',
help='Verify a data file previously signed by "sign_data", using the public key.')
p.add_argument('--keyfile', '-k', help="Public key file for verification. Can be the private key file (public key is embedded).",
p.add_argument('--keyfile', '-k', help="Public key file for verification. Can be private or public key in PEM format, " +
"or a binary public key produced by extract_public_key command.",
type=argparse.FileType('rb'), required=True)
p.add_argument('datafile', help="Signed data file to verify signature.", type=argparse.FileType('rb'))

p = subparsers.add_parser('extract_public_key',
help='Extract the public verification key for signatures, save it as a raw binary file.')
p.add_argument('--keyfile', '-k', help="Private key file to extract the public verification key from.", type=argparse.FileType('rb'),
p.add_argument('--keyfile', '-k', help="Private key file (PEM format) to extract the public verification key from.", type=argparse.FileType('rb'),
required=True)
p.add_argument('public_keyfile', help="File to save new public key) into", type=argparse.FileType('wb'))
p.add_argument('public_keyfile', help="File to save new public key into", type=argparse.FileType('wb'))

p = subparsers.add_parser('digest_private_key', help='Generate an SHA-256 digest of the private signing key. ' +
'This can be used as a reproducible secure bootloader or flash encryption key.')
p.add_argument('--keyfile', '-k', help="Private key file to generate a digest from.", type=argparse.FileType('rb'),
p.add_argument('--keyfile', '-k', help="Private key file (PEM format) to generate a digest from.", type=argparse.FileType('rb'),
required=True)
p.add_argument('--keylen', '-l', help="Length of private key digest file to generate (in bits).",
choices=['192','256'], default='256')
Expand Down
5 changes: 5 additions & 0 deletions test/ecdsa_secure_boot_signing_key2.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIINRYjKaXM0dQJn4FLwoHYj3ovmpRee7UWv8ksQ9IS2toAoGCCqGSM49
AwEHoUQDQgAEYWCmqAxxAmbr8cZk4AjTYkQJ0pCZOsESk2bPAe6lrzHrFKKR2t2W
ge+cNvXU2dYswEdgWr/fdnyAbHHEVEBSrA==
-----END EC PRIVATE KEY-----
5 changes: 5 additions & 0 deletions test/secure_images/ecdsa_secure_boot_signing_key2.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIP9iY7XY8g3qSaUkKwTbq6HEq/AwenIxrssLqXGTS0z3oAoGCCqGSM49
AwEHoUQDQgAEG+Ah4OAejTBYKQNvJkEOP9AifgulBMr4E9f+OqRU1Uno9Efi1kMc
fzwZyx0A4mib0HfLUg9JNh8dNrUxLeVb4Q==
-----END EC PRIVATE KEY-----
4 changes: 4 additions & 0 deletions test/secure_images/ecdsa_secure_boot_signing_pubkey.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENM762z/ushk+c0XOIYpi8wLSWuF5
COnU0VAQnt3spTSX6l3bpwfuppsemDdwy+aKbdgeyMYDxFbROLOPTRbYJw==
-----END PUBLIC KEY-----
4 changes: 4 additions & 0 deletions test/secure_images/ecdsa_secure_boot_signing_pubkey2.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEG+Ah4OAejTBYKQNvJkEOP9Aifgul
BMr4E9f+OqRU1Uno9Efi1kMcfzwZyx0A4mib0HfLUg9JNh8dNrUxLeVb4Q==
-----END PUBLIC KEY-----
97 changes: 80 additions & 17 deletions test/test_espsecure.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,20 @@ def run_espsecure(self, args):
print(e.output)
raise e

def _open(image_file):
return open(os.path.join('secure_images', image_file), 'rb')
def setUp(self):
self.cleanup_files = [] # keep a list of files _open()ed by each test case

class ESP32SecureBootloaderTests(unittest.TestCase):
def tearDown(self):
for f in self.cleanup_files:
f.close()

def _open(self, image_file):
f = open(os.path.join('secure_images', image_file), 'rb')
self.cleanup_files.append(f)
return f


class ESP32SecureBootloaderTests(EspSecureTestCase):

def test_digest_bootloader(self):
DBArgs = namedtuple('digest_bootloader_args', [
Expand All @@ -59,19 +69,23 @@ def test_digest_bootloader(self):
output_file = tempfile.NamedTemporaryFile(delete=False)
output_file.close()

args = DBArgs(_open('256bit_key.bin'),
args = DBArgs(self._open('256bit_key.bin'),
output_file.name,
_open('256bit_iv.bin'),
_open('bootloader.bin'))
self._open('256bit_iv.bin'),
self._open('bootloader.bin'))
espsecure.digest_secure_bootloader(args)

with open(output_file.name, 'rb') as of:
with _open('bootloader_digested.bin') as ef:
with self._open('bootloader_digested.bin') as ef:
self.assertEqual(ef.read(), of.read())
finally:
os.unlink(output_file.name)

class ECDSASigningTests(unittest.TestCase):
class ECDSASigningTests(EspSecureTestCase):

VerifyArgs = namedtuple('verify_signature_args', [
'keyfile',
'datafile' ])

def test_sign_data(self):
SignArgs = namedtuple('sign_data_args', [
Expand All @@ -85,27 +99,76 @@ def test_sign_data(self):

# Note: signing bootloader is not actually needed
# for ESP32, it's just a handy file to sign
args = SignArgs(_open('ecdsa_secure_boot_signing_key.pem'),
args = SignArgs(self._open('ecdsa_secure_boot_signing_key.pem'),
output_file.name,
_open('bootloader.bin'))
self._open('bootloader.bin'))
espsecure.sign_data(args)

with open(output_file.name, 'rb') as of:
with _open('bootloader_signed.bin') as ef:
with self._open('bootloader_signed.bin') as ef:
self.assertEqual(ef.read(), of.read())

finally:
os.unlink(output_file.name)

def test_verify_signature(self):
VerifyArgs = namedtuple('verify_signature_args', [
'keyfile',
'datafile' ])
def test_verify_signature_signing_key(self):
# correct key
args = self.VerifyArgs(self._open('ecdsa_secure_boot_signing_key.pem'),
self._open('bootloader_signed.bin'))
espsecure.verify_signature(args)

args = VerifyArgs(_open('ecdsa_secure_boot_signing_key.pem'),
_open('bootloader_signed.bin'))
# wrong key
args = self.VerifyArgs(self._open('ecdsa_secure_boot_signing_key2.pem'),
self._open('bootloader_signed.bin'))
with self.assertRaises(esptool.FatalError) as cm:
espsecure.verify_signature(args)
self.assertIn("Signature is not valid", str(cm.exception))

def test_verify_signature_public_key(self):
# correct key
args = self.VerifyArgs(self._open('ecdsa_secure_boot_signing_pubkey.pem'),
self._open('bootloader_signed.bin'))
espsecure.verify_signature(args)

# wrong key
args = self.VerifyArgs(self._open('ecdsa_secure_boot_signing_pubkey2.pem'),
self._open('bootloader_signed.bin'))
with self.assertRaises(esptool.FatalError) as cm:
espsecure.verify_signature(args)
self.assertIn("Signature is not valid", str(cm.exception))

def test_extract_binary_public_key(self):
ExtractKeyArgs = namedtuple('extract_public_key_args',
[ 'keyfile', 'public_keyfile' ])

pub_keyfile = tempfile.NamedTemporaryFile(delete=False)
pub_keyfile2 = tempfile.NamedTemporaryFile(delete=False)
try:
args = ExtractKeyArgs(self._open('ecdsa_secure_boot_signing_key.pem'),
pub_keyfile)
espsecure.extract_public_key(args)

args = ExtractKeyArgs(self._open('ecdsa_secure_boot_signing_key2.pem'),
pub_keyfile2)
espsecure.extract_public_key(args)

pub_keyfile.seek(0)
pub_keyfile2.seek(0)

# use correct extracted public key to verify
args = self.VerifyArgs(pub_keyfile, self._open('bootloader_signed.bin'))
espsecure.verify_signature(args)

# use wrong extracted public key to try and verify
args = self.VerifyArgs(pub_keyfile2, self._open('bootloader_signed.bin'))
with self.assertRaises(esptool.FatalError) as cm:
espsecure.verify_signature(args)
self.assertIn("Signature is not valid", str(cm.exception))

finally:
os.unlink(pub_keyfile.name)
os.unlink(pub_keyfile2.name)


if __name__ == '__main__':
print("Running espsecure tests...")
Expand Down

0 comments on commit d978421

Please sign in to comment.