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

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

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

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|.
 …
}