blob: e81f6f6475694ff49e36624f6cf8103c5f57c286 [file] [log] [blame]
David Brown23f91ad2017-05-16 11:38:17 -06001"""
2Cryptographic key management for imgtool.
3"""
4
5from Crypto.Hash import SHA256
6from Crypto.PublicKey import RSA
David Brown07916c32017-05-31 13:38:31 -06007from Crypto.Signature import PKCS1_v1_5, PKCS1_PSS
David Brown23f91ad2017-05-16 11:38:17 -06008from ecdsa import SigningKey, NIST256p, util
David Brown07916c32017-05-31 13:38:31 -06009import hashlib
David Brown23f91ad2017-05-16 11:38:17 -060010from pyasn1.type import namedtype, univ
11from pyasn1.codec.der.encoder import encode
12
David Brown07916c32017-05-31 13:38:31 -060013# By default, we use RSA-PSS (PKCS 2.1). That can be overridden on
14# the command line to support the older (less secure) PKCS1.5
15sign_rsa_pss = True
16
David Brown23f91ad2017-05-16 11:38:17 -060017AUTOGEN_MESSAGE = "/* Autogenerated by imgtool.py, do not edit. */"
18
19class RSAPublicKey(univ.Sequence):
20 componentType = namedtype.NamedTypes(
21 namedtype.NamedType('modulus', univ.Integer()),
22 namedtype.NamedType('publicExponent', univ.Integer()))
23
24class RSA2048():
25 def __init__(self, key):
26 """Construct an RSA2048 key with the given key data"""
27 self.key = key
28
29 @staticmethod
30 def generate():
31 return RSA2048(RSA.generate(2048))
32
33 def export_private(self, path):
34 with open(path, 'wb') as f:
35 f.write(self.key.exportKey('PEM'))
36
37 def emit_c(self):
38 node = RSAPublicKey()
39 node['modulus'] = self.key.n
40 node['publicExponent'] = self.key.e
41 print(AUTOGEN_MESSAGE)
42 print("const unsigned char rsa_pub_key[] = {", end='')
43 encoded = bytearray(encode(node))
44 for count, b in enumerate(encoded):
45 if count % 8 == 0:
46 print("\n\t", end='')
47 else:
48 print(" ", end='')
49 print("0x{:02x},".format(b), end='')
50 print("\n};")
51 print("const unsigned int rsa_pub_key_len = {};".format(len(encoded)))
52
53 def sig_type(self):
54 """Return the type of this signature (as a string)"""
David Brown07916c32017-05-31 13:38:31 -060055 if sign_rsa_pss:
56 return "PKCS1_PSS_RSA2048_SHA256"
57 else:
58 return "PKCS15_RSA2048_SHA256"
David Brown23f91ad2017-05-16 11:38:17 -060059
60 def sig_len(self):
61 return 256
62
63 def sig_tlv(self):
64 return "RSA2048"
65
66 def sign(self, payload):
67 sha = SHA256.new(payload)
David Brown07916c32017-05-31 13:38:31 -060068 if sign_rsa_pss:
69 signer = PKCS1_PSS.new(self.key)
70 else:
71 signer = PKCS1_v1_5.new(self.key)
David Brown23f91ad2017-05-16 11:38:17 -060072 signature = signer.sign(sha)
73 assert len(signature) == self.sig_len()
74 return signature
75
76class ECDSA256P1():
77 def __init__(self, key):
78 """Construct an ECDSA P-256 private key"""
79 self.key = key
80
81 @staticmethod
82 def generate():
83 return ECDSA256P1(SigningKey.generate(curve=NIST256p))
84
85 def export_private(self, path):
86 with open(path, 'wb') as f:
David Brown8ae61c02017-07-27 10:23:02 -060087 f.write(self.key.to_pem())
David Brown23f91ad2017-05-16 11:38:17 -060088
89 def emit_c(self):
90 vk = self.key.get_verifying_key()
91 print(AUTOGEN_MESSAGE)
92 print("const unsigned char ecdsa_pub_key[] = {", end='')
93 encoded = bytes(vk.to_der())
94 for count, b in enumerate(encoded):
95 if count % 8 == 0:
96 print("\n\t", end='')
97 else:
98 print(" ", end='')
99 print("0x{:02x},".format(b), end='')
100 print("\n};")
101 print("const unsigned int ecdsa_pub_key_len = {};".format(len(encoded)))
102
103 def sign(self, payload):
104 # To make this fixed length, possibly pad with zeros.
105 sig = self.key.sign(payload, hashfunc=hashlib.sha256, sigencode=util.sigencode_der)
106 sig += b'\000' * (self.sig_len() - len(sig))
107 return sig
108
109 def sig_len(self):
110 # The DER encoding depends on the high bit, and can be
111 # anywhere from 70 to 72 bytes. Because we have to fill in
112 # the length field before computing the signature, however,
113 # we'll give the largest, and the sig checking code will allow
114 # for it to be up to two bytes larger than the actual
115 # signature.
116 return 72
117
118 def sig_type(self):
119 """Return the type of this signature (as a string)"""
120 return "ECDSA256_SHA256"
121
122 def sig_tlv(self):
123 return "ECDSA256"
124
125def load(path):
126 with open(path, 'rb') as f:
127 pem = f.read()
128 try:
129 key = RSA.importKey(pem)
130 if key.n.bit_length() != 2048:
131 raise Exception("Unsupported RSA bit length, only 2048 supported")
132 return RSA2048(key)
133 except ValueError:
134 key = SigningKey.from_pem(pem)
135 if key.curve.name != 'NIST256p':
136 raise Exception("Unsupported ECDSA curve")
137 return ECDSA256P1(key)