Skip to content

Commit

Permalink
espsecure: Add ECDSA signature support
Browse files Browse the repository at this point in the history
  • Loading branch information
projectgus committed Nov 4, 2016
1 parent 95dae16 commit 68ed7c7
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 4 deletions.
74 changes: 71 additions & 3 deletions espsecure.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,28 @@
import sys
import os
import hashlib
import struct
import pyaes
import ecdsa

def get_chunks(source, chunk_len):
""" Returns an iterator over 'chunk_len' chunks of 'source' """
return (source[i: i+chunk_len] for i in range(0, len(source), chunk_len))

def generate_key(args):
""" Save 32 random bytes for use as a pre-generated secure bootloader siging key """
if os.path.exists(args.keyfile):
raise FatalError("Output key file %s already exists." % args.keyfile)
with open(args.keyfile, "wb") as f:
# Python docs say os.urandom is "suitable for cryptographic use"
f.write(os.urandom(32))
print "Private secure bootloader key written to %s" % args.keyfile


def digest_secure_bootloader(args):
""" Calculate the digest of a bootloader image, in the same way the hardware
secure boot engine would do so. Can be used with a pre-loaded key to update a
secure bootloader. """
if args.iv is not None:
print "WARNING: --iv argument is for TESTING PURPOSES ONLY"
iv = args.iv.read(128)
Expand Down Expand Up @@ -79,6 +86,51 @@ def digest_secure_bootloader(args):
print "digest+image written to %s" % args.output


def generate_signing_key(args):
""" Generate an ECDSA signing key for signing secure boot images (post-bootloader) """
if os.path.exists(args.keyfile):
raise esptool.FatalError("ERROR: Key file %s already exists" % args.keyfile)
sk = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p)
with open(args.keyfile, "wb") as f:
f.write(sk.to_pem())
print("ECDSA NIST256p private key in PEM format written to %s" % args.keyfile)


def _load_key(args):
sk = ecdsa.SigningKey.from_pem(args.keyfile.read())
if sk.curve != ecdsa.NIST256p:
raise esptool.FatalError("Signing key uses incorrect curve. ESP32 Secure Boot only supports NIST256p (openssl calls this curve 'prime256v1")
return sk

def sign_data(args):
""" Sign a data file with a ECDSA private key, append binary signature to file contents """
sk = _load_key(args)

# calculate SHA512 digest of binary & sign it
digest = hashlib.sha512()
binary_content = args.datafile.read()
digest.update(binary_content)
signature = sk.sign_deterministic(digest.digest())

if args.output is None or os.path.abspath(args.output) == os.path.abspath(args.datafile.name): # append signature to input file
args.datafile.close()
outfile = open(args.datafile.name, "ab")
else: # write file & signature to new file
outfile = open(args.output, "wb")
outfile.write(binary_content)
outfile.write(struct.pack("I", 0)) # Version indicator, allow for different curves/formats later
outfile.write(signature)
outfile.close()
print "Signed %d bytes of data from %s with key %s" % (len(binary_content), args.datafile.name, args.keyfile.name)


def extract_public_key(args):
""" Load an ECDSA private key and extract the embedded public key as raw binary data. """
sk = _load_key(args)
vk = sk.get_verifying_key()
args.public_keyfile.write(vk.to_string())
print "%s public key extracted to %s" % (args.keyfile.name, args.public_keyfile.name)

def main():
parser = argparse.ArgumentParser(description='espsecure.py v%s - ESP32 Secure Boot & Flash Encryption tool' % esptool.__version__, prog='espsecure')

Expand All @@ -91,10 +143,26 @@ def main():

p = subparsers.add_parser('digest_secure_bootloader',
help='Take a bootloader binary image and a secure boot key, and output a combined digest+binary suitable for flashing along with the precalculated secure boot key.')
p.add_argument('--keyfile', '-k', help="256 bit key for secure boot digest.", type=file, required=True)
p.add_argument('--keyfile', '-k', help="256 bit key for secure boot digest.", type=argparse.FileType('rb'), required=True)
p.add_argument('--output', '-o', help="Output file for signed digest image.")
p.add_argument('--iv', help="128 byte IV file. Supply a file for testing purposes only, if not supplied an IV will be randomly generated.", type=file)
p.add_argument('image', help="Bootloader image file to calculate digest from", type=file)
p.add_argument('--iv', help="128 byte IV file. Supply a file for testing purposes only, if not supplied an IV will be randomly generated.", type=argparse.FileType('rb'))
p.add_argument('image', help="Bootloader image file to calculate digest from", type=argparse.FileType('rb'))

p = subparsers.add_parser('generate_signing_key',
help='Generate a private key for signing secure boot images. Key file is generated in PEM format, and contains a ECDSA NIST256p private key and matching public key.')
p.add_argument('keyfile', help="Filename for private key file (embedded public key)")

p = subparsers.add_parser('sign_data',
help='Sign a data file for use with secure boot. Signing algorithm is determinsitic ECDSA w/ SHA-512.')
p.add_argument('--keyfile', '-k', help="Private key file for signing. Key is in PEM format, ECDSA NIST256p curve. generate_signing_key command can be used to generate a suitable signing key.", type=argparse.FileType('rb'), required=True)
p.add_argument('--output', '-o', help="Output file for signed digest image. Default is to append signature to existing file.")
p.add_argument('datafile', help="Data file to sign.", 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'),
required=True)
p.add_argument('public_keyfile', help="File to save new public key) into", type=argparse.FileType('wb'))

args = parser.parse_args()
print 'espsecure.py v%s' % esptool.__version__
Expand Down
1 change: 0 additions & 1 deletion esptool.py
Original file line number Diff line number Diff line change
Expand Up @@ -1340,7 +1340,6 @@ def div_roundup(a, b):
"""
return (int(a) + int(b) - 1) / int(b)


def align_file_position(f, size):
""" Align the position in the file to the next block of specified size """
align = (size - 1) - (f.tell() % size)
Expand Down

0 comments on commit 68ed7c7

Please sign in to comment.