Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[python] Add support for production signing with Azure Key Vault #1020

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 25 additions & 11 deletions Documentation/devel/building.rst
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,14 @@ The ``-Dsgx_driver`` parameter controls which SGX driver to use:

* ``upstream`` (default) for upstreamed in-kernel driver (mainline Linux kernel
5.11+),
* ``dcap1.6`` for Intel DCAP version 1.6 or higher, but below 1.10,
* ``dcap1.10`` for Intel DCAP version 1.10 or higher,
* ``oot`` for non-DCAP, out-of-tree version of the driver.

The ``-Dsgx_driver_include_path`` parameter must point to the absolute path
where the SGX driver was downloaded or installed in the previous step. For
example, for the DCAP version 1.41 of the SGX driver, you must specify
``-Dsgx_driver_include_path="/usr/src/sgx-1.41/include/"``. If this parameter is
omitted, Gramine's build system will try to determine the right path.
example, for the OOT driver installed at the default path, you can specify
``-Dsgx_driver_include_path="/opt/intel/linux-sgx-driver"``. If this parameter
is omitted, Gramine's build system will try to determine the right path, so,
it's usually not needed.

.. note::

Expand Down Expand Up @@ -296,17 +295,32 @@ Additional build options

.. _FSGSBASE:

Prepare a signing key
---------------------
Signing key
-----------

Only for SGX enclave development, and if you haven't already, run the following
command::
SGX enclaves need to build and signed using a 3072-bit RSA key. During development,
and if you haven't already, you can run the following command::

gramine-sgx-gen-private-key

This command generates an |~| RSA 3072 key suitable for signing SGX enclaves and
stores it in :file:`{HOME}/.config/gramine/enclave-key.pem`. This key needs to
be protected and should not be disclosed to anyone.
be protected and must not be disclosed to anyone. This is the default signing
method, but since this key is available in the clear, it is **not suitable**
for production deployments.

For production deployments, you must use a key secured in a HSM. Gramine
supports signing the enclave using a key from Azure Key Vault Managed HSM. You
must have Azure Subscription with access to Azure Key Vault's Managed HSM, and
have a 3072-bit RSA private key with a public exponent 3 created in it. (Please
note that only Managed HSM supports setting public exponent), and must have
given access permissions to yourself or the one who will use the key created (as
'Managed HSM Crypto User' using `az keyvault role assignment`). You must also be
logged in using Azure CLI before signing the enclave. Please see `create_rsa_key
<https://azuresdkdocs.blob.core.windows.net/$web/python/azure-keyvault-keys/latest/azure.keyvault.keys.html#azure.keyvault.keys.KeyClient.create_rsa_key>`__
for creating the key in Azure Key Vault Managed HSM, and `gramine-sgx-sign
<https://gramine.readthedocs.io/en/latest/manpages/gramine-sgx-sign.html>`__ for
the options to sign with Azure Key Vault.

After signing the application's manifest, users may ship the application and
Gramine binaries, along with an SGX-specific manifest (``.manifest.sgx``
Expand All @@ -320,7 +334,7 @@ Advanced: installing Linux kernel with FSGSBASE patches
FSGSBASE patchset was merged in Linux kernel version 5.9. For older kernels it
is available as `separate patches
<https://github.com/oscarlab/graphene-sgx-driver/tree/master/fsgsbase_patches>`__.
(Note that Gramine was prevously called *Graphene* and was hosted under a
(Note that Gramine was previously called *Graphene* and was hosted under a
different organization, hence the name of the linked repository.)

The following instructions to patch and compile a Linux kernel with FSGSBASE
Expand Down
13 changes: 11 additions & 2 deletions Documentation/manpages/gramine-sgx-sign.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,18 @@ Command line arguments

Path to the output manifest file (with Trusted Files expanded).

.. option:: --key key_file, -k key_file
.. option:: --keytype key_type, -t key_type

Path to the private key used for signing.
Type of signing key: `pem` for a local private key in .pem format or
`akv` for Azure Key Vault Managed HSM.

.. option:: --key key_path, -k key_path

Path to .pem file for a local private key or
`vault_url:key_name` for Azure Key Vault Managed HSM. For example, if
we have a key named sgx_sign_key available in the Managed HSM with
URL https://myakv-mhsm.managedhsm.azure.net, the key path will be
`https://myakv-mhsm.managedhsm.azure.net:sgx_sign_key`

.. option:: --manifest manifest_file, -m manifest_file

Expand Down
33 changes: 27 additions & 6 deletions python/gramine-sgx-sign
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,28 @@ import os
import click

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

@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('--keytype', '-t', type=click.STRING, default='pem',
help='Type of signing key: "pem" for a local private key in .pem format, '
'"akv" for Azure Key Vault Managed HSM')
@click.option('--key', '-k', type=click.STRING,
default=SGX_RSA_KEY_PATH,
help='Signing key: path to .pem file for a local private key, '
'vault_url:key_name for Azure Key Vault Managed HSM')
@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):
def main(output, libpal, keytype, key, manifest_file, sigfile, depfile, verbose):
# pylint: disable=too-many-arguments

manifest = Manifest.load(manifest_file)
Expand All @@ -45,7 +49,24 @@ 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)

