mirror of
https://github.com/clearlinux/graphene.git
synced 2026-09-06 05:41:37 +00:00
[Pal/Linux-SGX] Make pal-sgx-sign code importable
This moves the Python implementation of pal-sgx-sign into pal_sgx_sign.py and replaces the cmdline tool with a tiny wrapper. This allows other code to import it as a module and thereby re-use its code. Designing an API and then putting it into a proper Python package is left for the future.
This commit is contained in:
committed by
Michał Kowalczyk
parent
0daeed847f
commit
330432aed9
@@ -1,946 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# This is just a wrapper that provides the cmdline tool. All code should go
|
||||
# into pal_sgx_sign.py.
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import functools
|
||||
import hashlib
|
||||
import os
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
import generated_offsets as offs # pylint: disable=import-error,wrong-import-position
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from pal_sgx_sign import main # pylint: disable=import-error,wrong-import-position
|
||||
|
||||
# pylint: enable=invalid-name
|
||||
|
||||
# Default / Architectural Options
|
||||
|
||||
ARCHITECTURE = "amd64"
|
||||
|
||||
SSAFRAMESIZE = offs.PAGESIZE
|
||||
|
||||
DEFAULT_ENCLAVE_SIZE = '256M'
|
||||
DEFAULT_THREAD_NUM = 4
|
||||
ENCLAVE_HEAP_MIN = offs.DEFAULT_HEAP_MIN
|
||||
|
||||
|
||||
# Utilities
|
||||
|
||||
ZERO_PAGE = bytes(offs.PAGESIZE)
|
||||
|
||||
|
||||
def roundup(addr):
|
||||
remaining = addr % offs.PAGESIZE
|
||||
if remaining:
|
||||
return addr + (offs.PAGESIZE - remaining)
|
||||
return addr
|
||||
|
||||
|
||||
def rounddown(addr):
|
||||
return addr - addr % offs.PAGESIZE
|
||||
|
||||
|
||||
def parse_size(value):
|
||||
scale = 1
|
||||
if value.endswith("K"):
|
||||
scale = 1024
|
||||
if value.endswith("M"):
|
||||
scale = 1024 * 1024
|
||||
if value.endswith("G"):
|
||||
scale = 1024 * 1024 * 1024
|
||||
if scale != 1:
|
||||
value = value[:-1]
|
||||
return int(value, 0) * scale
|
||||
|
||||
|
||||
# Reading / Writing Manifests
|
||||
|
||||
def read_manifest(filename):
|
||||
manifest = dict()
|
||||
manifest_layout = []
|
||||
with open(filename, "r") as file:
|
||||
for line in file:
|
||||
if line == "":
|
||||
manifest_layout.append((None, None))
|
||||
break
|
||||
|
||||
pound = line.find("#")
|
||||
if pound != -1:
|
||||
comment = line[pound:].strip()
|
||||
line = line[:pound]
|
||||
else:
|
||||
comment = None
|
||||
|
||||
line = line.strip()
|
||||
equal = line.find("=")
|
||||
if equal != -1:
|
||||
key = line[:equal].strip()
|
||||
manifest[key] = line[equal + 1:].strip()
|
||||
else:
|
||||
key = None
|
||||
|
||||
manifest_layout.append((key, comment))
|
||||
|
||||
return (manifest, manifest_layout)
|
||||
|
||||
|
||||
def exec_sig_manifest(args, manifest):
|
||||
if 'exec' not in args or args.get('depend'):
|
||||
if 'loader.exec' in manifest:
|
||||
args['exec'] = resolve_manifest_uri(args['manifest'],
|
||||
manifest['loader.exec'])
|
||||
|
||||
if 'sgx.sigfile' in manifest:
|
||||
args['sigfile'] = resolve_uri(manifest['sgx.sigfile'],
|
||||
check_exist=False)
|
||||
else:
|
||||
sigfile = args['output']
|
||||
for ext in ['.manifest.sgx.d', '.manifest.sgx', '.manifest']:
|
||||
if sigfile.endswith(ext):
|
||||
sigfile = sigfile[:-len(ext)]
|
||||
break
|
||||
args['sigfile'] = sigfile + '.sig'
|
||||
manifest['sgx.sigfile'] = 'file:' + os.path.basename(args['sigfile'])
|
||||
|
||||
if args.get('libpal', None) is None:
|
||||
if 'sgx.enclave_pal_file' in manifest:
|
||||
args['libpal'] = resolve_manifest_uri(args['manifest'],
|
||||
manifest['sgx.enclave_pal_file'])
|
||||
else:
|
||||
print("Either --libpal or sgx.enclave_pal_file must be given", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def output_manifest(filename, manifest, manifest_layout):
|
||||
with open(filename, 'w') as file:
|
||||
written = []
|
||||
|
||||
file.write('# DO NOT MODIFY. THIS FILE WAS AUTO-GENERATED.\n\n')
|
||||
|
||||
for (key, comment) in manifest_layout:
|
||||
line = ''
|
||||
if key is not None:
|
||||
line += key + ' = ' + manifest[key]
|
||||
written.append(key)
|
||||
if comment is not None:
|
||||
if line != '':
|
||||
line += ' '
|
||||
line += comment
|
||||
file.write(line)
|
||||
file.write('\n')
|
||||
|
||||
file.write('\n')
|
||||
file.write('# Generated by Graphene\n')
|
||||
file.write('\n')
|
||||
|
||||
for key in sorted(manifest):
|
||||
if key not in written:
|
||||
file.write("%s = %s\n" % (key, manifest[key]))
|
||||
|
||||
|
||||
# Loading Enclave Attributes
|
||||
|
||||
def get_enclave_attributes(manifest):
|
||||
sgx_flags = {
|
||||
'FLAG_DEBUG': struct.pack("<Q", offs.SGX_FLAGS_DEBUG),
|
||||
'FLAG_MODE64BIT': struct.pack("<Q", offs.SGX_FLAGS_MODE64BIT),
|
||||
}
|
||||
|
||||
sgx_xfrms = {
|
||||
'XFRM_LEGACY': struct.pack("<Q", offs.SGX_XFRM_LEGACY),
|
||||
'XFRM_AVX': struct.pack("<Q", offs.SGX_XFRM_AVX),
|
||||
'XFRM_AVX512': struct.pack("<Q", offs.SGX_XFRM_AVX512),
|
||||
'XFRM_MPX': struct.pack("<Q", offs.SGX_XFRM_MPX),
|
||||
}
|
||||
|
||||
sgx_miscs = {
|
||||
'MISC_EXINFO': struct.pack("<L", offs.SGX_MISCSELECT_EXINFO),
|
||||
}
|
||||
|
||||
default_attributes = {
|
||||
'FLAG_DEBUG',
|
||||
'XFRM_LEGACY',
|
||||
}
|
||||
|
||||
if ARCHITECTURE == 'amd64':
|
||||
default_attributes.add('FLAG_MODE64BIT')
|
||||
|
||||
manifest_options = {
|
||||
'debug': 'FLAG_DEBUG',
|
||||
'require_avx': 'XFRM_AVX',
|
||||
'require_avx512': 'XFRM_AVX512',
|
||||
'require_mpx': 'XFRM_MPX',
|
||||
'support_exinfo': 'MISC_EXINFO',
|
||||
}
|
||||
|
||||
attributes = default_attributes
|
||||
|
||||
for opt in manifest_options:
|
||||
key = 'sgx.' + opt
|
||||
if key in manifest:
|
||||
if manifest[key] == '1':
|
||||
attributes.add(manifest_options[opt])
|
||||
else:
|
||||
attributes.discard(manifest_options[opt])
|
||||
|
||||
flags_raw = struct.pack("<Q", 0)
|
||||
xfrms_raw = struct.pack("<Q", 0)
|
||||
miscs_raw = struct.pack("<L", 0)
|
||||
|
||||
for attr in attributes:
|
||||
if attr in sgx_flags:
|
||||
flags_raw = bytes([a | b for a, b in
|
||||
zip(flags_raw, sgx_flags[attr])])
|
||||
if attr in sgx_xfrms:
|
||||
xfrms_raw = bytes([a | b for a, b in
|
||||
zip(xfrms_raw, sgx_xfrms[attr])])
|
||||
if attr in sgx_miscs:
|
||||
miscs_raw = bytes([a | b for a, b in
|
||||
zip(miscs_raw, sgx_miscs[attr])])
|
||||
|
||||
return flags_raw, xfrms_raw, miscs_raw
|
||||
|
||||
|
||||
# Generate Checksums / Measurement
|
||||
|
||||
def resolve_uri(uri, check_exist=True):
|
||||
orig_uri = uri
|
||||
if uri.startswith('file:'):
|
||||
target = os.path.normpath(uri[len('file:'):])
|
||||
else:
|
||||
target = os.path.normpath(uri)
|
||||
if check_exist and not os.path.exists(target):
|
||||
raise Exception(
|
||||
'Cannot resolve ' + orig_uri + ' or the file does not exist.')
|
||||
return target
|
||||
|
||||
# Resolve an URI relative to manifest file to its absolute path
|
||||
def resolve_manifest_uri(manifest_path, uri):
|
||||
if not uri.startswith('file:'):
|
||||
raise Exception('URI ' + uri + ' is not a local file')
|
||||
path = uri[len('file:'):]
|
||||
if os.path.isabs(path):
|
||||
return path
|
||||
return os.path.join(os.path.dirname(manifest_path), path)
|
||||
|
||||
def get_checksum(filename):
|
||||
digest = hashlib.sha256()
|
||||
with open(filename, 'rb') as file:
|
||||
digest.update(file.read())
|
||||
return digest.digest()
|
||||
|
||||
|
||||
def get_trusted_files(manifest, args, check_exist=True, do_checksum=True):
|
||||
targets = dict()
|
||||
|
||||
if 'exec' in args:
|
||||
targets['exec'] = (args['exec'], resolve_uri(args['exec'],
|
||||
check_exist))
|
||||
|
||||
if 'loader.preload' in manifest:
|
||||
for i, uri in enumerate(str.split(manifest['loader.preload'], ',')):
|
||||
targets['preload' + str(i)] = (uri, resolve_uri(uri, check_exist))
|
||||
|
||||
for (key, val) in manifest.items():
|
||||
if not key.startswith('sgx.trusted_files.'):
|
||||
continue
|
||||
key = key[len('sgx.trusted_files.'):]
|
||||
if key in targets:
|
||||
raise Exception(
|
||||
'repeated key in manifest: sgx.trusted_files.' + key)
|
||||
targets[key] = (val, resolve_uri(val, check_exist))
|
||||
|
||||
if do_checksum:
|
||||
for (key, val) in targets.items():
|
||||
(uri, target) = val
|
||||
checksum = get_checksum(target).hex()
|
||||
targets[key] = (uri, target, checksum)
|
||||
|
||||
return targets
|
||||
|
||||
|
||||
def get_trusted_children(manifest, check_exist=True, do_checksum=True):
|
||||
targets = dict()
|
||||
|
||||
for (key, val) in manifest.items():
|
||||
if not key.startswith('sgx.trusted_children.'):
|
||||
continue
|
||||
key = key[len('sgx.trusted_children.'):]
|
||||
if key in targets:
|
||||
raise Exception(
|
||||
'repeated key in manifest: sgx.trusted_children.' + key)
|
||||
|
||||
target = resolve_uri(val, check_exist)
|
||||
if not target.endswith('.sig'):
|
||||
target += '.sig'
|
||||
if do_checksum:
|
||||
sig = open(target, 'rb').read()[
|
||||
offs.SGX_ARCH_ENCLAVE_CSS_ENCLAVE_HASH:
|
||||
offs.SGX_ARCH_ENCLAVE_CSS_ENCLAVE_HASH + offs.SGX_HASH_SIZE].hex()
|
||||
targets[key] = (val, target, sig)
|
||||
else:
|
||||
targets[key] = (val, target)
|
||||
return targets
|
||||
|
||||
|
||||
# Populate Enclave Memory
|
||||
|
||||
PAGEINFO_R = 0x1
|
||||
PAGEINFO_W = 0x2
|
||||
PAGEINFO_X = 0x4
|
||||
PAGEINFO_TCS = 0x100
|
||||
PAGEINFO_REG = 0x200
|
||||
|
||||
|
||||
def get_loadcmds(filename):
|
||||
loadcmds = []
|
||||
proc = subprocess.Popen(['readelf', '-l', '-W', filename],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
while True:
|
||||
line = proc.stdout.readline()
|
||||
if not line:
|
||||
break
|
||||
line = line.decode()
|
||||
stripped = line.strip()
|
||||
if not stripped.startswith('LOAD'):
|
||||
continue
|
||||
tokens = stripped.split()
|
||||
if len(tokens) < 6:
|
||||
continue
|
||||
if len(tokens) >= 7 and tokens[7] == "E":
|
||||
tokens[6] += tokens[7]
|
||||
prot = 0
|
||||
for token in tokens[6]:
|
||||
if token == "R":
|
||||
prot = prot | 4
|
||||
if token == "W":
|
||||
prot = prot | 2
|
||||
if token == "E":
|
||||
prot = prot | 1
|
||||
|
||||
loadcmds.append((int(tokens[1][2:], 16), # offset
|
||||
int(tokens[2][2:], 16), # addr
|
||||
int(tokens[4][2:], 16), # filesize
|
||||
int(tokens[5][2:], 16), # memsize
|
||||
prot))
|
||||
proc.wait()
|
||||
if proc.returncode != 0:
|
||||
return None
|
||||
return loadcmds
|
||||
|
||||
|
||||
class MemoryArea:
|
||||
# pylint: disable=too-few-public-methods,too-many-instance-attributes
|
||||
def __init__(self, desc, file=None, content=None, addr=None, size=None,
|
||||
flags=None, measure=True):
|
||||
# pylint: disable=too-many-arguments
|
||||
self.desc = desc
|
||||
self.file = file
|
||||
self.content = content
|
||||
self.addr = addr
|
||||
self.size = size
|
||||
self.flags = flags
|
||||
self.is_binary = False
|
||||
self.measure = measure
|
||||
|
||||
if file:
|
||||
loadcmds = get_loadcmds(file)
|
||||
if loadcmds:
|
||||
mapaddr = 0xffffffffffffffff
|
||||
mapaddr_end = 0
|
||||
for (_, addr_, _, memsize, _) in loadcmds:
|
||||
if rounddown(addr_) < mapaddr:
|
||||
mapaddr = rounddown(addr_)
|
||||
if roundup(addr_ + memsize) > mapaddr_end:
|
||||
mapaddr_end = roundup(addr_ + memsize)
|
||||
|
||||
self.is_binary = True
|
||||
self.size = mapaddr_end - mapaddr
|
||||
if mapaddr > 0:
|
||||
self.addr = mapaddr
|
||||
else:
|
||||
self.size = os.stat(file).st_size
|
||||
|
||||
if self.addr is not None:
|
||||
self.addr = rounddown(self.addr)
|
||||
if self.size is not None:
|
||||
self.size = roundup(self.size)
|
||||
|
||||
|
||||
def get_memory_areas(attr, args):
|
||||
areas = []
|
||||
areas.append(
|
||||
MemoryArea('ssa',
|
||||
size=attr['thread_num'] * SSAFRAMESIZE * offs.SSAFRAMENUM,
|
||||
flags=PAGEINFO_R | PAGEINFO_W | PAGEINFO_REG))
|
||||
areas.append(MemoryArea('tcs', size=attr['thread_num'] * offs.TCS_SIZE,
|
||||
flags=PAGEINFO_TCS))
|
||||
areas.append(MemoryArea('tls', size=attr['thread_num'] * offs.PAGESIZE,
|
||||
flags=PAGEINFO_R | PAGEINFO_W | PAGEINFO_REG))
|
||||
|
||||
for _ in range(attr['thread_num']):
|
||||
areas.append(MemoryArea('stack', size=offs.ENCLAVE_STACK_SIZE,
|
||||
flags=PAGEINFO_R | PAGEINFO_W | PAGEINFO_REG))
|
||||
for _ in range(attr['thread_num']):
|
||||
areas.append(MemoryArea('sig_stack', size=offs.ENCLAVE_SIG_STACK_SIZE,
|
||||
flags=PAGEINFO_R | PAGEINFO_W | PAGEINFO_REG))
|
||||
|
||||
areas.append(MemoryArea('pal', file=args['libpal'], flags=PAGEINFO_REG))
|
||||
|
||||
if 'exec' in args:
|
||||
areas.append(MemoryArea('exec', file=args['exec'],
|
||||
flags=PAGEINFO_W | PAGEINFO_REG))
|
||||
return areas
|
||||
|
||||
|
||||
def find_areas(areas, desc):
|
||||
return [area for area in areas if area.desc == desc]
|
||||
|
||||
|
||||
def find_area(areas, desc, allow_none=False):
|
||||
matching = find_areas(areas, desc)
|
||||
|
||||
if not matching and allow_none:
|
||||
return None
|
||||
|
||||
if len(matching) != 1:
|
||||
raise KeyError(
|
||||
"Could not find exactly one MemoryArea '{}'".format(desc))
|
||||
|
||||
return matching[0]
|
||||
|
||||
|
||||
def entry_point(elf_path):
|
||||
env = os.environ
|
||||
env['LC_ALL'] = 'C'
|
||||
out = subprocess.check_output(
|
||||
['readelf', '-l', '--', elf_path], env=env)
|
||||
for line in out.splitlines():
|
||||
line = line.decode()
|
||||
if line.startswith("Entry point "):
|
||||
return int(line[12:], 0)
|
||||
raise ValueError("Could not find entry point of elf file")
|
||||
|
||||
|
||||
def baseaddr():
|
||||
if ENCLAVE_HEAP_MIN == 0:
|
||||
return offs.ENCLAVE_HIGH_ADDRESS
|
||||
return 0
|
||||
|
||||
|
||||
def gen_area_content(attr, areas):
|
||||
# pylint: disable=too-many-locals
|
||||
manifest_area = find_area(areas, 'manifest')
|
||||
exec_area = find_area(areas, 'exec', True)
|
||||
pal_area = find_area(areas, 'pal')
|
||||
ssa_area = find_area(areas, 'ssa')
|
||||
tcs_area = find_area(areas, 'tcs')
|
||||
tls_area = find_area(areas, 'tls')
|
||||
stacks = find_areas(areas, 'stack')
|
||||
sig_stacks = find_areas(areas, 'sig_stack')
|
||||
|
||||
tcs_data = bytearray(tcs_area.size)
|
||||
|
||||
def set_tcs_field(t, offset, pack_fmt, value):
|
||||
struct.pack_into(pack_fmt, tcs_data, t * offs.TCS_SIZE + offset, value)
|
||||
|
||||
tls_data = bytearray(tls_area.size)
|
||||
|
||||
def set_tls_field(t, offset, value):
|
||||
struct.pack_into('<Q', tls_data, t * offs.PAGESIZE + offset, value)
|
||||
|
||||
enclave_heap_max = pal_area.addr - offs.MEMORY_GAP
|
||||
|
||||
# Sanity check that we measure everything except the heap which is zeroed
|
||||
# on enclave startup.
|
||||
for area in areas:
|
||||
if (area.addr + area.size <= ENCLAVE_HEAP_MIN or
|
||||
area.addr >= enclave_heap_max or area is exec_area):
|
||||
if not area.measure:
|
||||
raise ValueError("Memory area, which is not the heap, "
|
||||
"is not measured")
|
||||
elif area.desc != 'free':
|
||||
raise ValueError("Unexpected memory area is in heap range")
|
||||
|
||||
for t in range(0, attr['thread_num']):
|
||||
ssa_offset = ssa_area.addr + SSAFRAMESIZE * offs.SSAFRAMENUM * t
|
||||
ssa = baseaddr() + ssa_offset
|
||||
set_tcs_field(t, offs.TCS_OSSA, '<Q', ssa_offset)
|
||||
set_tcs_field(t, offs.TCS_NSSA, '<L', offs.SSAFRAMENUM)
|
||||
set_tcs_field(t, offs.TCS_OENTRY, '<Q',
|
||||
pal_area.addr + entry_point(pal_area.file))
|
||||
set_tcs_field(t, offs.TCS_OGS_BASE, '<Q', tls_area.addr + offs.PAGESIZE * t)
|
||||
set_tcs_field(t, offs.TCS_OFS_LIMIT, '<L', 0xfff)
|
||||
set_tcs_field(t, offs.TCS_OGS_LIMIT, '<L', 0xfff)
|
||||
|
||||
set_tls_field(t, offs.SGX_COMMON_SELF,
|
||||
tls_area.addr + offs.PAGESIZE * t + baseaddr())
|
||||
set_tls_field(t, offs.SGX_ENCLAVE_SIZE, attr['enclave_size'])
|
||||
set_tls_field(t, offs.SGX_TCS_OFFSET, tcs_area.addr + offs.TCS_SIZE * t)
|
||||
set_tls_field(t, offs.SGX_INITIAL_STACK_OFFSET,
|
||||
stacks[t].addr + stacks[t].size)
|
||||
set_tls_field(t, offs.SGX_SIG_STACK_LOW, baseaddr() + sig_stacks[t].addr)
|
||||
set_tls_field(t, offs.SGX_SIG_STACK_HIGH,
|
||||
baseaddr() + sig_stacks[t].addr + sig_stacks[t].size)
|
||||
set_tls_field(t, offs.SGX_SSA, ssa)
|
||||
set_tls_field(t, offs.SGX_GPR, ssa + SSAFRAMESIZE - offs.SGX_GPR_SIZE)
|
||||
set_tls_field(t, offs.SGX_MANIFEST_SIZE,
|
||||
os.stat(manifest_area.file).st_size)
|
||||
set_tls_field(t, offs.SGX_HEAP_MIN, baseaddr() + ENCLAVE_HEAP_MIN)
|
||||
set_tls_field(t, offs.SGX_HEAP_MAX, baseaddr() + enclave_heap_max)
|
||||
if exec_area is not None:
|
||||
set_tls_field(t, offs.SGX_EXEC_ADDR, baseaddr() + exec_area.addr)
|
||||
set_tls_field(t, offs.SGX_EXEC_SIZE, exec_area.size)
|
||||
|
||||
tcs_area.content = tcs_data
|
||||
tls_area.content = tls_data
|
||||
|
||||
|
||||
def populate_memory_areas(attr, areas):
|
||||
populating = attr['enclave_size']
|
||||
|
||||
for area in areas:
|
||||
if area.addr is not None:
|
||||
continue
|
||||
|
||||
area.addr = populating - area.size
|
||||
if area.addr < ENCLAVE_HEAP_MIN:
|
||||
raise Exception("Enclave size is not large enough")
|
||||
populating = max(area.addr - offs.MEMORY_GAP, 0)
|
||||
|
||||
free_areas = []
|
||||
for area in areas:
|
||||
if area.addr + area.size + offs.MEMORY_GAP < populating:
|
||||
addr = area.addr + area.size + offs.MEMORY_GAP
|
||||
flags = PAGEINFO_R | PAGEINFO_W | PAGEINFO_X | PAGEINFO_REG
|
||||
free_areas.append(
|
||||
MemoryArea('free', addr=addr, size=populating - addr,
|
||||
flags=flags, measure=False))
|
||||
populating = max(area.addr - offs.MEMORY_GAP, 0)
|
||||
|
||||
if populating > ENCLAVE_HEAP_MIN:
|
||||
flags = PAGEINFO_R | PAGEINFO_W | PAGEINFO_X | PAGEINFO_REG
|
||||
free_areas.append(
|
||||
MemoryArea('free', addr=ENCLAVE_HEAP_MIN,
|
||||
size=populating - ENCLAVE_HEAP_MIN, flags=flags,
|
||||
measure=False))
|
||||
|
||||
gen_area_content(attr, areas)
|
||||
|
||||
return areas + free_areas
|
||||
|
||||
|
||||
def generate_measurement(attr, areas):
|
||||
# pylint: disable=too-many-statements,too-many-branches,too-many-locals
|
||||
|
||||
def do_ecreate(digest, size):
|
||||
data = struct.pack("<8sLQ44s", b"ECREATE", SSAFRAMESIZE // offs.PAGESIZE,
|
||||
size, b"")
|
||||
digest.update(data)
|
||||
|
||||
def do_eadd(digest, offset, flags):
|
||||
data = struct.pack("<8sQQ40s", b"EADD", offset, flags, b"")
|
||||
digest.update(data)
|
||||
|
||||
def do_eextend(digest, offset, content):
|
||||
if len(content) != 256:
|
||||
raise ValueError("Exactly 256 bytes expected")
|
||||
|
||||
data = struct.pack("<8sQ48s", b"EEXTEND", offset, b"")
|
||||
digest.update(data)
|
||||
digest.update(content)
|
||||
|
||||
def include_page(digest, offset, flags, content, measure):
|
||||
if len(content) != offs.PAGESIZE:
|
||||
raise ValueError("Exactly one page expected")
|
||||
|
||||
do_eadd(digest, offset, flags)
|
||||
if measure:
|
||||
for i in range(0, offs.PAGESIZE, 256):
|
||||
do_eextend(digest, offset + i, content[i:i + 256])
|
||||
|
||||
mrenclave = hashlib.sha256()
|
||||
do_ecreate(mrenclave, attr['enclave_size'])
|
||||
|
||||
def print_area(addr, size, flags, desc, measured):
|
||||
if flags & PAGEINFO_REG:
|
||||
type_ = 'REG'
|
||||
if flags & PAGEINFO_TCS:
|
||||
type_ = 'TCS'
|
||||
prot = ['-', '-', '-']
|
||||
if flags & PAGEINFO_R:
|
||||
prot[0] = 'R'
|
||||
if flags & PAGEINFO_W:
|
||||
prot[1] = 'W'
|
||||
if flags & PAGEINFO_X:
|
||||
prot[2] = 'X'
|
||||
prot = ''.join(prot)
|
||||
|
||||
desc = '(' + desc + ')'
|
||||
if measured:
|
||||
desc += ' measured'
|
||||
|
||||
if size == offs.PAGESIZE:
|
||||
print(" %016x [%s:%s] %s" % (addr, type_, prot, desc))
|
||||
else:
|
||||
print(" %016x-%016lx [%s:%s] %s" %
|
||||
(addr, addr + size, type_, prot, desc))
|
||||
|
||||
def load_file(digest, file, offset, addr, filesize, memsize, desc, flags):
|
||||
# pylint: disable=too-many-arguments
|
||||
f_addr = rounddown(offset)
|
||||
m_addr = rounddown(addr)
|
||||
m_size = roundup(addr + memsize) - m_addr
|
||||
|
||||
print_area(m_addr, m_size, flags, desc, True)
|
||||
|
||||
for page in range(m_addr, m_addr + m_size, offs.PAGESIZE):
|
||||
start = page - m_addr + f_addr
|
||||
end = start + offs.PAGESIZE
|
||||
start_zero = b""
|
||||
if start < offset:
|
||||
if offset - start >= offs.PAGESIZE:
|
||||
start_zero = ZERO_PAGE
|
||||
else:
|
||||
start_zero = bytes(offset - start)
|
||||
end_zero = b""
|
||||
if end > offset + filesize:
|
||||
if end - offset - filesize >= offs.PAGESIZE:
|
||||
end_zero = ZERO_PAGE
|
||||
else:
|
||||
end_zero = bytes(end - offset - filesize)
|
||||
start += len(start_zero)
|
||||
end -= len(end_zero)
|
||||
if start < end:
|
||||
file.seek(start)
|
||||
data = file.read(end - start)
|
||||
else:
|
||||
data = b""
|
||||
if len(start_zero + data + end_zero) != offs.PAGESIZE:
|
||||
raise Exception("wrong calculation")
|
||||
|
||||
include_page(digest, page, flags, start_zero + data + end_zero, True)
|
||||
|
||||
for area in areas:
|
||||
if area.file:
|
||||
with open(area.file, 'rb') as file:
|
||||
if area.is_binary:
|
||||
loadcmds = get_loadcmds(area.file)
|
||||
if loadcmds:
|
||||
mapaddr = 0xffffffffffffffff
|
||||
for (offset, addr, filesize, memsize,
|
||||
prot) in loadcmds:
|
||||
if rounddown(addr) < mapaddr:
|
||||
mapaddr = rounddown(addr)
|
||||
baseaddr_ = area.addr - mapaddr
|
||||
for (offset, addr, filesize, memsize, prot) in loadcmds:
|
||||
flags = area.flags
|
||||
if prot & 4:
|
||||
flags = flags | PAGEINFO_R
|
||||
if prot & 2:
|
||||
flags = flags | PAGEINFO_W
|
||||
if prot & 1:
|
||||
flags = flags | PAGEINFO_X
|
||||
|
||||
if flags & PAGEINFO_X:
|
||||
desc = 'code'
|
||||
else:
|
||||
desc = 'data'
|
||||
load_file(mrenclave, file, offset, baseaddr_ + addr,
|
||||
filesize, memsize, desc, flags)
|
||||
else:
|
||||
load_file(mrenclave, file, 0, area.addr,
|
||||
os.stat(area.file).st_size, area.size,
|
||||
area.desc, area.flags)
|
||||
else:
|
||||
for addr in range(area.addr, area.addr + area.size, offs.PAGESIZE):
|
||||
data = ZERO_PAGE
|
||||
if area.content is not None:
|
||||
start = addr - area.addr
|
||||
end = start + offs.PAGESIZE
|
||||
data = area.content[start:end]
|
||||
|
||||
include_page(mrenclave, addr, area.flags, data, area.measure)
|
||||
|
||||
print_area(area.addr, area.size, area.flags, area.desc,
|
||||
area.measure)
|
||||
|
||||
return mrenclave.digest()
|
||||
|
||||
|
||||
def generate_sigstruct(attr, args, mrenclave):
|
||||
'''Generate Sigstruct.
|
||||
|
||||
field format: (offset, type, value)
|
||||
''' # pylint: disable=too-many-locals
|
||||
|
||||
fields = {
|
||||
'header': (offs.SGX_ARCH_ENCLAVE_CSS_HEADER,
|
||||
"<4L", 0x00000006, 0x000000e1, 0x00010000, 0x00000000),
|
||||
'module_vendor': (offs.SGX_ARCH_ENCLAVE_CSS_MODULE_VENDOR, "<L", 0x00000000),
|
||||
'date': (offs.SGX_ARCH_ENCLAVE_CSS_DATE, "<HBB", attr['year'], attr['month'], attr['day']),
|
||||
'header2': (offs.SGX_ARCH_ENCLAVE_CSS_HEADER2,
|
||||
"<4L", 0x00000101, 0x00000060, 0x00000060, 0x00000001),
|
||||
'hw_version': (offs.SGX_ARCH_ENCLAVE_CSS_HW_VERSION, "<L", 0x00000000),
|
||||
'misc_select': (offs.SGX_ARCH_ENCLAVE_CSS_MISC_SELECT, "4s", attr['misc_select']),
|
||||
'misc_mask': (offs.SGX_ARCH_ENCLAVE_CSS_MISC_MASK, "4s", attr['misc_select']),
|
||||
'attributes': (offs.SGX_ARCH_ENCLAVE_CSS_ATTRIBUTES, "8s8s", attr['flags'], attr['xfrms']),
|
||||
'attribute_mask': (offs.SGX_ARCH_ENCLAVE_CSS_ATTRIBUTE_MASK,
|
||||
"8s8s", attr['flags'], attr['xfrms']),
|
||||
'enclave_hash': (offs.SGX_ARCH_ENCLAVE_CSS_ENCLAVE_HASH, "32s", mrenclave),
|
||||
'isv_prod_id': (offs.SGX_ARCH_ENCLAVE_CSS_ISV_PROD_ID, "<H", attr['isv_prod_id']),
|
||||
'isv_svn': (offs.SGX_ARCH_ENCLAVE_CSS_ISV_SVN, "<H", attr['isv_svn']),
|
||||
}
|
||||
|
||||
sign_buffer = bytearray(128 + 128)
|
||||
|
||||
for field in fields.values():
|
||||
if field[0] >= offs.SGX_ARCH_ENCLAVE_CSS_MISC_SELECT:
|
||||
struct.pack_into(field[1], sign_buffer,
|
||||
field[0] - offs.SGX_ARCH_ENCLAVE_CSS_MISC_SELECT + 128,
|
||||
*field[2:])
|
||||
else:
|
||||
struct.pack_into(field[1], sign_buffer, field[0], *field[2:])
|
||||
|
||||
proc = subprocess.Popen(
|
||||
['openssl', 'rsa', '-modulus', '-in', args['key'], '-noout'],
|
||||
stdout=subprocess.PIPE)
|
||||
modulus_out, _ = proc.communicate()
|
||||
modulus = bytes.fromhex(modulus_out[8:8+offs.SE_KEY_SIZE*2].decode())
|
||||
modulus = bytes(reversed(modulus))
|
||||
|
||||
proc = subprocess.Popen(
|
||||
['openssl', 'sha256', '-binary', '-sign', args['key']],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
signature, _ = proc.communicate(sign_buffer)
|
||||
signature = signature[::-1]
|
||||
|
||||
modulus_int = int.from_bytes(modulus, byteorder='little')
|
||||
signature_int = int.from_bytes(signature, byteorder='little')
|
||||
|
||||
tmp1 = signature_int * signature_int
|
||||
q1_int = tmp1 // modulus_int
|
||||
tmp2 = tmp1 % modulus_int
|
||||
q2_int = tmp2 * signature_int // modulus_int
|
||||
|
||||
q1 = q1_int.to_bytes(384, byteorder='little') # pylint: disable=invalid-name
|
||||
q2 = q2_int.to_bytes(384, byteorder='little') # pylint: disable=invalid-name
|
||||
|
||||
fields.update({
|
||||
'modulus': (offs.SGX_ARCH_ENCLAVE_CSS_MODULUS, "384s", modulus),
|
||||
'exponent': (offs.SGX_ARCH_ENCLAVE_CSS_EXPONENT, "<L", 3),
|
||||
'signature': (offs.SGX_ARCH_ENCLAVE_CSS_SIGNATURE, "384s", signature),
|
||||
|
||||
'q1': (offs.SGX_ARCH_ENCLAVE_CSS_Q1, "384s", q1),
|
||||
'q2': (offs.SGX_ARCH_ENCLAVE_CSS_Q2, "384s", q2),
|
||||
})
|
||||
|
||||
buffer = bytearray(offs.SGX_ARCH_ENCLAVE_CSS_SIZE)
|
||||
|
||||
for field in fields.values():
|
||||
struct.pack_into(field[1], buffer, field[0], *field[2:])
|
||||
|
||||
return buffer
|
||||
|
||||
|
||||
# Main Program
|
||||
|
||||
argparser = argparse.ArgumentParser(
|
||||
epilog='With sign mode(without -depend), libpal and key are also required. '
|
||||
'exec and libpal may be given through manifest options '
|
||||
'loader.exec and sgx.enclave_pal_file.')
|
||||
argparser.add_argument('--output', '-output', metavar='OUTPUT',
|
||||
type=str, required=True,
|
||||
help='Output .manifest.sgx file '
|
||||
'(manifest augmented with autogenerated fields)')
|
||||
argparser.add_argument('--libpal', '-libpal', metavar='LIBPAL',
|
||||
type=str, required=False,
|
||||
help='Input libpal file '
|
||||
'(required as part of the enclave measurement)')
|
||||
argparser.add_argument('--key', '-key', metavar='KEY',
|
||||
type=str, required=False,
|
||||
help='specify signing key(.pem) file')
|
||||
argparser.add_argument('--manifest', '-manifest', metavar='MANIFEST',
|
||||
type=str, required=True,
|
||||
help='Input .manifest file '
|
||||
'(user-prepared manifest template)')
|
||||
argparser.add_argument('--exec', '-exec', metavar='EXEC',
|
||||
type=str, required=False,
|
||||
help='Input executable file '
|
||||
'(required as part of the enclave measurement)')
|
||||
argparser.add_argument('--depend', '-depend',
|
||||
action='store_true', required=False,
|
||||
help='Generate dependency for Makefile')
|
||||
|
||||
|
||||
def parse_args(args):
|
||||
args = argparser.parse_args(args)
|
||||
args_dict = {
|
||||
'output': args.output,
|
||||
'libpal': args.libpal,
|
||||
'key': args.key,
|
||||
'manifest': args.manifest,
|
||||
}
|
||||
if args.exec is not None:
|
||||
args_dict['exec'] = args.exec
|
||||
if args.depend:
|
||||
args_dict['depend'] = True
|
||||
else:
|
||||
# key is required and not found in manifest
|
||||
if args.key is None:
|
||||
argparser.error("a key is required to sign")
|
||||
return None
|
||||
|
||||
return args_dict
|
||||
|
||||
|
||||
def main_sign(args):
|
||||
# pylint: disable=too-many-statements,too-many-branches,too-many-locals
|
||||
manifest, manifest_layout = read_manifest(args['manifest'])
|
||||
|
||||
if exec_sig_manifest(args, manifest) != 0:
|
||||
return 1
|
||||
|
||||
# Get attributes from manifest
|
||||
attr = dict()
|
||||
|
||||
parse_int = functools.partial(int, base=0)
|
||||
|
||||
for key, default, parse, attr_key in [
|
||||
('enclave_size', DEFAULT_ENCLAVE_SIZE, parse_size, 'enclave_size'),
|
||||
('thread_num', str(DEFAULT_THREAD_NUM), parse_int, 'thread_num'),
|
||||
('isvprodid', '0', parse_int, 'isv_prod_id'),
|
||||
('isvsvn', '0', parse_int, 'isv_svn'),
|
||||
]:
|
||||
attr[attr_key] = parse(manifest.setdefault('sgx.' + key, default))
|
||||
|
||||
(attr['flags'], attr['xfrms'], attr['misc_select']) = get_enclave_attributes(manifest)
|
||||
|
||||
today = datetime.date.today()
|
||||
attr['year'] = today.year
|
||||
attr['month'] = today.month
|
||||
attr['day'] = today.day
|
||||
|
||||
print("Attributes:")
|
||||
print(" size: %d" % attr['enclave_size'])
|
||||
print(" thread_num: %d" % attr['thread_num'])
|
||||
print(" isv_prod_id: %d" % attr['isv_prod_id'])
|
||||
print(" isv_svn: %d" % attr['isv_svn'])
|
||||
print(" attr.flags: %016x" % int.from_bytes(attr['flags'], byteorder='big'))
|
||||
print(" attr.xfrm: %016x" % int.from_bytes(attr['xfrms'], byteorder='big'))
|
||||
print(" misc_select: %08x" % int.from_bytes(attr['misc_select'], byteorder='big'))
|
||||
print(" date: %d-%02d-%02d" % (attr['year'], attr['month'], attr['day']))
|
||||
|
||||
# Check client info for remote attestation
|
||||
# (if sgx.ra_client.spid is provided)
|
||||
print("Attestation:")
|
||||
if 'sgx.ra_client_spid' in manifest and manifest['sgx.ra_client_spid']:
|
||||
print(" spid: " + manifest['sgx.ra_client_spid'])
|
||||
if 'sgx.ra_client_key' in manifest and manifest['sgx.ra_client_key']:
|
||||
print(" key: " + manifest['sgx.ra_client_key'])
|
||||
else:
|
||||
print(" *** sgx.ra_client_key not specified ***")
|
||||
return 1
|
||||
if 'sgx.ra_client_linkable' in manifest:
|
||||
print(" linkable: " + manifest['sgx.ra_client_linkable'])
|
||||
else:
|
||||
print(" linkable: 0")
|
||||
else:
|
||||
print(" *** Client info is not specified. Graphene will not perform"
|
||||
" remote attestation before execution. Please provide"
|
||||
" sgx.ra_client_spid and sgx.ra_client_key in the manifest. ***")
|
||||
|
||||
# Get trusted checksums and measurements
|
||||
print("Trusted files:")
|
||||
for key, val in get_trusted_files(manifest, args).items():
|
||||
(uri, _, checksum) = val
|
||||
print(" %s %s" % (checksum, uri))
|
||||
manifest['sgx.trusted_checksum.' + key] = checksum
|
||||
|
||||
print("Trusted children:")
|
||||
for key, val in get_trusted_children(manifest).items():
|
||||
(uri, _, mrenclave) = val
|
||||
print(" %s %s" % (mrenclave, uri))
|
||||
manifest['sgx.trusted_mrenclave.' + key] = mrenclave
|
||||
|
||||
# Try populate memory areas
|
||||
memory_areas = get_memory_areas(attr, args)
|
||||
|
||||
if manifest.get('sgx.static_address', None) is None:
|
||||
# If static_address is not specified explicitly, deduce from executable
|
||||
if any([a.addr is not None for a in memory_areas]):
|
||||
manifest['sgx.static_address'] = '1'
|
||||
else:
|
||||
global ENCLAVE_HEAP_MIN # pylint: disable=global-statement
|
||||
ENCLAVE_HEAP_MIN = 0
|
||||
manifest['sgx.static_address'] = '0'
|
||||
|
||||
if manifest.get('sgx.allow_file_creation', None) is None:
|
||||
manifest['sgx.allow_file_creation'] = '0'
|
||||
|
||||
output_manifest(args['output'], manifest, manifest_layout)
|
||||
|
||||
memory_areas = [
|
||||
MemoryArea('manifest', file=args['output'],
|
||||
flags=PAGEINFO_R | PAGEINFO_REG)
|
||||
] + memory_areas
|
||||
|
||||
memory_areas = populate_memory_areas(attr, memory_areas)
|
||||
|
||||
print("Memory:")
|
||||
# Generate measurement
|
||||
mrenclave = generate_measurement(attr, memory_areas)
|
||||
print("Measurement:")
|
||||
print(" %s" % mrenclave.hex())
|
||||
|
||||
# Generate sigstruct
|
||||
open(args['sigfile'], 'wb').write(
|
||||
generate_sigstruct(attr, args, mrenclave))
|
||||
return 0
|
||||
|
||||
|
||||
def make_depend(args):
|
||||
manifest_file = args['manifest']
|
||||
output = args['output']
|
||||
|
||||
(manifest, _) = read_manifest(manifest_file)
|
||||
if exec_sig_manifest(args, manifest) != 0:
|
||||
return 1
|
||||
|
||||
dependencies = set()
|
||||
for filename in get_trusted_files(manifest, args, check_exist=False,
|
||||
do_checksum=False).values():
|
||||
dependencies.add(filename[1])
|
||||
for filename in get_trusted_children(manifest, check_exist=False,
|
||||
do_checksum=False).values():
|
||||
dependencies.add(filename[1])
|
||||
dependencies.add(args['libpal'])
|
||||
|
||||
with open(output, 'w') as file:
|
||||
manifest_sgx = output
|
||||
if manifest_sgx.endswith('.d'):
|
||||
manifest_sgx = manifest_sgx[:-len('.d')]
|
||||
file.write('%s %s:' % (manifest_sgx, args['sigfile']))
|
||||
for filename in dependencies:
|
||||
file.write(' \\\n\t%s' % filename)
|
||||
file.write('\n')
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def main(args=None):
|
||||
args = parse_args(args)
|
||||
if args is None:
|
||||
return 1
|
||||
|
||||
if args.get('depend'):
|
||||
return make_depend(args)
|
||||
return main_sign(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
sys.exit(main())
|
||||
|
||||
@@ -0,0 +1,941 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import functools
|
||||
import hashlib
|
||||
import os
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
import generated_offsets as offs # pylint: disable=import-error,wrong-import-position
|
||||
|
||||
# pylint: enable=invalid-name
|
||||
|
||||
# Default / Architectural Options
|
||||
|
||||
ARCHITECTURE = "amd64"
|
||||
|
||||
SSAFRAMESIZE = offs.PAGESIZE
|
||||
|
||||
DEFAULT_ENCLAVE_SIZE = '256M'
|
||||
DEFAULT_THREAD_NUM = 4
|
||||
ENCLAVE_HEAP_MIN = offs.DEFAULT_HEAP_MIN
|
||||
|
||||
|
||||
# Utilities
|
||||
|
||||
ZERO_PAGE = bytes(offs.PAGESIZE)
|
||||
|
||||
|
||||
def roundup(addr):
|
||||
remaining = addr % offs.PAGESIZE
|
||||
if remaining:
|
||||
return addr + (offs.PAGESIZE - remaining)
|
||||
return addr
|
||||
|
||||
|
||||
def rounddown(addr):
|
||||
return addr - addr % offs.PAGESIZE
|
||||
|
||||
|
||||
def parse_size(value):
|
||||
scale = 1
|
||||
if value.endswith("K"):
|
||||
scale = 1024
|
||||
if value.endswith("M"):
|
||||
scale = 1024 * 1024
|
||||
if value.endswith("G"):
|
||||
scale = 1024 * 1024 * 1024
|
||||
if scale != 1:
|
||||
value = value[:-1]
|
||||
return int(value, 0) * scale
|
||||
|
||||
|
||||
# Reading / Writing Manifests
|
||||
|
||||
def read_manifest(filename):
|
||||
manifest = dict()
|
||||
manifest_layout = []
|
||||
with open(filename, "r") as file:
|
||||
for line in file:
|
||||
if line == "":
|
||||
manifest_layout.append((None, None))
|
||||
break
|
||||
|
||||
pound = line.find("#")
|
||||
if pound != -1:
|
||||
comment = line[pound:].strip()
|
||||
line = line[:pound]
|
||||
else:
|
||||
comment = None
|
||||
|
||||
line = line.strip()
|
||||
equal = line.find("=")
|
||||
if equal != -1:
|
||||
key = line[:equal].strip()
|
||||
manifest[key] = line[equal + 1:].strip()
|
||||
else:
|
||||
key = None
|
||||
|
||||
manifest_layout.append((key, comment))
|
||||
|
||||
return (manifest, manifest_layout)
|
||||
|
||||
|
||||
def exec_sig_manifest(args, manifest):
|
||||
if 'exec' not in args or args.get('depend'):
|
||||
if 'loader.exec' in manifest:
|
||||
args['exec'] = resolve_manifest_uri(args['manifest'],
|
||||
manifest['loader.exec'])
|
||||
|
||||
if 'sgx.sigfile' in manifest:
|
||||
args['sigfile'] = resolve_uri(manifest['sgx.sigfile'],
|
||||
check_exist=False)
|
||||
else:
|
||||
sigfile = args['output']
|
||||
for ext in ['.manifest.sgx.d', '.manifest.sgx', '.manifest']:
|
||||
if sigfile.endswith(ext):
|
||||
sigfile = sigfile[:-len(ext)]
|
||||
break
|
||||
args['sigfile'] = sigfile + '.sig'
|
||||
manifest['sgx.sigfile'] = 'file:' + os.path.basename(args['sigfile'])
|
||||
|
||||
if args.get('libpal', None) is None:
|
||||
if 'sgx.enclave_pal_file' in manifest:
|
||||
args['libpal'] = resolve_manifest_uri(args['manifest'],
|
||||
manifest['sgx.enclave_pal_file'])
|
||||
else:
|
||||
print("Either --libpal or sgx.enclave_pal_file must be given", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def output_manifest(filename, manifest, manifest_layout):
|
||||
with open(filename, 'w') as file:
|
||||
written = []
|
||||
|
||||
file.write('# DO NOT MODIFY. THIS FILE WAS AUTO-GENERATED.\n\n')
|
||||
|
||||
for (key, comment) in manifest_layout:
|
||||
line = ''
|
||||
if key is not None:
|
||||
line += key + ' = ' + manifest[key]
|
||||
written.append(key)
|
||||
if comment is not None:
|
||||
if line != '':
|
||||
line += ' '
|
||||
line += comment
|
||||
file.write(line)
|
||||
file.write('\n')
|
||||
|
||||
file.write('\n')
|
||||
file.write('# Generated by Graphene\n')
|
||||
file.write('\n')
|
||||
|
||||
for key in sorted(manifest):
|
||||
if key not in written:
|
||||
file.write("%s = %s\n" % (key, manifest[key]))
|
||||
|
||||
|
||||
# Loading Enclave Attributes
|
||||
|
||||
def get_enclave_attributes(manifest):
|
||||
sgx_flags = {
|
||||
'FLAG_DEBUG': struct.pack("<Q", offs.SGX_FLAGS_DEBUG),
|
||||
'FLAG_MODE64BIT': struct.pack("<Q", offs.SGX_FLAGS_MODE64BIT),
|
||||
}
|
||||
|
||||
sgx_xfrms = {
|
||||
'XFRM_LEGACY': struct.pack("<Q", offs.SGX_XFRM_LEGACY),
|
||||
'XFRM_AVX': struct.pack("<Q", offs.SGX_XFRM_AVX),
|
||||
'XFRM_AVX512': struct.pack("<Q", offs.SGX_XFRM_AVX512),
|
||||
'XFRM_MPX': struct.pack("<Q", offs.SGX_XFRM_MPX),
|
||||
}
|
||||
|
||||
sgx_miscs = {
|
||||
'MISC_EXINFO': struct.pack("<L", offs.SGX_MISCSELECT_EXINFO),
|
||||
}
|
||||
|
||||
default_attributes = {
|
||||
'FLAG_DEBUG',
|
||||
'XFRM_LEGACY',
|
||||
}
|
||||
|
||||
if ARCHITECTURE == 'amd64':
|
||||
default_attributes.add('FLAG_MODE64BIT')
|
||||
|
||||
manifest_options = {
|
||||
'debug': 'FLAG_DEBUG',
|
||||
'require_avx': 'XFRM_AVX',
|
||||
'require_avx512': 'XFRM_AVX512',
|
||||
'require_mpx': 'XFRM_MPX',
|
||||
'support_exinfo': 'MISC_EXINFO',
|
||||
}
|
||||
|
||||
attributes = default_attributes
|
||||
|
||||
for opt in manifest_options:
|
||||
key = 'sgx.' + opt
|
||||
if key in manifest:
|
||||
if manifest[key] == '1':
|
||||
attributes.add(manifest_options[opt])
|
||||
else:
|
||||
attributes.discard(manifest_options[opt])
|
||||
|
||||
flags_raw = struct.pack("<Q", 0)
|
||||
xfrms_raw = struct.pack("<Q", 0)
|
||||
miscs_raw = struct.pack("<L", 0)
|
||||
|
||||
for attr in attributes:
|
||||
if attr in sgx_flags:
|
||||
flags_raw = bytes([a | b for a, b in
|
||||
zip(flags_raw, sgx_flags[attr])])
|
||||
if attr in sgx_xfrms:
|
||||
xfrms_raw = bytes([a | b for a, b in
|
||||
zip(xfrms_raw, sgx_xfrms[attr])])
|
||||
if attr in sgx_miscs:
|
||||
miscs_raw = bytes([a | b for a, b in
|
||||
zip(miscs_raw, sgx_miscs[attr])])
|
||||
|
||||
return flags_raw, xfrms_raw, miscs_raw
|
||||
|
||||
|
||||
# Generate Checksums / Measurement
|
||||
|
||||
def resolve_uri(uri, check_exist=True):
|
||||
orig_uri = uri
|
||||
if uri.startswith('file:'):
|
||||
target = os.path.normpath(uri[len('file:'):])
|
||||
else:
|
||||
target = os.path.normpath(uri)
|
||||
if check_exist and not os.path.exists(target):
|
||||
raise Exception(
|
||||
'Cannot resolve ' + orig_uri + ' or the file does not exist.')
|
||||
return target
|
||||
|
||||
# Resolve an URI relative to manifest file to its absolute path
|
||||
def resolve_manifest_uri(manifest_path, uri):
|
||||
if not uri.startswith('file:'):
|
||||
raise Exception('URI ' + uri + ' is not a local file')
|
||||
path = uri[len('file:'):]
|
||||
if os.path.isabs(path):
|
||||
return path
|
||||
return os.path.join(os.path.dirname(manifest_path), path)
|
||||
|
||||
def get_checksum(filename):
|
||||
digest = hashlib.sha256()
|
||||
with open(filename, 'rb') as file:
|
||||
digest.update(file.read())
|
||||
return digest.digest()
|
||||
|
||||
|
||||
def get_trusted_files(manifest, args, check_exist=True, do_checksum=True):
|
||||
targets = dict()
|
||||
|
||||
if 'exec' in args:
|
||||
targets['exec'] = (args['exec'], resolve_uri(args['exec'],
|
||||
check_exist))
|
||||
|
||||
if 'loader.preload' in manifest:
|
||||
for i, uri in enumerate(str.split(manifest['loader.preload'], ',')):
|
||||
targets['preload' + str(i)] = (uri, resolve_uri(uri, check_exist))
|
||||
|
||||
for (key, val) in manifest.items():
|
||||
if not key.startswith('sgx.trusted_files.'):
|
||||
continue
|
||||
key = key[len('sgx.trusted_files.'):]
|
||||
if key in targets:
|
||||
raise Exception(
|
||||
'repeated key in manifest: sgx.trusted_files.' + key)
|
||||
targets[key] = (val, resolve_uri(val, check_exist))
|
||||
|
||||
if do_checksum:
|
||||
for (key, val) in targets.items():
|
||||
(uri, target) = val
|
||||
checksum = get_checksum(target).hex()
|
||||
targets[key] = (uri, target, checksum)
|
||||
|
||||
return targets
|
||||
|
||||
|
||||
def get_trusted_children(manifest, check_exist=True, do_checksum=True):
|
||||
targets = dict()
|
||||
|
||||
for (key, val) in manifest.items():
|
||||
if not key.startswith('sgx.trusted_children.'):
|
||||
continue
|
||||
key = key[len('sgx.trusted_children.'):]
|
||||
if key in targets:
|
||||
raise Exception(
|
||||
'repeated key in manifest: sgx.trusted_children.' + key)
|
||||
|
||||
target = resolve_uri(val, check_exist)
|
||||
if not target.endswith('.sig'):
|
||||
target += '.sig'
|
||||
if do_checksum:
|
||||
sig = open(target, 'rb').read()[
|
||||
offs.SGX_ARCH_ENCLAVE_CSS_ENCLAVE_HASH:
|
||||
offs.SGX_ARCH_ENCLAVE_CSS_ENCLAVE_HASH + offs.SGX_HASH_SIZE].hex()
|
||||
targets[key] = (val, target, sig)
|
||||
else:
|
||||
targets[key] = (val, target)
|
||||
return targets
|
||||
|
||||
|
||||
# Populate Enclave Memory
|
||||
|
||||
PAGEINFO_R = 0x1
|
||||
PAGEINFO_W = 0x2
|
||||
PAGEINFO_X = 0x4
|
||||
PAGEINFO_TCS = 0x100
|
||||
PAGEINFO_REG = 0x200
|
||||
|
||||
|
||||
def get_loadcmds(filename):
|
||||
loadcmds = []
|
||||
proc = subprocess.Popen(['readelf', '-l', '-W', filename],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
while True:
|
||||
line = proc.stdout.readline()
|
||||
if not line:
|
||||
break
|
||||
line = line.decode()
|
||||
stripped = line.strip()
|
||||
if not stripped.startswith('LOAD'):
|
||||
continue
|
||||
tokens = stripped.split()
|
||||
if len(tokens) < 6:
|
||||
continue
|
||||
if len(tokens) >= 7 and tokens[7] == "E":
|
||||
tokens[6] += tokens[7]
|
||||
prot = 0
|
||||
for token in tokens[6]:
|
||||
if token == "R":
|
||||
prot = prot | 4
|
||||
if token == "W":
|
||||
prot = prot | 2
|
||||
if token == "E":
|
||||
prot = prot | 1
|
||||
|
||||
loadcmds.append((int(tokens[1][2:], 16), # offset
|
||||
int(tokens[2][2:], 16), # addr
|
||||
int(tokens[4][2:], 16), # filesize
|
||||
int(tokens[5][2:], 16), # memsize
|
||||
prot))
|
||||
proc.wait()
|
||||
if proc.returncode != 0:
|
||||
return None
|
||||
return loadcmds
|
||||
|
||||
|
||||
class MemoryArea:
|
||||
# pylint: disable=too-few-public-methods,too-many-instance-attributes
|
||||
def __init__(self, desc, file=None, content=None, addr=None, size=None,
|
||||
flags=None, measure=True):
|
||||
# pylint: disable=too-many-arguments
|
||||
self.desc = desc
|
||||
self.file = file
|
||||
self.content = content
|
||||
self.addr = addr
|
||||
self.size = size
|
||||
self.flags = flags
|
||||
self.is_binary = False
|
||||
self.measure = measure
|
||||
|
||||
if file:
|
||||
loadcmds = get_loadcmds(file)
|
||||
if loadcmds:
|
||||
mapaddr = 0xffffffffffffffff
|
||||
mapaddr_end = 0
|
||||
for (_, addr_, _, memsize, _) in loadcmds:
|
||||
if rounddown(addr_) < mapaddr:
|
||||
mapaddr = rounddown(addr_)
|
||||
if roundup(addr_ + memsize) > mapaddr_end:
|
||||
mapaddr_end = roundup(addr_ + memsize)
|
||||
|
||||
self.is_binary = True
|
||||
self.size = mapaddr_end - mapaddr
|
||||
if mapaddr > 0:
|
||||
self.addr = mapaddr
|
||||
else:
|
||||
self.size = os.stat(file).st_size
|
||||
|
||||
if self.addr is not None:
|
||||
self.addr = rounddown(self.addr)
|
||||
if self.size is not None:
|
||||
self.size = roundup(self.size)
|
||||
|
||||
|
||||
def get_memory_areas(attr, args):
|
||||
areas = []
|
||||
areas.append(
|
||||
MemoryArea('ssa',
|
||||
size=attr['thread_num'] * SSAFRAMESIZE * offs.SSAFRAMENUM,
|
||||
flags=PAGEINFO_R | PAGEINFO_W | PAGEINFO_REG))
|
||||
areas.append(MemoryArea('tcs', size=attr['thread_num'] * offs.TCS_SIZE,
|
||||
flags=PAGEINFO_TCS))
|
||||
areas.append(MemoryArea('tls', size=attr['thread_num'] * offs.PAGESIZE,
|
||||
flags=PAGEINFO_R | PAGEINFO_W | PAGEINFO_REG))
|
||||
|
||||
for _ in range(attr['thread_num']):
|
||||
areas.append(MemoryArea('stack', size=offs.ENCLAVE_STACK_SIZE,
|
||||
flags=PAGEINFO_R | PAGEINFO_W | PAGEINFO_REG))
|
||||
for _ in range(attr['thread_num']):
|
||||
areas.append(MemoryArea('sig_stack', size=offs.ENCLAVE_SIG_STACK_SIZE,
|
||||
flags=PAGEINFO_R | PAGEINFO_W | PAGEINFO_REG))
|
||||
|
||||
areas.append(MemoryArea('pal', file=args['libpal'], flags=PAGEINFO_REG))
|
||||
|
||||
if 'exec' in args:
|
||||
areas.append(MemoryArea('exec', file=args['exec'],
|
||||
flags=PAGEINFO_W | PAGEINFO_REG))
|
||||
return areas
|
||||
|
||||
|
||||
def find_areas(areas, desc):
|
||||
return [area for area in areas if area.desc == desc]
|
||||
|
||||
|
||||
def find_area(areas, desc, allow_none=False):
|
||||
matching = find_areas(areas, desc)
|
||||
|
||||
if not matching and allow_none:
|
||||
return None
|
||||
|
||||
if len(matching) != 1:
|
||||
raise KeyError(
|
||||
"Could not find exactly one MemoryArea '{}'".format(desc))
|
||||
|
||||
return matching[0]
|
||||
|
||||
|
||||
def entry_point(elf_path):
|
||||
env = os.environ
|
||||
env['LC_ALL'] = 'C'
|
||||
out = subprocess.check_output(
|
||||
['readelf', '-l', '--', elf_path], env=env)
|
||||
for line in out.splitlines():
|
||||
line = line.decode()
|
||||
if line.startswith("Entry point "):
|
||||
return int(line[12:], 0)
|
||||
raise ValueError("Could not find entry point of elf file")
|
||||
|
||||
|
||||
def baseaddr():
|
||||
if ENCLAVE_HEAP_MIN == 0:
|
||||
return offs.ENCLAVE_HIGH_ADDRESS
|
||||
return 0
|
||||
|
||||
|
||||
def gen_area_content(attr, areas):
|
||||
# pylint: disable=too-many-locals
|
||||
manifest_area = find_area(areas, 'manifest')
|
||||
exec_area = find_area(areas, 'exec', True)
|
||||
pal_area = find_area(areas, 'pal')
|
||||
ssa_area = find_area(areas, 'ssa')
|
||||
tcs_area = find_area(areas, 'tcs')
|
||||
tls_area = find_area(areas, 'tls')
|
||||
stacks = find_areas(areas, 'stack')
|
||||
sig_stacks = find_areas(areas, 'sig_stack')
|
||||
|
||||
tcs_data = bytearray(tcs_area.size)
|
||||
|
||||
def set_tcs_field(t, offset, pack_fmt, value):
|
||||
struct.pack_into(pack_fmt, tcs_data, t * offs.TCS_SIZE + offset, value)
|
||||
|
||||
tls_data = bytearray(tls_area.size)
|
||||
|
||||
def set_tls_field(t, offset, value):
|
||||
struct.pack_into('<Q', tls_data, t * offs.PAGESIZE + offset, value)
|
||||
|
||||
enclave_heap_max = pal_area.addr - offs.MEMORY_GAP
|
||||
|
||||
# Sanity check that we measure everything except the heap which is zeroed
|
||||
# on enclave startup.
|
||||
for area in areas:
|
||||
if (area.addr + area.size <= ENCLAVE_HEAP_MIN or
|
||||
area.addr >= enclave_heap_max or area is exec_area):
|
||||
if not area.measure:
|
||||
raise ValueError("Memory area, which is not the heap, "
|
||||
"is not measured")
|
||||
elif area.desc != 'free':
|
||||
raise ValueError("Unexpected memory area is in heap range")
|
||||
|
||||
for t in range(0, attr['thread_num']):
|
||||
ssa_offset = ssa_area.addr + SSAFRAMESIZE * offs.SSAFRAMENUM * t
|
||||
ssa = baseaddr() + ssa_offset
|
||||
set_tcs_field(t, offs.TCS_OSSA, '<Q', ssa_offset)
|
||||
set_tcs_field(t, offs.TCS_NSSA, '<L', offs.SSAFRAMENUM)
|
||||
set_tcs_field(t, offs.TCS_OENTRY, '<Q',
|
||||
pal_area.addr + entry_point(pal_area.file))
|
||||
set_tcs_field(t, offs.TCS_OGS_BASE, '<Q', tls_area.addr + offs.PAGESIZE * t)
|
||||
set_tcs_field(t, offs.TCS_OFS_LIMIT, '<L', 0xfff)
|
||||
set_tcs_field(t, offs.TCS_OGS_LIMIT, '<L', 0xfff)
|
||||
|
||||
set_tls_field(t, offs.SGX_COMMON_SELF,
|
||||
tls_area.addr + offs.PAGESIZE * t + baseaddr())
|
||||
set_tls_field(t, offs.SGX_ENCLAVE_SIZE, attr['enclave_size'])
|
||||
set_tls_field(t, offs.SGX_TCS_OFFSET, tcs_area.addr + offs.TCS_SIZE * t)
|
||||
set_tls_field(t, offs.SGX_INITIAL_STACK_OFFSET,
|
||||
stacks[t].addr + stacks[t].size)
|
||||
set_tls_field(t, offs.SGX_SIG_STACK_LOW, baseaddr() + sig_stacks[t].addr)
|
||||
set_tls_field(t, offs.SGX_SIG_STACK_HIGH,
|
||||
baseaddr() + sig_stacks[t].addr + sig_stacks[t].size)
|
||||
set_tls_field(t, offs.SGX_SSA, ssa)
|
||||
set_tls_field(t, offs.SGX_GPR, ssa + SSAFRAMESIZE - offs.SGX_GPR_SIZE)
|
||||
set_tls_field(t, offs.SGX_MANIFEST_SIZE,
|
||||
os.stat(manifest_area.file).st_size)
|
||||
set_tls_field(t, offs.SGX_HEAP_MIN, baseaddr() + ENCLAVE_HEAP_MIN)
|
||||
set_tls_field(t, offs.SGX_HEAP_MAX, baseaddr() + enclave_heap_max)
|
||||
if exec_area is not None:
|
||||
set_tls_field(t, offs.SGX_EXEC_ADDR, baseaddr() + exec_area.addr)
|
||||
set_tls_field(t, offs.SGX_EXEC_SIZE, exec_area.size)
|
||||
|
||||
tcs_area.content = tcs_data
|
||||
tls_area.content = tls_data
|
||||
|
||||
|
||||
def populate_memory_areas(attr, areas):
|
||||
populating = attr['enclave_size']
|
||||
|
||||
for area in areas:
|
||||
if area.addr is not None:
|
||||
continue
|
||||
|
||||
area.addr = populating - area.size
|
||||
if area.addr < ENCLAVE_HEAP_MIN:
|
||||
raise Exception("Enclave size is not large enough")
|
||||
populating = max(area.addr - offs.MEMORY_GAP, 0)
|
||||
|
||||
free_areas = []
|
||||
for area in areas:
|
||||
if area.addr + area.size + offs.MEMORY_GAP < populating:
|
||||
addr = area.addr + area.size + offs.MEMORY_GAP
|
||||
flags = PAGEINFO_R | PAGEINFO_W | PAGEINFO_X | PAGEINFO_REG
|
||||
free_areas.append(
|
||||
MemoryArea('free', addr=addr, size=populating - addr,
|
||||
flags=flags, measure=False))
|
||||
populating = max(area.addr - offs.MEMORY_GAP, 0)
|
||||
|
||||
if populating > ENCLAVE_HEAP_MIN:
|
||||
flags = PAGEINFO_R | PAGEINFO_W | PAGEINFO_X | PAGEINFO_REG
|
||||
free_areas.append(
|
||||
MemoryArea('free', addr=ENCLAVE_HEAP_MIN,
|
||||
size=populating - ENCLAVE_HEAP_MIN, flags=flags,
|
||||
measure=False))
|
||||
|
||||
gen_area_content(attr, areas)
|
||||
|
||||
return areas + free_areas
|
||||
|
||||
|
||||
def generate_measurement(attr, areas):
|
||||
# pylint: disable=too-many-statements,too-many-branches,too-many-locals
|
||||
|
||||
def do_ecreate(digest, size):
|
||||
data = struct.pack("<8sLQ44s", b"ECREATE", SSAFRAMESIZE // offs.PAGESIZE,
|
||||
size, b"")
|
||||
digest.update(data)
|
||||
|
||||
def do_eadd(digest, offset, flags):
|
||||
data = struct.pack("<8sQQ40s", b"EADD", offset, flags, b"")
|
||||
digest.update(data)
|
||||
|
||||
def do_eextend(digest, offset, content):
|
||||
if len(content) != 256:
|
||||
raise ValueError("Exactly 256 bytes expected")
|
||||
|
||||
data = struct.pack("<8sQ48s", b"EEXTEND", offset, b"")
|
||||
digest.update(data)
|
||||
digest.update(content)
|
||||
|
||||
def include_page(digest, offset, flags, content, measure):
|
||||
if len(content) != offs.PAGESIZE:
|
||||
raise ValueError("Exactly one page expected")
|
||||
|
||||
do_eadd(digest, offset, flags)
|
||||
if measure:
|
||||
for i in range(0, offs.PAGESIZE, 256):
|
||||
do_eextend(digest, offset + i, content[i:i + 256])
|
||||
|
||||
mrenclave = hashlib.sha256()
|
||||
do_ecreate(mrenclave, attr['enclave_size'])
|
||||
|
||||
def print_area(addr, size, flags, desc, measured):
|
||||
if flags & PAGEINFO_REG:
|
||||
type_ = 'REG'
|
||||
if flags & PAGEINFO_TCS:
|
||||
type_ = 'TCS'
|
||||
prot = ['-', '-', '-']
|
||||
if flags & PAGEINFO_R:
|
||||
prot[0] = 'R'
|
||||
if flags & PAGEINFO_W:
|
||||
prot[1] = 'W'
|
||||
if flags & PAGEINFO_X:
|
||||
prot[2] = 'X'
|
||||
prot = ''.join(prot)
|
||||
|
||||
desc = '(' + desc + ')'
|
||||
if measured:
|
||||
desc += ' measured'
|
||||
|
||||
if size == offs.PAGESIZE:
|
||||
print(" %016x [%s:%s] %s" % (addr, type_, prot, desc))
|
||||
else:
|
||||
print(" %016x-%016lx [%s:%s] %s" %
|
||||
(addr, addr + size, type_, prot, desc))
|
||||
|
||||
def load_file(digest, file, offset, addr, filesize, memsize, desc, flags):
|
||||
# pylint: disable=too-many-arguments
|
||||
f_addr = rounddown(offset)
|
||||
m_addr = rounddown(addr)
|
||||
m_size = roundup(addr + memsize) - m_addr
|
||||
|
||||
print_area(m_addr, m_size, flags, desc, True)
|
||||
|
||||
for page in range(m_addr, m_addr + m_size, offs.PAGESIZE):
|
||||
start = page - m_addr + f_addr
|
||||
end = start + offs.PAGESIZE
|
||||
start_zero = b""
|
||||
if start < offset:
|
||||
if offset - start >= offs.PAGESIZE:
|
||||
start_zero = ZERO_PAGE
|
||||
else:
|
||||
start_zero = bytes(offset - start)
|
||||
end_zero = b""
|
||||
if end > offset + filesize:
|
||||
if end - offset - filesize >= offs.PAGESIZE:
|
||||
end_zero = ZERO_PAGE
|
||||
else:
|
||||
end_zero = bytes(end - offset - filesize)
|
||||
start += len(start_zero)
|
||||
end -= len(end_zero)
|
||||
if start < end:
|
||||
file.seek(start)
|
||||
data = file.read(end - start)
|
||||
else:
|
||||
data = b""
|
||||
if len(start_zero + data + end_zero) != offs.PAGESIZE:
|
||||
raise Exception("wrong calculation")
|
||||
|
||||
include_page(digest, page, flags, start_zero + data + end_zero, True)
|
||||
|
||||
for area in areas:
|
||||
if area.file:
|
||||
with open(area.file, 'rb') as file:
|
||||
if area.is_binary:
|
||||
loadcmds = get_loadcmds(area.file)
|
||||
if loadcmds:
|
||||
mapaddr = 0xffffffffffffffff
|
||||
for (offset, addr, filesize, memsize,
|
||||
prot) in loadcmds:
|
||||
if rounddown(addr) < mapaddr:
|
||||
mapaddr = rounddown(addr)
|
||||
baseaddr_ = area.addr - mapaddr
|
||||
for (offset, addr, filesize, memsize, prot) in loadcmds:
|
||||
flags = area.flags
|
||||
if prot & 4:
|
||||
flags = flags | PAGEINFO_R
|
||||
if prot & 2:
|
||||
flags = flags | PAGEINFO_W
|
||||
if prot & 1:
|
||||
flags = flags | PAGEINFO_X
|
||||
|
||||
if flags & PAGEINFO_X:
|
||||
desc = 'code'
|
||||
else:
|
||||
desc = 'data'
|
||||
load_file(mrenclave, file, offset, baseaddr_ + addr,
|
||||
filesize, memsize, desc, flags)
|
||||
else:
|
||||
load_file(mrenclave, file, 0, area.addr,
|
||||
os.stat(area.file).st_size, area.size,
|
||||
area.desc, area.flags)
|
||||
else:
|
||||
for addr in range(area.addr, area.addr + area.size, offs.PAGESIZE):
|
||||
data = ZERO_PAGE
|
||||
if area.content is not None:
|
||||
start = addr - area.addr
|
||||
end = start + offs.PAGESIZE
|
||||
data = area.content[start:end]
|
||||
|
||||
include_page(mrenclave, addr, area.flags, data, area.measure)
|
||||
|
||||
print_area(area.addr, area.size, area.flags, area.desc,
|
||||
area.measure)
|
||||
|
||||
return mrenclave.digest()
|
||||
|
||||
|
||||
def generate_sigstruct(attr, args, mrenclave):
|
||||
'''Generate Sigstruct.
|
||||
|
||||
field format: (offset, type, value)
|
||||
''' # pylint: disable=too-many-locals
|
||||
|
||||
fields = {
|
||||
'header': (offs.SGX_ARCH_ENCLAVE_CSS_HEADER,
|
||||
"<4L", 0x00000006, 0x000000e1, 0x00010000, 0x00000000),
|
||||
'module_vendor': (offs.SGX_ARCH_ENCLAVE_CSS_MODULE_VENDOR, "<L", 0x00000000),
|
||||
'date': (offs.SGX_ARCH_ENCLAVE_CSS_DATE, "<HBB", attr['year'], attr['month'], attr['day']),
|
||||
'header2': (offs.SGX_ARCH_ENCLAVE_CSS_HEADER2,
|
||||
"<4L", 0x00000101, 0x00000060, 0x00000060, 0x00000001),
|
||||
'hw_version': (offs.SGX_ARCH_ENCLAVE_CSS_HW_VERSION, "<L", 0x00000000),
|
||||
'misc_select': (offs.SGX_ARCH_ENCLAVE_CSS_MISC_SELECT, "4s", attr['misc_select']),
|
||||
'misc_mask': (offs.SGX_ARCH_ENCLAVE_CSS_MISC_MASK, "4s", attr['misc_select']),
|
||||
'attributes': (offs.SGX_ARCH_ENCLAVE_CSS_ATTRIBUTES, "8s8s", attr['flags'], attr['xfrms']),
|
||||
'attribute_mask': (offs.SGX_ARCH_ENCLAVE_CSS_ATTRIBUTE_MASK,
|
||||
"8s8s", attr['flags'], attr['xfrms']),
|
||||
'enclave_hash': (offs.SGX_ARCH_ENCLAVE_CSS_ENCLAVE_HASH, "32s", mrenclave),
|
||||
'isv_prod_id': (offs.SGX_ARCH_ENCLAVE_CSS_ISV_PROD_ID, "<H", attr['isv_prod_id']),
|
||||
'isv_svn': (offs.SGX_ARCH_ENCLAVE_CSS_ISV_SVN, "<H", attr['isv_svn']),
|
||||
}
|
||||
|
||||
sign_buffer = bytearray(128 + 128)
|
||||
|
||||
for field in fields.values():
|
||||
if field[0] >= offs.SGX_ARCH_ENCLAVE_CSS_MISC_SELECT:
|
||||
struct.pack_into(field[1], sign_buffer,
|
||||
field[0] - offs.SGX_ARCH_ENCLAVE_CSS_MISC_SELECT + 128,
|
||||
*field[2:])
|
||||
else:
|
||||
struct.pack_into(field[1], sign_buffer, field[0], *field[2:])
|
||||
|
||||
proc = subprocess.Popen(
|
||||
['openssl', 'rsa', '-modulus', '-in', args['key'], '-noout'],
|
||||
stdout=subprocess.PIPE)
|
||||
modulus_out, _ = proc.communicate()
|
||||
modulus = bytes.fromhex(modulus_out[8:8+offs.SE_KEY_SIZE*2].decode())
|
||||
modulus = bytes(reversed(modulus))
|
||||
|
||||
proc = subprocess.Popen(
|
||||
['openssl', 'sha256', '-binary', '-sign', args['key']],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
signature, _ = proc.communicate(sign_buffer)
|
||||
signature = signature[::-1]
|
||||
|
||||
modulus_int = int.from_bytes(modulus, byteorder='little')
|
||||
signature_int = int.from_bytes(signature, byteorder='little')
|
||||
|
||||
tmp1 = signature_int * signature_int
|
||||
q1_int = tmp1 // modulus_int
|
||||
tmp2 = tmp1 % modulus_int
|
||||
q2_int = tmp2 * signature_int // modulus_int
|
||||
|
||||
q1 = q1_int.to_bytes(384, byteorder='little') # pylint: disable=invalid-name
|
||||
q2 = q2_int.to_bytes(384, byteorder='little') # pylint: disable=invalid-name
|
||||
|
||||
fields.update({
|
||||
'modulus': (offs.SGX_ARCH_ENCLAVE_CSS_MODULUS, "384s", modulus),
|
||||
'exponent': (offs.SGX_ARCH_ENCLAVE_CSS_EXPONENT, "<L", 3),
|
||||
'signature': (offs.SGX_ARCH_ENCLAVE_CSS_SIGNATURE, "384s", signature),
|
||||
|
||||
'q1': (offs.SGX_ARCH_ENCLAVE_CSS_Q1, "384s", q1),
|
||||
'q2': (offs.SGX_ARCH_ENCLAVE_CSS_Q2, "384s", q2),
|
||||
})
|
||||
|
||||
buffer = bytearray(offs.SGX_ARCH_ENCLAVE_CSS_SIZE)
|
||||
|
||||
for field in fields.values():
|
||||
struct.pack_into(field[1], buffer, field[0], *field[2:])
|
||||
|
||||
return buffer
|
||||
|
||||
|
||||
# Main Program
|
||||
|
||||
argparser = argparse.ArgumentParser(
|
||||
epilog='With sign mode(without -depend), libpal and key are also required. '
|
||||
'exec and libpal may be given through manifest options '
|
||||
'loader.exec and sgx.enclave_pal_file.')
|
||||
argparser.add_argument('--output', '-output', metavar='OUTPUT',
|
||||
type=str, required=True,
|
||||
help='Output .manifest.sgx file '
|
||||
'(manifest augmented with autogenerated fields)')
|
||||
argparser.add_argument('--libpal', '-libpal', metavar='LIBPAL',
|
||||
type=str, required=False,
|
||||
help='Input libpal file '
|
||||
'(required as part of the enclave measurement)')
|
||||
argparser.add_argument('--key', '-key', metavar='KEY',
|
||||
type=str, required=False,
|
||||
help='specify signing key(.pem) file')
|
||||
argparser.add_argument('--manifest', '-manifest', metavar='MANIFEST',
|
||||
type=str, required=True,
|
||||
help='Input .manifest file '
|
||||
'(user-prepared manifest template)')
|
||||
argparser.add_argument('--exec', '-exec', metavar='EXEC',
|
||||
type=str, required=False,
|
||||
help='Input executable file '
|
||||
'(required as part of the enclave measurement)')
|
||||
argparser.add_argument('--depend', '-depend',
|
||||
action='store_true', required=False,
|
||||
help='Generate dependency for Makefile')
|
||||
|
||||
|
||||
def parse_args(args):
|
||||
args = argparser.parse_args(args)
|
||||
args_dict = {
|
||||
'output': args.output,
|
||||
'libpal': args.libpal,
|
||||
'key': args.key,
|
||||
'manifest': args.manifest,
|
||||
}
|
||||
if args.exec is not None:
|
||||
args_dict['exec'] = args.exec
|
||||
if args.depend:
|
||||
args_dict['depend'] = True
|
||||
else:
|
||||
# key is required and not found in manifest
|
||||
if args.key is None:
|
||||
argparser.error("a key is required to sign")
|
||||
return None
|
||||
|
||||
return args_dict
|
||||
|
||||
|
||||
def main_sign(args):
|
||||
# pylint: disable=too-many-statements,too-many-branches,too-many-locals
|
||||
manifest, manifest_layout = read_manifest(args['manifest'])
|
||||
|
||||
if exec_sig_manifest(args, manifest) != 0:
|
||||
return 1
|
||||
|
||||
# Get attributes from manifest
|
||||
attr = dict()
|
||||
|
||||
parse_int = functools.partial(int, base=0)
|
||||
|
||||
for key, default, parse, attr_key in [
|
||||
('enclave_size', DEFAULT_ENCLAVE_SIZE, parse_size, 'enclave_size'),
|
||||
('thread_num', str(DEFAULT_THREAD_NUM), parse_int, 'thread_num'),
|
||||
('isvprodid', '0', parse_int, 'isv_prod_id'),
|
||||
('isvsvn', '0', parse_int, 'isv_svn'),
|
||||
]:
|
||||
attr[attr_key] = parse(manifest.setdefault('sgx.' + key, default))
|
||||
|
||||
(attr['flags'], attr['xfrms'], attr['misc_select']) = get_enclave_attributes(manifest)
|
||||
|
||||
today = datetime.date.today()
|
||||
attr['year'] = today.year
|
||||
attr['month'] = today.month
|
||||
attr['day'] = today.day
|
||||
|
||||
print("Attributes:")
|
||||
print(" size: %d" % attr['enclave_size'])
|
||||
print(" thread_num: %d" % attr['thread_num'])
|
||||
print(" isv_prod_id: %d" % attr['isv_prod_id'])
|
||||
print(" isv_svn: %d" % attr['isv_svn'])
|
||||
print(" attr.flags: %016x" % int.from_bytes(attr['flags'], byteorder='big'))
|
||||
print(" attr.xfrm: %016x" % int.from_bytes(attr['xfrms'], byteorder='big'))
|
||||
print(" misc_select: %08x" % int.from_bytes(attr['misc_select'], byteorder='big'))
|
||||
print(" date: %d-%02d-%02d" % (attr['year'], attr['month'], attr['day']))
|
||||
|
||||
# Check client info for remote attestation
|
||||
# (if sgx.ra_client.spid is provided)
|
||||
print("Attestation:")
|
||||
if 'sgx.ra_client_spid' in manifest and manifest['sgx.ra_client_spid']:
|
||||
print(" spid: " + manifest['sgx.ra_client_spid'])
|
||||
if 'sgx.ra_client_key' in manifest and manifest['sgx.ra_client_key']:
|
||||
print(" key: " + manifest['sgx.ra_client_key'])
|
||||
else:
|
||||
print(" *** sgx.ra_client_key not specified ***")
|
||||
return 1
|
||||
if 'sgx.ra_client_linkable' in manifest:
|
||||
print(" linkable: " + manifest['sgx.ra_client_linkable'])
|
||||
else:
|
||||
print(" linkable: 0")
|
||||
else:
|
||||
print(" *** Client info is not specified. Graphene will not perform"
|
||||
" remote attestation before execution. Please provide"
|
||||
" sgx.ra_client_spid and sgx.ra_client_key in the manifest. ***")
|
||||
|
||||
# Get trusted checksums and measurements
|
||||
print("Trusted files:")
|
||||
for key, val in get_trusted_files(manifest, args).items():
|
||||
(uri, _, checksum) = val
|
||||
print(" %s %s" % (checksum, uri))
|
||||
manifest['sgx.trusted_checksum.' + key] = checksum
|
||||
|
||||
print("Trusted children:")
|
||||
for key, val in get_trusted_children(manifest).items():
|
||||
(uri, _, mrenclave) = val
|
||||
print(" %s %s" % (mrenclave, uri))
|
||||
manifest['sgx.trusted_mrenclave.' + key] = mrenclave
|
||||
|
||||
# Try populate memory areas
|
||||
memory_areas = get_memory_areas(attr, args)
|
||||
|
||||
if manifest.get('sgx.static_address', None) is None:
|
||||
# If static_address is not specified explicitly, deduce from executable
|
||||
if any([a.addr is not None for a in memory_areas]):
|
||||
manifest['sgx.static_address'] = '1'
|
||||
else:
|
||||
global ENCLAVE_HEAP_MIN # pylint: disable=global-statement
|
||||
ENCLAVE_HEAP_MIN = 0
|
||||
manifest['sgx.static_address'] = '0'
|
||||
|
||||
if manifest.get('sgx.allow_file_creation', None) is None:
|
||||
manifest['sgx.allow_file_creation'] = '0'
|
||||
|
||||
output_manifest(args['output'], manifest, manifest_layout)
|
||||
|
||||
memory_areas = [
|
||||
MemoryArea('manifest', file=args['output'],
|
||||
flags=PAGEINFO_R | PAGEINFO_REG)
|
||||
] + memory_areas
|
||||
|
||||
memory_areas = populate_memory_areas(attr, memory_areas)
|
||||
|
||||
print("Memory:")
|
||||
# Generate measurement
|
||||
mrenclave = generate_measurement(attr, memory_areas)
|
||||
print("Measurement:")
|
||||
print(" %s" % mrenclave.hex())
|
||||
|
||||
# Generate sigstruct
|
||||
open(args['sigfile'], 'wb').write(
|
||||
generate_sigstruct(attr, args, mrenclave))
|
||||
return 0
|
||||
|
||||
|
||||
def make_depend(args):
|
||||
manifest_file = args['manifest']
|
||||
output = args['output']
|
||||
|
||||
(manifest, _) = read_manifest(manifest_file)
|
||||
if exec_sig_manifest(args, manifest) != 0:
|
||||
return 1
|
||||
|
||||
dependencies = set()
|
||||
for filename in get_trusted_files(manifest, args, check_exist=False,
|
||||
do_checksum=False).values():
|
||||
dependencies.add(filename[1])
|
||||
for filename in get_trusted_children(manifest, check_exist=False,
|
||||
do_checksum=False).values():
|
||||
dependencies.add(filename[1])
|
||||
dependencies.add(args['libpal'])
|
||||
|
||||
with open(output, 'w') as file:
|
||||
manifest_sgx = output
|
||||
if manifest_sgx.endswith('.d'):
|
||||
manifest_sgx = manifest_sgx[:-len('.d')]
|
||||
file.write('%s %s:' % (manifest_sgx, args['sigfile']))
|
||||
for filename in dependencies:
|
||||
file.write(' \\\n\t%s' % filename)
|
||||
file.write('\n')
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def main(args=None):
|
||||
args = parse_args(args)
|
||||
if args is None:
|
||||
return 1
|
||||
|
||||
if args.get('depend'):
|
||||
return make_depend(args)
|
||||
return main_sign(args)
|
||||
Reference in New Issue
Block a user