Delete ES10 related sb-tools folder from TARGET_PSOC6, post build now use cysecuretools

pull/13122/head
Roman Okhrimenko 2020-06-10 22:33:56 +03:00
parent f689c05db7
commit edcda5192a
20 changed files with 0 additions and 2023 deletions

View File

@ -1 +0,0 @@
packet

View File

@ -1,74 +0,0 @@
#### Version of Python required is 3.7+
This directory contains scripts for adding signatures .
These files are relevant to CY8CPROTO_064_SB target.
**_NOTE_:** Before starting work with Cypress Secure Boot enabled target please read User Guide https://www.cypress.com/secureboot-sdk-user-guide
## UPGRADE IMAGES
Secure Boot enabled targets support image upgrades, if specified by policy. There are two types of upgrade images supported:
- signed, non encrypted
- signed, encrypted
The upgrade images types are determined by the following policy setting (firmware sections):
- **_"smif_id":_** should be set to 1 for CY8CPROTO_064_SB onboard SMIF, default is 0 - SMIF disabled
- **_"upgrade":_** true/false, - should be set to *true* if UPGRADE supported, *false* - if disabled
- **_"encrypt":_** true/false, - should be set to *true* if encrypted UPGRADE supported, *false* - if disabled
- **_"encrypt_key_id":_** 1, - should remain unchanged, means that Device Key will be used in ECDH/HKDF protocol
Requirements:
- Policy with **_smif.json** from policy/ folder should be used.
For encrypted image:
- aes.key generated, as described in user guide
- dev_pub_key.pem must be placed in keys/ folder (this key is generated in provisioning procedure)
- secure_image_parameters.json file in the target directory must contain valid keys' paths
Non encrypted UPGRADE image
**_Example policy for CY8CPROTO_064_SB:_**
"smif_id": 1,
"upgrade": true,
"encrypt": false,
"encrypt_key_id": 1,
Encrypted UPGRADE image:
**_Example policy for CY8CPROTO_064_SB:_**
"smif_id": 1,
"upgrade": true,
"encrypt": true,
"encrypt_key_id": 1,
Modified policy file should be used for provisioning the device, as described in User Guide.
Now mbed-os application or test can be built as described in section **TESTS**. Images for UPGRADE are generated at build time, according to policy.
- Non enrypted UPGRADE image file name ends with **_upgrade.hex_**
- Enrypted UPGRADE image file name ends with **_enc_upgrade.hex_**
Upgrade image can be programmed to target board using Daplink. Upgrade procedure is performed after first reset.
**_Encrypt generic image:_**
The generic HEX file (for example one that is produced by mbed-os build system) can be converted into encrypted image by using encrypted_image_runner.py script located in sb-tools. Usage example:
python encrypted_image_runner.py --sdk-path . --hex-file someApplication.hex --key-priv keys/MCUBOOT_CM0P_KEY_PRIV.pem --key-pub keys/dev_pub_key.pem --key-aes keys/aes.key --ver 0.1 --img-id 3 --rlb-count 0 --slot-size 0x50000 --pad 1 --img-offset 402653184
- **_--sdk-path_** - Path to Secure Boot tools folder
- **_--key-priv_** - ECC Private key used for image signing and for generating shared secret as per ECDH/HKDF.
- **_--key-pub_** - ECC Public key used for image signing and for generating shared secret as per ECDH/HKDF. Only device Key can be used in current implementation. It is generated by provisioning procedure.
- **_--key-aes_** - AES128 key and IV file raw image will be encrypted with.
- **_--img-id_** - Image ID of encrypted image. Must match one mentioned in policy for UPGRADE image.
- **_--slot-size_** - Slot_1 (UPGRADE) size. Must match one mentioned in policy for UPGRADE image.
- **_--ver_** - Version of image. Make sure it matches one defined in secure_image_parameters.json for a given HEX.
- **_--rlb-count_** - Rollback counter. Make sure it matches one defined in secure_image_parameters.json for a given HEX.
- **_--img-offset_** - Starting address offset for UPGRADE image - passed as integer, as represented in policy
# TESTS
1. Build and run tests for CY8CPROTO_064_SB target with command:
Run commands:
mbed test --compile -m CY8CPROTO_064_SB -t GCC_ARM -n tests-mbed* -v

View File

