Files
2026-08-24 10:03:24 +08:00

617 lines
18 KiB
Python
Executable File

#!/usr/bin/python3 -B
import argparse
import binascii
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Random import new as Random
#
# Global variables
#
global_vars = {}
#
# Functions
#
def modinv(a, m):
from Crypto.Util.number import inverse
return inverse(a, m)
def load_vars_from_file(var_filename, varname, varsize):
global_vars[varname] = None
if vars(args)[var_filename] is not None:
with open(vars(args)[var_filename], 'rb') as f:
ba_var = f.read()
assert len(ba_var) == varsize
global_vars[varname] = ba_var
#
# Classes
#
class _Util:
def genDUID(usage):
duid_full = SHA256.new(usage).digest()
duid = duid_full[:16]
duid_ext = duid_full[16:]
return (duid, duid_ext)
class CommonHeader:
def __init__(self, ba_name):
magic = SHA256.new(ba_name).digest()
self.__magic64 = b'@ML ' + magic[:4]
#print(binascii.hexlify(self.__magic64))
self.setVersion(0, 0)
self.setFlags(0)
self.setLength(0)
def setVersion(self, major, minor):
assert major.bit_length() <= 8
assert minor.bit_length() <= 8
self.__version = major.to_bytes(1, byteorder='little')
self.__version += minor.to_bytes(1, byteorder='little')
def setFlags(self, flags):
assert flags.bit_length() <= 16
self.__flags = flags
def setLength(self, length):
assert length.bit_length() <= 32
self.__length = length
def packData(self):
data = b''
data += self.__magic64
data += self.__version
data += self.__flags.to_bytes(2, byteorder='little')
data += self.__length.to_bytes(4, byteorder='little')
return data
""" Data Structure: Public RSA Key """
class DSPubRSAKey:
def __init__(self, pem_file):
self.__rsakey = None
# Common Header
self.__cmn_hdr = CommonHeader(b'AML: PUBLIC RSA KEY')
self.__cmn_hdr.setVersion(0, 0)
self.__cmn_hdr.setFlags(0)
""" Load RSA key from PEM file """
if pem_file is not None:
f_rsakey = open(pem_file)
self.__rsakey = RSA.importKey(f_rsakey.read())
self.__nbits_rsakey = self.__rsakey.n.bit_length()
while True:
if self.__nbits_rsakey == 2048:
break
if self.__nbits_rsakey == 4096:
break
assert False, "RSA Key must be 2048-bit or 4096-bit"
f_rsakey.close();
self.__prepare_pubrsa_keydata()
return
def __prepare_pubrsa_keydata(self):
""" Preparation """
# Modulus: N
self.__nbytes_rsakey = self.__nbits_rsakey//8
self.__ba_rsakey_n = self.__rsakey.n.to_bytes(self.__nbytes_rsakey,
byteorder='little')
self.__ba_rsakey_n += b'\x00' * (512 - len(self.__ba_rsakey_n))
#print(binascii.hexlify(self.__ba_rsakey_n))
# Exponent: E
self.__ebits_rsakey = self.__rsakey.e.bit_length()
assert self.__ebits_rsakey <= 32
self.__ba_rsakey_e = self.__rsakey.e.to_bytes(4, byteorder='little')
#print(binascii.hexlify(self.__ba_rsakey_e))
# Reserved
self.__ba_reserved = b'\x00' *8
# RR: R*R mod N
rr = pow(2, self.__nbits_rsakey*2, self.__rsakey.n)
rinv = modinv(2**self.__nbits_rsakey, self.__rsakey.n)
assert rr*rinv*rinv % self.__rsakey.n == 1
self.__ba_rr = rr.to_bytes(self.__nbytes_rsakey, byteorder='little')
self.__ba_rr += b'\x00' * (512 - len(self.__ba_rr))
#print('0x{:x}'.format(rr))
#print(binascii.hexlify(self.__ba_rr))
# Ninv: -(N^-1) % 2^128
ninv = modinv(self.__rsakey.n, 2**self.__nbits_rsakey)
ninv = 2**self.__nbits_rsakey - ninv
assert ((ninv*self.__rsakey.n+1) % 2**self.__nbits_rsakey) == 0
ba_ninv = ninv.to_bytes(self.__nbytes_rsakey, byteorder='little')
self.__ba_ninv128 = ba_ninv[:(128//8)]
ninv128 = int.from_bytes(self.__ba_ninv128, byteorder='little')
assert (ninv128*(self.__rsakey.n % (2**128)) + 1) % (2**128) == 0
def getRSAKey(self):
return self.__rsakey
def __packData(self):
data = b''
data += self.__cmn_hdr.packData()
data += (self.__nbytes_rsakey//4).to_bytes(4, byteorder='little')
data += self.__ba_rsakey_e
data += self.__ba_reserved
data += self.__ba_rsakey_n
data += self.__ba_rr
data += self.__ba_ninv128
return data
def packData(self):
if self.__rsakey is None:
return b'\0' * 1072
length = len(self.__packData())
#print(length)
assert length == 1072
self.__cmn_hdr.setLength(length)
return self.__packData()
class DSRootCert:
def __init__(self):
self.__aeskey = None
# Common Header
self.__cmn_hdr = CommonHeader(b'AML: ROOT CERTIFICATE')
# DUID
usage = b'DUID/CERT: SECPU ROOT CERTIFICATE'
( self.__meta_duid, _ ) = _Util.genDUID(usage + b' - METADATA')
#print(self.__meta_duid)
#print(binascii.hexlify(self.__meta_duid))
# Tables of Hashes
self.__tbl_hashes = [ b'\x00' * 32, b'\x00' * 32,
b'\x00' * 32, b'\x00' * 32 ]
self.__vld_hashes = [ False, False, False, False ]
# EPKs: EPK1_Lo, EPK2_Lo, EPK2_Hi, EPK2_Hi
self.__epks = [ b'\x00' * 16, b'\x00' * 16,
b'\x00' * 16, b'\x00' * 16 ]
### Associated data of Public RSA Key ###
self.__idx_rsakey = -1
self.__ba_reserved = b'\x00' * 31
# Random nonce for protecting Public RSA Key
self.__ba_rnd_nonce = Random().read(16)
# Empty buffer for Public RSA Key
self.__ba_pubrsakey = b'\x00' * 1072
### DUID of Root Public RSA Key ###
( self.__rootrsa_duid, _ ) = _Util.genDUID(usage + b' - ROOT PUBLIC RSA KEY')
def setHash(self, idx, ba_key):
assert len(ba_key) == 1072
if ba_key == b'\0' * 1072:
self.__tbl_hashes[idx] = b'\0' * 32
self.__vld_hashes[idx] = True
return
""" Compose PubRSA key with associated data """
buf = idx.to_bytes(1, byteorder='little')
buf += self.__ba_reserved
buf += b'\0' * 16
buf += ba_key
buf += self.__rootrsa_duid
assert len(buf) == 1136
#print(binascii.hexlify(buf))
self.__tbl_hashes[idx] = SHA256.new(buf).digest()
self.__vld_hashes[idx] = True
def setPubRSAKey(self, idx, ba_key):
self.__idx_rsakey = idx
assert len(self.__ba_pubrsakey) == len(ba_key)
self.__ba_pubrsakey = ba_key
def setAESKey(self, key):
self.__aeskey = key
assert len(self.__aeskey) == 16
def setEk2PubRSA(self, ek):
if ek is None:
return
assert len(ek) == 16
self.__epks[0] = ek
def setEk2Loader(self, ek):
if ek is None:
return
assert len(ek) == 16
self.__epks[1] = ek
def setEk1PubRSA(self, ek):
if ek is None:
return
assert len(ek) == 16
self.__epks[2] = ek
def setEk1Loader(self, ek):
if ek is None:
return
assert len(ek) == 16
self.__epks[3] = ek
def setNoncePubRSA(self, nonce):
if nonce is None:
return
assert len(nonce) == 16
self.__ba_rnd_nonce = nonce
def __packData(self, metaonly=False):
data = b''
# Metadata
data += self.__cmn_hdr.packData()
data += self.__tbl_hashes[0]
data += self.__tbl_hashes[1]
data += self.__tbl_hashes[2]
data += self.__tbl_hashes[3]
data += self.__epks[0]
data += self.__epks[1]
data += self.__epks[2]
data += self.__epks[3]
# Reserved
data += b'\0' * 48
# DUID of Root Certificate Metadata
data += self.__meta_duid
# Return only Metadata
if metaonly:
return data
### Public RSA Key and Associated Information ###
buf = b''
buf += self.__idx_rsakey.to_bytes(1, byteorder='little')
buf += self.__ba_reserved
# Fill nonce with 0s for blank PubRSA Key
assert len(self.__ba_pubrsakey) == 1072
if self.__ba_pubrsakey == b'\0' * 1072:
self.__ba_rnd_nonce = b'\0' * 16
# Encrypt Public RSA Key with random nonce
tmpbuf = b''
tmpbuf += self.__ba_rnd_nonce
tmpbuf += self.__ba_pubrsakey
if self.__aeskey is not None:
cipher = AES.new(self.__aeskey, AES.MODE_CBC, b'\x00' * 16)
tmpbuf = cipher.encrypt(tmpbuf)
buf += tmpbuf
# DUID of Root Public RSA Key
buf += self.__rootrsa_duid
data += buf
return data
def packData(self):
assert self.__vld_hashes[0]
assert self.__vld_hashes[1]
assert self.__vld_hashes[2]
assert self.__vld_hashes[3]
assert self.__idx_rsakey >= 0
assert self.__idx_rsakey <= 3
length = len(self.__packData())
#print(length)
assert length == 1408
self.__cmn_hdr.setLength(length)
return self.__packData()
def getHashLock(self):
data = self.__packData(True)
#print(len(data))
#print(binascii.hexlify(data))
return SHA256.new(data).digest()
""" Data: SECPU Loader """
class DataSECPULoader:
def __init__(self, loader_file):
self.__aeskey = None
self.__sig_loader = None
### Controlling Data of SECPU Loader
self.__seg_id = None
self.__mask_segid = 0xFFFFFFFF;
self.__arb_vers = None
self.__mask_arbvers = 0xFFFFFFFF;
### DUID of SECPU Loader ###
( self.__loader_duid, _ ) = _Util.genDUID(b'DUID/BLOB: SECPU LOADER')
#print(binascii.hexlify(self.__loader_duid))
""" Read file and do sanity check"""
f_loader = open(loader_file, 'rb')
self.__ba_loader = f_loader.read()
len_loader = len(self.__ba_loader)
while True:
if len_loader == (28*1024):
break;
if len_loader == (60*1024):
break;
if len_loader == (76*1024):
break;
if len_loader == (92*1024):
break;
assert False, "Length of Loader file must be 28 KiB, 60 KiB, 76 KiB or 92 KiB"
f_loader.close()
def setAESKey(self, key):
self.__aeskey = key
assert len(self.__aeskey) == 16
def setSegID(self, seg_id):
if seg_id is None:
return
assert isinstance(seg_id, int)
assert seg_id.bit_length() <= 32
self.__seg_id = seg_id;
def setArbVers(self,arb_vers):
if arb_vers is None:
return
assert isinstance(arb_vers, int)
assert arb_vers.bit_length() <= 32
self.__arb_vers = arb_vers
def updateCtrlData(self):
# Not inject DUID and controlling data if bot SegID and ArbVers are None
if self.__seg_id is None and self.__arb_vers is None:
return
# Inject DUID and controlling data
buf = b''
buf += self.__loader_duid
buf += self.__seg_id.to_bytes(4, byteorder='little')
buf += self.__mask_segid.to_bytes(4, byteorder='little')
buf += self.__arb_vers.to_bytes(4, byteorder='little')
buf += self.__mask_arbvers.to_bytes(4, byteorder='little')
assert len(buf) == 32
sz_loader = len(self.__ba_loader)
self.__ba_loader = self.__ba_loader[:-32] + buf
assert sz_loader == len(self.__ba_loader)
def genSig(self, rsakey):
hash_loader = SHA256.new(self.__ba_loader)
#print(hash_loader.hexdigest())
self.__sig_loader = PKCS1_v1_5.new(rsakey).sign(hash_loader)
#self.__sig_loader = b'\x00' + self.__sig_loader[1:]
#self.__sig_loader = b'd' + self.__sig_loader[1:]
assert PKCS1_v1_5.new(rsakey).verify(hash_loader, self.__sig_loader)
def packLoader(self):
data = self.__ba_loader
if self.__aeskey is not None:
cipher = AES.new(self.__aeskey, AES.MODE_CBC, b'\x00' * 16)
data = cipher.encrypt(data)
return data
def packSig(self):
if self.__sig_loader is None:
return b'\0' * 512
data = self.__sig_loader
data += b'\x00' * (512 - len(data))
return data
#
# Main
#
""" Parse arguments """
parser = argparse.ArgumentParser(description='Generate SECPU Loader Image')
parser.add_argument('--aes128-keyfile-pubrsa', dest='aes128-keyfile-pubrsa')
parser.add_argument('--aes128-keyfile-loader', dest='aes128-keyfile-loader')
parser.add_argument('--ek2file-pubrsa', dest='ek2file-pubrsa')
parser.add_argument('--ek2file-loader', dest='ek2file-loader')
parser.add_argument('--ek1file-pubrsa', dest='ek1file-pubrsa')
parser.add_argument('--ek1file-loader', dest='ek1file-loader')
parser.add_argument('--noncefile-pubrsa', dest='noncefile-pubrsa')
parser.add_argument('--seg-id', dest='seg-id')
parser.add_argument('--arb-vers', dest='arb-vers')
parser.add_argument('--rootrsa-priv-key', dest='rootrsa-priv-key', required=False)
parser.add_argument('--rootrsa-idx', dest='rootrsa-idx', required=False, default=0)
parser.add_argument('--rootrsa-key-0', dest='rootrsa-key-0', required=False)
parser.add_argument('--rootrsa-key-1', dest='rootrsa-key-1', required=False)
parser.add_argument('--rootrsa-key-2', dest='rootrsa-key-2', required=False)
parser.add_argument('--rootrsa-key-3', dest='rootrsa-key-3', required=False)
parser.add_argument('--output', dest='output-file', required=True)
parser.add_argument('--out-hash', dest='output-hash-file')
parser.add_argument('LOADER_FILE', help='SECPU Loader')
args = parser.parse_args()
rootrsa_idx = int(vars(args)['rootrsa-idx'])
assert 0 <= rootrsa_idx and rootrsa_idx <= 3
""" AES Key for PubRSA """
if vars(args)['aes128-keyfile-pubrsa'] is not None:
f_key = open(vars(args)['aes128-keyfile-pubrsa'], 'rb')
ba_key_pubrsa = f_key.read()
assert len(ba_key_pubrsa) == 16
f_key.close()
else:
ba_key_pubrsa = None
""" AES Key for Loader """
if vars(args)['aes128-keyfile-loader'] is not None:
f_key = open(vars(args)['aes128-keyfile-loader'], 'rb')
ba_key_loader = f_key.read()
assert len(ba_key_loader) == 16
f_key.close()
else:
ba_key_loader = None
""" EKs """
load_vars_from_file('ek2file-pubrsa', 'ek2-pubrsa', 16)
load_vars_from_file('ek2file-loader', 'ek2-loader', 16)
load_vars_from_file('ek1file-pubrsa', 'ek1-pubrsa', 16)
load_vars_from_file('ek1file-loader', 'ek1-loader', 16)
""" Nonce for protecting PubRSA """
load_vars_from_file('noncefile-pubrsa', 'nonce-pubrsa', 16)
""" Controlling Data """
global_vars['seg-id'] = None
strval = vars(args)['seg-id']
if strval is not None:
val = int(strval, 0)
assert val.bit_length() <= 32
global_vars['seg-id'] = val
global_vars['arb-vers'] = None
strval = vars(args)['arb-vers']
if strval is not None:
val = int(strval, 0)
assert val.bit_length() <= 32
global_vars['arb-vers'] = val
""" Private RSA Key """
if vars(args)['rootrsa-priv-key'] is not None:
with open(vars(args)['rootrsa-priv-key'], 'r') as f_privkey:
obj_priv_rsakey = RSA.importKey(f_privkey.read())
assert obj_priv_rsakey.has_private() is True
else:
obj_priv_rsakey = None
""" Public RSA Key """
tbl_ds_pubrsakey = [ None, None, None, None]
tbl_ba_pubrsa = [ None, None, None, None]
for i in range(4):
tbl_ds_pubrsakey[i] = DSPubRSAKey(vars(args)['rootrsa-key-' + str(i)])
tbl_ba_pubrsa[i] = tbl_ds_pubrsakey[i].packData()
#print(binascii.hexlify(tbl_ba_pubrsa[0]))
""" Load SECPU Loader """
secpu_loader = DataSECPULoader(args.LOADER_FILE)
if ba_key_loader is not None:
secpu_loader.setAESKey(ba_key_loader)
secpu_loader.setSegID(global_vars['seg-id'])
secpu_loader.setArbVers(global_vars['arb-vers'])
secpu_loader.updateCtrlData()
""" Generate RSA Signature """
if obj_priv_rsakey is not None:
obj_pub_rsakey = tbl_ds_pubrsakey[rootrsa_idx].getRSAKey()
assert obj_pub_rsakey.n == obj_priv_rsakey.n
assert obj_pub_rsakey.e == obj_priv_rsakey.e
secpu_loader.genSig(obj_priv_rsakey)
""" Root Certificate """
ds_rootcert = DSRootCert()
if ba_key_pubrsa is not None:
ds_rootcert.setAESKey(ba_key_pubrsa)
for i in range(4):
ds_rootcert.setHash(i, tbl_ba_pubrsa[i])
ds_rootcert.setPubRSAKey(rootrsa_idx, tbl_ba_pubrsa[rootrsa_idx])
ds_rootcert.setEk2PubRSA(global_vars['ek2-pubrsa'])
ds_rootcert.setEk2Loader(global_vars['ek2-loader'])
ds_rootcert.setEk1PubRSA(global_vars['ek1-pubrsa'])
ds_rootcert.setEk1Loader(global_vars['ek1-loader'])
ds_rootcert.setNoncePubRSA(global_vars['nonce-pubrsa'])
ba_rootcert = ds_rootcert.packData()
ba_hashlock = ds_rootcert.getHashLock()
#print(binascii.hexlify(ba_rootcert))
print("SHA256 for OTP Hash-Lock:")
print(binascii.hexlify(ba_hashlock).decode('UTF-8'))
fname_hash = vars(args)['output-hash-file']
if fname_hash is not None:
f_hash = open(fname_hash, 'wb')
f_hash.write(ba_hashlock)
f_hash.close()
#
# Pack SECPU Loader Image
#
ba_sig = secpu_loader.packSig()
ba_loader = secpu_loader.packLoader()
buf = ba_rootcert
buf += (4096 - 32 - len(ba_sig) - len(buf)) * b'\x00'
buf += ba_sig
assert len(buf) == (4096 - 32)
buf += ba_loader
chksum = SHA256.new(buf).digest()
buf = chksum + buf
#print(len(buf))
#print(binascii.hexlify(buf))
f_img = open(vars(args)['output-file'], "wb")
f_img.write(buf)
f_img.close()
# vim: set expandtab ai tabstop=4 shiftwidth=4 softtabstop=-1: