Skip to content

Commit 4a5477a

Browse files
committed
Add new verify command
imgtool verify -k <some-key.(pub|sec)> <img-file> Allow imgtool to validate that an image has a valid sha256sum and that it was signed by the supplied key. NOTE: this does not yet support verifying encrypted images Signed-off-by: Fabio Utzig <[email protected]>
1 parent 05b594b commit 4a5477a

File tree

4 files changed

+92
-0
lines changed

4 files changed

+92
-0
lines changed

scripts/imgtool/image.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"""
2020

2121
from . import version as versmod
22+
from enum import Enum
2223
from intelhex import IntelHex
2324
import hashlib
2425
import struct
@@ -27,6 +28,7 @@
2728
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
2829
from cryptography.hazmat.backends import default_backend
2930
from cryptography.hazmat.primitives import hashes
31+
from cryptography.exceptions import InvalidSignature
3032

3133
IMAGE_MAGIC = 0x96f3b83d
3234
IMAGE_HEADER_SIZE = 32
@@ -55,6 +57,7 @@
5557
'DEPENDENCY': 0x40
5658
}
5759

60+
TLV_SIZE = 4
5861
TLV_INFO_SIZE = 4
5962
TLV_INFO_MAGIC = 0x6907
6063

@@ -69,6 +72,13 @@
6972
'big': '>'
7073
}
7174

75+
VerifyResult = Enum('VerifyResult',
76+
"""
77+
OK INVALID_MAGIC INVALID_TLV_INFO_MAGIC INVALID_HASH
78+
INVALID_SIGNATURE
79+
""")
80+
81+
7282
class TLV():
7383
def __init__(self, endian):
7484
self.buf = bytearray()
@@ -307,3 +317,47 @@ def pad_to(self, size):
307317
pbytes += b'\xff' * (tsize - len(boot_magic))
308318
pbytes += boot_magic
309319
self.payload += pbytes
320+
321+
@staticmethod
322+
def verify(imgfile, key):
323+
with open(imgfile, "rb") as f:
324+
b = f.read()
325+
326+
magic, _, header_size, _, img_size = struct.unpack('IIHHI', b[:16])
327+
if magic != IMAGE_MAGIC:
328+
return VerifyResult.INVALID_MAGIC
329+
330+
tlv_info = b[header_size+img_size:header_size+img_size+TLV_INFO_SIZE]
331+
magic, tlv_tot = struct.unpack('HH', tlv_info)
332+
if magic != TLV_INFO_MAGIC:
333+
return VerifyResult.INVALID_TLV_INFO_MAGIC
334+
335+
sha = hashlib.sha256()
336+
sha.update(b[:header_size+img_size])
337+
digest = sha.digest()
338+
339+
tlv_off = header_size + img_size
340+
tlv_end = tlv_off + tlv_tot
341+
tlv_off += TLV_INFO_SIZE # skip tlv info
342+
while tlv_off < tlv_end:
343+
tlv = b[tlv_off:tlv_off+TLV_SIZE]
344+
tlv_type, _, tlv_len = struct.unpack('BBH', tlv)
345+
if tlv_type == TLV_VALUES["SHA256"]:
346+
off = tlv_off + TLV_SIZE
347+
if digest == b[off:off+tlv_len]:
348+
if key is None:
349+
return VerifyResult.OK
350+
else:
351+
return VerifyResult.INVALID_HASH
352+
elif key is not None and tlv_type == TLV_VALUES[key.sig_tlv()]:
353+
off = tlv_off + TLV_SIZE
354+
tlv_sig = b[off:off+tlv_len]
355+
payload = b[:header_size+img_size]
356+
try:
357+
key.verify(tlv_sig, payload)
358+
return VerifyResult.OK
359+
except InvalidSignature:
360+
# continue to next TLV
361+
pass
362+
tlv_off += TLV_SIZE + tlv_len
363+
return VerifyResult.INVALID_SIGNATURE

scripts/imgtool/keys/ecdsa.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ def sig_len(self):
5757
# signature.
5858
return 72
5959

60+
def verify(self, signature, payload):
61+
k = self.key
62+
if isinstance(self.key, ec.EllipticCurvePrivateKey):
63+
k = self.key.public_key()
64+
return k.verify(signature=signature, data=payload,
65+
signature_algorithm=ec.ECDSA(SHA256()))
66+
67+
6068
class ECDSA256P1(ECDSA256P1Public):
6169
"""
6270
Wrapper around an ECDSA private key.

scripts/imgtool/keys/rsa.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ def sig_tlv(self):
6262
def sig_len(self):
6363
return self.key_size() / 8
6464

65+
def verify(self, signature, payload):
66+
k = self.key
67+
if isinstance(self.key, rsa.RSAPrivateKey):
68+
k = self.key.public_key()
69+
return k.verify(signature=signature, data=payload,
70+
padding=PSS(mgf=MGF1(SHA256()), salt_length=32),
71+
algorithm=SHA256())
72+
6573

6674
class RSA(RSAPublic):
6775
"""

scripts/imgtool/main.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import click
2020
import getpass
2121
import imgtool.keys as keys
22+
import sys
2223
from imgtool import image
2324
from imgtool.version import decode_version
2425

@@ -98,6 +99,26 @@ def getpub(key, lang):
9899
raise ValueError("BUG: should never get here!")
99100

100101

102+
@click.argument('imgfile')
103+
@click.option('-k', '--key', metavar='filename')
104+
@click.command(help="Check that signed image can be verified by given key")
105+
def verify(key, imgfile):
106+
key = load_key(key) if key else None
107+
ret = image.Image.verify(imgfile, key)
108+
if ret == image.VerifyResult.OK:
109+
print("Image was correctly validated")
110+
return
111+
elif ret == image.VerifyResult.INVALID_MAGIC:
112+
print("Invalid image magic; is this an MCUboot image?")
113+
elif ret == image.VerifyResult.INVALID_MAGIC:
114+
print("Invalid TLV info magic; is this an MCUboot image?")
115+
elif ret == image.VerifyResult.INVALID_HASH:
116+
print("Image has an invalid sha256 digest")
117+
elif ret == image.VerifyResult.INVALID_SIGNATURE:
118+
print("No signature found for the given key")
119+
sys.exit(1)
120+
121+
101122
def validate_version(ctx, param, value):
102123
try:
103124
decode_version(value)
@@ -226,6 +247,7 @@ def imgtool():
226247

227248
imgtool.add_command(keygen)
228249
imgtool.add_command(getpub)
250+
imgtool.add_command(verify)
229251
imgtool.add_command(sign)
230252

231253

0 commit comments

Comments
 (0)