Skip to content

Commit c6e7e9b

Browse files
adeaarmutzig
authored andcommitted
imgtool: Improve ECDSA key generation
This patch improves the existing ECDSA key generation feature in the imgtool by: - Fix a bug in the 'minimal' representation of PKCS#8 keys where the resulting ASN.1 DER encoding is not compliant - Add the option to export ECDSA private keys in SEC1 format by providing a command line option -f or --format that can be 'openssl' (for SEC1 format) or 'pkcs8'. This format ends up in key encodings which are generally smaller than PKCS#8. Signed-off-by: Antonio de Angelis <[email protected]>
1 parent 284b8fe commit c6e7e9b

File tree

3 files changed

+54
-24
lines changed

3 files changed

+54
-24
lines changed

scripts/imgtool/keys/ecdsa.py

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
# SPDX-License-Identifier: Apache-2.0
6+
import os.path
67

78
from cryptography.hazmat.backends import default_backend
89
from cryptography.hazmat.primitives import serialization
@@ -102,37 +103,60 @@ def generate():
102103
def _get_public(self):
103104
return self.key.public_key()
104105

105-
def _build_minimal_ecdsa_privkey(self, der):
106+
def _build_minimal_ecdsa_privkey(self, der, format):
106107
'''
107108
Builds a new DER that only includes the EC private key, removing the
108109
public key that is added as an "optional" BITSTRING.
109110
'''
110-
offset_PUB = 68
111+
112+
if format == serialization.PrivateFormat.OpenSSH:
113+
print(os.path.basename(__file__) +
114+
': Warning: --minimal is supported only for PKCS8 '
115+
'or TraditionalOpenSSL formats')
116+
return bytearray(der)
117+
111118
EXCEPTION_TEXT = "Error parsing ecdsa key. Please submit an issue!"
112-
if der[offset_PUB] != 0xa1:
113-
raise ECDSAUsageError(EXCEPTION_TEXT)
114-
len_PUB = der[offset_PUB + 1]
115-
b = bytearray(der[:-offset_PUB])
116-
offset_SEQ = 29
117-
if b[offset_SEQ] != 0x30:
118-
raise ECDSAUsageError(EXCEPTION_TEXT)
119-
b[offset_SEQ + 1] -= len_PUB
120-
offset_OCT_STR = 27
121-
if b[offset_OCT_STR] != 0x04:
122-
raise ECDSAUsageError(EXCEPTION_TEXT)
123-
b[offset_OCT_STR + 1] -= len_PUB
124-
if b[0] != 0x30 or b[1] != 0x81:
125-
raise ECDSAUsageError(EXCEPTION_TEXT)
126-
b[2] -= len_PUB
119+
if format == serialization.PrivateFormat.PKCS8:
120+
offset_PUB = 68 # where the context specific TLV starts (tag 0xA1)
121+
if der[offset_PUB] != 0xa1:
122+
raise ECDSAUsageError(EXCEPTION_TEXT)
123+
len_PUB = der[offset_PUB + 1] + 2 # + 2 for 0xA1 0x44 bytes
124+
b = bytearray(der[:offset_PUB]) # remove the TLV with the PUB key
125+
offset_SEQ = 29
126+
if b[offset_SEQ] != 0x30:
127+
raise ECDSAUsageError(EXCEPTION_TEXT)
128+
b[offset_SEQ + 1] -= len_PUB
129+
offset_OCT_STR = 27
130+
if b[offset_OCT_STR] != 0x04:
131+
raise ECDSAUsageError(EXCEPTION_TEXT)
132+
b[offset_OCT_STR + 1] -= len_PUB
133+
if b[0] != 0x30 or b[1] != 0x81:
134+
raise ECDSAUsageError(EXCEPTION_TEXT)
135+
# as b[1] has bit7 set, the length is on b[2]
136+
b[2] -= len_PUB
137+
if b[2] < 0x80:
138+
del(b[1])
139+
140+
elif format == serialization.PrivateFormat.TraditionalOpenSSL:
141+
offset_PUB = 51
142+
if der[offset_PUB] != 0xA1:
143+
raise ECDSAUsageError(EXCEPTION_TEXT)
144+
len_PUB = der[offset_PUB + 1] + 2
145+
b = bytearray(der[0:offset_PUB])
146+
b[1] -= len_PUB
147+
127148
return b
128149

129-
def get_private_bytes(self, minimal):
150+
def get_private_bytes(self, minimal, format):
151+
formats = {'pkcs8': serialization.PrivateFormat.PKCS8,
152+
'openssl': serialization.PrivateFormat.TraditionalOpenSSL
153+
}
130154
priv = self.key.private_bytes(
131155
encoding=serialization.Encoding.DER,
132-
format=serialization.PrivateFormat.PKCS8,
156+
format=formats[format],
133157
encryption_algorithm=serialization.NoEncryption())
134158
if minimal:
135-
priv = self._build_minimal_ecdsa_privkey(priv)
159+
priv = self._build_minimal_ecdsa_privkey(priv, formats[format])
136160
return priv
137161

138162
def export_private(self, path, passwd=None):

scripts/imgtool/keys/general.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
AUTOGEN_MESSAGE = "/* Autogenerated by imgtool.py, do not edit. */"
88

9+
910
class KeyClass(object):
1011
def _emit(self, header, trailer, encoded_bytes, indent, file=sys.stdout, len_format=None):
1112
print(AUTOGEN_MESSAGE, file=file)
@@ -40,11 +41,11 @@ def emit_rust_public(self, file=sys.stdout):
4041
def emit_public_pem(self, file=sys.stdout):
4142
print(str(self.get_public_pem(), 'utf-8'), file=file, end='')
4243

43-
def emit_private(self, minimal, file=sys.stdout):
44+
def emit_private(self, minimal, format, file=sys.stdout):
4445
self._emit(
4546
header="const unsigned char enc_priv_key[] = {",
4647
trailer="};",
47-
encoded_bytes=self.get_private_bytes(minimal),
48+
encoded_bytes=self.get_private_bytes(minimal, format),
4849
indent=" ",
4950
len_format="const unsigned int enc_priv_key_len = {};",
5051
file=file)

scripts/imgtool/main.py

100755100644
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def gen_x25519(keyfile, passwd):
6969
'ed25519': gen_ed25519,
7070
'x25519': gen_x25519,
7171
}
72+
valid_formats = ['openssl', 'pkcs8']
7273

7374
def load_signature(sigfile):
7475
with open(sigfile, 'rb') as f:
@@ -150,13 +151,17 @@ def getpub(key, encoding, lang):
150151
'might require changes to the build config. Check the docs!'
151152
)
152153
@click.option('-k', '--key', metavar='filename', required=True)
154+
@click.option('-f', '--format',
155+
type=click.Choice(valid_formats),
156+
help='Valid formats: {}'.format(', '.join(valid_formats)),
157+
default='pkcs8')
153158
@click.command(help='Dump private key from keypair')
154-
def getpriv(key, minimal):
159+
def getpriv(key, minimal, format):
155160
key = load_key(key)
156161
if key is None:
157162
print("Invalid passphrase")
158163
try:
159-
key.emit_private(minimal)
164+
key.emit_private(minimal, format)
160165
except (RSAUsageError, ECDSAUsageError, Ed25519UsageError,
161166
X25519UsageError) as e:
162167
raise click.UsageError(e)

0 commit comments

Comments
 (0)