gRPC Authn and Authz
Introduction
Enclaves can be used to locally protect data at rest using sealing keys provided by the hardware. To protect data in transit, it is necessary to establish a secure, authenticated channel over which data is encrypted and integrity-protected in transit to an authorized peer.
Secure communication channels are a fundamental building block for developing large-scale, distributed systems of enclaves. However, commonly-used security protocols like TLS don’t support enclave identities, nor do they support the unique attestation mechanisms available to enclaves. To solve this problem, Asylo provides support for secure enclave gRPC communication channels where enclave attestation is built into channel establishment by way of a novel security protocol.
Attestation During Channel Establishment
Asylo applications can host secure gRPC services inside their enclave. Such enclavized gRPC services may handle security-sensitive RPCs and, consequently, must be secured so that only authenticated and authorized peers can call those RPCs.
Asylo provides a custom gRPC transport security stack that enables developers to set up secure channels between enclavized applications. These channels are backed by enclave attestation; in the case of SGX, these attestations are produced by the hardware.
Asylo’s gRPC security stack can be used to establish a secure channel between two enclave applications, or between an enclave application and a non-enclave application. In the process of creating a secure channel, the two endpoints mutually authenticate and establish a shared key bound to their identities and to the session. Asylo’s gRPC transport security stack accomplishes this using a key-exchange protocol that is enlightened about enclave identities like SGX identities. Attestation is handled during channel establishment so each endpoint is aware of its peer’s identities when the channel is live.
Enclave Key Exchange Protocol
The Enclave Key Exchange Protocol (EKEP) is an authenticated Elliptic Curve Diffie-Hellman key negotiation protocol. This protocol is based on the Application Layer Transport Security (ALTS) handshake protocol, which was developed by Google for securing RPC communications. The ALTS handshake protocol has many desired properties of a key-negotiation protocol used to establish communication to and from enclaves, but does not support enclave identities.
EKEP is derived from the ALTS handshake protocol, but with several modifications:
- Participants can assert arbitrary identities
- Participants can assert multiple identities
- Participants negotiate which identities are asserted
- Participants are always expected to use ephemeral Diffie-Hellman keys; if both participants comply, then perfect forward secrecy is guaranteed
- Session resumption is unsupported
Note that although there are a number of similarities between the two protocols, an EKEP endpoint is not compatible with an ALTS endpoint.
The specification for the Enclave Key Exchange Protocol can be found here.
Authentication
To configure security-related options on a gRPC endpoint (a client or server), an entity must define the authentication policy that is to be enforced on a channel. Such a policy specifies:
- What types of identities the entity will accept from a peer
- What types of identities the entity is willing to present to a peer
A client’s and server’s configurations are compatible if both of the following are true:
- The client is willing to present a non-empty subset of the types of identities accepted by the server
- The server is willing to present a non-empty subset of the types of identities accepted by the client
If a client’s and server’s configurations are incompatible then a secure channel cannot be created between the endpoints.
Asylo authentication policies are encapsulated within
asylo::EnclaveCredentialsOptions
objects. Asylo provides various factories for commonly-used authentication
policies.
The following sections describe the various asylo::EnclaveCredentialsOptions
that are supported in Asylo.
Bidirectional SGX Local Authentication
This credentials configuration enables the client and server to mutually authenticate using SGX local attestation.
#include "asylo/grpc/auth/sgx_local_credentials_options.h"
asylo::EnclaveCredentialsOptions options = asylo::BidirectionalSgxLocalCredentialsOptions();
SGX local attestation is facilitated by the CPU, which generates SGX hardware REPORTs for locally-running enclaves. As a result, this configuration is only intended for channels set up between two SGX enclaves running on the same SGX-capable CPU.
One important use for SGX local attestation is in communicating with Asylo’s Assertion Generator Enclave (AGE). A quoting enclave is an enclave that produces signed attestations, or quotes, on behalf of other locally-running enclaves.
Enclave Configuration
To order to use SGX local attestation, an enclave must be configured to use the
SGX local assertion authority in its
asylo::EnclaveConfig
,
which is passed to the enclave via
asylo::TrustedApplication::Initialize
:
#include "asylo/enclave.proto.h"
#include "asylo/identity/enclave_assertion_authority_config.proto.h"
#include "asylo/identity/enclave_assertion_authority_configs.h"
#include "asylo/util/statusor.h"
// See additional reference below about |attestation_domain|.
std::string attestation_domain = ...
asylo::StatusOr<asylo::EnclaveAssertionAuthorityConfig> result =
asylo::CreateSgxLocalAssertionAuthorityConfig(attestation_domain);
if (!result.ok()) {
// Log or return error
}
asylo::EnclaveConfig config;
*config.add_enclave_assertion_authority_configs() =
std::move(result).value()
Reference
- SGX local credentials options documentation
CreateSgxLocalAssertionAuthorityConfig()
documentationSgxLocalAssertionAuthorityConfig
and “Attestation Domain” documentation
Bidirectional SGX Remote Authentication
This credentials configuration enables a client and server to mutually authenticate using SGX remote attestation. It is based on quotes produced by Asylo’s Assertion Generator Enclave (AGE).
This configuration can only be used between two SGX enclaves.
#include "asylo/grpc/auth/sgx_age_remote_credentials_options.h"
asylo::EnclaveCredentialsOptions options =
asylo::BidirectionalSgxAgeRemoteCredentialsOptions();
Enclave Configuration
In order to use SGX remote attestation, an enclave must be configured to use the
SGX remote assertion authority in its
asylo::EnclaveConfig
,
which is passed to the enclave via
asylo::TrustedApplication::Initialize
:
#include "asylo/enclave.proto.h"
#include "asylo/identity/enclave_assertion_authority_config.proto.h"
#include "asylo/identity/enclave_assertion_authority_configs.h"
#include "asylo/identity/platform/sgx/sgx_identity.proto.h"
std::string age_server_address = ... // Set this to the address of the AGE's gRPC server.
asylo::SgxIdentity age_sgx_identity = ... // Set this to the AGE's expected identity.
asylo::EnclaveConfig config;
*config.add_enclave_assertion_authority_configs() =
asylo::CreateSgxAgeRemoteAssertionAuthorityConfig(age_server_address,
age_sgx_identity);
Reference
- SGX AGE bidirectional remote credentials options documentation
CreateSgxAgeRemoteAssertionAuthorityConfig()
documentation
Unidirectional SGX Remote Authentication
Asylo also supports setting up connections in which only one side of the connection uses SGX remote attestation. The peer does not have to be an SGX enclave. Remote assertions are generated by Asylo’s Assertion Generator Enclave (AGE).
To use unidirectional SGX remote attestation, the enclave endpoint that is using SGX remote attestation should use the following configuration:
#include "asylo/grpc/auth/sgx_age_remote_credentials_options.h"
asylo::EnclaveCredentialsOptions options =
asylo::SelfSgxAgeRemoteCredentialsOptions();
The peer that is verifying the enclave’s identity should use the following configuration:
#include "asylo/grpc/auth/peer_sgx_age_remote_credentials_options.h"
asylo::EnclaveCredentialsOptions options =
asylo::PeerSgxAgeRemoteCredentialsOptions();
Enclave Configuration
Enclave configuration is similar to that described for Bidirectional SGX Remote Authentication.
A non-enclave peer verifier should use the following configuration in the application setup:
std::string age_server_address = ... // Set this to the address of the AGE's gRPC server.
asylo::SgxIdentity age_sgx_identity = ... // Set this to the AGE's expected identity.
std::vector<asylo::EnclaveAssertionAuthorityConfig> configs =
{asylo::CreateSgxAgeRemoteAssertionAuthorityConfig(age_server_address,
age_sgx_identity)};
asylo::InitializeEnclaveAssertionAuthorities(configs.begin(), configs.end());
Reference
- SGX AGE self remote credentials options documentation
- SGX AGE peer remote credentials options documentation
CreateSgxAgeRemoteAssertionAuthorityConfig()
documentation
Bidirectional Null Authentication
This credentials configuration is used for a mutually unauthenticated connection. This configuration can be used between any two enclave or non-enclave entities.
#include "asylo/grpc/auth/null_credentials_options.h"
asylo::EnclaveCredentialsOptions options =
asylo::BidirectionalNullCredentialsOptions();
WARNING: This is considered an insecure configuration, like using
grpc::InsecureChannelCredentials()
on
the client and grpc::InsecureServerCredentials()
on the server, and should not
be used for production systems. This configuration does provide channel
encryption but the channel is completely unauthenticated. You may find it useful
for testing your gRPC client and server before setting up real security policies
in your application.
Null credentials are most useful in setting up unidirectionally authenticated connections, which are occasionally useful in production systems. See Custom Authentication Policy for more information about setting up unidirectionally authenticated connections.
Enclave Configuration
In order to use null attestation, an enclave must be configured to use the null
assertion authority in its
asylo::EnclaveConfig
,
which is passed to the enclave via
asylo::TrustedApplication::Initialize
:
#include "asylo/enclave.proto.h"
#include "asylo/identity/enclave_assertion_authority_config.proto.h"
#include "asylo/identity/enclave_assertion_authority_configs.h"
asylo::EnclaveConfig config;
*config.add_enclave_assertion_authority_configs() =
asylo::CreateNullAssertionAuthorityConfig();
Reference
Custom Authentication Policy
For situations where Asylo’s pre-configured options do not match the exact
security requirements of an application, users can also craft a credentials
options object that reflects their custom authentication policy. This can be
done by combining asylo::EnclaveCredentialsOptions
objects with the
asylo::EnclaveCredentialsOptions::Add()
method.
For example, to configure a secure channel in which the server authenticates using SGX remote attestation attested, but the client is unauthenticated, we can combine two credentials options objects:
#include "asylo/grpc/auth/null_credentials_options.h"
#include "asylo/grpc/auth/sgx_remote_credentials_options.h"
asylo::EnclaveCredentialsOptions options =
asylo::PeerSgxLocalCredentialsOptions().Add(
asylo::SelfNullCredentialsOptions());
The same pattern can be used to craft other more complex authentication
policies. Policies on self (i.e., which types of identities are presented to the
peer) are set with Self*()
factories, and policies on the peer (i.e., which
types of identities are required to be presented by the peer) are set with
Peer*()
factories.
Server Credentials
The
asylo::EnclaveServerCredentials()
function can be used to create a grpc::ServerCredentials
that enforces the
authentication policy defined in a given asylo::EnclaveCredentialsOptions
object. The resulting credentials object can be used to build and start a gRPC
server.
#include "asylo/grpc/auth/enclave_credentials_options.h"
#include "asylo/grpc/auth/enclave_server_credentials.h"
// 1. Create credentials options to configure the server's authentication
// policy.
asylo::EnclaveCredentialsOptions credentials_options = …
// 2. Create a server credentials object.
std::shared_ptr<grpc::ServerCredentials> server_credentials =
asylo::EnclaveServerCredentials(credentials_options);
// 3. Create a server using the credentials.
::grpc::ServerBuilder builder;
builder.AddListeningPort("localhost:1234", server_credentials);
Channel Credentials
Configuring a gRPC client’s authentication policy is similar to configuring a
gRPC server. The
asylo::EnclaveChannelCredentials()
function can be used to create a grpc::ChannelCredentials
object that enforces
the authentication policy defined in a given asylo::EnclaveCredentialsOptions
object. The resulting credentials object can be used to create a
grpc::Channel
:
#include "asylo/grpc/auth/enclave_credentials_options.h"
#include "asylo/grpc/auth/enclave_channel_credentials.h"
// 1. Create credentials options to configure the client's authentication
// policy.
asylo::EnclaveCredentialsOptions credentials_options = …
// 2. Create a channel credentials object.
std::shared_ptr<grpc::ChannelCredentials> channel_credentials =
asylo::EnclaveChannelCredentials(credentials_options);
// 3. Create a channel using the credentials.
std::shared_ptr<::grpc::Channel> channel =
::grpc::CreateChannel("localhost:1234", channel_credentials);
Authorization
Enforcing an authentication policy on a channel ensures that a peer has authenticated with an expected set of identities (e.g., “My peer must be an SGX enclave”).
Most applications writers will also want to enforce authorization policies that specify additional constraints on an accepted peer’s identity (e.g., “My peer must be an SGX enclave signed by Foo and must have an ISVPRODID of 2 and an ISVSVN of 3 or higher”).
Asylo allows users to develop and enforce complex authorization policies, both client-side and server-side, in their applications:
- Server-side authorization policies can be enforced at the channel level or at the call level.
- Client-side authorization policies can be enforced only at the channel level.
Defining an ACL
An ACL is defined as an
asylo::IdentityAclPredicate
,
which is a recursive structure that allows individual conditions to be combined
with logical operators (AND
, OR
, and NOT
). An
asylo::EnclaveIdentityExpectation
can be used to define a single expectation against an identity. An
asylo::EnclaveIdentityExpectation
comprises a reference identity and a match
spec that is applied to that identity.
SGX Identity ACLs
ACLs for SGX identities are defined as
asylo::SgxIdentityExpectation
messages. An asylo::SgxIdentityExpectation
is composed of an
asylo::SgxIdentity
and an
asylo::SgxIdentityMatchSpec
.
It can be serialized into the match_spec
field of an
asylo::EnclaveIdentityExpectation
.
Asylo provides several common variants of asylo::SgxIdentityMatchSpec
:
DEFAULT
, STRICT_LOCAL
, and STRICT_REMOTE
. These can be constructed with
asylo::CreateSgxIdentityMatchSpec
,
and further modified to fit the exact expectation of an application. Match specs
are combined with identities to form expectations using
asylo::CreateSgxIdentityExpectation
.
See the
sgx_identity_util.h
library for more operations on SGX identities, match specs, and expectations.
Channel-level Authorization
A gRPC client or server can enforce an authorization policy at the channel level
by configuring the asylo::EnclaveCredentialsOptions
object used to create the
grpc::ClientCredentials
or grpc::ServerCredentials
with a peer_acl
:
asylo::EnclaveCredentialsOptions credentials_options = …
credentials_options.peer_acl = …
The peer_acl
field contains an asylo::IdentityAclPredicate
proto and can be
defined as described above.
Note that if the ACL is not met then the connection will be terminated. If more fine-grained access-control is desired, consider using Call-level Authorization instead.
Call-level Authorization (Server only)
Authentication Context
A gRPC server using Asylo’s gRPC transport security stack can access the
authenticated peer’s identities, as well as other properties of the channel,
through an
asylo::EnclaveAuthContext
object.
Evaluating an ACL
The
asylo::EnclaveAuthContext::EvaluateAcl()
method can be used to evaluate the authenticated peer’s identities against an
ACL.
#include "asylo/grpc/auth/enclave_auth_context.h"
#include "asylo/identity/identity_acl.proto.h"
::grpc::Status FooImpl::Bar(
::grpc::ServerContext *context,
const BarRequest *request,
BarResponse *response) {
// Create an EnclaveAuthContext object.
asylo::EnclaveAuthContext enclave_auth_context = …
std::string explanation;
asylo::IdentityAclPredicate acl = …
auto evaluate_result = enclave_auth_context.EvaluateAcl(acl, &explanation);
if (!evaluate_result.ok()) {
// Log the error and/or return
}
if (!evaluate_result.value()) {
// Peer is unauthorized! Log the explanation and/or return.
}
}
Extracting Peer Identities
For more specialized use-cases, the peer’s identities can be extracted and
examined directly using the
asylo::EnclaveAuthContext::FindEnclaveIdentity()
method. Users can select specific identities by specifying an
asylo::EnclaveIdentityDescription
.
See
all supported identity types.
#include "asylo/grpc/auth/enclave_auth_context.h"
#include "asylo/identity/descriptions.h"
#include "asylo/identity/identity.proto.h"
::grpc::Status FooImpl::Bar(
::grpc::ServerContext *context,
const BarRequest *request,
BarResponse *response) {
// Create an EnclaveAuthContext object.
asylo::EnclaveAuthContext enclave_auth_context = …
asylo:: EnclaveIdentityDescription identity_description;
// Select an SGX identity using the SGX identity description.
asylo::SetSgxIdentityDescription(&identity_description);
auto identity_result =
enclave_auth_context.FindEnclaveIdentity(identity_description);
if (!identity_result.ok()) {
// Log the error and/or return
}
const asylo::EnclaveIdentity &identity = *identity_result.value();
// Do something with |identity|.
…
}