but there isn’t all that much detail on that page to understand what is signed in the firmware, and how. Stay along and we’ll discuss that here.
Flash layout
The firmware distributed by Supermicro follows the layout of the flash available on the platform.
On an Aspeed Ast2400/Ast2500 based IPMI, this would look as shown in Figure 1:
To note, there is a JFFS (flash-optimized file system) located in between the u-boot segment and the rootfs, that is used by the IPMI to store non-volatile data (configuration, certificate, keys, …). That is not distributed, and this is re-created by the firmware if absent from the flash at first boot.
Firmware signature
There is a bunch of space still available, and Supermicro used that space to store public keys and signatures.
The public key used to sign the payload is stored at the address 0x16ff800 in the PEM DER ASN.1 PKCS#1 format (-----BEGIN RSA PUBLIC KEY-----). In the firmware payload distributed, it is stored at the offset 0x16ff804. The key may or may not be the same as the one stored in the flash. This is used to update the signing key should it need to be.
The key used to sign the payload is currently an RSA 2048bits key.
So how is the firmware signed? Two signatures are used.
The first signature (at 0x16ffe00) checks the content of the u-boot, the rootfs, the webfs, the kernel as well as the key located at 0x16ff804.
Should this first signature succeed, then the key located in the distributed firmware is loaded, and it would then check the second signature (at 0x16ffc00) of u-boot, the rootfs and the webfs sections for the firmware to be signed with that key.
Below is an implementation of the firmware verification mechanism in python.
#!/usr/bin/env nix-shell#!nix-shell -i python3 -p python3 python3Packages.cryptographyimport argparse
import os
import struct
import sys
from mmap import mmap, PROT_READ
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import utils, padding
classInvalidSignature(Exception):
passdefcheck(infile, public_key=None):
with open(infile, "r+b") as f:
with mmap(f.fileno(), 0) as mm:
chosen_hash = hashes.SHA256()
outer_checksum = hashes.Hash(chosen_hash)
# Add uboot
outer_checksum.update(mm[0:0x100000])
# Add rootfs
outer_checksum.update(mm[0x400000:0x400000+0x1000000])
# Add kernel, inner key and signature
outer_checksum.update(mm[0x1400000:0x1400000+0x2ffc00])
# Add webfs
outer_checksum.update(mm[0x1700000:0x1700000+0x840000])
# Load public keyif public_key:
with open(public_key, 'r+b') as pk:
outer_key = serialization.load_pem_public_key(pk.read())
else:
key_len = struct.unpack('<I', mm[0x016ff800:0x016ff804])[0]
key_data = mm[0x016ff804:0x016ff804+key_len]
outer_key = serialization.load_pem_public_key(key_data)
# Load signature
signature = mm[0x16ffe00:0x16ffe00+0x100]
# And check the signaturetry:
outer_key.verify(
signature,
outer_checksum.finalize(),
padding.PKCS1v15(),
utils.Prehashed(chosen_hash)
)
exceptException:
raise InvalidSignature('outer signature invalid')
# outer signature is now verified, and there is a second signature# on a subset of the firmware already checked# Should the key change, it would be checked with the new key
inner_checksum = hashes.Hash(chosen_hash)
# Add uboot
inner_checksum.update(mm[0:0x40000])
# Add rootfs# Note: length looks wrong here. This is only partially checks# the rootfs. but it's checked by the outer signature already# a typo?
inner_checksum.update(mm[0x400000:0x400000+0x100000])
# Add kernel, and a lot more thing.
inner_checksum.update(mm[0x1400000:0x1400000+0x100000])
# Add webfs
inner_checksum.update(mm[0x1700000:0x1700000+0x100000])
# add parts of the key segment
inner_checksum.update(mm[0x16f0000:0x16f0000+0xfc00])
# Load public key
key_len = struct.unpack('<I', mm[0x016ff800:0x016ff804])[0]
key_data = mm[0x016ff804:0x016ff804+key_len]
inner_key = serialization.load_pem_public_key(key_data)
# Load signature
signature = mm[0x16ffc00:0x16ffc00+0x100]
# And check the signaturetry:
inner_key.verify(
signature,
inner_checksum.finalize(),
padding.PKCS1v15(),
utils.Prehashed(chosen_hash)
)
exceptException:
raise InvalidSignature('inner signature invalid')
if __name__ =='__main__':
parser = argparse.ArgumentParser(description='check ipmi firmware signature')
parser.add_argument('infile', metavar='FIRMWARE', help='file to check')
parser.add_argument('--public_key', nargs='?', help='public key used to sign the firmware')
args = parser.parse_args()
try:
check(args.infile, args.public_key)
print('success')
except InvalidSignature as e:
print(e.args[0])
sys.exit(1)
exceptExceptionas e:
print(e)
sys.exit(2)