Skip to content

Commit

Permalink
[python] Add sgx-sign plugins
Browse files Browse the repository at this point in the history
Signed-off-by: Wojtek Porczyk <[email protected]>
  • Loading branch information
woju committed Aug 1, 2023
1 parent 24319e4 commit 096163e
Show file tree
Hide file tree
Showing 13 changed files with 206 additions and 23 deletions.
1 change: 1 addition & 0 deletions .ci/ubuntu20.04.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ RUN apt-get update && env DEBIAN_FRONTEND=noninteractive apt-get install -y \
python3-lxml \
python3-numpy \
python3-pip \
python3-pkg-resources \
python3-protobuf \
python3-pyelftools \
python3-pytest \
Expand Down
1 change: 1 addition & 0 deletions .ci/ubuntu22.04.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ RUN apt-get update && env DEBIAN_FRONTEND=noninteractive apt-get install -y \
python3-lxml \
python3-numpy \
python3-pip \
python3-pkg-resources \
python3-protobuf \
python3-pyelftools \
python3-pytest \
Expand Down
5 changes: 4 additions & 1 deletion Documentation/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,10 @@ def setup(app):

manpages_url = 'https://manpages.debian.org/{path}'

intersphinx_mapping = {'python': ('https://docs.python.org/3', None)}
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
'click': ('https://click.palletsprojects.com/en/latest', None),
}

# -- Options for HTML output -------------------------------------------------

Expand Down
1 change: 1 addition & 0 deletions Documentation/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ Indices and tables
devel/debugging
devel/packaging
python/api
python/writing-sgx-sign-plugins
devel/new-syscall
libos/libos-init
pal/host-abi
Expand Down
90 changes: 90 additions & 0 deletions Documentation/python/writing-sgx-sign-plugins.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
.. default-domain:: py
.. highlight:: py

Writing plugins for signing SGX enclaves
========================================

SGX cryptosystem uses RSA-3072 with modulus 3 for signing a SIGSTRUCT. However,
there are different arrangements where suitable keys are kept and used for
operations. A |~| keyfile is not always available (e.g., HSMs explicitly prevent
users from extracting keys), so we need adaptable ways of signing enclaves. This
document describes how to implement a |~| plugin that allows Gramine to access
different APIs for signing SGX enclaves.

You need to provide a |~| click subcommand, which is a |~| Python function
wrapped in :func:`click.command` decorator. This command can accept any
command-line arguments you need to complete the signing (like path to keyfile,
URL to some external API, PIN to smartcard). It is strongly recommended that you
provide ``--help-PLUGIN`` option (with your plugin name substituted for
``PLUGIN``). Also, consider prefixing your options with ``--PLUGIN-`` to avoid
conflicting with generic options.

Furthermore, your subcommand needs to be packaged into Python distribution,
which will include an entry point from ``gramine.sgx_sign`` group. The entry
point needs to be named as your plugin and the callable it points to needs to be
the click command.

The click command will be called with ``standalone_mode=False``. It needs to
return signing function that will be passed to ``Sigstruct.sign``. The signing
function should return a |~| 3-tuple:

- exponent (always ``3``)
- modulus (:class:`int`)
- signature (:class:`int`)

The signing function accepts a |~| single argument, the data to be signed. If
your signing function needs to accept additional arguments, use
:func:`functools.partial`.

Alternatively, the click command can return 2-tuple of:

- the signing function, as described above;
- iterable of local files that were accessed during signature generation, for
the purpose of tracking dependencies

If you return just the function, it's equivalent to returning 2-tuple with empty
iterable, i.e. no dependent files.

.. seealso::

https://setuptools.pypa.io/en/latest/userguide/entry_point.html#advertising-behavior
Introduction to entrypoints

https://packaging.python.org/en/latest/specifications/entry-points/
Entrypoints specification

Example
-------

For full example, please see ``sgx_sign.py`` file (note that ``graminelibos``
package is not packaged with ``setuptools``, so metadata is provided manually).

The relevant parts are:

