Skip to content

Commit 1169693

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 2c400dc commit 1169693

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
#
@@ -24,6 +24,7 @@
2424
import sys
2525
import base64
2626
from imgtool import image, imgtool_version
27+
from imgtool.keys.general import key_types_matching
2728
from imgtool.version import decode_version
2829
from imgtool.dumpinfo import dump_imginfo
2930
from .keys import (
@@ -87,12 +88,29 @@ def save_signature(sigfile, sig):
8788

8889

8990
def load_key(keyfile):
90-
# TODO: better handling of invalid pass-phrase
91-
key = keys.load(keyfile)
91+
try:
92+
key = keys.load(keyfile)
93+
except FileNotFoundError as e:
94+
print("Key File Not Found in the path: " + keyfile)
95+
raise e
9296
if key is not None:
9397
return key
98+
99+
# Key is password protected
94100
passwd = getpass.getpass("Enter key passphrase: ").encode('utf-8')
95-
return keys.load(keyfile, passwd)
101+
try:
102+
key = keys.load(keyfile, passwd)
103+
except Exception as e:
104+
msg = str(e)
105+
if "Invalid passphrase" in msg:
106+
print(msg)
107+
exit(1)
108+
else:
109+
raise e
110+
if key is None:
111+
print("Could not load key for unknown error")
112+
exit(1)
113+
return key
96114

97115

98116
def get_password():
@@ -145,9 +163,8 @@ def getpub(key, encoding, lang, output):
145163

146164
if not output:
147165
output = sys.stdout
148-
if key is None:
149-
print("Invalid passphrase")
150-
elif lang == 'c' or encoding == 'lang-c':
166+
167+
if lang == 'c' or encoding == 'lang-c':
151168
key.emit_c_public(file=output)
152169
elif lang == 'rust' or encoding == 'lang-rust':
153170
key.emit_rust_public(file=output)
@@ -177,9 +194,8 @@ def getpubhash(key, output, encoding):
177194

178195
if not output:
179196
output = sys.stdout
180-
if key is None:
181-
print("Invalid passphrase")
182-
elif encoding == 'lang-c':
197+
198+
if encoding == 'lang-c':
183199
key.emit_c_public_hash(file=output)
184200
elif encoding == 'raw':
185201
key.emit_raw_public_hash(file=output)
@@ -200,8 +216,6 @@ def getpubhash(key, output, encoding):
200216
@click.command(help='Dump private key from keypair')
201217
def getpriv(key, minimal, format):
202218
key = load_key(key)
203-
if key is None:
204-
print("Invalid passphrase")
205219
try:
206220
key.emit_private(minimal, format)
207221
except (RSAUsageError, ECDSAUsageError, Ed25519UsageError,
@@ -430,16 +444,9 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
430444
img.load(infile)
431445
key = load_key(key) if key else None
432446
enckey = load_key(encrypt) if encrypt else None
433-
if enckey and key:
434-
if ((isinstance(key, keys.ECDSA256P1) and
435-
not isinstance(enckey, keys.ECDSA256P1Public))
436-
or (isinstance(key, keys.ECDSA384P1) and
437-
not isinstance(enckey, keys.ECDSA384P1Public))
438-
or (isinstance(key, keys.RSA) and
439-
not isinstance(enckey, keys.RSAPublic))):
440-
# FIXME
441-
raise click.UsageError("Signing and encryption must use the same "
442-
"type of key")
447+
if enckey and key and not key_types_matching(key, enckey):
448+
raise click.UsageError("Encryption must use the public pair of the "
449+
"same type of key used for signing")
443450

444451
if pad_sig and hasattr(key, 'pad_sig'):
445452
key.pad_sig = True

0 commit comments

Comments
 (0)