Skip to content

Commit 4824e36

Browse files
committed
imgtool: Improve key type checks and invalid pass-phrase handling
Key type checking moved to separate function and added support for ed25519, enckey is enforced to be a public key Invalid pass-phrase was not reachable in methods as in that case an exception raised instead of returning "None". load_key function improved by adding handling for invalid pass-phrase and FileNotFound exception. Signed-off-by: Rustam Ismayilov <[email protected]> Change-Id: I0caa0a6100fc95c8339403e991d6de0337c7a725
1 parent b95b3b5 commit 4824e36

File tree

3 files changed

+83
-56
lines changed

3 files changed

+83
-56
lines changed

scripts/imgtool/keys/__init__.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Copyright 2017 Linaro Limited
2-
# Copyright 2023 Arm Limited
2+
# Copyright 2023-2024 Arm Limited
33
#
44
# SPDX-License-Identifier: Apache-2.0
55
#
@@ -58,14 +58,24 @@ def load(path, passwd=None):
5858
except TypeError as e:
5959
msg = str(e)
6060
if "private key is encrypted" in msg:
61+
print(msg)
6162
return None
6263
raise e
63-
except ValueError:
64+
except ValueError as e:
65+
msg1 = str(e)
6466
# This seems to happen if the key is a public key, let's try
6567
# loading it as a public key.
66-
pk = serialization.load_pem_public_key(
68+
try:
69+
pk = serialization.load_pem_public_key(
6770
raw_pem,
6871
backend=default_backend())
72+
except ValueError as e:
73+
# If loading as public key also fails, that indicates wrong
74+
# passphrase input
75+
msg2 = str(e)
76+
if ("password may be incorrect" in msg1 and
77+
"Are you sure this is a public key" in msg2):
78+
raise Exception("Invalid passphrase")
6979

7080
if isinstance(pk, RSAPrivateKey):
7181
if pk.key_size not in RSA_KEY_SIZES:

scripts/imgtool/keys/general.py

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,25 @@
22

33
# SPDX-License-Identifier: Apache-2.0
44

5-
import binascii
6-
import io
75
import os
86
import sys
7+
8+
from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes, PublicKeyTypes
99
from cryptography.hazmat.primitives.hashes import Hash, SHA256
1010

11+
from imgtool import keys
12+
1113
AUTOGEN_MESSAGE = "/* Autogenerated by imgtool.py, do not edit. */"
1214

1315

16+
def key_types_matching(key: PrivateKeyTypes, enckey: PublicKeyTypes):
17+
type_dict = {keys.ECDSA256P1: keys.ECDSA256P1Public,
18+
keys.ECDSA384P1: keys.ECDSA384P1Public,
19+
keys.Ed25519: keys.X25519Public,
20+
keys.RSA: keys.RSAPublic}
21+
return type_dict[type(key)] == type(enckey)
22+
23+
1424
class FileHandler(object):
1525
def __init__(self, file, *args, **kwargs):
1626
self.file_in = file
@@ -34,7 +44,7 @@ def _emit(self, header, trailer, encoded_bytes, indent, file=sys.stdout,
3444
len_format=None):
3545
with FileHandler(file, 'w') as file:
3646
self._emit_to_output(header, trailer, encoded_bytes, indent,
37-
file, len_format)
47+
file, len_format)
3848

3949
def _emit_to_output(self, header, trailer, encoded_bytes, indent, file,
4050
len_format):
@@ -62,27 +72,27 @@ def _emit_raw(self, encoded_bytes, file):
6272

6373
def emit_c_public(self, file=sys.stdout):
6474
self._emit(
65-
header="const unsigned char {}_pub_key[] = {{"
66-
.format(self.shortname()),
67-
trailer="};",
68-
encoded_bytes=self.get_public_bytes(),
69-
indent=" ",
70-
len_format="const unsigned int {}_pub_key_len = {{}};"
71-
.format(self.shortname()),
72-
file=file)
75+
header="const unsigned char {}_pub_key[] = {{"
76+
.format(self.shortname()),
77+
trailer="};",
78+
encoded_bytes=self.get_public_bytes(),
79+
indent=" ",
80+
len_format="const unsigned int {}_pub_key_len = {{}};"
81+
.format(self.shortname()),
82+
file=file)
7383

7484
def emit_c_public_hash(self, file=sys.stdout):
7585
digest = Hash(SHA256())
7686
digest.update(self.get_public_bytes())
7787
self._emit(
78-
header="const unsigned char {}_pub_key_hash[] = {{"
79-
.format(self.shortname()),
80-
trailer="};",
81-
encoded_bytes=digest.finalize(),
82-
indent=" ",
83-
len_format="const unsigned int {}_pub_key_hash_len = {{}};"
84-
.format(self.shortname()),
85-
file=file)
88+
header="const unsigned char {}_pub_key_hash[] = {{"
89+
.format(self.shortname()),
90+
trailer="};",
91+
encoded_bytes=digest.finalize(),
92+
indent=" ",
93+
len_format="const unsigned int {}_pub_key_hash_len = {{}};"
94+
.format(self.shortname()),
95+
file=file)
8696

8797
def emit_raw_public(self, file=sys.stdout):
8898
self._emit_raw(self.get_public_bytes(), file=file)
@@ -94,22 +104,22 @@ def emit_raw_public_hash(self, file=sys.stdout):
94104

95105
def emit_rust_public(self, file=sys.stdout):
96106
self._emit(
97-
header="static {}_PUB_KEY: &[u8] = &["
98-
.format(self.shortname().upper()),
99-
trailer="];",
100-
encoded_bytes=self.get_public_bytes(),
101-
indent=" ",
102-
file=file)
107+
header="static {}_PUB_KEY: &[u8] = &["
108+
.format(self.shortname().upper()),
109+
trailer="];",
110+
encoded_bytes=self.get_public_bytes(),
111+
indent=" ",
112+
file=file)
103113

104114
def emit_public_pem(self, file=sys.stdout):
105115
with FileHandler(file, 'w') as file:
106116
print(str(self.get_public_pem(), 'utf-8'), file=file, end='')
107117

108118
def emit_private(self, minimal, format, file=sys.stdout):
109119
self._emit(
110-
header="const unsigned char enc_priv_key[] = {",
111-
trailer="};",
112-
encoded_bytes=self.get_private_bytes(minimal, format),
113-
indent=" ",
114-
len_format="const unsigned int enc_priv_key_len = {};",
115-
file=file)
120+
header="const unsigned char enc_priv_key[] = {",
121+
trailer="};",
122+
encoded_bytes=self.get_private_bytes(minimal, format),
123+
indent=" ",
124+
len_format="const unsigned int enc_priv_key_len = {};",
125+
file=file)

scripts/imgtool/main.py

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#! /usr/bin/env python3
22
#
33
# Copyright 2017-2020 Linaro Limited
4-
# Copyright 2019-2023 Arm Limited
4+
# Copyright 2019-2024 Arm Limited
55
#
66
# SPDX-License-Identifier: Apache-2.0
77
#
@@ -28,6 +28,7 @@
2828
import hashlib
2929
import base64
3030
from imgtool import image, imgtool_version
31+
from imgtool.keys.general import key_types_matching
3132
from imgtool.version import decode_version
3233
from imgtool.dumpinfo import dump_imginfo
3334
from .keys import (
@@ -99,12 +100,29 @@ def save_signature(sigfile, sig):
99100

100101

101102
def load_key(keyfile):
102-
# TODO: better handling of invalid pass-phrase
103-
key = keys.load(keyfile)
103+
try:
104+
key = keys.load(keyfile)
105+
except FileNotFoundError as e:
106+
print("Key File Not Found in the path: " + keyfile)
107+
raise e
104108
if key is not None:
105109
return key
110+
111+
# Key is password protected
106112
passwd = getpass.getpass("Enter key passphrase: ").encode('utf-8')
107-
return keys.load(keyfile, passwd)
113+
try:
114+
key = keys.load(keyfile, passwd)
115+
except Exception as e:
116+
msg = str(e)
117+
if "Invalid passphrase" in msg:
118+
print(msg)
119+
exit(1)
120+
else:
121+
raise e
122+
if key is None:
123+
print("Could not load key for unknown error")
124+
exit(1)
125+
return key
108126

109127

110128
def get_password():
@@ -157,9 +175,8 @@ def getpub(key, encoding, lang, output):
157175

158176
if not output:
159177
output = sys.stdout
160-
if key is None:
161-
print("Invalid passphrase")
162-
elif lang == 'c' or encoding == 'lang-c':
178+
179+
if lang == 'c' or encoding == 'lang-c':
163180
key.emit_c_public(file=output)
164181
elif lang == 'rust' or encoding == 'lang-rust':
165182
key.emit_rust_public(file=output)
@@ -189,9 +206,8 @@ def getpubhash(key, output, encoding):
189206

190207
if not output:
191208
output = sys.stdout
192-
if key is None:
193-
print("Invalid passphrase")
194-
elif encoding == 'lang-c':
209+
210+
if encoding == 'lang-c':
195211
key.emit_c_public_hash(file=output)
196212
elif encoding == 'raw':
197213
key.emit_raw_public_hash(file=output)
@@ -212,8 +228,6 @@ def getpubhash(key, output, encoding):
212228
@click.command(help='Dump private key from keypair')
213229
def getpriv(key, minimal, format):
214230
key = load_key(key)
215-
if key is None:
216-
print("Invalid passphrase")
217231
try:
218232
key.emit_private(minimal, format)
219233
except (RSAUsageError, ECDSAUsageError, Ed25519UsageError,
@@ -460,16 +474,9 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
460474
img.load(infile)
461475
key = load_key(key) if key else None
462476
enckey = load_key(encrypt) if encrypt else None
463-
if enckey and key:
464-
if ((isinstance(key, keys.ECDSA256P1) and
465-
not isinstance(enckey, keys.ECDSA256P1Public))
466-
or (isinstance(key, keys.ECDSA384P1) and
467-
not isinstance(enckey, keys.ECDSA384P1Public))
468-
or (isinstance(key, keys.RSA) and
469-
not isinstance(enckey, keys.RSAPublic))):
470-
# FIXME
471-
raise click.UsageError("Signing and encryption must use the same "
472-
"type of key")
477+
if enckey and key and not key_types_matching(key, enckey):
478+
raise click.UsageError("Encryption must use the public pair of the "
479+
"same type of key used for signing")
473480

474481
if pad_sig and hasattr(key, 'pad_sig'):
475482
key.pad_sig = True

0 commit comments

Comments
 (0)