""" Cryptographic key management for imgtool. """ from Crypto.Hash import SHA256 from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5, PKCS1_PSS from ecdsa import SigningKey, NIST256p, util import hashlib from pyasn1.type import namedtype, univ from pyasn1.codec.der.encoder import encode # By default, we use RSA-PSS (PKCS 2.1). That can be overridden on # the command line to support the older (less secure) PKCS1.5 sign_rsa_pss = True AUTOGEN_MESSAGE = "/* Autogenerated by imgtool.py, do not edit. */" class RSAPublicKey(univ.Sequence): componentType = namedtype.NamedTypes( namedtype.NamedType('modulus', univ.Integer()), namedtype.NamedType('publicExponent', univ.Integer())) class RSA2048(): def __init__(self, key): """Construct an RSA2048 key with the given key data""" self.key = key @staticmethod def generate(): return RSA2048(RSA.generate(2048)) def export_private(self, path): with open(path, 'wb') as f: f.write(self.key.exportKey('PEM')) def emit_c(self): node = RSAPublicKey() node['modulus'] = self.key.n node['publicExponent'] = self.key.e print(AUTOGEN_MESSAGE) print("const unsigned char rsa_pub_key[] = {", end='') encoded = bytearray(encode(node)) for count, b in enumerate(encoded): if count % 8 == 0: print("\n\t", end='') else: print(" ", end='') print("0x{:02x},".format(b), end='') print("\n};") print("const unsigned int rsa_pub_key_len = {};".format(len(encoded))) def sig_type(self): """Return the type of this signature (as a string)""" if sign_rsa_pss: return "PKCS1_PSS_RSA2048_SHA256" else: return "PKCS15_RSA2048_SHA256" def sig_len(self): return 256 def sig_tlv(self): return "RSA2048" def sign(self, payload): sha = SHA256.new(payload) if sign_rsa_pss: signer = PKCS1_PSS.new(self.key) else: signer = PKCS1_v1_5.new(self.key) signature = signer.sign(sha) assert len(signature) == self.sig_len() return signature class ECDSA256P1(): def __init__(self, key): """Construct an ECDSA P-256 private key""" self.key = key @staticmethod def generate(): return ECDSA256P1(SigningKey.generate(curve=NIST256p)) def export_private(self, path): with open(path, 'wb') as f: f.write(key.to_pem()) def emit_c(self): vk = self.key.get_verifying_key() print(AUTOGEN_MESSAGE) print("const unsigned char ecdsa_pub_key[] = {", end='') encoded = bytes(vk.to_der()) for count, b in enumerate(encoded): if count % 8 == 0: print("\n\t", end='') else: print(" ", end='') print("0x{:02x},".format(b), end='') print("\n};") print("const unsigned int ecdsa_pub_key_len = {};".format(len(encoded))) def sign(self, payload): # To make this fixed length, possibly pad with zeros. sig = self.key.sign(payload, hashfunc=hashlib.sha256, sigencode=util.sigencode_der) sig += b'\000' * (self.sig_len() - len(sig)) return sig def sig_len(self): # The DER encoding depends on the high bit, and can be # anywhere from 70 to 72 bytes. Because we have to fill in # the length field before computing the signature, however, # we'll give the largest, and the sig checking code will allow # for it to be up to two bytes larger than the actual # signature. return 72 def sig_type(self): """Return the type of this signature (as a string)""" return "ECDSA256_SHA256" def sig_tlv(self): return "ECDSA256" def load(path): with open(path, 'rb') as f: pem = f.read() try: key = RSA.importKey(pem) if key.n.bit_length() != 2048: raise Exception("Unsupported RSA bit length, only 2048 supported") return RSA2048(key) except ValueError: key = SigningKey.from_pem(pem) if key.curve.name != 'NIST256p': raise Exception("Unsupported ECDSA curve") return ECDSA256P1(key)