.. code-block:: python
:caption: sgx_sign.py
@click.command(add_help_option=False)
@click.help_option('--help-file')
@click.option('--key', '-k', metavar='FILE',
type=click.Path(exists=True, dir_okay=False),
default=os.fspath(SGX_RSA_KEY_PATH),
help='specify signing key (.pem) file')
def sign_with_file(key):
return functools.partial(sign, key=key), [key]
def sign(data, *, key):
# sign data with key
return exponent, modulus, signature
.. code-block:: python
:caption: setup.py
setuptools.setup(
...,
entry_points={
'gramine.sgx_sign': [
'file = graminelibos.sgx_sign:sign_with_file',
]
}
)
1 change: 1 addition & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Depends:
libcurl4 (>= 7.58),
libprotobuf-c1,
python3,
python3 (>= 3.10) | python3-pkg-resources,
python3-click,
python3-cryptography,
python3-jinja2,
Expand Down
3 changes: 2 additions & 1 deletion debian/gramine.install
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
usr/bin/gramine-*
usr/bin/is-sgx-available
usr/lib/python3/dist-packages/graminelibos/
usr/lib/python3/dist-packages/_graminelibos_offsets.py
usr/lib/python3/dist-packages/graminelibos/
usr/lib/python3/dist-packages/graminelibos.dist-info/
usr/lib/${DEB_HOST_MULTIARCH}/gramine/direct/libpal.so
usr/lib/${DEB_HOST_MULTIARCH}/gramine/direct/loader
usr/lib/${DEB_HOST_MULTIARCH}/gramine/libsysdb.so
Expand Down
92 changes: 71 additions & 21 deletions python/gramine-sgx-sign
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,80 @@
# Borys Popławski <[email protected]>

import datetime
import os
import sys
import textwrap

import click

from graminelibos import (
Manifest, get_tbssigstruct, sign_with_local_key, SGX_LIBPAL, SGX_RSA_KEY_PATH,
Manifest, get_tbssigstruct, SGX_LIBPAL,
)

@click.command()
@click.option('--output', '-o', type=click.Path(), required=True,
help='Output .manifest.sgx file (manifest augmented with autogenerated fields)')
@click.option('--libpal', '-l', type=click.Path(exists=True, dir_okay=False), default=SGX_LIBPAL,
help='Input libpal file')
@click.option('--key', '-k', type=click.Path(exists=True, dir_okay=False),
default=os.fspath(SGX_RSA_KEY_PATH),
help='specify signing key (.pem) file')
@click.option('--manifest', '-m', 'manifest_file', type=click.File('r', encoding='utf-8'),
required=True, help='Input .manifest file')
@click.option('--sigfile', '-s', help='Output .sig file')
@click.option('--depfile', type=click.File('w'), help='Generate dependencies for .manifest.sgx '
'and .sig files')
@click.option('--verbose/--quiet', '-v/-q', default=True, help='Display details (on by default)')
def main(output, libpal, key, manifest_file, sigfile, depfile, verbose):
# pylint: disable=too-many-arguments
# TODO: after python (>= 3.10) simplify this
# NOTE: we can't `try: importlib.metadata`, because the API has changed between 3.9 and 3.10
# (in 3.9 and in backported importlib_metadata entry_points() doesn't accept group argument)
if sys.version_info >= (3, 10):
from importlib.metadata import entry_points # pylint: disable=import-error,no-name-in-module
else:
from pkg_resources import iter_entry_points as entry_points

def list_sgx_sign_plugins():
return tuple(ep.name for ep in entry_points(group='gramine.sgx_sign'))

_sgx_sign_plugins = list_sgx_sign_plugins()

def get_sgx_sign_plugin(name):
for ep in entry_points(group='gramine.sgx_sign'):
if ep.name == name:
return ep.load()
raise KeyError(name)

@click.command(
context_settings={'ignore_unknown_options': True},
epilog=textwrap.dedent(f'''
Use --with=PLUGIN --help-PLUGIN to get help about particular plugin.
Available plugins: {", ".join(_sgx_sign_plugins)}'''),
)
@click.option('--with', 'with_', metavar='PLUGIN',
type=click.Choice(_sgx_sign_plugins),
default='file',
help='Choose plugin with which to sign the enclave (default: file)')
@click.option('--output', '-o',
type=click.Path(),
required=True,
help='Output .manifest.sgx file (manifest augmented with autogenerated fields)')
@click.option('--libpal', '-l',
type=click.Path(exists=True, dir_okay=False),
default=SGX_LIBPAL,
help='Input libpal file')
@click.option('--manifest', '-m', 'manifest_file',
type=click.File('r', encoding='utf-8'),
required=True,
help='Input .manifest file')
@click.option('--sigfile', '-s',
help='Output .sig file')
@click.option('--depfile',
type=click.File('w'),
help='Generate dependencies for .manifest.sgx and .sig files')
@click.option('--verbose/--quiet', '-v/-q',
default=True,
help='Display details (on by default)')
@click.argument('plugin_args',
nargs=-1,
type=click.UNPROCESSED)
def main(with_, output, libpal, manifest_file, sigfile, depfile, verbose, plugin_args):
# pylint: disable=too-many-arguments, too-many-locals