@ -1,215 +0,0 @@
# Copyright 2019 Cypress Semiconductor Corporation
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
import click
import subprocess
import binascii
from pathlib import Path, PurePath
from intelhex import IntelHex, hex2bin, bin2hex
from intelhex.compat import asbytes
HEADER_SIZE = 0x400
AES_HEADER="aes_header.txt" # near the script file
def check_file_exist(file):
if not Path(file).exists():
print("ERROR: File %s not found. Check script arguments."% file)
return False
else:
return True
def get_final_hex_name(file):
"""
Determine if script is called from mbed-os build system
for Secure Boot target processing or directly
"""
for part in PurePath(file).parts:
if "_unsigned.hex" in part:
# suppose file came from mbed-os build execution
return file[:-13] + "_enc_upgrade.hex"
# suppose stand alone script execution
return file[:-4] + "_enc_upgrade.hex"
def manage_output(process, input_f, output_f):
"""
Function takes care of subprocess
"""
stderr = process.communicate()[1]
rc = process.wait()
if rc != 0:
print("ERROR: Encryption script ended with error!")
print("ERROR: " + stderr.decode("utf-8"))
raise Exception("imgtool finished execution with errors!")
if check_file_exist(output_f):
os.remove(input_f)
@click.command()
@click.option('--sdk-path', 'sdk_path',
default=Path("."),
type=click.STRING,
help='Path to Secure Boot tools in case running script from outside')
@click.option('--hex-file', 'hex_file',
default=None,
type=click.STRING,
help='Hex file to process')
@click.option('--key-priv', 'key_priv',
default=None,
type=click.STRING,
help='Private key file to use for signing BOOT or UPGRADE image')
@click.option('--key-pub', 'key_pub',
default=None,
type=click.STRING,
help='Path to device public key - obtained from device on provisioning stage')
@click.option('--key-aes', 'key_aes',
default=None,
type=click.STRING,
help='Path to encryption key')
@click.option('--ver', 'version',
default=None,
type=click.STRING,
help='Version')
@click.option('--img-id', 'img_id',
default=None,
type=click.STRING,
help='Image ID - should correspond to values, used in policy file')
@click.option('--rlb-count', 'rlb_count',
default=None,
type=click.STRING,
help='Rollback counter value')
@click.option('--slot-size', 'slot_size',
default=None,
type=click.STRING,
help='Size of slot available for BOOT or UPGRADE image')
@click.option('--pad', 'pad',
default=False,
is_flag=True,
help='Add padding to image - required for UPGRADE image')
@click.option('--img-offset', 'img_offset',
default=None,
type=click.STRING,
help='Offset of hex file for UPGRADE image')
def main(sdk_path,
hex_file,
key_priv,
key_pub,
key_aes,
version,
img_id,
rlb_count,
slot_size,
pad,
img_offset):
"""
Function consequentially performs operations with provided hex file
and produces an encrypted and signed hex file for UPGRADE
"""
check_file_exist(key_priv)
check_file_exist(key_pub)
check_file_exist(key_aes)
check_file_exist(hex_file)
in_f = hex_file[:-4] + "_i.bin"
out_f = hex_file[:-4] + "_o.bin"
hex_file_final = get_final_hex_name(hex_file)
print("Image UPGRADE:" + hex_file_final)
# ih = IntelHex(hex_file)
# img_start_addr = ih.start_addr['EIP']
hex2bin(hex_file, in_f) #bin_file)
# $PYTHON $IMGTOOL sign --key $KEY --header-size $HEADER_SIZE --pad-header --align 8 --version $VERSION --image-id $ID --rollback_counter $ROLLBACK_COUNTER --slot-size $SLOT_SIZE --overwrite-only $binFileName $signedFileName is_file_created $signedFileName
# call imgtool for signature
process = subprocess.Popen([sys.executable, sdk_path + "/imgtool/imgtool.py", "sign",
"--key", key_priv,
"--header-size", str(hex(HEADER_SIZE)),
"--pad-header",
"--align", "8",
"--version", version,
"--image-id", img_id,
"--rollback_counter", rlb_count,
"--slot-size", slot_size,
"--overwrite-only",
in_f,
out_f],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
manage_output(process, in_f, out_f)
# AES
# $PYTHON $(dirname "${IMGTOOL}")"/create_aesHeader.py" -k $KEY -p $KEY_DEV --key_to_encrypt "$KEY_AES" $AES_HEADER
# call aesHeader for crypto header generation
process = subprocess.Popen([sys.executable, sdk_path + "/imgtool/create_aesHeader.py",
"-k", key_priv,
"-p", key_pub,
"--key_to_encrypt", key_aes,
AES_HEADER],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# catch stderr outputs
stderr = process.communicate()
rc = process.wait()
check_file_exist(AES_HEADER)
# aes_cipher.py script file should be in the same folder as imgtool.py
# $PYTHON $(dirname "${IMGTOOL}")"/aes_cipher.py" -k $KEY_AES $signedFileName $aes_encryptedFileName
# is_file_created $aes_encryptedFileName
# encrypt signed image
process = subprocess.Popen([sys.executable, sdk_path + "/imgtool/aes_cipher.py",
"-k", key_aes,
out_f,
in_f],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
manage_output(process, out_f, in_f)
# second part - obtain signed image from encrypted file - with padding - for staging area
# $PYTHON $IMGTOOL sign --key $KEY --header-size $HEADER_SIZE --pad-header --align 8 --version $VERSION --image-id $ID --rollback_counter $ROLLBACK_COUNTER --slot-size $SLOT_SIZE --overwrite-only $PAD -a $AES_HEADER $aes_encryptedFileName $signedEncFileName
# is_file_created $signedEncFileName
# call imgtool for signature
process = subprocess.Popen([sys.executable, sdk_path + "/imgtool/imgtool.py", "sign",
"--key", key_priv,
"--header-size", str(hex(HEADER_SIZE)),
"--pad-header",
"--align", "8",
"--version", version,
"--image-id", img_id,
"--rollback_counter", rlb_count,
"--slot-size", slot_size,
"--overwrite-only",
"--pad",
"-a", AES_HEADER,
in_f,
out_f],
#bin_sig_enc,
#bin_sig_enc_sig],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
manage_output(process, in_f, out_f)
bin2hex(out_f, hex_file_final, int(img_offset))
os.remove(out_f)
os.remove(AES_HEADER)
if __name__ == "__main__":
main()

View File

@ -1,121 +0,0 @@
# Copyright 2019 Cypress Semiconductor Corporation
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import click
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
class AESCipher(object):
def __init__(self, key, IV):
self.backend = default_backend()
self.key = AESCipher.get_bytes(key)
self.iv = AESCipher.get_bytes(IV)
self.block_size = 128
@staticmethod
def get_bytes(inputdata):
if type(inputdata) is str:
return str.encode(inputdata,'utf-8')
elif type(inputdata) is bytes:
return inputdata
else:
raise Exception("Unknown input data type...")
class AESCipherCBC(AESCipher):
def __init__(self, key, IV):
super().__init__(key, IV)
self.cipher = Cipher(algorithms.AES(self.key), modes.CBC(self.iv), backend=self.backend)
def encrypt(self, raw):
encryptor = self.cipher.encryptor()
padder = padding.PKCS7(self.block_size).padder()
return encryptor.update(padder.update(raw) + padder.finalize()) + encryptor.finalize()
def decrypt(self, enc):
decryptor = self.cipher.decryptor()
unpadder = padding.PKCS7(self.block_size).unpadder()
return unpadder.update(decryptor.update(enc) + decryptor.finalize()) + unpadder.finalize()
class AESCipherGCM(AESCipher):
def __init__(self, key, IV, auth_data):
super().__init__(key, IV)
self.cipher = AESGCM(self.key)
self.auth_data = str.encode(auth_data,'utf-8')
def encrypt(self, raw):
return self.cipher.encrypt(self.iv, raw, self.auth_data)
def decrypt(self, enc):
return self.cipher.decrypt(self.iv, enc, self.auth_data)
def read_key_from_file(keyfile):
with open(keyfile) as f:
content = f.read().splitlines()
if len(content) < 2:
raise Exception("Not anough AES input data: in the file should be two lines: key, iv ...")
key = bytes.fromhex(content[0])
iv = bytes.fromhex(content[1])
if 8*len(key) not in set([128, 192, 256]):
raise Exception("Invalid AES Key length: should be 128, 192 or 256 bits")
check_iv_length(iv)
return key, iv
def check_iv_length(iv):
if 8*len(iv) != 128:
raise Exception("Invalid AES IV length: should be 128 bits")
return True
@click.command()
@click.option('-k', '--keyfile')
@click.option('-a', '--auth_data', default='default data')
@click.option('-m', '--mode', default='CBC')
@click.argument('inputfile')
@click.argument('outputfile')
def main(keyfile, auth_data, mode, inputfile, outputfile):
key, iv = read_key_from_file(keyfile)
if mode == 'CBC':
check_iv_length(iv)
aes = AESCipherCBC(key, iv)
elif mode == 'GCM':
aes = AESCipherGCM(key, iv, auth_data)
else:
raise Exception("Selected mode is not supported...")
inFile = open(inputfile,"rb")
outFile = open(outputfile,"wb")
outFile.write(aes.encrypt(inFile.read()))
inFile.close()
outFile.close()
if __name__ == "__main__":
main()

View File

@ -1,61 +0,0 @@
# Copyright 2019 Cypress Semiconductor Corporation
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import codecs
import click
from aes_cipher import *
from ecc_kdf import ECDH_KDF
def get_header_info(kdf_object, key_to_encrypt_file):
aes = AESCipherCBC(kdf_object.aes_key, kdf_object.iv)
#print (kdf_object.aes_key.hex())
key_to_enc, iv_to_enc = read_key_from_file(key_to_encrypt_file)
key_encrypted = aes.encrypt(key_to_enc + iv_to_enc)
if kdf_object.salt is None or kdf_object.info is None:
raise Exception('salt and info should be presented...')
if len(kdf_object.salt) != 16 or len(kdf_object.info) != 16:
raise Exception('salt and info fields length should be 16 bytes...')
return key_encrypted + kdf_object.salt + kdf_object.info
@click.command()
@click.option('-a', '--algorithm', default='ECC') #assymetric algorithm for KDF
@click.option('-k', '--private_key') #host side key pair file
@click.option('-p', '--public_key') #device side public key
@click.option('-l', '--key_length', default=16) #derived key (AES) length
@click.option('-s', '--salt', default=None) #salt for KDF
@click.option('-i', '--info', default=b'_handshake_data_') #info data for KDF
@click.option('--key_to_encrypt') #AES key file name (key and iv are used for image encryption), should be AES encrypted, using derived key
@click.argument('outputfile') #AES_header info file name
def main(algorithm, private_key, public_key, key_length, salt, info, key_to_encrypt, outputfile):
kdf_object = None
if(algorithm == 'ECC'):
kdf_object = ECDH_KDF(private_key, public_key, key_length, salt, info)
else:
raise Exception('Algorithm not supported...')
aes_header = get_header_info(kdf_object, key_to_encrypt)
with open(outputfile, 'wb') as header_out:
header_out.write(aes_header)
if __name__ == "__main__":
main()

View File

@ -1,83 +0,0 @@
# Copyright 2019 Cypress Semiconductor Corporation
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import serialization
import codecs
import os
class ECDH_KDF(object):
def __init__(self, private_key, public_key, key_length, salt, info):
salt_length = 16
self.backend = default_backend()
self.host_key_pair = serialization.load_pem_private_key(self.read_key_bytes(private_key), password=None, backend=self.backend)
#Deserialize public key: extract from private or directly from pem file
try:
self.device_public_key = serialization.load_pem_private_key(self.read_key_bytes(public_key), password=None, backend=self.backend).public_key()
except:
self.device_public_key = serialization.load_pem_public_key(self.read_key_bytes(public_key), backend=self.backend)
self.key_length = key_length
self.iv_length = 16
if salt is not None:
self.salt = ECDH_KDF.get_bytes(salt)
else:
self.salt = os.urandom(salt_length)
self.info = ECDH_KDF.get_bytes(info)
self.derived_key = self.derive_key()
self.aes_key = self.derived_key[:self.key_length]
self.iv = self.derived_key[self.key_length:]
@staticmethod
def get_bytes(inputdata):
if type(inputdata) is str:
return str.encode(inputdata,'utf-8')
elif type(inputdata) is bytes:
return inputdata
else:
raise Exception("Unknown input data type...")
def read_key_bytes(self, key_file):
with open(key_file, "rb") as key_file:
return key_file.read()
def derive_key(self):
shared_key = self.host_key_pair.exchange(ec.ECDH(), self.device_public_key)
derived_key = HKDF(algorithm=hashes.SHA256(), # Perform key derivation.
length=self.key_length + self.iv_length,
salt=self.salt,
info=self.info,
backend=self.backend).derive(shared_key)
return derived_key
def main():
pass
if __name__ == "__main__":
main()

View File

@ -1,198 +0,0 @@
#! /usr/bin/env python3
#
# Copyright 2017 Linaro Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import click
import getpass
from imgtool import keys
from imgtool import image
from imgtool.version import decode_version
def gen_rsa2048(keyfile, passwd):
keys.RSA2048.generate().export_private(path=keyfile, passwd=passwd)
def gen_ecdsa_p256(keyfile, passwd):
keys.ECDSA256P1.generate().export_private(keyfile, passwd=passwd)
def gen_ecdsa_p224(keyfile, passwd):
print("TODO: p-224 not yet implemented")
valid_langs = ['c', 'rust']
keygens = {
'rsa-2048': gen_rsa2048,
'ecdsa-p256': gen_ecdsa_p256,
'ecdsa-p224': gen_ecdsa_p224,
}
def load_key(keyfile):
# TODO: better handling of invalid pass-phrase
key = keys.load(keyfile)
if key is not None:
return key
passwd = getpass.getpass("Enter key passphrase: ").encode('utf-8')
return keys.load(keyfile, passwd)
def get_password():
while True:
passwd = getpass.getpass("Enter key passphrase: ")
passwd2 = getpass.getpass("Reenter passphrase: ")
if passwd == passwd2:
break
print("Passwords do not match, try again")
# Password must be bytes, always use UTF-8 for consistent
# encoding.
return passwd.encode('utf-8')
@click.option('-p', '--password', is_flag=True,
help='Prompt for password to protect key')
@click.option('-t', '--type', metavar='type', required=True,
type=click.Choice(keygens.keys()))
@click.option('-k', '--key', metavar='filename', required=True)
@click.command(help='Generate pub/private keypair')
def keygen(type, key, password):
password = get_password() if password else None
keygens[type](key, password)
@click.option('-l', '--lang', metavar='lang', default=valid_langs[0],
type=click.Choice(valid_langs))
@click.option('-k', '--key', metavar='filename', required=True)
@click.command(help='Get public key from keypair')
def getpub(key, lang):
key = load_key(key)
if key is None:
print("Invalid passphrase")
elif lang == 'c':
key.emit_c()
elif lang == 'rust':
key.emit_rust()
else:
raise ValueError("BUG: should never get here!")
def validate_version(ctx, param, value):
try:
decode_version(value)
return value
except ValueError as e:
raise click.BadParameter("{}".format(e))
class BasedIntParamType(click.ParamType):
name = 'integer'
def convert(self, value, param, ctx):
try:
if value[:2].lower() == '0x':
return int(value[2:], 16)
elif value[:1] == '0':
return int(value, 8)
return int(value, 10)
except ValueError:
self.fail('%s is not a valid integer' % value, param, ctx)
def load_data_from_file(filename):
FileObj = open(filename, 'rb')
data = FileObj.read()
FileObj.close()
return data
@click.argument('outfile')
@click.argument('infile')
@click.option('--overwrite-only', default=False, is_flag=True,
help='Use overwrite-only instead of swap upgrades')
@click.option('-M', '--max-sectors', type=int,
help='When padding allow for this amount of sectors (defaults to 128)')
@click.option('--pad', default=False, is_flag=True,
help='Pad image to --slot-size bytes, adding trailer magic')
@click.option('-S', '--slot-size', type=BasedIntParamType(), required=True,
help='Size of the slot where the image will be written')
@click.option('--pad-header', default=False, is_flag=True,
help='Add --header-size zeroed bytes at the beginning of the image')
@click.option('-H', '--header-size', type=BasedIntParamType(), required=True)
@click.option('-v', '--version', callback=validate_version, required=True)
@click.option('--align', type=click.Choice(['1', '2', '4', '8']),
required=True)
@click.option('-k', '--key', metavar='filename')
@click.option('-a', '--aes-header-file', default=None, metavar='filename')
@click.option('--image-id', required=True, type=int, help='Image ID')
@click.option('--rollback_counter', default=None, type=int, help='Rollback monotonic counter value')
@click.command(help='Create a signed or unsigned image')
def sign(key, align, version, header_size, pad_header, slot_size, pad,
max_sectors, overwrite_only, aes_header_file, image_id, rollback_counter, infile, outfile):
if aes_header_file is not None :
aes_header = load_data_from_file(aes_header_file)
else:
aes_header = None
img = image.Image.load(infile, version=decode_version(version),
header_size=header_size, pad_header=pad_header,
pad=pad, align=int(align), slot_size=slot_size,
max_sectors=max_sectors,
overwrite_only=overwrite_only, aes_header_data=aes_header,
image_id=image_id, rollback_counter=rollback_counter)
key = load_key(key) if key else None
img.sign(key)
if pad:
img.pad_to(slot_size)
img.save(outfile)
class AliasesGroup(click.Group):
_aliases = {
"create": "sign",
}
def list_commands(self, ctx):
cmds = [k for k in self.commands]
aliases = [k for k in self._aliases]
return sorted(cmds + aliases)
def get_command(self, ctx, cmd_name):
rv = click.Group.get_command(self, ctx, cmd_name)
if rv is not None:
return rv
if cmd_name in self._aliases:
return click.Group.get_command(self, ctx, self._aliases[cmd_name])
return None
@click.command(cls=AliasesGroup,
context_settings=dict(help_option_names=['-h', '--help']))
def imgtool():
pass
imgtool.add_command(keygen)
imgtool.add_command(getpub)
imgtool.add_command(sign)
if __name__ == '__main__':
imgtool()

View File

@ -1,13 +0,0 @@
# Copyright 2017 Linaro Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

View File

@ -1,269 +0,0 @@
# Copyright 2018 Nordic Semiconductor ASA
# Copyright 2017 Linaro Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Image signing and management.
"""
from . import version as versmod
from intelhex import IntelHex
import hashlib
import struct
import os.path
import os
IMAGE_MAGIC = 0x96f3b83d
IMAGE_HEADER_SIZE = 32
BIN_EXT = "bin"
INTEL_HEX_EXT = "hex"
DEFAULT_MAX_SECTORS = 128
# Image header flags.
IMAGE_F = {
'PIC': 0x0000001,
'NON_BOOTABLE': 0x0000010, }
TLV_VALUES = {
'KEYHASH': 0x01,
'SHA256': 0x10,
'RSA2048': 0x20,
'ECDSA224': 0x21,
'ECDSA256': 0x22, }
TLV_INFO_SIZE = 4
TLV_INFO_MAGIC = 0x6907
boot_magic = bytes([
0x77, 0xc2, 0x95, 0xf3,
0x60, 0xd2, 0xef, 0x7f,
0x35, 0x52, 0x50, 0x0f,
0x2c, 0xb6, 0x79, 0x80, ])
class TLV():
def __init__(self):
self.buf = bytearray()
def add(self, kind, payload):
"""Add a TLV record. Kind should be a string found in TLV_VALUES above."""
buf = struct.pack('<BBH', TLV_VALUES[kind], 0, len(payload))
self.buf += buf
self.buf += payload
def get(self):
header = struct.pack('<HH', TLV_INFO_MAGIC, TLV_INFO_SIZE + len(self.buf))
return header + bytes(self.buf)
class Image():
@classmethod
def load(cls, path, pad_header=False, **kwargs):
"""Load an image from a given file"""
ext = os.path.splitext(path)[1][1:].lower()
if ext == INTEL_HEX_EXT:
cls = HexImage
else:
cls = BinImage
obj = cls(**kwargs)
obj.payload, obj.base_addr = obj.load(path)
# Add the image header if needed.
if pad_header and obj.header_size > 0:
if obj.base_addr:
# Adjust base_addr for new header
obj.base_addr -= obj.header_size
obj.payload = (b'\000' * obj.header_size) + obj.payload
obj.check()
return obj
def __init__(self, version=None, header_size=IMAGE_HEADER_SIZE, pad=0,
align=1, slot_size=0, max_sectors=DEFAULT_MAX_SECTORS,
overwrite_only=False, aes_header_data=None, image_id=1, rollback_counter=0):
self.version = version or versmod.decode_version("0")
self.header_size = header_size or IMAGE_HEADER_SIZE
self.pad = pad
self.align = align
self.slot_size = slot_size
self.max_sectors = max_sectors
self.overwrite_only = overwrite_only
self.aes_header_data = aes_header_data
self.image_id = image_id
self.rollback_counter = rollback_counter
def __repr__(self):
return "<Image version={}, header_size={}, base_addr={}, \
align={}, slot_size={}, max_sectors={}, overwrite_only={}, \
format={}, payloadlen=0x{:x}>".format(
self.version,
self.header_size,
self.base_addr if self.base_addr is not None else "N/A",
self.align,
self.slot_size,
self.max_sectors,
self.overwrite_only,
self.__class__.__name__,
len(self.payload))
def check(self):
"""Perform some sanity checking of the image."""
# If there is a header requested, make sure that the image
# starts with all zeros.
if self.header_size > 0:
if any(v != 0 for v in self.payload[0:self.header_size]):
raise Exception("Padding requested, but image does not start with zeros")
if self.slot_size > 0:
tsize = self._trailer_size(self.align, self.max_sectors,
self.overwrite_only)
padding = self.slot_size - (len(self.payload) + tsize)
if padding < 0:
msg = "Image size (0x{:x}) + trailer (0x{:x}) exceeds requested size 0x{:x}".format(
len(self.payload), tsize, self.slot_size)
raise Exception(msg)
def sign(self, key, add_padding = True):
DECRYPT_BLOCK_SIZE = 256 #future AES decription buffer size, is used for align FW image and trailer size
if add_padding:
pl_size = len(self.payload)
pad_len = (DECRYPT_BLOCK_SIZE - pl_size % DECRYPT_BLOCK_SIZE) % DECRYPT_BLOCK_SIZE
#self.payload += bytearray(os.urandom(pad_len))
self.payload += bytearray(pad_len)
self.add_header(key)
if (self.aes_header_data is not None):
self.add_AES_header()
tlv = TLV()
# Note that ecdsa wants to do the hashing itself, which means
# we get to hash it twice.
sha = hashlib.sha256()
sha.update(self.payload)
digest = sha.digest()
tlv.add('SHA256', digest)
if key is not None:
pub = key.get_public_bytes()
sha = hashlib.sha256()
sha.update(pub)
pubbytes = sha.digest()
tlv.add('KEYHASH', pubbytes)
sig = key.sign(bytes(self.payload))
tlv.add(key.sig_tlv(), sig)
trailer = tlv.get()
self.payload += trailer
if add_padding:
trailer_size = len(trailer)
tr_rem_len = (DECRYPT_BLOCK_SIZE - trailer_size % DECRYPT_BLOCK_SIZE ) % DECRYPT_BLOCK_SIZE
#self.payload += bytearray(os.urandom(tr_rem_len))
self.payload += bytearray(tr_rem_len)
def add_header(self, key):
"""Install the image header.
The key is needed to know the type of signature, and
approximate the size of the signature."""
flags = 0
fmt = ('<' +
# type ImageHdr struct {
'I' + # Magic uint32
'I' + # LoadAddr uint32
'H' + # HdrSz uint16
'B' + # Image ID uint8
'B' + # Rollback monotonic counter value uint8
'I' + # ImgSz uint32
'I' + # Flags uint32
'BBHI' + # Vers ImageVersion
'I' # Pad2 uint32
) # }
assert struct.calcsize(fmt) == IMAGE_HEADER_SIZE
header = struct.pack(fmt,
IMAGE_MAGIC,
0, # LoadAddr
self.header_size,
self.image_id,
self.rollback_counter,
len(self.payload) - self.header_size, # ImageSz
flags, # Flags
self.version.major,
self.version.minor or 0,
self.version.revision or 0,
self.version.build or 0,
0) # Pad2
self.payload = bytearray(self.payload)
self.payload[:len(header)] = header
def add_AES_header(self):
"""Install AES header just after main image header
The header contains:
AES Key, IV (encrypted with AES CBC)
salt - random sequence for ECDH KDF
info - information for ECDH KDF
"""
aes_header_bytes = self.aes_header_data #str.encode(self.aes_header_data,'utf-8')
headerAES = struct.pack( "@%ds" % (len(aes_header_bytes)), aes_header_bytes )
self.payload[IMAGE_HEADER_SIZE:IMAGE_HEADER_SIZE + len(headerAES)] = headerAES
def _trailer_size(self, write_size, max_sectors, overwrite_only):
# NOTE: should already be checked by the argument parser
if overwrite_only:
return 8 * 2 + 16
else:
if write_size not in set([1, 2, 4, 8]):
raise Exception("Invalid alignment: {}".format(write_size))
m = DEFAULT_MAX_SECTORS if max_sectors is None else max_sectors
return m * 3 * write_size + 8 * 2 + 16
def pad_to(self, size):
"""Pad the image to the given size, with the given flash alignment."""
tsize = self._trailer_size(self.align, self.max_sectors,
self.overwrite_only)
padding = size - (len(self.payload) + tsize)
pbytes = b'\xff' * padding
pbytes += b'\xff' * (tsize - len(boot_magic))
pbytes += boot_magic
self.payload += pbytes
class HexImage(Image):
def load(self, path):
ih = IntelHex(path)
return ih.tobinarray(), ih.minaddr()
def save(self, path):
h = IntelHex()
h.frombytes(bytes = self.payload, offset = self.base_addr)
h.tofile(path, 'hex')
class BinImage(Image):
def load(self, path):
with open(path, 'rb') as f:
return f.read(), None
def save(self, path):
with open(path, 'wb') as f:
f.write(self.payload)

View File

@ -1,76 +0,0 @@
# Copyright 2017 Linaro Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Cryptographic key management for imgtool.
"""
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey, EllipticCurvePublicKey
from .rsa import RSA2048, RSA2048Public, RSAUsageError
from .ecdsa import ECDSA256P1, ECDSA256P1Public, ECDSAUsageError
class PasswordRequired(Exception):
"""Raised to indicate that the key is password protected, but a
password was not specified."""
pass
def load(path, passwd=None):
"""Try loading a key from the given path. Returns None if the password wasn't specified."""
with open(path, 'rb') as f:
raw_pem = f.read()
try:
pk = serialization.load_pem_private_key(
raw_pem,
password=passwd,
backend=default_backend())
# Unfortunately, the crypto library raises unhelpful exceptions,
# so we have to look at the text.
except TypeError as e:
msg = str(e)
if "private key is encrypted" in msg:
return None
raise e
except ValueError:
# This seems to happen if the key is a public key, let's try
# loading it as a public key.
pk = serialization.load_pem_public_key(
raw_pem,
backend=default_backend())
if isinstance(pk, RSAPrivateKey):
if pk.key_size != 2048:
raise Exception("Unsupported RSA key size: " + pk.key_size)
return RSA2048(pk)
elif isinstance(pk, RSAPublicKey):
if pk.key_size != 2048:
raise Exception("Unsupported RSA key size: " + pk.key_size)
return RSA2048Public(pk)
elif isinstance(pk, EllipticCurvePrivateKey):
if pk.curve.name != 'secp256r1':
raise Exception("Unsupported EC curve: " + pk.curve.name)
if pk.key_size != 256:
raise Exception("Unsupported EC size: " + pk.key_size)
return ECDSA256P1(pk)
elif isinstance(pk, EllipticCurvePublicKey):
if pk.curve.name != 'secp256r1':
raise Exception("Unsupported EC curve: " + pk.curve.name)
if pk.key_size != 256:
raise Exception("Unsupported EC size: " + pk.key_size)
return ECDSA256P1Public(pk)
else:
raise Exception("Unknown key type: " + str(type(pk)))

View File

@ -1,117 +0,0 @@
# Copyright 2019 Cypress Semiconductor Corporation
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
ECDSA key management
"""
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.hashes import SHA256
from .general import KeyClass
class ECDSAUsageError(Exception):
pass
class ECDSA256P1Public(KeyClass):
def __init__(self, key):
self.key = key
def shortname(self):
return "ecdsa"
def _unsupported(self, name):
raise ECDSAUsageError("Operation {} requires private key".format(name))
def _get_public(self):
return self.key
def get_public_bytes(self):
# The key is embedded into MBUboot in "SubjectPublicKeyInfo" format
return self._get_public().public_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.SubjectPublicKeyInfo)
def export_private(self, path, passwd=None):
self._unsupported('export_private')
def export_public(self, path):
"""Write the public key to the given file."""
pem = self._get_public().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo)
with open(path, 'wb') as f:
f.write(pem)
def sig_type(self):
return "ECDSA256_SHA256"
def sig_tlv(self):
return "ECDSA256"
def sig_len(self):
# The DER encoding depends on the high bit, and can be
# anywhere from 70 to 72 bytes. Because we have to fill in
# the length field before computing the signature, however,
# we'll give the largest, and the sig checking code will allow
# for it to be up to two bytes larger than the actual
# signature.
return 72
class ECDSA256P1(ECDSA256P1Public):
"""
Wrapper around an ECDSA private key.
"""
def __init__(self, key):
"""key should be an instance of EllipticCurvePrivateKey"""
self.key = key
@staticmethod
def generate():
pk = ec.generate_private_key(
ec.SECP256R1(),
backend=default_backend())
return ECDSA256P1(pk)
def _get_public(self):
return self.key.public_key()
def export_private(self, path, passwd=None):
"""Write the private key to the given file, protecting it with the optional password."""
if passwd is None:
enc = serialization.NoEncryption()
else:
enc = serialization.BestAvailableEncryption(passwd)
pem = self.key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=enc)
with open(path, 'wb') as f:
f.write(pem)
def raw_sign(self, payload):
"""Return the actual signature"""
return self.key.sign(
data=payload,
signature_algorithm=ec.ECDSA(SHA256()))
def sign(self, payload):
# To make fixed length, pad with one or two zeros.
sig = self.raw_sign(payload)
sig += b'\000' * (self.sig_len() - len(sig))
return sig

View File

@ -1,114 +0,0 @@
# Copyright 2019 Cypress Semiconductor Corporation
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Tests for ECDSA keys
"""
import io
import os.path
import sys
import tempfile
import unittest
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.hashes import SHA256
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
from imgtool.keys import load, ECDSA256P1, ECDSAUsageError
class EcKeyGeneration(unittest.TestCase):
def setUp(self):
self.test_dir = tempfile.TemporaryDirectory()
def tname(self, base):
return os.path.join(self.test_dir.name, base)
def tearDown(self):
self.test_dir.cleanup()
def test_keygen(self):
name1 = self.tname("keygen.pem")
k = ECDSA256P1.generate()
k.export_private(name1, b'secret')
self.assertIsNone(load(name1))
k2 = load(name1, b'secret')
pubname = self.tname('keygen-pub.pem')
k2.export_public(pubname)
pk2 = load(pubname)
# We should be able to export the public key from the loaded
# public key, but not the private key.
pk2.export_public(self.tname('keygen-pub2.pem'))
self.assertRaises(ECDSAUsageError,
pk2.export_private, self.tname('keygen-priv2.pem'))
def test_emit(self):
"""Basic sanity check on the code emitters."""
k = ECDSA256P1.generate()
ccode = io.StringIO()
k.emit_c(ccode)
self.assertIn("ecdsa_pub_key", ccode.getvalue())
self.assertIn("ecdsa_pub_key_len", ccode.getvalue())
rustcode = io.StringIO()
k.emit_rust(rustcode)
self.assertIn("ECDSA_PUB_KEY", rustcode.getvalue())
def test_emit_pub(self):
"""Basic sanity check on the code emitters."""
pubname = self.tname("public.pem")
k = ECDSA256P1.generate()
k.export_public(pubname)
k2 = load(pubname)
ccode = io.StringIO()
k2.emit_c(ccode)
self.assertIn("ecdsa_pub_key", ccode.getvalue())
self.assertIn("ecdsa_pub_key_len", ccode.getvalue())
rustcode = io.StringIO()
k2.emit_rust(rustcode)
self.assertIn("ECDSA_PUB_KEY", rustcode.getvalue())
def test_sig(self):
k = ECDSA256P1.generate()
buf = b'This is the message'
sig = k.raw_sign(buf)
# The code doesn't have any verification, so verify this
# manually.
k.key.public_key().verify(
signature=sig,
data=buf,
signature_algorithm=ec.ECDSA(SHA256()))
# Modify the message to make sure the signature fails.
self.assertRaises(InvalidSignature,
k.key.public_key().verify,
signature=sig,
data=b'This is thE message',
signature_algorithm=ec.ECDSA(SHA256()))
if __name__ == '__main__':
unittest.main()

View File

@ -1,50 +0,0 @@
# Copyright 2019 Cypress Semiconductor Corporation
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""General key class."""
import sys
AUTOGEN_MESSAGE = "/* Autogenerated by imgtool.py, do not edit. */"
class KeyClass(object):
def _public_emit(self, header, trailer, indent, file=sys.stdout, len_format=None):
print(AUTOGEN_MESSAGE, file=file)
print(header, end='', file=file)
encoded = self.get_public_bytes()
for count, b in enumerate(encoded):
if count % 8 == 0:
print("\n" + indent, end='', file=file)
else:
print(" ", end='', file=file)
print("0x{:02x},".format(b), end='', file=file)
print("\n" + trailer, file=file)
if len_format is not None:
print(len_format.format(len(encoded)), file=file)
def emit_c(self, file=sys.stdout):
self._public_emit(
header="const unsigned char {}_pub_key[] = {{".format(self.shortname()),
trailer="};",
indent=" ",
len_format="const unsigned int {}_pub_key_len = {{}};".format(self.shortname()),
file=file)
def emit_rust(self, file=sys.stdout):
self._public_emit(
header="static {}_PUB_KEY: &'static [u8] = &[".format(self.shortname().upper()),
trailer="];",
indent=" ",
file=file)

View File

@ -1,110 +0,0 @@
# Copyright 2019 Cypress Semiconductor Corporation
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
RSA Key management
"""
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric.padding import PSS, MGF1
from cryptography.hazmat.primitives.hashes import SHA256
from .general import KeyClass
class RSAUsageError(Exception):
pass
class RSA2048Public(KeyClass):
"""The public key can only do a few operations"""
def __init__(self, key):
self.key = key
def shortname(self):
return "rsa"
def _unsupported(self, name):
raise RSAUsageError("Operation {} requires private key".format(name))
def _get_public(self):
return self.key
def get_public_bytes(self):
# The key embedded into MCUboot is in PKCS1 format.
return self._get_public().public_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.PKCS1)
def export_private(self, path, passwd=None):
self._unsupported('export_private')
def export_public(self, path):
"""Write the public key to the given file."""
pem = self._get_public().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo)
with open(path, 'wb') as f:
f.write(pem)
def sig_type(self):
return "PKCS1_PSS_RSA2048_SHA256"
def sig_tlv(self):
return "RSA2048"
def sig_len(self):
return 256
class RSA2048(RSA2048Public):
"""
Wrapper around an 2048-bit RSA key, with imgtool support.
"""
def __init__(self, key):
"""The key should be a private key from cryptography"""
self.key = key
@staticmethod
def generate():
pk = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend())
return RSA2048(pk)
def _get_public(self):
return self.key.public_key()
def export_private(self, path, passwd=None):
"""Write the private key to the given file, protecting it with the optional password."""
if passwd is None:
enc = serialization.NoEncryption()
else:
enc = serialization.BestAvailableEncryption(passwd)
pem = self.key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=enc)
with open(path, 'wb') as f:
f.write(pem)
def sign(self, payload):
# The verification code only allows the salt length to be the
# same as the hash length, 32.
return self.key.sign(
data=payload,
padding=PSS(mgf=MGF1(SHA256()), salt_length=32),
algorithm=SHA256())

View File

@ -1,117 +0,0 @@
# Copyright 2019 Cypress Semiconductor Corporation
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Tests for RSA keys
"""
import io
import os
import sys
import tempfile
import unittest
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.padding import PSS, MGF1
from cryptography.hazmat.primitives.hashes import SHA256
# Setup sys path so 'imgtool' is in it.
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
from imgtool.keys import load, RSA2048, RSAUsageError
class KeyGeneration(unittest.TestCase):
def setUp(self):
self.test_dir = tempfile.TemporaryDirectory()
def tname(self, base):
return os.path.join(self.test_dir.name, base)
def tearDown(self):
self.test_dir.cleanup()
def test_keygen(self):
name1 = self.tname("keygen.pem")
k = RSA2048.generate()
k.export_private(name1, b'secret')
# Try loading the key without a password.
self.assertIsNone(load(name1))
k2 = load(name1, b'secret')
pubname = self.tname('keygen-pub.pem')
k2.export_public(pubname)
pk2 = load(pubname)
# We should be able to export the public key from the loaded
# public key, but not the private key.
pk2.export_public(self.tname('keygen-pub2.pem'))
self.assertRaises(RSAUsageError, pk2.export_private, self.tname('keygen-priv2.pem'))
def test_emit(self):
"""Basic sanity check on the code emitters."""
k = RSA2048.generate()
ccode = io.StringIO()
k.emit_c(ccode)
self.assertIn("rsa_pub_key", ccode.getvalue())
self.assertIn("rsa_pub_key_len", ccode.getvalue())
rustcode = io.StringIO()
k.emit_rust(rustcode)
self.assertIn("RSA_PUB_KEY", rustcode.getvalue())
def test_emit_pub(self):
"""Basic sanity check on the code emitters, from public key."""
pubname = self.tname("public.pem")
k = RSA2048.generate()
k.export_public(pubname)
k2 = load(pubname)
ccode = io.StringIO()
k2.emit_c(ccode)
self.assertIn("rsa_pub_key", ccode.getvalue())
self.assertIn("rsa_pub_key_len", ccode.getvalue())
rustcode = io.StringIO()
k2.emit_rust(rustcode)
self.assertIn("RSA_PUB_KEY", rustcode.getvalue())
def test_sig(self):
k = RSA2048.generate()
buf = b'This is the message'
sig = k.sign(buf)
# The code doesn't have any verification, so verify this
# manually.
k.key.public_key().verify(
signature=sig,
data=buf,
padding=PSS(mgf=MGF1(SHA256()), salt_length=32),
algorithm=SHA256())
# Modify the message to make sure the signature fails.
self.assertRaises(InvalidSignature,
k.key.public_key().verify,
signature=sig,
data=b'This is thE message',
padding=PSS(mgf=MGF1(SHA256()), salt_length=32),
algorithm=SHA256())
if __name__ == '__main__':
unittest.main()

View File

@ -1,53 +0,0 @@
# Copyright 2017 Linaro Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Semi Semantic Versioning
Implements a subset of semantic versioning that is supportable by the image
header.
"""
from collections import namedtuple
import re
SemiSemVersion = namedtuple('SemiSemVersion', ['major', 'minor', 'revision',
'build'])
version_re = re.compile(
r"""^([1-9]\d*|0)(\.([1-9]\d*|0)(\.([1-9]\d*|0)(\+([1-9]\d*|0))?)?)?$""")
def decode_version(text):
"""Decode the version string, which should be of the form maj.min.rev+build
"""
m = version_re.match(text)
if m:
result = SemiSemVersion(
int(m.group(1)) if m.group(1) else 0,
int(m.group(3)) if m.group(3) else 0,
int(m.group(5)) if m.group(5) else 0,
int(m.group(7)) if m.group(7) else 0)
return result
else:
msg = "Invalid version number, should be maj.min.rev+build with later "
msg += "parts optional"
raise ValueError(msg)
if __name__ == '__main__':
print(decode_version("1.2"))
print(decode_version("1.0"))
print(decode_version("0.0.2+75"))
print(decode_version("0.0.0+00"))

