Skip to content

Commit e8ab849

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 b7747f4 commit e8ab849

File tree

3 files changed

+68
-55
lines changed

3 files changed

+68
-55
lines changed

scripts/imgtool/keys/__init__.py

Lines changed: 5 additions & 2 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
#
@@ -60,7 +60,10 @@ def load(path, passwd=None):
6060
if "private key is encrypted" in msg:
6161
return None
6262
raise e
63-
except ValueError:
63+
except ValueError as e:
64+
msg = str(e)
65+
if "Bad decrypt. Incorrect password?" in msg:
66+
raise Exception("Invalid passphrase")
6467
# This seems to happen if the key is a public key, let's try
6568
# loading it as a public key.
6669
pk = serialization.load_pem_public_key(

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: 22 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,22 @@ 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
9498
passwd = getpass.getpass("Enter key passphrase: ").encode('utf-8')
95-
return keys.load(keyfile, passwd)
99+
try:
100+
key = keys.load(keyfile, passwd)
101+
except Exception as e:
102+
msg = str(e)
103+
if "Invalid passphrase" in msg:
104+
print(msg)
105+
exit(1)
106+
return key
96107

97108

98109
def get_password():
@@ -145,9 +156,8 @@ def getpub(key, encoding, lang, output):
145156

146157
if not output:
147158
output = sys.stdout
148-
if key is None:
149-
print("Invalid passphrase")
150-
elif lang == 'c' or encoding == 'lang-c':
159+
160+
if lang == 'c' or encoding == 'lang-c':
151161
key.emit_c_public(file=output)
152162
elif lang == 'rust' or encoding == 'lang-rust':
153163
key.emit_rust_public(file=output)
@@ -177,9 +187,8 @@ def getpubhash(key, output, encoding):
177187

178188
if not output:
179189
output = sys.stdout
180-
if key is None:
181-
print("Invalid passphrase")
182-
elif encoding == 'lang-c':
190+
191+
if encoding == 'lang-c':
183192
key.emit_c_public_hash(file=output)
184193
elif encoding == 'raw':
185194
key.emit_raw_public_hash(file=output)
@@ -200,8 +209,6 @@ def getpubhash(key, output, encoding):
200209
@click.command(help='Dump private key from keypair')
201210
def getpriv(key, minimal, format):
202211
key = load_key(key)
203-
if key is None:
204-
print("Invalid passphrase")
205212
try:
206213
key.emit_private(minimal, format)
207214
except (RSAUsageError, ECDSAUsageError, Ed25519UsageError,
@@ -427,16 +434,9 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
427434
img.load(infile)
428435
key = load_key(key) if key else None
429436
enckey = load_key(encrypt) if encrypt else None
430-
if enckey and key:
431-
if ((isinstance(key, keys.ECDSA256P1) and
432-
not isinstance(enckey, keys.ECDSA256P1Public))
433-
or (isinstance(key, keys.ECDSA384P1) and
434-
not isinstance(enckey, keys.ECDSA384P1Public))
435-
or (isinstance(key, keys.RSA) and
436-
not isinstance(enckey, keys.RSAPublic))):
437-
# FIXME
438-
raise click.UsageError("Signing and encryption must use the same "
439-
"type of key")
437+
if enckey and key and not key_types_matching(key, enckey):
438+
raise click.UsageError("Encryption must use the public pair of the "
439+
"same type of key used for signing")
440440

441441
if pad_sig and hasattr(key, 'pad_sig'):
442442
key.pad_sig = True

0 commit comments

Comments
 (0)