sign_func = get_sgx_sign_plugin(with_)(args=plugin_args, standalone_mode=False)
try:
it = iter(sign_func)
# no TypeError, therefore we've got tuple or list, or sth else iterable
sign_func, extra_deps = it
except TypeError:
# sign_func is probably just a callable (no need to check, will break later if it isn't)
# and extra dependencies were not provided
extra_deps = ()

manifest = Manifest.load(manifest_file)

Expand All @@ -45,7 +95,7 @@ def main(output, libpal, key, manifest_file, sigfile, depfile, verbose):

today = datetime.date.today()
sigstruct = get_tbssigstruct(output, today, libpal, verbose=verbose)
sigstruct.sign(sign_with_local_key, key)
sigstruct.sign(sign_func)

with open(sigfile, 'wb') as f:
f.write(sigstruct.to_bytes())
Expand All @@ -54,15 +104,15 @@ def main(output, libpal, key, manifest_file, sigfile, depfile, verbose):
# Dependencies:
#
# - `.manifest.sgx` depends on all files we just expanded
# - `.sig` additionally depends on libpal and key
# - `.sig` additionally depends on libpal
#
# TODO (Ninja 1.10): We print all these as dependencies for `.manifest.sgx`. This will still
# cause `.sig` to be rebuilt when necessary: we build both these files together, so it's not
# possible to rebuild one without the other.
#
# This is a workaround for the fact that Ninja prior to version 1.10 does not
# support depfiles with multiple outputs (and parses such depfiles incorrectly).
deps = [*expanded, libpal, key]
deps = [*expanded, libpal, *extra_deps]

depfile.write(f'{output}:')
for filename in deps:
Expand Down
5 changes: 5 additions & 0 deletions python/graminelibos.dist-info/METADATA.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Metadata-Version: 2.1
Name: @NAME@libos
Version: @VERSION@
Home-page: https://gramineproject.io/
License: @LICENSE@
2 changes: 2 additions & 0 deletions python/graminelibos.dist-info/entry_points.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[gramine.sgx_sign]
file = graminelibos.sgx_sign:sign_with_file
15 changes: 15 additions & 0 deletions python/graminelibos.dist-info/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
install_dir = python3_platlib / 'graminelibos.dist-info'
conf = configuration_data()
conf.set('NAME', meson.project_name())
conf.set('VERSION', meson.project_version())
conf.set('LICENSE', ', '.join(meson.project_license()))

# https://packaging.python.org/en/latest/specifications/core-metadata/
configure_file(
input: 'METADATA.in',
output: 'METADATA',
install_dir: install_dir,
configuration: conf,
)

install_data('entry_points.txt', install_dir: install_dir)
12 changes: 12 additions & 0 deletions python/graminelibos/sgx_sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
# Wojtek Porczyk <[email protected]>
#

import functools
import hashlib
import os
import pathlib
import struct
import subprocess

import click

from cryptography.hazmat import backends
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
Expand Down Expand Up @@ -539,6 +542,15 @@ def get_tbssigstruct(manifest_path, date, libpal=SGX_LIBPAL, verbose=False):
return sig


@click.command(add_help_option=False)
@click.help_option('--help-file')
@click.option('--key', '-k', metavar='FILE',
type=click.Path(exists=True, dir_okay=False),
default=os.fspath(SGX_RSA_KEY_PATH),
help='specify signing key (.pem) file')
def sign_with_file(key):
return functools.partial(sign_with_local_key, key=key), [key]

def sign_with_local_key(data, key):
"""Signs *data* using *key*.
Expand Down
1 change: 1 addition & 0 deletions python/meson.build
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
subdir('graminelibos')
subdir('graminelibos.dist-info')

install_data([
'gramine-gen-depend',
Expand Down

0 comments on commit 096163e

Please sign in to comment.