if keytype == 'akv':
sign_fn = sign_with_akv
if verbose == True:
key_details = key.rsplit(':', 1)
vault_url = key_details[0]
key_name = key_details[1]
print('Signing with key', key_name, 'from Azure Key Vault Managed HSM:', vault_url)
elif keytype == 'pem':
key = os.fspath(key)
sign_fn = sign_with_local_key
if verbose == True:
print('Signing with local private key:', key)
else:
print(f"Invalid keytype specified: `{keytype}`!")
exit(1)

sigstruct.sign(sign_fn, key)

with open(sigfile, 'wb') as f:
f.write(sigstruct.to_bytes())
Expand Down
2 changes: 1 addition & 1 deletion python/graminelibos/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@
from .manifest import Manifest, ManifestError
if _CONFIG_SGX_ENABLED:
from .sgx_get_token import get_token
from .sgx_sign import get_tbssigstruct, sign_with_local_key, SGX_LIBPAL, SGX_RSA_KEY_PATH
from .sgx_sign import get_tbssigstruct, sign_with_akv, sign_with_local_key, SGX_LIBPAL, SGX_RSA_KEY_PATH
from .sigstruct import Sigstruct
53 changes: 52 additions & 1 deletion python/graminelibos/sgx_sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,8 +537,58 @@ def get_tbssigstruct(manifest_path, date, libpal=SGX_LIBPAL, verbose=False):
return sig


def sign_with_akv(data, key):
"""Signs *data* using *key* from Azure Key Vault's Managed HSM.

Function used to generate an RSA signature over provided data using a 3072-bit private key with
the public exponent of 3 (hard Intel SGX requirement on the key size and the exponent).
Suitable to be used as a callback to :py:func:`graminelibos.Sigstruct.sign()`. This function
requires that the user has an active subscription to Azure and Azure Key Vault's Managed HSM
(MHSM), a 3072-bit RSA key created in the MHSM enabled for signing and the user is logged into
Azure CLI. This function qualifies for production signing.

Args:
data (bytes): Data to calculate the signature over.
key (str): Details of RSA private key in the AKV's Managed HSM in vault_url:key_name format.

Returns:
(int, int, int): Tuple of exponent, modulus and signature respectively.
"""

from azure.identity import DefaultAzureCredential
from azure.keyvault.keys import KeyClient
from azure.keyvault.keys.crypto import CryptographyClient, SignatureAlgorithm

key_details = key.rsplit(':', 1)
vault_url = key_details[0]
key_name = key_details[1]

credential = DefaultAzureCredential(exclude_managed_identity_credential=True,
exclude_visual_studio_code_credential=True,
exclude_environment_credential=True,
exclude_shared_token_cache_credential=True,
exclude_powershell_credential=True)

key_client = KeyClient(vault_url=vault_url, credential=credential)

rsa_key = key_client.get_key(key_name)

crypto_client = CryptographyClient(rsa_key, credential=credential)

digest = hashlib.sha256(data).digest()

result = crypto_client.sign(SignatureAlgorithm.rs256, digest)
signature = result.signature

exponent_int = int.from_bytes(rsa_key.key.e, byteorder='big')
modulus_int = int.from_bytes(rsa_key.key.n, byteorder='big')
signature_int = int.from_bytes(signature, byteorder='big')

return exponent_int, modulus_int, signature_int


def sign_with_local_key(data, key):
"""Signs *data* using *key*.
"""Signs *data* using local *key* in .pem format.

Function used to generate an RSA signature over provided data using a 3072-bit private key with
the public exponent of 3 (hard Intel SGX requirement on the key size and the exponent).
Expand All @@ -551,6 +601,7 @@ def sign_with_local_key(data, key):
Returns:
(int, int, int): Tuple of exponent, modulus and signature respectively.
"""

proc = subprocess.Popen(
['openssl', 'rsa', '-modulus', '-in', key, '-noout'],
stdout=subprocess.PIPE)
Expand Down