Source code for auditee.sgx

"""Wrapper around sgx_sign command. Invokes the command via subprocess."""
import itertools
import functools
import os
import pathlib
import struct
import subprocess
from hashlib import sha256

from .errors import SGXSignError


SGX_SDK = os.environ.get("SGX_SDK", "/opt/sgxsdk")
"""str: Directory where the Linux SGX SDK is installed.

It can be set via the environment variable ``SGX_SDK``.
Defaults to :file:`/opt/sgxsdk`.
"""

SGX_SIGN_CMD = str(pathlib.Path(SGX_SDK).joinpath("bin/x64/sgx_sign"))
"""str: Location of the ``sgx_sign`` tool.

Defaults to :file:`/opt/sgxsdk/bin/x64/sgx_sign`.
"""


def _sgx_sign(
    cmd,
    *,
    enclave,
    # key=None,
    # config=None,
    # out=None,
    # sig=None,
    # unsigned=None,
    # dumpfile=None,
    # cssfile=None,
    ignore_rel_error=False,
    ignore_init_sec_error=False,
    **opts,
):
    """Wrapper around the SGX signing tool.

Usage: sgx_sign <commands> [options] file...
Commands:
   sign                    Sign the enclave using the private key
   gendata                 Generate enclave signing material to be signed
   catsig                  Generate the signed enclave with the input signature file, the
                           public key and the enclave signing material
   dump                    Dump metadata information for a signed enclave file
Options:
   -enclave                Specify the enclave file to be signed or already signed
                           It is a required option for the four commands
   -key                    Specify the key file
                           It is a required option for "sign" and "catsig"
   -config                 Specify the configuration for the enclave
   -out                    Specify the output file
                           It is a required option for "sign", "gendata" and "catsig"
   -sig                    Specify the signature file for the enclave signing material
                           It is a required option for "catsig"
   -unsigned               Specify the enclave signing material generated by "gendata"
                           It is a required option for "catsig"
   -dumpfile               Specify a file to dump metadata information (text format)
                           It is a required option for "dump"
   -cssfile                Specify a file to dump the enclave SIGSTRUCT information (binary format)
   -ignore-rel-error       By default, sgx_sign provides an error for enclaves with
                           text relocations. You can ignore the error and continue signing
                           by providing this option. But it is recommended you eliminate the
                           text relocations instead of bypassing the error with this option.
   -ignore-init-sec-error  By default, sgx_sign provides an error for enclaves with .init section.
                           You can ignore the error and continue signing by providing this option.
                           But it is recommended you eliminate the section instead of bypassing
                           the error with this option.
   -resign                 By default, sgx_sign reports an error if an input enclave has already been signed.
                           You can force sgx_sign to resign the enclave by providing this option.

Run "sgx_sign -help" to get this help and exit.
Run "sgx_sign -version" to output version information and exit.
"""  # noqa
    # popenargs = [SGX_SIGN_CMD, cmd] + list(
    #    itertools.chain.from_iterable(list(opts.items()))
    # )
    popenargs = [SGX_SIGN_CMD, cmd, "-enclave", enclave]
    # if key is not None:
    #    popenargs += ['-key', key]
    popenargs += list(
        itertools.chain.from_iterable((f"-{opt}", value) for opt, value in opts.items())
    )
    if ignore_rel_error:
        popenargs.append("-ignore-rel-error")
    if ignore_init_sec_error:
        popenargs.append("-ignore-init-sec-error")

    return subprocess.run(popenargs).returncode


"""Dump metadata information for a signed enclave file"""
_dump = functools.partial(_sgx_sign, cmd="dump")


"""Sign the enclave using the private key"""
_sign = functools.partial(_sgx_sign, cmd="sign")

"""
   gendata                 Generate enclave signing material to be signed
   catsig                  Generate the signed enclave with the input signature file, the
                           public key and the enclave signing material
"""


def dump_enclave_sigstruct(enclave, cssfile):
    """Dump the enclave SIGSTRUCT to file."""
    _dump(enclave=enclave, cssfile=cssfile, dumpfile="/dev/null")


