mirror of https://github.com/ARMmbed/mbed-os.git
Delete ES10 related sb-tools folder from TARGET_PSOC6, post build now use cysecuretools
parent
f689c05db7
commit
edcda5192a
|
@ -1 +0,0 @@
|
|||
packet
|
|
@ -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
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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.
|
|
@ -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)
|
|
@ -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)))
|
|
@ -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
|
|
@ -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()
|
|
@ -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)
|
|
@ -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())
|
|
@ -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()
|
|
@ -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"))
|
|
@ -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-----
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue