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 <utzig@apache.org>
diff --git a/scripts/imgtool/image.py b/scripts/imgtool/image.py
index ad156a1..20f8e75 100644
--- a/scripts/imgtool/image.py
+++ b/scripts/imgtool/image.py
@@ -19,6 +19,7 @@
"""
from . import version as versmod
+from enum import Enum
from intelhex import IntelHex
import hashlib
import struct
@@ -27,6 +28,7 @@
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
+from cryptography.exceptions import InvalidSignature
IMAGE_MAGIC = 0x96f3b83d
IMAGE_HEADER_SIZE = 32
@@ -55,6 +57,7 @@
'DEPENDENCY': 0x40
}
+TLV_SIZE = 4
TLV_INFO_SIZE = 4
TLV_INFO_MAGIC = 0x6907
@@ -69,6 +72,13 @@
'big': '>'
}
+VerifyResult = Enum('VerifyResult',
+ """
+ OK INVALID_MAGIC INVALID_TLV_INFO_MAGIC INVALID_HASH
+ INVALID_SIGNATURE
+ """)
+
+
class TLV():
def __init__(self, endian):
self.buf = bytearray()
@@ -307,3 +317,47 @@
pbytes += b'\xff' * (tsize - len(boot_magic))
pbytes += boot_magic
self.payload += pbytes
+
+ @staticmethod
+ def verify(imgfile, key):
+ with open(imgfile, "rb") as f:
+ b = f.read()
+
+ magic, _, header_size, _, img_size = struct.unpack('IIHHI', b[:16])
+ if magic != IMAGE_MAGIC:
+ return VerifyResult.INVALID_MAGIC
+
+ tlv_info = b[header_size+img_size:header_size+img_size+TLV_INFO_SIZE]
+ magic, tlv_tot = struct.unpack('HH', tlv_info)
+ if magic != TLV_INFO_MAGIC:
+ return VerifyResult.INVALID_TLV_INFO_MAGIC
+
+ sha = hashlib.sha256()
+ sha.update(b[:header_size+img_size])
+ digest = sha.digest()
+
+ tlv_off = header_size + img_size
+ tlv_end = tlv_off + tlv_tot
+ tlv_off += TLV_INFO_SIZE # skip tlv info
+ while tlv_off < tlv_end:
+ tlv = b[tlv_off:tlv_off+TLV_SIZE]
+ tlv_type, _, tlv_len = struct.unpack('BBH', tlv)
+ if tlv_type == TLV_VALUES["SHA256"]:
+ off = tlv_off + TLV_SIZE
+ if digest == b[off:off+tlv_len]:
+ if key is None:
+ return VerifyResult.OK
+ else:
+ return VerifyResult.INVALID_HASH
+ elif key is not None and tlv_type == TLV_VALUES[key.sig_tlv()]:
+ off = tlv_off + TLV_SIZE
+ tlv_sig = b[off:off+tlv_len]
+ payload = b[:header_size+img_size]
+ try:
+ key.verify(tlv_sig, payload)
+ return VerifyResult.OK
+ except InvalidSignature:
+ # continue to next TLV
+ pass
+ tlv_off += TLV_SIZE + tlv_len
+ return VerifyResult.INVALID_SIGNATURE
diff --git a/scripts/imgtool/keys/ecdsa.py b/scripts/imgtool/keys/ecdsa.py
index f541d16..f93783d 100644
--- a/scripts/imgtool/keys/ecdsa.py
+++ b/scripts/imgtool/keys/ecdsa.py
@@ -57,6 +57,14 @@
# signature.
return 72
+ def verify(self, signature, payload):
+ k = self.key
+ if isinstance(self.key, ec.EllipticCurvePrivateKey):
+ k = self.key.public_key()
+ return k.verify(signature=signature, data=payload,
+ signature_algorithm=ec.ECDSA(SHA256()))
+
+
class ECDSA256P1(ECDSA256P1Public):
"""
Wrapper around an ECDSA private key.
diff --git a/scripts/imgtool/keys/rsa.py b/scripts/imgtool/keys/rsa.py
index 94af064..0f9a905 100644
--- a/scripts/imgtool/keys/rsa.py
+++ b/scripts/imgtool/keys/rsa.py
@@ -62,6 +62,14 @@
def sig_len(self):
return self.key_size() / 8
+ def verify(self, signature, payload):
+ k = self.key
+ if isinstance(self.key, rsa.RSAPrivateKey):
+ k = self.key.public_key()
+ return k.verify(signature=signature, data=payload,
+ padding=PSS(mgf=MGF1(SHA256()), salt_length=32),
+ algorithm=SHA256())
+
class RSA(RSAPublic):
"""
diff --git a/scripts/imgtool/main.py b/scripts/imgtool/main.py
index cb204b0..476884c 100755
--- a/scripts/imgtool/main.py
+++ b/scripts/imgtool/main.py
@@ -19,6 +19,7 @@
import click
import getpass
import imgtool.keys as keys
+import sys
from imgtool import image
from imgtool.version import decode_version
@@ -98,6 +99,26 @@
raise ValueError("BUG: should never get here!")
+@click.argument('imgfile')
+@click.option('-k', '--key', metavar='filename')
+@click.command(help="Check that signed image can be verified by given key")
+def verify(key, imgfile):
+ key = load_key(key) if key else None
+ ret = image.Image.verify(imgfile, key)
+ if ret == image.VerifyResult.OK:
+ print("Image was correctly validated")
+ return
+ elif ret == image.VerifyResult.INVALID_MAGIC:
+ print("Invalid image magic; is this an MCUboot image?")
+ elif ret == image.VerifyResult.INVALID_MAGIC:
+ print("Invalid TLV info magic; is this an MCUboot image?")
+ elif ret == image.VerifyResult.INVALID_HASH:
+ print("Image has an invalid sha256 digest")
+ elif ret == image.VerifyResult.INVALID_SIGNATURE:
+ print("No signature found for the given key")
+ sys.exit(1)
+
+
def validate_version(ctx, param, value):
try:
decode_version(value)
@@ -226,6 +247,7 @@
imgtool.add_command(keygen)
imgtool.add_command(getpub)
+imgtool.add_command(verify)
imgtool.add_command(sign)