-
Notifications
You must be signed in to change notification settings - Fork 203
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Wojtek Porczyk <[email protected]>
- Loading branch information
Showing
15 changed files
with
242 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 a |~| 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', | ||
] | ||
} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,30 +4,95 @@ | |
# 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(), | ||
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'), | ||
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) | ||
@click.pass_context | ||
def main(ctx, with_, output, libpal, manifest_file, sigfile, depfile, verbose, plugin_args): | ||
# pylint: disable=too-many-arguments, too-many-locals | ||
|
||
ret = get_sgx_sign_plugin(with_)(args=plugin_args, standalone_mode=False) | ||
|
||
if isinstance(ret, int): | ||
# What happened: | ||
# - user wrote --with=PLUGIN --help-PLUGIN, | ||
# - subcommand's help_option called ctx.exit() eagerly | ||
# - Context.exit() raised click.Exit exception | ||
# - this exception was caught in Context.main() and handled depending on standalone_mode | ||
# and in standalone_mode=False, it just returned e.exit_code (which is probably 0). | ||
# Therefore, we also exit with the same exit_code. | ||
ctx.exit(ret) | ||
|
||
if output is None: | ||
ctx.fail('Missing option --output') | ||
if manifest_file is None: | ||
ctx.fail('Missing option --manifest') | ||
|
||
try: | ||
it = iter(ret) | ||
# 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 | ||
sign_func, extra_deps = ret, () | ||
|
||
manifest = Manifest.load(manifest_file) | ||
|
||
|
@@ -45,7 +110,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()) | ||
|
@@ -54,15 +119,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: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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@ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[gramine.sgx_sign] | ||
file = graminelibos.sgx_sign:sign_with_file |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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*. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
|