View File

@ -1,10 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIBTzCB9qADAgECAhQCJF8kCV5oVGofjI+lrnVsCSI+cjAKBggqhkjOPQQDAjAg
MR4wHAYDVQQDDBVDeXByZXNzIFNlbWljb25kdWN0b3IwHhcNMTkwNzE1MTkzNzQ3
WhcNMjAwNzE0MTkzNzQ3WjAeMRwwGgYDVQQDDBNFeGFtcGxlIGNlcnRpZmljYXRl
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvfb7/jewTxpFVINcXdrZQJBArC5i
grN0BLc783FigrP2sEFQpfOmPUDkrt/E+0Rol2x+jsmP/CwXstNktz6w86MQMA4w
DAYDVR0TAQH/BAIwADAKBggqhkjOPQQDAgNIADBFAiEA3I3zaBbwMzSJ6xU9ngUM
Dyk4XstQF3tLzmvBRUkX8woCICk0YiVqk4tD2wvgUYkPztBKu6tVl/OqF2Ee+aQs
uwQc
-----END CERTIFICATE-----

View File

@ -1,113 +0,0 @@
{
"debug" :
{
"m0p" : {
"permission" : "disabled",
"control" : "firmware",
"key" : 5
},
"m4" : {
"permission" : "allowed",
"control" : "firmware",
"key" : 5
},
"system" : {
"permission" : "enabled",
"control" : "firmware",
"key" : 5,
"syscall": true,
"mmio": true,
"flash": true,
"workflash": true,
"sflash": true,
"sram": true
},
"rma" : {
"permission" : "allowed",
"destroy_fuses" : [
{
"start" : 888,
"size" : 136
},
{
"start" : 648,
"size" : 104
}
],
"destroy_flash" : [
{
"start" : 268435456,
"size" : 851968
},
{
"start" : 269483520,
"size" : 16
}
],
"key" : 5
}
},
"wounding" :
{
},
"boot_upgrade" :
{
"title": "upgrade_policy",
"firmware": [
{
"boot_auth": [
3
],
"id": 0,
"launch": 4,
"smif_id": 0,
"upgrade": false,
"upgrade_auth": [
3
],
"resources": [
{
"type": "FLASH_PC1_SPM",
"address": 269287424,
"size": 65536
},
{
"type": "SRAM_SPM_PRIV",
"address": 134348800,
"size": 65536
},
{
"type": "SRAM_DAP",
"address": 134397952,
"size": 16384
}
]
},
{
"boot_auth": [
8
],
"id": 4,
"monotonic": 0,
"smif_id": 0,
"upgrade": true,
"upgrade_auth": [
8
],
"resources": [
{
"type": "BOOT",
"address": 268435456,
"size": 327680
},
{
"type": "UPGRADE",
"address": 268763136,
"size": 327680
}
]
}
]
}
}