[docs]def sign(enclave, *, key, out, config): """ Sign the given enclave with the given key. This function invokes the Linux SGX SDK ``sgx_sign`` tool, using Python's :py:mod:`subprocess` module. .. attention:: The SGX SDK must be installed on the system where this function is invoked. The path to the ``sgx_sign`` tool can be set via the environment variable :attr:`~.SGX_SDK`. It defaults to :file:`/opt/sgxsdk/bin/x64/sgx_sign`. Parameters ---------- enclave: str Local file path to the unsigned enclave binary. key: str Local file path to a signing key with which to sign the enclave. out: str Local file path where the signed enclave should be written to. config: str Local file path to the enclave configuration file. Raises ------ :py:exc:`~.errors.SGXSignError`: If something wrong happen when invoking the ``sgx_sign`` tool. Returns ------- bytes: Signed enclave bytes. Examples -------- .. code-block:: python from auditee import sgx sgx.sign('enclv.so', key='key.pem', out='enclv.sig.so', config='config.xml') The above is equivalent to invoking the ``sgx_sign`` tool in a shell: .. code-block:: shell $ sgx_sign sign -enclave enclv.so -key key.pem -out enclv.sig.so -config config.xml """ # FIXME if the returncode is not zero, try getting more informaton about the error returncode = _sign(enclave=enclave, key=key, out=out, config=config) if returncode != 0: raise SGXSignError( f"sgx_sign failed for enclave file: {enclave}, key: {key}, out: {out}, config: {config}" ) # FIXME handle errors in the above call, rather than proceeding forward despite # errors -- for instance, errors in signing can result in no file being written # to 'out' thus causing the following open() instruction to fail. with open(out, "rb") as f: signed_enclave_bytes = f.read() return signed_enclave_bytes
def get_enclave_sigstruct(enclave, cssfile="/tmp/sigstruct"): dump_enclave_sigstruct(enclave, cssfile) with open(cssfile, "rb") as f: sigstruct_bytes = f.read() return sigstruct_bytes def get_mrenclave(enclave, cssfile="/tmp/sigstruct"): sigstruct = get_enclave_sigstruct(enclave, cssfile=cssfile) return bytes(unpack_mrenclave(sigstruct)) def get_mrsigner(enclave, cssfile="/tmp/sigstruct"): sigstruct = get_enclave_sigstruct(enclave, cssfile=cssfile) return sha256(unpack_key_modulus(sigstruct)).digest() """Functions to unpack data from an Enclave Signature Structure also known as SIGSTRUCT. For now using ``struct``, but some work is currently being done to use cffi to bind to the C structs. See https://github.com/intel/linux-sgx/blob/bb3d1a5a302511954fcd1b20df4466554e129df1/common/inc/internal/arch.h#L198-L252 .. code-block:: c /**************************************************************************** * Definitions for enclave signature ****************************************************************************/ #define SE_KEY_SIZE 384 /* in bytes */ #define SE_EXPONENT_SIZE 4 /* RSA public key exponent size in bytes */ typedef struct _css_header_t { /* 128 bytes */ uint8_t header[12]; /* (0) must be (06000000E100000000000100H) */ uint32_t type; /* (12) bit 31: 0 = prod, 1 = debug; Bit 30-0: Must be zero */ uint32_t module_vendor; /* (16) Intel=0x8086, ISV=0x0000 */ uint32_t date; /* (20) build date as yyyymmdd */ uint8_t header2[16]; /* (24) must be (01010000600000006000000001000000H) */ uint32_t hw_version; /* (40) For Launch Enclaves: HWVERSION != 0. Others, HWVERSION = 0 */ uint8_t reserved[84]; /* (44) Must be 0 */ } css_header_t; se_static_assert(sizeof(css_header_t) == 128); typedef struct _css_key_t { /* 772 bytes */ uint8_t modulus[SE_KEY_SIZE]; /* (128) Module Public Key (keylength=3072 bits) */ uint8_t exponent[SE_EXPONENT_SIZE]; /* (512) RSA Exponent = 3 */ uint8_t signature[SE_KEY_SIZE]; /* (516) Signature over Header and Body */ } css_key_t; se_static_assert(sizeof(css_key_t) == 772); typedef struct _css_body_t { /* 128 bytes */ sgx_misc_select_t misc_select; /* (900) The MISCSELECT that must be set */ sgx_misc_select_t misc_mask; /* (904) Mask of MISCSELECT to enforce */ uint8_t reserved[4]; /* (908) Reserved. Must be 0. */ sgx_isvfamily_id_t isv_family_id; /* (912) ISV assigned Family ID */ sgx_attributes_t attributes; /* (928) Enclave Attributes that must be set */ sgx_attributes_t attribute_mask; /* (944) Mask of Attributes to Enforce */ sgx_measurement_t enclave_hash; /* (960) MRENCLAVE - (32 bytes) */ uint8_t reserved2[16]; /* (992) Must be 0 */ sgx_isvext_prod_id_t isvext_prod_id; /* (1008) ISV assigned Extended Product ID */ uint16_t isv_prod_id; /* (1024) ISV assigned Product ID */ uint16_t isv_svn; /* (1026) ISV assigned SVN */ } css_body_t; se_static_assert(sizeof(css_body_t) == 128); typedef struct _css_buffer_t { /* 780 bytes */ uint8_t reserved[12]; /* (1028) Must be 0 */ uint8_t q1[SE_KEY_SIZE]; /* (1040) Q1 value for RSA Signature Verification */ uint8_t q2[SE_KEY_SIZE]; /* (1424) Q2 value for RSA Signature Verification */ } css_buffer_t; se_static_assert(sizeof(css_buffer_t) == 780); typedef struct _enclave_css_t { /* 1808 bytes */ css_header_t header; /* (0) */ css_key_t key; /* (128) */ css_body_t body; /* (900) */ css_buffer_t buffer; /* (1028) */ } enclave_css_t; se_static_assert(sizeof(enclave_css_t) == 1808); """ # noqa MODULUS_OFFSET = 128 MODULUS_SIZE = 384 ENCLAVEHASH_OFFSET = 960 ENCLAVEHASH_SIZE = 32 isvprodid_OFFSET = 1024 """ typedef struct _css_header_t { /* 128 bytes */ uint8_t header[12]; /* (0) must be (06000000E100000000000100H) */ uint32_t type; /* (12) bit 31: 0 = prod, 1 = debug; Bit 30-0: Must be zero */ uint32_t module_vendor; /* (16) Intel=0x8086, ISV=0x0000 */ uint32_t date; /* (20) build date as yyyymmdd */ uint8_t header2[16]; /* (24) must be (01010000600000006000000001000000H) */ uint32_t hw_version; /* (40) For Launch Enclaves: HWVERSION != 0. Others, HWVERSION = 0 */ uint8_t reserved[84]; /* (44) Must be 0 */ } css_header_t; se_static_assert(sizeof(css_header_t) == 128); """ # noqa E501 CSS_HEADER_FORMAT = "12BIII16BI84B" """ typedef struct _css_key_t { /* 772 bytes */ uint8_t modulus[SE_KEY_SIZE]; /* (128) Module Public Key (keylength=3072 bits) */ uint8_t exponent[SE_EXPONENT_SIZE]; /* (512) RSA Exponent = 3 */ uint8_t signature[SE_KEY_SIZE]; /* (516) Signature over Header and Body */ } css_key_t; se_static_assert(sizeof(css_key_t) == 772); """ # noqa E501 SE_KEY_SIZE = 384 # in bytes */ SE_EXPONENT_SIZE = 4 # RSA public key exponent size in bytes CSS_KEY_FORMAT = f"{SE_KEY_SIZE}B{SE_EXPONENT_SIZE}B{SE_KEY_SIZE}B" """ common/inc/sgx_attributes.h --------------------------- typedef struct _attributes_t { uint64_t flags; uint64_t xfrm; } sgx_attributes_t; /* define MISCSELECT - all bits are currently reserved */ typedef uint32_t sgx_misc_select_t; common/inc/sgx_report.h ----------------------- #define SGX_HASH_SIZE 32 /* SHA256 */ #define SGX_MAC_SIZE 16 /* Message Authentication Code - 16 bytes */ #define SGX_REPORT_DATA_SIZE 64 #define SGX_ISVEXT_PROD_ID_SIZE 16 #define SGX_ISV_FAMILY_ID_SIZE 16 typedef struct _sgx_measurement_t { uint8_t m[SGX_HASH_SIZE]; } sgx_measurement_t; typedef uint8_t sgx_mac_t[SGX_MAC_SIZE]; typedef struct _sgx_report_data_t { uint8_t d[SGX_REPORT_DATA_SIZE]; } sgx_report_data_t; typedef uint16_t sgx_prod_id_t; typedef uint8_t sgx_isvext_prod_id_t[SGX_ISVEXT_PROD_ID_SIZE]; typedef uint8_t sgx_isvfamily_id_t[SGX_ISV_FAMILY_ID_SIZE]; common/inc/internal/arch.h -------------------------- typedef struct _css_body_t { /* 128 bytes */ sgx_misc_select_t misc_select; /* (900) The MISCSELECT that must be set */ sgx_misc_select_t misc_mask; /* (904) Mask of MISCSELECT to enforce */ uint8_t reserved[4]; /* (908) Reserved. Must be 0. */ sgx_isvfamily_id_t isv_family_id; /* (912) ISV assigned Family ID */ sgx_attributes_t attributes; /* (928) Enclave Attributes that must be set */ sgx_attributes_t attribute_mask; /* (944) Mask of Attributes to Enforce */ sgx_measurement_t enclave_hash; /* (960) MRENCLAVE - (32 bytes) */ uint8_t reserved2[16]; /* (992) Must be 0 */ sgx_isvext_prod_id_t isvext_prod_id; /* (1008) ISV assigned Extended Product ID */ uint16_t isv_prod_id; /* (1024) ISV assigned Product ID */ uint16_t isv_svn; /* (1026) ISV assigned SVN */ } css_body_t; se_static_assert(sizeof(css_body_t) == 128); """ # noqa E501 SGX_HASH_SIZE = 32 # SHA256 SGX_MAC_SIZE = 16 # Message Authentication Code - 16 bytes SGX_REPORT_DATA_SIZE = 64 SGX_ISVEXT_PROD_ID_SIZE = 16 SGX_ISV_FAMILY_ID_SIZE = 16 CSS_BODY_FORMAT = ( f"II4B{SGX_ISV_FAMILY_ID_SIZE}BQQQQ" f"{SGX_HASH_SIZE}B16B{SGX_ISVEXT_PROD_ID_SIZE}BHH" ) def read_sigstruct(cssfile): with open(cssfile, "rb") as f: sigstruct = f.read() return sigstruct def unpack_key_modulus(sigstruct): fmt = f"<{MODULUS_SIZE}s" return struct.unpack_from(fmt, sigstruct, MODULUS_OFFSET)[0] def unpack_mrenclave(sigstruct): fmt = f"<{ENCLAVEHASH_SIZE}B" return struct.unpack_from(fmt, sigstruct, ENCLAVEHASH_OFFSET) def unpack_isvprodid(sigstruct): """ 1024 2 ISV assigned Product ID. Y """ offset = 1024 fmt = "<H" return struct.unpack_from(fmt, sigstruct, offset)[0] def unpack_isvsvn(sigstruct): """ ISVSVN 1026 2 ISV assigned SVN (security version number). Y """ offset = 1026 fmt = "<H" return struct.unpack_from(fmt, sigstruct, offset)[0] def unpack_sigstruct(sigstruct): """ """ # noqa raise NotImplementedError class Sigstruct: def __init__(self, sigstruct_bytes): self.mrsigner = sha256(unpack_key_modulus(sigstruct_bytes)).digest() self.mrenclave = bytes(unpack_mrenclave(sigstruct_bytes)) self.isvprodid = unpack_isvprodid(sigstruct_bytes) self.isvsvn = unpack_isvsvn(sigstruct_bytes) @classmethod def from_file(cls, cssfile): with open(cssfile, "rb") as f: sigstruct = f.read() return cls(sigstruct) @classmethod def from_enclave_file(cls, enclave_file): sigstruct = get_enclave_sigstruct(enclave_file) return cls(sigstruct) def cmp(self, other): return { attr: getattr(self, attr) == getattr(other, attr) for attr in ("mrenclave", "mrsigner", "isvprodid", "isvsvn") }