View File

@ -1,113 +0,0 @@
{
"debug" :
{
"m0p" : {
"permission" : "disabled",
"control" : "firmware",
"key" : 5
},
"m4" : {
"permission" : "allowed",
"control" : "firmware",
"key" : 5
},
"system" : {
"permission" : "enabled",
"control" : "firmware",
"key" : 5,
"syscall": true,
"mmio": true,
"flash": true,
"workflash": true,
"sflash": true,
"sram": true
},
"rma" : {
"permission" : "allowed",
"destroy_fuses" : [
{
"start" : 888,
"size" : 136
},
{
"start" : 648,
"size" : 104
}
],
"destroy_flash" : [
{
"start" : 268435456,
"size" : 851968
},
{
"start" : 269483520,
"size" : 16
}
],
"key" : 5
}
},
"wounding" :
{
},
"boot_upgrade" :
{
"title": "upgrade_policy",
"firmware": [
{
"boot_auth": [
3
],
"id": 0,
"launch": 4,
"smif_id": 0,
"upgrade": false,
"upgrade_auth": [
3
],
"resources": [
{
"type": "FLASH_PC1_SPM",
"address": 269287424,
"size": 65536
},
{
"type": "SRAM_SPM_PRIV",
"address": 134348800,
"size": 65536
},
{
"type": "SRAM_DAP",
"address": 134397952,
"size": 16384
}
]
},
{
"boot_auth": [
8
],
"id": 4,
"monotonic": 0,
"smif_id": 0,
"upgrade": true,
"upgrade_auth": [
8
],
"resources": [
{
"type": "BOOT",
"address": 268435456,
"size": 655360
},
{
"type": "UPGRADE",
"address": 269090816,
"size": 655360
}
]
}
]
}
}

View File

@ -1,115 +0,0 @@
{
"debug" :
{
"m0p" : {
"permission" : "disabled",
"control" : "firmware",
"key" : 5
},
"m4" : {
"permission" : "allowed",
"control" : "firmware",
"key" : 5
},
"system" : {
"permission" : "enabled",
"control" : "firmware",
"key" : 5,
"syscall": true,
"mmio": true,
"flash": true,
"workflash": true,
"sflash": true,
"sram": true
},
"rma" : {
"permission" : "allowed",
"destroy_fuses" : [
{
"start" : 888,
"size" : 136
},
{
"start" : 648,
"size" : 104
}
],
"destroy_flash" : [
{
"start" : 268435456,
"size" : 851968
},
{
"start" : 269483520,
"size" : 16
}
],
"key" : 5
}
},
"wounding" :
{
},
"boot_upgrade" :
{
"title": "upgrade_policy",
"firmware": [
{
"boot_auth": [
3
],
"id": 0,
"launch": 4,
"smif_id": 0,
"upgrade": false,
"upgrade_auth": [
3
],
"resources": [
{
"type": "FLASH_PC1_SPM",
"address": 269287424,
"size": 65536
},
{
"type": "SRAM_SPM_PRIV",
"address": 134348800,
"size": 65536
},
{
"type": "SRAM_DAP",
"address": 134397952,
"size": 16384
}
]
},
{
"boot_auth": [
8
],
"id": 4,
"monotonic": 0,
"smif_id": 1,
"upgrade": false,
"encrypt": false,
"encrypt_key_id": 1,
"upgrade_auth": [
8
],
"resources": [
{
"type": "BOOT",
"address": 268435456,
"size": 327680
},
{
"type": "UPGRADE",
"address": 402653184,
"size": 327680
}
]
}
]
}
}