ENT-970 - SGX remote attestation host (#173)

* ENT-970 - SGX remote attestation host
 * Remote attestation enclave
 * Client for the remote attestation host
    * Communicates with ISV / RA server, which in turn communicates with
      the Intel Attestation Service
    * Native library bridging the client code running on the JVM with
      the native bits controlling and communicating with the enclave

* ENT-970 - Address comments from code review
* ENT-970 - More updates addressing review comments
* ENT-970 - Integrate with root Gradle project for SGX
This commit is contained in:
Tommy Lillehagen 2017-12-13 17:45:33 +00:00 committed by GitHub
parent 65ccd2318f
commit 83d6a248a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 5249 additions and 0 deletions

6
sgx-jvm/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.idea/
.vscode/
obj/
build/
log/
tags

View File

@ -0,0 +1,45 @@
# Remote Attestation
## Project Organisation
* **Enclave**
The enclave (`enclave/`) is responsible for initialising and coordinating
the remote attestation process from the client side, and will eventually
operate on a secret provisioned from the challenger (once successfully
attested by Intel's Attestation Service).
* **Host**
The host JVM (`host/`) is running in an untrusted environment and
facilitates the communication between the challenger and its enclave.
To coordinate with the enclave, the host uses a native JNI library (in
`host/native/`)
* **Challenger**
The challenger JVM does not require SGX-enabled hardware and is essentially
the party asking the host to prove that it has spun up a program in an
enclave on trusted hardware (that cannot be tampered with), so that
consequently, it can provision an encrypted secret to said enclave.
* **IAS Proxy**
The proxy is responsible for talking to the Intel Attestation Service over
mutual TLS to verify attestation evidence received from the host. The proxy
needs a client certificate and a service provider identifier (SPID) issued
by Intel. In turn, it will forward any received proof from Intel to the
host and challenger, making it possible for the challenger to trust the
host and thus provision the secret. The proof is signed with Intel's root
certificate.
## Getting Started
To get started, run the following commands in `sgx-jvm`:
```bash
> source environment
> sx help
```
Further documentation is available in `sgx-jvm/tools/sx/README.md`.

View File

@ -0,0 +1,6 @@
*_t.c
*_t.h
*_t.o
*_u.c
*_u.h
*_u.o

View File

@ -0,0 +1,213 @@
.PHONY: info all clean \
unsigned signed-openssl signed-hsm \
obj-trusted obj-untrusted
# === GENERAL PARAMETERS ==========================================================================
SHELL = /bin/bash
MAKEFILE_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
MODE ?= DEBUG
GRADLE_FILE = $(MAKEFILE_DIR)/../host/build.gradle
NAME = corda_sgx_ra
VERSION := $(shell sed -n "s/^version = '\([^']*\)'.*/\\1/p" $(GRADLE_FILE))
PLATFORM := $(shell uname -s | tr [:upper:] [:lower:])
OUT_DIR = $(MAKEFILE_DIR)/build
OBJ_DIR = $(MAKEFILE_DIR)/obj
ENCLAVE = $(OUT_DIR)/$(NAME)_enclave.a
ENCLAVE_SIGNED = $(OUT_DIR)/$(NAME)_enclave.so
ENCLAVE_UNSIGNED = $(OUT_DIR)/$(NAME)_enclave_unsigned.so
ENCLAVE_BLOB = $(OUT_DIR)/$(NAME)_enclave_blob.bin
ENCLAVE_OPENSSL = $(OUT_DIR)/$(NAME)_enclave_openssl.so
OPENSSL_PRIVATE_KEY = $(MAKEFILE_DIR)/../../sign_helper/selfsigning.pem
OPENSSL_PUBLIC_KEY = $(OUT_DIR)/selfsigning.public.pem
OPENSSL_SIGNATURE = $(OUT_DIR)/$(NAME)_enclave.signature.openssl.sha256
ENCLAVE_HSM = $(OUT_DIR)/$(NAME)_enclave_hsm.so
HSM_PROFILE ?= dev_sim
HSM_PUBLIC_KEY = $(OUT_DIR)/hsm.public.pem
HSM_SIGNATURE = $(OUT_DIR)/$(NAME)_enclave.signature.hsm.sha256
HSM_BUILD_DIR = $(MAKEFILE_DIR)/../../hsm-tool/build/libs/sgx-jvm
HSM_TOOL_VERSION = 1.0-SNAPSHOT
HSM_TOOL = $(HSM_BUILD_DIR)/hsm-tool-$(HSM_TOOL_VERSION).jar
# === BUILD PARAMETERS ============================================================================
CPP = g++
GRADLE = $(MAKEFILE_DIR)/../../../gradlew
CPPFLAGS_BASE = $(INC_DIRS) -Wall -fPIC
CPPFLAGS_DEBUG = $(CPPFLAGS_BASE) -g
CPPFLAGS_RELEASE = $(CPPFLAGS_BASE) -s -DNDEBUG
LDFLAGS_BASE =
LDFLAGS_DEBUG = $(LDFLAGS_BASE)
LDFLAGS_RELEASE = $(LDFLAGS_BASE) -s
# === SGX-SPECIFIC BUILD PARAMETERS ===============================================================
SGX_SDK := $(MAKEFILE_DIR)/../../linux-sgx
include $(MAKEFILE_DIR)/sgx.mk
SGX_CONFIG = $(MAKEFILE_DIR)/config/$(SGX_MODE_NAME).xml
RPC_DIR = $(MAKEFILE_DIR)/rpc
# === MODE-SPECIFIC BUILD PARAMETERS ==============================================================
ifeq ($(subst release,RELEASE,$(MODE)),RELEASE)
CPPFLAGS = $(CPPFLAGS_RELEASE) $(SGX_CPPFLAGS_RELEASE) -I$(RPC_DIR)
LDFLAGS = $(LDFLAGS_RELEASE) $(SGX_LDFLAGS_RELEASE)
else
CPPFLAGS = $(CPPFLAGS_DEBUG) $(SGX_CPPFLAGS_DEBUG) -I$(RPC_DIR)
LDFLAGS = $(LDFLAGS_DEBUG) $(SGX_LDFLAGS_DEBUG)
endif
# === ENCLAVE SOURCES AND OUTPUTS =================================================================
ENCLAVE_SOURCES = $(wildcard *.cpp)
ENCLAVE_OBJECTS = $(addprefix $(OBJ_DIR)/, $(ENCLAVE_SOURCES:.cpp=.o))
TGEN_SOURCES = $(addprefix $(RPC_DIR)/, enclave_t.c)
TGEN_HEADERS = $(TGEN_SOURCES:.c=.h)
TGEN_OBJECTS = $(patsubst $(RPC_DIR)/%,$(OBJ_DIR)/%,$(TGEN_SOURCES:.c=.o))
UGEN_SOURCES = $(addprefix $(RPC_DIR)/, enclave_u.c)
UGEN_HEADERS = $(UGEN_SOURCES:.c=.h)
UGEN_OBJECTS = $(patsubst $(RPC_DIR)/%,$(OBJ_DIR)/%,$(UGEN_SOURCES:.c=.o))
# === PSEUDO TARGETS ==============================================================================
info: # Show available targets
@echo "Build Targets:"
@sed -n 's/^\([a-z-]*\): .*# \(.*\)$$/ \1 - \2/p' Makefile | expand -t24 | sort
@echo
@echo "Configuration:"
@echo " HSM_PROFILE = dev_sim (or dev_hsm, prod)"
@echo " MODE = DEBUG (or RELEASE)"
@echo " SGX_DEBUG_MODE = TRUE (or FALSE)"
@echo " SGX_IS_PRERELEASE = FALSE (or TRUE)"
@echo " SGX_USE_HARDWARE = FALSE (or TRUE)"
@echo
all: $(ENCLAVE) signed-openssl # Build enclave (self-signed using OpenSSL)
clean: # Clean build files
@$(RM) -rf $(OUT_DIR)
@$(RM) -rf $(OBJ_DIR)
@$(RM) -rf $(RPC_DIR)
# === SIGNING =====================================================================================
mode: # Show state of SGX specific build variables
@echo "SGX_MODE: $(SGX_MODE)"
@echo "SGX_DEBUG: $(SGX_DEBUG)"
@echo "SGX_PRERELEASE: $(SGX_PRERELEASE)"
@echo "HSM_PROFILE: $(HSM_PROFILE)"
unsigned: $(ENCLAVE_BLOB) # Construct an unsigned enclave
signed-openssl: $(ENCLAVE_OPENSSL) # Construct a self-signed enclave using OpenSSL
signed-hsm: $(ENCLAVE_HSM) # Construct an enclave signed by an HSM
mev_sim|dev_hsm|prod)
$(ENCLAVE_UNSIGNED): $(ENCLAVE)
@echo "Using mode=$(MODE), config=$(SGX_CONFIG)"
$(CPP) -o $(ENCLAVE_UNSIGNED) $(LDFLAGS)
$(ENCLAVE_BLOB): $(ENCLAVE_UNSIGNED)
$(SGX_SIGNER) gendata \
-enclave $(ENCLAVE_UNSIGNED) \
-out $(ENCLAVE_BLOB) \
-config $(SGX_CONFIG)
$(ENCLAVE_OPENSSL): $(ENCLAVE_BLOB) $(OPENSSL_PUBLIC_KEY) $(OPENSSL_SIGNATURE)
$(SGX_SIGNER) catsig \
-enclave $(ENCLAVE_UNSIGNED) \
-key $(OPENSSL_PUBLIC_KEY) \
-sig $(OPENSSL_SIGNATURE) \
-unsigned $(ENCLAVE_BLOB) \
-config $(SGX_CONFIG) \
-out $(ENCLAVE_OPENSSL)
@cp $(ENCLAVE_OPENSSL) $(ENCLAVE_SIGNED)
$(OPENSSL_PUBLIC_KEY): $(OPENSSL_PRIVATE_KEY)
openssl rsa \
-in $(OPENSSL_PRIVATE_KEY) \
-pubout -out $(OPENSSL_PUBLIC_KEY)
$(OPENSSL_SIGNATURE): $(OPENSSL_PRIVATE_KEY) $(ENCLAVE_BLOB)
openssl dgst \
-sha256 \
-sign $(OPENSSL_PRIVATE_KEY) \
-out $(OPENSSL_SIGNATURE) \
$(ENCLAVE_BLOB)
$(ENCLAVE_HSM): $(ENCLAVE_BLOB) $(HSM_PUBLIC_KEY) $(HSM_SIGNATURE)
$(SGX_SIGNER) catsig \
-enclave $(ENCLAVE_UNSIGNED) \
-key $(HSM_PUBLIC_KEY) \
-sig $(HSM_SIGNATURE) \
-unsigned $(ENCLAVE_BLOB) \
-config $(SGX_CONFIG) \
-out $(ENCLAVE_HSM)
@cp $(ENCLAVE_HSM) $(ENCLAVE_SIGNED)
$(HSM_PUBLIC_KEY) $(HSM_SIGNATURE): $(HSM_TOOL) $(ENCLAVE_BLOB)
@echo "Using HSM profile=$(HSM_PROFILE)"
java -jar $(HSM_TOOL) \
--mode=Sign \
--source=$(ENCLAVE_BLOB) \
--pubkey=$(HSM_PUBLIC_KEY) \
--signature=$(HSM_SIGNATURE) \
--profile=$(HSM_PROFILE)
$(HSM_TOOL):
$(GRADLE) sgx-hsm-tool:jar
# === ENCLAVE =====================================================================================
obj-trusted: $(TGEN_OBJECTS) # Object files for trusted zone
obj-untrusted: $(UGEN_OBJECTS) # Object files for untrusted zone
$(ENCLAVE): $(TGEN_OBJECTS) $(ENCLAVE_OBJECTS) | $(OUT_DIR)
ar qc $@ $^
ranlib $@
$(TGEN_SOURCES) $(TGEN_HEADERS) $(UGEN_SOURCES) $(UGEN_HEADERS): enclave.edl | $(RPC_DIR)
$(SGX_EDGER8R) \
--search-path $(SGX_INC_DIR) \
--trusted-dir $(RPC_DIR) \
--untrusted-dir $(RPC_DIR) \
enclave.edl
$(ENCLAVE_OBJECTS): $(ENCLAVE_SOURCES) | $(OBJ_DIR)
$(TGEN_OBJECTS): $(TGEN_SOURCES)
$(UGEN_OBJECTS): $(UGEN_SOURCES)
$(OBJ_DIR)/%.o: $(RPC_DIR)/%.c
@mkdir -p $(@D)
$(CPP) $(CPPFLAGS) $(SGX_DEBUG_FLAGS) $(SGX_DEFS) -o $@ -c $<
$(OBJ_DIR)/%.o: %.cpp
@mkdir -p $(@D)
$(CPP) $(CPPFLAGS) $(SGX_DEBUG_FLAGS) $(SGX_DEFS) -o $@ -c $<
# === BUILD DIRECTORIES ===========================================================================
$(OUT_DIR):
@mkdir -p $(OUT_DIR)
$(OBJ_DIR):
@mkdir -p $(OBJ_DIR)
$(RPC_DIR):
@mkdir -p $(RPC_DIR)

View File

@ -0,0 +1,11 @@
<EnclaveConfiguration>
<ProdID>0</ProdID>
<ISVSVN>0</ISVSVN>
<StackMaxSize>0x40000</StackMaxSize>
<HeapMaxSize>0x100000</HeapMaxSize>
<TCSNum>1</TCSNum>
<TCSPolicy>1</TCSPolicy>
<DisableDebug>0</DisableDebug>
<MiscSelect>0</MiscSelect>
<MiscMask>0xFFFFFFFF</MiscMask>
</EnclaveConfiguration>

View File

@ -0,0 +1,11 @@
<EnclaveConfiguration>
<ProdID>0</ProdID>
<ISVSVN>0</ISVSVN>
<StackMaxSize>0x40000</StackMaxSize>
<HeapMaxSize>0x100000</HeapMaxSize>
<TCSNum>1</TCSNum>
<TCSPolicy>1</TCSPolicy>
<DisableDebug>1</DisableDebug>
<MiscSelect>0</MiscSelect>
<MiscMask>0xFFFFFFFF</MiscMask>
</EnclaveConfiguration>

View File

@ -0,0 +1,216 @@
#include <cstring>
#include <sgx_tcrypto.h>
#include <sgx_tkey_exchange.h>
#include "enclave_t.h"
#define CHECKED(expr) { \
sgx_status_t _status = (expr); \
if (SGX_SUCCESS != _status) { return _status; } \
}
#define MAX_SECRET_SIZE 128
extern "C" {
// === Initialization and Finalization =======================================
static inline sgx_status_t create_pse_session(
bool use_platform_services
) {
// If desired, try up to three times to establish a PSE session.
sgx_status_t status = SGX_SUCCESS;
if (use_platform_services) {
int retry_count = 3;
do { status = sgx_create_pse_session(); }
while (SGX_ERROR_BUSY == status && --retry_count);
}
return status;
}
static inline sgx_status_t close_pse_session(
bool use_platform_services
) {
// If a PSE session was created, close it properly.
sgx_status_t status = SGX_SUCCESS;
if (use_platform_services) {
status = sgx_close_pse_session();
}
return status;
}
// Initialize the remote attestation process.
sgx_status_t initializeRemoteAttestation(
bool use_platform_services,
sgx_ec256_public_t *challenger_key,
sgx_ra_context_t *context
) {
sgx_status_t status;
// Abort if the public key of the challenger and/or the output context
// variable is not provided.
if (NULL == challenger_key || NULL == context) {
return SGX_ERROR_INVALID_PARAMETER;
}
// If desired, try to establish a PSE session.
CHECKED(create_pse_session(use_platform_services));
// Initialize the remote attestation and key exchange process, and place
// the resulting context in `context`.
status = sgx_ra_init(challenger_key, use_platform_services, context);
// If a PSE session was created, close it properly.
CHECKED(close_pse_session(use_platform_services));
return status;
}
// Clean up and finalize the remote attestation process.
sgx_status_t finalizeRemoteAttestation(
sgx_ra_context_t context
) {
// Release the remote attestation and key exchange context after the
// process has been completed.
return sgx_ra_close(context);
}
// === Remote Attestation Verification =======================================
/*
* This function compares `len` bytes from buffer `b1` and `b2` in constant
* time to protect against side-channel attacks. For sensitive code running
* inside an enclave, this function is preferred over `memcmp`.
*/
static int consttime_memequal(const void *b1, const void *b2, size_t len) {
// Written by Matthias Drochner <drochner@NetBSD.org>. Public domain.
const unsigned char
*c1 = (unsigned char*)b1,
*c2 = (unsigned char*)b2;
unsigned int res = 0;
while (len--) res |= *c1++ ^ *c2++;
/*
* Map 0 to 1 and [1, 256) to 0 using only constant-time
* arithmetic.
*
* This is not simply `!res' because although many CPUs support branchless
* conditional moves and many compilers will take advantage of them,
* certain compilers generate branches on certain CPUs for `!res'.
*/
return (1 & ((res - 1) >> 8));
}
// Verify CMAC from the challenger to protect against spoofed results.
sgx_status_t verifyCMAC(
sgx_ra_context_t context,
uint8_t *message,
size_t message_size,
uint8_t *cmac,
size_t cmac_size
) {
// Check inputs.
if (sizeof(sgx_mac_t) != cmac_size || NULL == cmac) {
return SGX_ERROR_INVALID_PARAMETER;
}
if (UINT32_MAX < message_size || NULL == message) {
return SGX_ERROR_INVALID_PARAMETER;
}
// Get negotiated MK key of remote attestation and key exchange session.
sgx_ec_key_128bit_t mk_key = { 0 };
CHECKED(sgx_ra_get_keys(context, SGX_RA_KEY_MK, &mk_key));
// Perform 128-bit CMAC hash over the first four bytes of the status
// obtained from the challenger.
uint8_t computed_cmac[SGX_CMAC_MAC_SIZE] = { 0 };
CHECKED(sgx_rijndael128_cmac_msg(
&mk_key, message, message_size, &computed_cmac
));
// Compare the computed CMAC-SMK with the provided one.
if (0 == consttime_memequal(computed_cmac, cmac, sizeof(computed_cmac))) {
return SGX_ERROR_MAC_MISMATCH;
}
// Can further test number of uses of the secret data, and require
// re-attestation after X uses... But, for now, we're happy!
return SGX_SUCCESS;
}
// Verify attestation response from the challenger.
sgx_status_t verifyAttestationResponse(
sgx_ra_context_t context,
uint8_t *secret,
size_t secret_size,
uint8_t *gcm_iv,
uint8_t *gcm_mac,
size_t gcm_mac_size,
uint8_t *sealed_secret,
size_t sealed_secret_size
) {
// Check inputs.
if (secret_size > MAX_SECRET_SIZE || NULL == secret) {
return SGX_ERROR_INVALID_PARAMETER;
}
if (gcm_mac_size != SGX_AESGCM_MAC_SIZE || NULL == gcm_mac) {
return SGX_ERROR_INVALID_PARAMETER;
}
if (sealed_secret_size - sizeof(sgx_sealed_data_t) > MAX_SECRET_SIZE) {
return SGX_ERROR_INVALID_PARAMETER;
}
// Get negotiated SK key of remote attestation and key exchange session.
sgx_ec_key_128bit_t sk_key;
CHECKED(sgx_ra_get_keys(context, SGX_RA_KEY_SK, &sk_key));
// Decrypt using Rijndael AES-GCM.
uint8_t *decrypted_secret = (uint8_t*)malloc(secret_size);
CHECKED(sgx_rijndael128GCM_decrypt(
&sk_key, secret, secret_size, decrypted_secret, gcm_iv,
SGX_AESGCM_IV_SIZE, NULL, 0, (sgx_aes_gcm_128bit_tag_t*)gcm_mac
));
// Return sealed secret if requested.
if (NULL != sealed_secret && secret_size <= sealed_secret_size) {
// Seal the secret so that it can be returned to the untrusted
// environment.
CHECKED(sgx_seal_data(
0, NULL, secret_size, decrypted_secret,
sealed_secret_size, (sgx_sealed_data_t*)sealed_secret
));
}
// Free temporary memory.
free(decrypted_secret);
return SGX_SUCCESS;
}
// Check whether the sealed secret is unsealable or not.
sgx_status_t unsealSecret(
uint8_t *sealed_secret,
size_t sealed_secret_size
) {
// Allocate temporary buffer for the output of the operation.
uint8_t *buffer = (uint8_t*)malloc(sealed_secret_size);
uint32_t buffer_size = sealed_secret_size;
if (NULL == buffer) {
return SGX_ERROR_OUT_OF_MEMORY;
}
// Attempt to unseal the secret.
sgx_status_t status = sgx_unseal_data(
(sgx_sealed_data_t*)sealed_secret, NULL, NULL,
buffer, &buffer_size
);
// Free up the temporary memory buffer.
free(buffer);
return status;
}
}

View File

@ -0,0 +1,104 @@
enclave {
from "sgx_tkey_exchange.edl" import *;
include "sgx_key_exchange.h"
include "sgx_tseal.h"
trusted {
/**
* Initialize the remote attestation process.
*
* @param usePlatformServices If true, the enclave establishes a
* session with the PSE before initializing the attestation context.
* This provides additional nonce replay protection and a reliable
* monotonic counter.
* @param challengerKey ECDSA public key of the challenger with the 8
* magic bytes removed, and X and Y components changed to little
* endian.
* @param context The variable receiving the context constructed during
* initialization.
*
* @return Status code indicative of the outcome of the operation.
*/
public sgx_status_t initializeRemoteAttestation(
bool usePlatformServices,
[in] sgx_ec256_public_t *challengerKey,
[out] sgx_ra_context_t *context
);
/**
* Clean up and finalize the remote attestation process.
*
* @param context The context constructed during initialization.
*
* @return SGX_SUCCESS if successful, or SGX_ERROR_INVALID_PARAMETER if
* an invalid context is provided.
*/
public sgx_status_t finalizeRemoteAttestation(
sgx_ra_context_t context
);
/**
* Verify CMAC of attestation result from challenger using the MK key.
*
* @param context The context constructed during initialization.
* @param message The status obtained from the challenger as part of
* the attestation result.
* @param messageSize The size of the attestation status payload.
* @param cmac The CMAC received from the challenger.
* @param cmacSize The size of the CMAC received.
*
* @return Status code indicative of the outcome of the operation.
*/
public sgx_status_t verifyCMAC(
sgx_ra_context_t context,
[in,size=messageSize] uint8_t *message,
size_t messageSize,
[in,size=cmacSize] uint8_t *cmac,
size_t cmacSize
);
/**
* Verify an attestation response from the service provider.
*
* @param context The context constructed during initialization.
* @param secret Message containing the secret.
* @param secretSize Size of the secret message, in bytes.
* @param gcmIV The initialization vector used in the decryption.
* @param gcmMac Pointer to the AES-GCM MAC for the secret message.
* @param gcmMacSize Size of the AES-GCM MAC.
* @param sealedSecret Pre-allocated buffer receiving the sealed
* secret. If NULL, the sealed secret will not be returned.
* @param maxSealedSecretSize The maximum size of the sealed secret.
* This must be less than or equal to the size of the pre-allocated
* buffer above, and no larger than the upper limit of 128 bytes.
*
* @return Status code indicative of the outcome of the operation.
*/
public sgx_status_t verifyAttestationResponse(
sgx_ra_context_t context,
[in,size=secretSize] uint8_t *secret,
size_t secretSize,
[in,count=12] uint8_t *gcmIV,
[in,size=gcmMacSize] uint8_t *gcmMac,
size_t gcmMacSize,
[out,size=maxSealedSecretSize] uint8_t *sealedSecret,
size_t maxSealedSecretSize
);
/**
* Check whether the application enclave is able to unseal a secret.
*
* @param sealedSecret The previously sealed secret.
* @param sealedSecretSize The size of the sealed secret.
*
* @return Status code indicative of the outcome of the operation.
*/
public sgx_status_t unsealSecret(
[in,size=sealedSecretSize] uint8_t *sealedSecret,
size_t sealedSecretSize
);
};
};

View File

@ -0,0 +1,10 @@
corda_sgx_ra_enclave.so
{
global:
enclave_entry;
g_global_data_sim;
g_global_data;
g_peak_heap_used;
local:
*;
};

View File

@ -0,0 +1,84 @@
# === SGX-SPECIFIC BUILD PARAMETERS ===============================================================
SGX_LIB_DIR := $(SGX_SDK)/build/linux
SGX_INC_DIR := $(SGX_SDK)/common/inc
SGX_EDGER8R = $(SGX_SDK)/build/linux/sgx_edger8r
SGX_SIGNER := $(SGX_SDK)/build/linux/sgx_sign
SGX_MODE_NAME := $(shell echo $(MODE) | tr [:upper:] [:lower:])
SGX_USE_HARDWARE ?= FALSE
SGX_IS_PRERELEASE ?= FALSE
SGX_DEBUG_MODE ?= TRUE
SGX_CPPFLAGS_RELEASE = -fvisibility=hidden -fpie -fstack-protector \
-I$(SGX_INC_DIR) \
-s -DNDEBUG
SGX_CPPFLAGS_DEBUG = -fvisibility=hidden -fpie -fstack-protector \
-I$(SGX_INC_DIR)
ifeq ($(SGX_USE_HARDWARE),TRUE)
URTS_LIB = sgx_urts
TRTS_LIB = sgx_trts
SGX_SERVICE_LIB = sgx_tservice
CAPABLE_LIB = sgx_capable
UAE_SERVICE_LIB = sgx_uae_service
UKEY_EXCHNG = sgx_ukey_exchange
SGX_SIM = 0
SGX_MODE = HW
else
URTS_LIB = sgx_urts_sim
TRTS_LIB = sgx_trts_sim
SGX_SERVICE_LIB = sgx_tservice_sim
CAPABLE_LIB = sgx_capable
UAE_SERVICE_LIB = sgx_uae_service_sim
UKEY_EXCHNG = sgx_ukey_exchange
SGX_SIM = 1
SGX_MODE = SIM
endif
ifeq ($(SGX_DEBUG_MODE),TRUE)
SGX_DEBUG = 1
SGX_DEBUG_FLAGS = -DDEBUG -UNDEBUG -UEDEBUG
else
SGX_DEBUG = 0
SGX_DEBUG_FLAGS = -DNDEBUG -UEDEBUG -UDEBUG
endif
ifeq ($(SGX_IS_PRERELEASE),TRUE)
SGX_PRERELEASE = 1
SGX_DEBUG = 0
SGX_DEBUG_FLAGS = -DNDEBUG -DEDEBUG -UDEBUG
else
SGX_PRERELEASE = 0
endif
SGX_LIBS = -lsgx_tstdc -lsgx_tstdcxx -lsgx_tcrypto \
-lsgx_tkey_exchange \
-l$(SGX_SERVICE_LIB)
SGX_DEFS = -DSGX_SIM=$(SGX_SIM) \
-DSGX_MODE=$(SGX_MODE) \
-DSGX_DEBUG=$(SGX_DEBUG) \
-DSGX_PRERELEASE=$(SGX_PRERELEASE)
LINK_SCRIPT = $(MAKEFILE_DIR)/enclave.lds
SGX_LDFLAGS_BASE = -Wl,--no-undefined \
-nostdlib -nodefaultlibs -nostartfiles \
-L$(SGX_LIB_DIR) \
-Wl,--whole-archive \
-l$(TRTS_LIB) \
-Wl,--no-whole-archive \
-Wl,--start-group \
$(ENCLAVE) $(SGX_LIBS) \
-Wl,--end-group \
-Wl,-Bstatic -Wl,-Bsymbolic \
-Wl,-pie,-eenclave_entry \
-Wl,--export-dynamic \
-Wl,--defsym,__ImageBase=0 \
-Wl,--version-script=$(LINK_SCRIPT)
SGX_LDFLAGS_RELEASE = $(SGX_LDFLAGS_BASE)
SGX_LDFLAGS_DEBUG = $(SGX_LDFLAGS_BASE)

View File

@ -0,0 +1,72 @@
.PHONY: host host-native docs \
unit-tests unit-tests-force \
integration-tests integration-tests-force \
run run-local run-remote run-sgx \
clean
# === GENERAL PARAMETERS ==========================================================================
SHELL = /bin/bash
MAKEFILE_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
GRADLE ?= $(MAKEFILE_DIR)/../gradlew
# TODO Update to Dokka 0.9.16 (when available) to fix: https://github.com/Kotlin/dokka/issues/184
DOKKA_FILTER = grep -v "Can't find node by signature"
# === PSEUDO TARGETS ==============================================================================
host:
$(GRADLE) compileKotlin
host-native:
make -C native all
docs:
$(GRADLE) dokka | $(DOKKA_FILTER)
docs-force:
rm -rf $(MAKEFILE_DIR)/build/javadoc
$(GRADLE) dokka | $(DOKKA_FILTER)
unit-tests: host-native
$(GRADLE) test
unit-tests-force: host-native
$(GRADLE) cleanTest test
integration-tests: host-native
$(GRADLE) integrationTest
integration-tests-force: host-native
$(GRADLE) cleanIntegrationTest integrationTest
~/.classpath:
$(GRADLE) -q getClasspath > ~/.classpath
clean:
$(GRADLE) clean
rm -f ~/.classpath
# === TEST TARGETS ================================================================================
JAVA_PROG = $(JAVA_HOME)/bin/java
SGX_GDB = /sgx/sgxsdk/bin/sgx-gdb
HOST = localhost:2000
MAIN_CLASS = net.corda.sgx.cli.Program
JVM_DEFS = -Dcorda.sgx.enclave.path=/code/sgx-jvm/remote-attestation/enclave/build \
-Djava.library.path=/code/sgx-jvm/remote-attestation/host/native/build \
-Dfile.encoding=US-ASCII -Duser.country=US -Duser.language=en -Duser.variant \
-Dattestation.home=/code/sgx-jvm/remote-attestation/host/build/logs
JVM_ARGS = -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
run: ~/.classpath
$(JAVA_PROG) $(JVM_DEFS) $(JVM_ARGS) -cp `cat ~/.classpath` $(MAIN_CLASS)
run-local: ~/.classpath
gdb --args $(JAVA_PROG) $(JVM_DEFS) $(JVM_ARGS) -cp `cat ~/.classpath` $(MAIN_CLASS)
run-sgx: ~/.classpath
$(SGX_GDB) --args $(JAVA_PROG) $(JVM_DEFS) $(JVM_ARGS) -cp `cat ~/.classpath` $(MAIN_CLASS)
run-remote: ~/.classpath
gdbserver $(HOST) $(JAVA_PROG) $(JVM_DEFS) $(JVM_ARGS) -cp `cat ~/.classpath` $(MAIN_CLASS)

View File

@ -0,0 +1,82 @@
version = '0.1'
buildscript {
ext.dokka_version = "0.9.15"
ext.httpclient_version = "4.5.2"
ext.nativeBuildDir = "$projectDir/native/build"
ext.enclaveBuildDir = "$projectDir/../enclave/build"
ext.debugArgs = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
repositories {
mavenLocal()
mavenCentral()
jcenter()
}
dependencies {
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
}
}
apply plugin: 'kotlin'
apply plugin: 'application'
apply plugin: 'org.jetbrains.dokka'
mainClassName = 'net.corda.sgx.cli.Program'
description "Client side of SGX remote attestation process"
configurations {
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
}
sourceSets {
integrationTest {
kotlin {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
srcDir file('src/integration-test/kotlin')
}
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile "junit:junit:$junit_version"
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
compile "com.fasterxml.jackson.core:jackson-core:$jackson_version"
compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
compile "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version"
compile "org.apache.httpcomponents:httpclient:$httpclient_version"
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
compile "org.apache.logging.log4j:log4j-core:$log4j_version"
runtime "org.apache.logging.log4j:log4j-web:$log4j_version"
compile "org.slf4j:jcl-over-slf4j:$slf4j_version"
}
dokka {
outputFormat = 'html'
outputDirectory = "$buildDir/javadoc"
}
task integrationTest(type: Test) {
testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath
}
tasks.withType(Test) {
systemProperty "java.library.path", "$nativeBuildDir"
systemProperty "corda.sgx.enclave.path", "$enclaveBuildDir"
systemProperty "attestation.home", "$buildDir/logs"
jvmArgs "$debugArgs"
}
task getClasspath(type: Task) {
doLast {
println sourceSets.main.runtimeClasspath.collect { it.toString() }.join(":")
}
}

View File

@ -0,0 +1 @@
wrapper.hpp

View File

@ -0,0 +1,115 @@
.PHONY: all clean
# === GENERAL PARAMETERS ==========================================================================
SHELL = /bin/bash
MAKEFILE_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
MODE ?= DEBUG # or RELEASE
GRADLE_FILE = $(MAKEFILE_DIR)/../build.gradle
NAME = corda_sgx_ra
VERSION := $(shell sed -n "s/^version = '\([^']*\)'.*/\\1/p" $(GRADLE_FILE))
PLATFORM := $(shell uname -s | tr [:upper:] [:lower:])
OUT_DIR = $(MAKEFILE_DIR)/build
OBJ_DIR = $(MAKEFILE_DIR)/obj
HOST_LIBRARY = $(OUT_DIR)/lib$(NAME).so
# === BUILD PARAMETERS ============================================================================
ifeq ($(PLATFORM),linux)
JDK_HOME ?= $(dir $(word 1,$(wildcard /usr/lib/jvm/*/)))
JDK_INC_DIRS = -I$(JDK_HOME)include -I$(JDK_HOME)include/linux
endif
LOGGING_DEFS =
ifeq ($(LOGGING),TRUE)
LOGGING_DEFS = -DLOGGING
endif
CPP = g++
CPPFLAGS_BASE = $(JDK_INC_DIRS) -Wall -fPIC \
$(SGX_DEFS) $(LOGGING_DEFS)
CPPFLAGS_DEBUG = $(CPPFLAGS_BASE) -g -O0 -DDEBUG
CPPFLAGS_RELEASE = $(CPPFLAGS_BASE) -s -DNDEBUG
LDFLAGS_BASE = \
-shared \
-Wl,-soname,lib$(NAME).so \
-Wl,-rpath,$(SGX_LIB_DIR):../../linux-sgx/build/linux \
-Wl,-z,defs \
-Wl,--no-as-needed \
-L$(SGX_LIB_DIR) \
-l$(URTS_LIB) \
-l$(CAPABLE_LIB) \
-l$(UAE_SERVICE_LIB) \
-lpthread
LDFLAGS_DEBUG = $(LDFLAGS_BASE)
LDFLAGS_RELEASE = $(LDFLAGS_BASE) -s
# === SGX-SPECIFIC BUILD PARAMETERS ===============================================================
SGX_SDK := $(MAKEFILE_DIR)/../../../linux-sgx
include $(MAKEFILE_DIR)/../../enclave/sgx.mk
# === MODE-SPECIFIC BUILD PARAMETERS ==============================================================
ifeq ($(subst release,RELEASE,$(MODE)),RELEASE)
CPPFLAGS = $(CPPFLAGS_RELEASE) $(SGX_CPPFLAGS_RELEASE)
LDFLAGS = $(LDFLAGS_RELEASE)
else
CPPFLAGS = $(CPPFLAGS_DEBUG) $(SGX_CPPFLAGS_DEBUG)
LDFLAGS = $(LDFLAGS_DEBUG)
endif
# === PSEUDO TARGETS ==============================================================================
all: $(HOST_LIBRARY)
clean:
@rm -f $(JNI_HEADERS)
@$(RM) -rf $(OUT_DIR)
@$(RM) -rf $(OBJ_DIR)
# === HOST ========================================================================================
HOST_SOURCES = $(wildcard *.cpp)
HOST_OBJECTS = $(addprefix $(OBJ_DIR)/, $(HOST_SOURCES:.cpp=.o))
ENCLAVE_PROJECT = $(MAKEFILE_DIR)/../../enclave
ENCLAVE_OBJECTS = $(ENCLAVE_PROJECT)/obj/enclave_u.o
ENCLAVE_INC_DIR = $(MAKEFILE_DIR)/../../enclave/rpc
JNI_HEADERS = wrapper.hpp
JNI_SOURCES = $(JNI_HEADERS:.hpp=.cpp)
JNI_OBJECTS = $(addprefix $(OBJ_DIR)/, $(JNI_SOURCES:.cpp=.o))
CLASSPATH = $(MAKEFILE_DIR)/../build/classes/kotlin/main
$(HOST_LIBRARY): $(HOST_OBJECTS) $(ENCLAVE_OBJECTS) $(JNI_OBJECTS) | $(OUT_DIR)
$(CPP) $(LDFLAGS) -o $@ $^ \
$(SGX_LIB_DIR)/lib$(UKEY_EXCHNG).a
$(HOST_OBJECTS): $(HOST_SOURCES) | $(OBJ_DIR)
$(ENCLAVE_OBJECTS):
make -C $(ENCLAVE_PROJECT) obj-untrusted
$(JNI_OBJECTS): $(JNI_SOURCES) | $(JNI_HEADERS)
$(JNI_HEADERS): | $(JNI_SOURCES)
javah -o $@ -cp $(CLASSPATH) net.corda.sgx.bridge.wrapper.NativeWrapper
$(OBJ_DIR)/%.o: %.cpp
@mkdir -p $(@D)
$(CPP) $(CPPFLAGS) -I$(ENCLAVE_INC_DIR) -o $@ -c $<
# === BUILD DIRECTORIES ===========================================================================
$(OUT_DIR):
@mkdir -p $(OUT_DIR)
$(OBJ_DIR):
@mkdir -p $(OBJ_DIR)

View File

@ -0,0 +1,95 @@
#include <cstddef>
#include <sgx.h>
#include <sgx_key_exchange.h>
#include <sgx_uae_service.h>
#include "enclave-manager.hpp"
#include "logging.hpp"
// Instantiate a new enclave from a signed enclave binary, and return the
// identifier of the instance.
sgx_enclave_id_t create_enclave(
const char *path,
bool use_platform_services,
sgx_status_t *result,
sgx_launch_token_t *token
) {
int updated = 0; // Indication of whether the launch token was updated.
sgx_enclave_id_t enclave_id = 0; // The identifier of the created enclave.
// If the launch token is empty, then create a new enclave. Otherwise, try
// to re-activate the existing enclave. `SGX_DEBUG_FLAG` is automatically
// set to 1 in debug mode, and 0 in release mode.
sgx_status_t status = sgx_create_enclave(
path, SGX_DEBUG_FLAG, token, &updated, &enclave_id, NULL
);
LOG(enclave_id, status, 0, "sgx_create_enclave()");
// Store the return value of the operation.
if (NULL != result) {
*result = status;
}
// Return the identifier of the created enclave. Remember that if `status`
// is `SGX_ERROR_ENCLAVE_LOST`, the enclave should be destroyed and then
// re-created.
return (SGX_SUCCESS == status) ? enclave_id : 0;
}
// Destroy enclave if currently loaded.
bool destroy_enclave(sgx_enclave_id_t enclave_id) {
if (enclave_id != 0){
// Attempt to destroy the enclave if we are provided with a valid
// enclave identifier.
sgx_status_t status = sgx_destroy_enclave(enclave_id);
LOG(enclave_id, status, 0, "sgx_destroy_enclave()");
return SGX_SUCCESS == status;
}
return false;
}
// Check the status of the SGX device on the current machine.
sgx_device_status_t get_device_status(void) {
#if SGX_SIM == 1
#pragma message "get_device_status() is being simulated"
// If in simulation mode, simulate device capabilities.
return SGX_ENABLED;
#endif
// Try to retrieve the current status of the SGX device.
sgx_device_status_t status;
sgx_status_t ret = sgx_cap_enable_device(&status);
LOG(0, ret, 0, "sgx_cap_enable_device() = { status = %x }", status);
if (SGX_SUCCESS != ret) {
return SGX_DISABLED;
}
return status;
}
// Report which extended Intel EPID Group the client uses by default.
uint32_t get_extended_group_id(sgx_status_t *result) {
uint32_t egid;
// The extended EPID group identifier is indicative of which attestation
// service the client is supposed to be communicating with. Currently, only
// a value of zero is supported, which is referring to Intel. The user
// should verify the retrieved extended group identifier, as any other
// value than zero will be disregarded by the service provider.
sgx_status_t status = sgx_get_extended_epid_group_id(&egid);
LOG(0, status, 0, "sgx_get_extended_epid_group_id() = %u", egid);
// Store the return value of the operation.
if (NULL != result) {
*result = status;
}
return egid;
}

View File

@ -0,0 +1,55 @@
#ifndef __ENCLAVE_MANAGER_H__
#define __ENCLAVE_MANAGER_H__
#include <sgx_capable.h>
#include <sgx_urts.h>
/**
* Instantiate a new enclave from a signed enclave binary, and return the
* identifier of the instance.
*
* @param path The file name of the signed enclave binary to load.
* @param use_platform_services If true, Intel's platform services are used to
* add extra protection against replay attacks during nonce generation and to
* provide a trustworthy monotonic counter.
* @param result Variable receiving the result of the operation, if not NULL.
* @param token Pointer to launch token; cannot be NULL.
*
* @return The identifier of the created enclave.
*/
sgx_enclave_id_t create_enclave(
const char *path,
bool use_platform_services,
sgx_status_t *result,
sgx_launch_token_t *token
);
/**
* Destroy enclave if currently loaded.
*
* @param enclave_id The identifier of the enclave to destroy.
*
* @return True if the enclave was active and got destroyed. False otherwise.
*/
bool destroy_enclave(
sgx_enclave_id_t enclave_id
);
/**
* Check the status of the SGX device on the current machine.
*/
sgx_device_status_t get_device_status(void);
/**
* Report which extended Intel EPID Group the client uses by default. The key
* used to sign a Quote will be a member of the extended EPID Group reported in
* this API. The application will typically use this value to tell the ISV
* Service Provider which group to use during remote attestation.
*
* @param result Variable receiving the result of the operation, if not NULL.
*
* @return The extended EPID group identifier.
*/
uint32_t get_extended_group_id(sgx_status_t *result);
#endif /* __ENCLAVE_MANAGER_H__ */

View File

@ -0,0 +1,13 @@
#ifndef __JNI_HPP__
#define __JNI_HPP__
#include <jni.h>
#define NATIVE_WRAPPER(return_type, method) \
JNIEXPORT return_type JNICALL \
Java_net_corda_sgx_bridge_wrapper_NativeWrapper_##method
#define KLASS(name) \
("net/corda/sgx/bridge/wrapper/" name)
#endif /* __JNI_HPP__ */

View File

@ -0,0 +1,32 @@
#include <cstdio>
#include "logging.hpp"
void log(
sgx_enclave_id_t enclave_id,
sgx_status_t status,
sgx_ra_context_t context,
const char *message,
...
) {
char mode[4] = { 0 };
mode[0] = (SGX_SIM == 0) ? 'H' : 'S';
mode[1] = (SGX_DEBUG == 0) ? 'R' : 'D';
mode[2] = (SGX_PRERELEASE == 0) ? 'x' : 'P';
mode[3] = 0;
char buffer[1024];
va_list args;
va_start(args, message);
vsnprintf(buffer, sizeof(buffer), message, args);
va_end(args);
printf(
"SGX(id=%lx,status=%x,ctx=%u,mode=%s): %s\n",
(uint64_t)enclave_id,
(uint32_t)status,
(uint32_t)context,
mode,
buffer
);
}

View File

@ -0,0 +1,32 @@
#ifndef __LOGGING_HPP__
#define __LOGGING_HPP__
#include <cstdarg>
#include <sgx_key_exchange.h>
#include <sgx_urts.h>
#ifdef LOGGING
#define LOG(enclave_id, status, context, message, ...) \
log(enclave_id, (sgx_status_t)(status), context, message, ##__VA_ARGS__)
#else
#define LOG(enclave_id, status, context, message, ...) ;
#endif
/**
* Log message to standard output.
*
* @param enclave_id The enclave identifier.
* @param status The outcome of the last SGX operation.
* @param context The remote attestation context.
* @param message The message.
*/
void log(
sgx_enclave_id_t enclave_id,
sgx_status_t status,
sgx_ra_context_t context,
const char *message,
...
);
#endif /* __LOGGING_HPP__ */

View File

@ -0,0 +1,285 @@
#include <string.h>
#include <unistd.h>
#include <sgx_uae_service.h>
#include "enclave_u.h"
#include "logging.hpp"
#include "remote-attestation.hpp"
// Initialize the remote attestation.
sgx_status_t initialize_remote_attestation(
// Inputs
sgx_enclave_id_t enclave_id,
bool use_platform_services,
sgx_ec256_public_t *key_challenger,
// Outputs
sgx_ra_context_t *context
) {
sgx_status_t ret;
// Perform ECALL into the application enclave to initialize the remote
// attestation. The resulting attestation context will be stored in the
// variable referenced by the `context` parameter.
sgx_status_t _ret = initializeRemoteAttestation(
enclave_id, &ret, use_platform_services, key_challenger, context
);
LOG(enclave_id, _ret | ret, *context, "initialize_remote_attestation()");
// If the ECALL itself failed, report why. Otherwise, return the status of
// the underlying function call.
return (SGX_SUCCESS != _ret) ? _ret : ret;
}
// Clean up and finalize the remote attestation process.
sgx_status_t finalize_remote_attestation(
sgx_enclave_id_t enclave_id,
sgx_ra_context_t context
) {
sgx_status_t ret;
// Perform ECALL into the application enclave to close the current
// attestation context and tidy up.
sgx_status_t _ret = finalizeRemoteAttestation(enclave_id, &ret, context);
LOG(enclave_id, _ret | ret, context, "finalize_remote_attestation()");
// If the ECALL itself failed, report why. Otherwise, return the status of
// the underlying function call.
return (SGX_SUCCESS != _ret) ? _ret : ret;
}
// Retrieve the application enclave's public key and the platform's group
// identifier.
sgx_status_t get_public_key_and_group_identifier(
// Inputs
sgx_enclave_id_t enclave_id,
sgx_ra_context_t context,
// Outputs
sgx_ec256_public_t *public_key,
sgx_epid_group_id_t *group_id,
// Retry logic
int max_retry_count,
unsigned int retry_wait_in_secs
) {
sgx_status_t ret;
sgx_ra_msg1_t message;
// It is generally recommended that the caller should wait (typically
// several seconds to tens of seconds) and retry `sgx_ra_get_msg1()` if
// `SGX_ERROR_BUSY` is returned.
int retry_count = max_retry_count;
while (retry_count-- >= 0) {
// Using an ECALL proxy to `sgx_ra_get_ga()` in the `sgx_tkey_exchange`
// library to retrieve the public key of the application enclave.
ret = sgx_ra_get_msg1(context, enclave_id, sgx_ra_get_ga, &message);
LOG(enclave_id, ret, context, "sgx_ra_get_msg1()");
if (SGX_ERROR_BUSY == ret) {
// Wait before retrying...
sleep(retry_wait_in_secs);
} else if (SGX_SUCCESS != ret) {
return ret;
} else {
break;
}
}
// Store the public key; components X and Y, each 256 bits long.
if (NULL != public_key) {
memcpy(public_key, &message.g_a, sizeof(sgx_ec256_public_t));
}
// Store the EPID group identifier. Note, this is not the same as the
// extended group identifier.
if (NULL != group_id) {
memcpy(group_id, &message.gid, sizeof(sgx_epid_group_id_t));
}
return ret;
}
// Process details received from challenger via the service provider, and
// generate quote.
sgx_status_t process_challenger_details_and_generate_quote(
// Inputs
sgx_enclave_id_t enclave_id,
sgx_ra_context_t context,
sgx_ec256_public_t *challenger_public_key,
sgx_spid_t *service_provider_id,
uint16_t quote_type,
uint16_t key_derivation_function,
sgx_ec256_signature_t *signature,
sgx_mac_t *challenger_mac,
uint32_t revocation_list_size,
uint8_t *revocation_list,
// Outputs
sgx_mac_t *enclave_mac,
sgx_ec256_public_t *enclave_public_key,
sgx_ps_sec_prop_desc_t *security_properties,
uint8_t **quote,
size_t *quote_size,
// Retry logic
int max_retry_count,
unsigned int retry_wait_in_secs
) {
sgx_status_t ret = SGX_SUCCESS;
size_t msg_in_size = sizeof(sgx_ra_msg2_t) + revocation_list_size;
sgx_ra_msg2_t *msg_in = (sgx_ra_msg2_t*)malloc(msg_in_size);
sgx_ra_msg3_t *msg_out = NULL;
uint32_t msg_out_size;
if (NULL == msg_in) {
return SGX_ERROR_OUT_OF_MEMORY;
}
// Populate input message (message 2 in the Intel attestation flow).
memcpy(&msg_in->g_b, challenger_public_key, sizeof(sgx_ec256_public_t));
memcpy(&msg_in->spid, service_provider_id, sizeof(sgx_spid_t));
msg_in->quote_type = quote_type;
msg_in->kdf_id = key_derivation_function;
memcpy(&msg_in->sign_gb_ga, signature, sizeof(sgx_ec256_signature_t));
memcpy(&msg_in->mac, challenger_mac, sizeof(sgx_mac_t));
msg_in->sig_rl_size = revocation_list_size;
if (revocation_list_size > 0) {
memcpy(&msg_in->sig_rl, revocation_list, revocation_list_size);
}
// Nullify outputs.
*quote = NULL;
// It is generally recommended that the caller should wait (typically
// several seconds to tens of seconds) and retry `sgx_ra_proc_msg2()` if
// `SGX_ERROR_BUSY` is returned.
int retry_count = max_retry_count;
while (retry_count-- >= 0) {
// Using an ECALL proxy to `sgx_ra_proc_msg2_trusted()` in the
// `sgx_tkey_exchange` library to process the incoming details from the
// challenger, and `sgx_ra_get_msg3_trusted()` in the same library to
// generate the quote.
ret = sgx_ra_proc_msg2(
context,
enclave_id,
sgx_ra_proc_msg2_trusted,
sgx_ra_get_msg3_trusted,
msg_in,
sizeof(sgx_ra_msg2_t) + revocation_list_size,
&msg_out,
&msg_out_size
);
LOG(enclave_id, ret, context, "sgx_ra_proc_msg2()");
if (SGX_ERROR_BUSY == ret) {
// Wait before retrying...
sleep(retry_wait_in_secs);
} else {
break;
}
}
// Populate outputs from the returned message structure.
if (NULL != msg_out) {
memcpy(enclave_mac, &msg_out->mac, sizeof(sgx_mac_t));
memcpy(enclave_public_key, &msg_out->g_a, sizeof(sgx_ec256_public_t));
size_t z_sec_prop = sizeof(sgx_ps_sec_prop_desc_t);
memcpy(security_properties, &msg_out->ps_sec_prop, z_sec_prop);
}
// Populate the quote structure.
if (NULL != msg_out) {
*quote_size = msg_out_size - offsetof(sgx_ra_msg3_t, quote);
*quote = (uint8_t*)malloc(*quote_size);
if (NULL != quote) {
memcpy(*quote, &msg_out->quote, *quote_size);
}
} else {
*quote = NULL;
}
// The output message is generated by the library and thus has to be freed
// upon completion.
free(msg_out);
// Allocated due to the variable size revocation list. Free up the
// temporary structure.
free(msg_in);
// Check if the malloc() call for the output quote failed above; if it did,
// it was due to an out-of-memory condition.
if (NULL == quote && SGX_SUCCESS == ret) {
return SGX_ERROR_OUT_OF_MEMORY;
}
return ret;
}
sgx_status_t verify_attestation_response(
// Inputs
sgx_enclave_id_t enclave_id,
sgx_ra_context_t context,
uint8_t *message,
size_t message_size,
uint8_t *cmac,
size_t cmac_size,
uint8_t *secret,
size_t secret_size,
uint8_t *gcm_iv,
uint8_t *gcm_mac,
size_t gcm_mac_size,
// Outputs
uint8_t *sealed_secret,
size_t *sealed_secret_size,
sgx_status_t *cmac_status
) {
// Check the generated CMAC from the service provider.
sgx_status_t ret = SGX_SUCCESS;
sgx_status_t _ret = verifyCMAC(
enclave_id, &ret, context, message, message_size, cmac, cmac_size
);
*cmac_status = ret;
LOG(enclave_id, _ret, context, "verify_cmac() = %x", (uint32_t)ret);
// Abort if call failed. Otherwise, forward the outcome to the caller.
if (SGX_SUCCESS != _ret) {
return _ret;
}
// Try to decrypt and verify the attestation response.
_ret = verifyAttestationResponse(
enclave_id, &ret, context, secret, secret_size,
gcm_iv, gcm_mac, gcm_mac_size,
sealed_secret, sizeof(sgx_sealed_data_t) + secret_size
);
LOG(
enclave_id, _ret, context,
"verify_attestation_response() = %x",
(uint32_t)ret
);
// Abort if unable to verify attestation response.
if (SGX_SUCCESS != (_ret | ret)) {
return (SGX_SUCCESS != _ret) ? _ret : ret;
}
// Return sealed secret if requested. The buffer is populated by the ECALL
// above, if sealed_secret is non-null.
if (NULL != sealed_secret_size) {
*sealed_secret_size = sizeof(sgx_sealed_data_t) + secret_size;
}
return SGX_SUCCESS;
}

View File

@ -0,0 +1,181 @@
#ifndef __REMOTE_ATTESTATION_H__
#define __REMOTE_ATTESTATION_H__
#include <sgx_urts.h>
#include <sgx_key_exchange.h>
#include <sgx_ukey_exchange.h>
/**
* Initialize the remote attestation.
*
* @param enclave_id The identifier of the enclave facilitating the remote
* attestation.
* @param use_platform_services If true, the enclave establishes a session with
* the PSE before initializing the attestation context. This provides
* additional nonce replay protection and a reliable monotonic counter.
* @param key_challenger ECDSA public key of the challenger with the 8 magic
* bytes removed, and X and Y components changed to little-endian.
* @param context The variable receiving the context constructed during
* initialization.
*
* @return Status code indicative of the outcome of the operation.
*/
sgx_status_t initialize_remote_attestation(
// Inputs
sgx_enclave_id_t enclave_id,
bool use_platform_services,
sgx_ec256_public_t *key_challenger,
// Outputs
sgx_ra_context_t* context
);
/**
* Clean up and finalize the remote attestation process.
*
* @param enclave_id The identifier of the enclave facilitating the remote
* attestation.
* @param context The context constructed during initialization.
*
* @return SGX_SUCCESS if successful, or SGX_ERROR_INVALID_PARAMETER if
* an invalid context is provided.
*/
sgx_status_t finalize_remote_attestation(
sgx_enclave_id_t enclave_id,
sgx_ra_context_t context
);
/**
* Get the public key of the application enclave, and the identifier of the
* EPID group the platform belongs to.
*
* @param enclave_id The identifier of the application enclave.
* @param context The context constructed during initialization.
* @param public_key Variable receiving the elliptic curve public key of the
* application enclave, based on the NIST P-256 elliptic curve..
* @param group_id Variable receiving the identifier of the platform's EPID
* group.
* @param max_retry_count The maximum number of times to retry the operation.
* @param retry_wait_in_secs The number of seconds to wait between each retry.
*
* @return SGX_SUCCESS if successful, otherwise an error code indicative of
* what went wrong.
*/
sgx_status_t get_public_key_and_group_identifier(
// Inputs
sgx_enclave_id_t enclave_id,
sgx_ra_context_t context,
// Outputs
sgx_ec256_public_t *public_key,
sgx_epid_group_id_t *group_id,
// Retry logic
int max_retry_count,
unsigned int retry_wait_in_secs
);
/**
* Process details received from the challenger via the service provider, and
* generate quote. If the service provider accepts the quote, negotiated
* session keys between the application enclave and the challenger are ready
* for use. However, if it fails, the application should notify the service
* provider of the error or the service provider needs a time-out mechanism to
* terminate the remote attestation transaction when it does not receive the
* message.
*
* @param enclave_id The identifier of the application enclave.
* @param context The context constructed during initialization.
* @param challenger_public_key The public key of the challenger.
* @param service_provider_id The identifier of the service provider.
* @param quote_type Indicates the quote type, linkable (1) or unlinkable (0).
* @param key_derivation_function The ID of the key derivation function used.
* @param signature An ECDSA signature over the challenger and application
* enclave's public keys.
* @param mac A 128-bit AES-CMAC generated by the service provider.
* @param revocation_list_size The size of revocation_list, in bytes.
* @param revocation_list The signature revocation list certificate of the
* Intel EPID group identified by the group identifier in message 1.
* @param enclave_mac AES-CMAC generated by the enclave.
* @param enclave_public_key Variable receiving the public key of the
* application enclave.
* @param security_properties Variable receiving the security properties of the
* Intel SGX platform service. If the security property information is not
* required in the remote attestation and key exchange process, this field will
* be all zeros.
* @param quote Variable receiving a pointer to the quote returned from
* sgx_get_quote. This should be freed after use.
* @param quote_size Variable receiving the size of the quote.
* @param max_retry_count The maximum number of times to retry the operation.
* @param retry_wait_in_secs The number of seconds to wait between each retry.
*
* @return SGX_SUCCESS if successful, otherwise an error code indicative of
* what went wrong.
*/
sgx_status_t process_challenger_details_and_generate_quote(
// Inputs
sgx_enclave_id_t enclave_id,
sgx_ra_context_t context,
sgx_ec256_public_t *challenger_public_key,
sgx_spid_t *service_provider_id,
uint16_t quote_type,
uint16_t key_derivation_function,
sgx_ec256_signature_t *signature,
sgx_mac_t *challenger_mac,
uint32_t revocation_list_size,
uint8_t *revocation_list,
// Outputs
sgx_mac_t *enclave_mac,
sgx_ec256_public_t *enclave_public_key,
sgx_ps_sec_prop_desc_t *security_properties,
uint8_t **quote,
size_t *quote_size,
// Retry logic
int max_retry_count,
unsigned int retry_wait_in_secs
);
/**
* Verify attestation response from service provider.
*
* @param enclave_id The identifier of the application enclave.
* @param context The context constructed during initialization.
* @param message The received attestation result message.
* @param message_size The size of the attestation result message.
* @param cmac The CMAC computed over the attestation result message.
* @param cmac_size The size of the CMAC.
* @param secret The encrypted secret provisioned by the challenger.
* @param secret_size The size of the encrypted secret.
* @param gcm_iv The 12-byte initialization vector used in the decryption.
* @param gcm_mac The GCM-MAC generated over the secret.
* @param gcm_mac_size The size of the GCM-MAC.
* @param sealed_secret Pre-allocated buffer receiving the sealed secret.
* @param sealed_secret_size The size of the sealed secret.
* @param cmac_status The variable receiving the outcome of the CMAC check.
*
* @return SGX_SUCCESS if successful, otherwise an error code indicative of
* what went wrong.
*/
sgx_status_t verify_attestation_response(
// Inputs
sgx_enclave_id_t enclave_id,
sgx_ra_context_t context,
uint8_t *message,
size_t message_size,
uint8_t *cmac,
size_t cmac_size,
uint8_t *secret,
size_t secret_size,
uint8_t *gcm_iv,
uint8_t *gcm_mac,
size_t gcm_mac_size,
// Outputs
uint8_t *sealed_secret,
size_t *sealed_secret_size,
sgx_status_t *cmac_status
);
#endif /* __REMOTE_ATTESTATION_H__ */

View File

@ -0,0 +1,15 @@
#include "enclave_u.h"
#include "sealing.hpp"
// Check whether the application enclave is able to unseal a secret.
sgx_status_t unseal_secret(
sgx_enclave_id_t enclave_id,
uint8_t *sealed_secret,
size_t sealed_secret_size
) {
sgx_status_t status = SGX_SUCCESS;
sgx_status_t ret = unsealSecret(
enclave_id, &status, sealed_secret, sealed_secret_size
);
return SGX_SUCCESS != ret ? ret : status;
}

View File

@ -0,0 +1,24 @@
#ifndef __SEALING_H__
#define __SEALING_H__
#include <cstdlib>
#include <sgx_urts.h>
/**
* Check whether the application enclave is able to unseal a persisted, sealed
* secret.
*
* @param enclave_id The identifier of the application enclave.
* @param sealed_secret The pre-existing, sealed secret.
* @param sealed_secret_size The size of the sealed secret.
*
* @return An indication of whether or not the enclave was able to unseal the
* secret.
*/
sgx_status_t unseal_secret(
sgx_enclave_id_t enclave_id,
uint8_t *sealed_secret,
size_t sealed_secret_size
);
#endif /* __SEALING_H__ */

View File

@ -0,0 +1,384 @@
#include <cstdlib>
#include <cstring>
#include <sgx_tcrypto.h>
#include <sgx_tseal.h>
#include "wrapper.hpp"
#include "jni.hpp"
#include "logging.hpp"
#include "enclave-manager.hpp"
#include "remote-attestation.hpp"
#include "sealing.hpp"
NATIVE_WRAPPER(jint, getDeviceStatus)
(JNIEnv *, jobject)
{
// Get the status of the SGX device on the local machine.
return get_device_status();
}
NATIVE_WRAPPER(jobject, getExtendedGroupIdentifier)
(JNIEnv *env, jobject)
{
// FindClass/GetMethodID both throw an exception upon failure, so we don't
// need to perform any further NULL-checks.
jclass klass = env->FindClass(KLASS("ExtendedGroupIdentifierResult"));
jmethodID cid = env->GetMethodID(klass, "<init>", "(IJ)V");
if (cid == NULL) { return NULL; }
// Get the extended EPID group identifier from SGX.
sgx_status_t status = SGX_ERROR_UNEXPECTED;
uint32_t extended_group_id = get_extended_group_id(&status);
// Construct and return ExtendedGroupIdentifierResult(identifier, status).
return env->NewObject(klass, cid, extended_group_id, status);
}
NATIVE_WRAPPER(jobject, createEnclave)
(JNIEnv *env, jobject, jstring path, jboolean use_platform_services,
jbyteArray in_launch_token)
{
// FindClass/GetMethodID both throw an exception upon failure, so we don't
// need to perform any further NULL-checks.
jclass klass = env->FindClass(KLASS("EnclaveResult"));
jmethodID cid = env->GetMethodID(klass, "<init>", "(J[BJ)V");
if (cid == NULL) { return NULL; }
// Marshall inputs.
const char *n_path = env->GetStringUTFChars(path, NULL);
sgx_status_t status;
// Initialize launch token.
sgx_launch_token_t launch_token = { 0 };
env->GetByteArrayRegion(
in_launch_token, 0, sizeof(sgx_launch_token_t), (jbyte*)&launch_token
);
// Create the enclave.
sgx_enclave_id_t enclave_id = create_enclave(
n_path, (bool)use_platform_services, &status,
&launch_token
);
// Construct resulting launch token (could be the same as the input).
jbyteArray _launch_token = env->NewByteArray(sizeof(sgx_launch_token_t));
env->SetByteArrayRegion(
_launch_token, 0, sizeof(sgx_launch_token_t), (jbyte*)&launch_token
);
// Free up memory.
env->ReleaseStringUTFChars(path, n_path);
// Construct and return EnclaveResult(identifier, launch_token, status).
return env->NewObject(klass, cid, enclave_id, _launch_token, status);
}
NATIVE_WRAPPER(jboolean, destroyEnclave)
(JNIEnv *, jobject, jlong enclave_id)
{
// Destroy the enclave if a valid identifier has been passed in.
if (enclave_id != 0) {
return destroy_enclave((sgx_enclave_id_t)enclave_id);
} else {
return false;
}
}
NATIVE_WRAPPER(jobject, initializeRemoteAttestation)
(JNIEnv *env, jobject, jlong enclave_id, jboolean use_platform_services,
jbyteArray in_key_challenger)
{
// FindClass/GetMethodID both throw an exception upon failure, so we don't
// need to perform any further NULL-checks.
jclass klass = env->FindClass(KLASS("InitializationResult"));
jmethodID cid = env->GetMethodID(klass, "<init>", "(IJ)V");
if (cid == NULL) { return NULL; }
// Marshall the public key passed in from the JVM.
sgx_ec256_public_t key_challenger = { 0 };
env->GetByteArrayRegion(
in_key_challenger, 0, sizeof(sgx_ec256_public_t),
(jbyte*)&key_challenger
);
// Initialize the remote attestation context.
sgx_ra_context_t context;
sgx_status_t status = initialize_remote_attestation(
enclave_id, use_platform_services, &key_challenger, &context
);
// Construct and return InitializationResult(context, status).
return env->NewObject(klass, cid, context, status);
}
NATIVE_WRAPPER(jlong, finalizeRemoteAttestation)
(JNIEnv *, jobject, jlong enclave_id, jint context)
{
// Finalize the remote attestation
return finalize_remote_attestation(enclave_id, context);
}
NATIVE_WRAPPER(jobject, getPublicKeyAndGroupIdentifier)
(JNIEnv *env, jobject, jlong enclave_id, jint context, jint max_retry_count,
jint retry_wait_in_secs)
{
// FindClass/GetMethodID both throw an exception upon failure, so we don't
// need to perform any further NULL-checks.
jclass klass = env->FindClass(KLASS("PublicKeyAndGroupIdentifier"));
jmethodID cid = env->GetMethodID(klass, "<init>", "([BIJ)V");
if (cid == NULL) { return NULL; }
// Get the public key of the application enclave, and the group identifier
// of the platform.
sgx_ec256_public_t public_key;
sgx_epid_group_id_t group_id;
sgx_status_t status = get_public_key_and_group_identifier(
enclave_id, context, &public_key, &group_id, max_retry_count,
retry_wait_in_secs
);
// Cast group identifier into an unsigned integer.
uint32_t gid = *((uint32_t*)group_id);
// Create managed array used to return the enclave's public key.
jbyteArray _public_key = env->NewByteArray(sizeof(sgx_ec256_public_t));
if (NULL == _public_key) {
// Out of memory - abort
return NULL;
}
// Copy public key bytes over to managed array.
env->SetByteArrayRegion(
_public_key, 0, sizeof(sgx_ec256_public_t), (jbyte*)&public_key
);
// Return PublicKeyAndGroupIdentifier(publicKey, groupIdentifier, status).
return env->NewObject(klass, cid, _public_key, gid, status);
}
NATIVE_WRAPPER(jobject, processServiceProviderDetailsAndGenerateQuote)
(JNIEnv *env, jobject, jlong enclave_id, jint context,
jbyteArray in_challenger_public_key, jbyteArray in_service_provider_id,
jshort quote_type, jshort key_derivation_function, jbyteArray in_signature,
jbyteArray in_mac, jint revocation_list_size,
jbyteArray in_revocation_list, jint max_retry_count,
jint retry_wait_in_secs)
{
// FindClass/GetMethodID both throw an exception upon failure, so we don't
// need to perform any further NULL-checks.
jclass klass = env->FindClass(KLASS("QuoteResult"));
jmethodID cid = env->GetMethodID(klass, "<init>", "([B[B[B[BJ)V");
if (cid == NULL) { return NULL; }
// Marshal inputs.
sgx_ec256_public_t challenger_public_key;
sgx_spid_t service_provider_id;
sgx_ec256_signature_t signature;
sgx_mac_t mac;
uint8_t *revocation_list = (uint8_t*)malloc(revocation_list_size);
// Check if there's enough free memory to allocate a buffer for the
// revocation list.
if (NULL == revocation_list) {
return NULL;
}
env->GetByteArrayRegion(
in_challenger_public_key, 0, sizeof(sgx_ec256_public_t),
(jbyte*)&challenger_public_key
);
env->GetByteArrayRegion(
in_service_provider_id, 0, sizeof(sgx_spid_t),
(jbyte*)&service_provider_id
);
env->GetByteArrayRegion(
in_signature, 0, sizeof(sgx_ec256_signature_t),
(jbyte*)&signature
);
env->GetByteArrayRegion(
in_mac, 0, sizeof(sgx_mac_t),
(jbyte*)&mac
);
env->GetByteArrayRegion(
in_revocation_list, 0, revocation_list_size, (jbyte*)revocation_list
);
// Output variables.
sgx_mac_t enclave_mac = { 0 };
sgx_ec256_public_t enclave_public_key = { 0 };
sgx_ps_sec_prop_desc_t security_properties = { 0 };
uint8_t *quote = NULL;
size_t quote_size = 0;
// Process details received from challenger via the service provider, and
// generate quote.
sgx_status_t status = process_challenger_details_and_generate_quote(
// Inputs
enclave_id, context, &challenger_public_key, &service_provider_id,
quote_type, key_derivation_function, &signature, &mac,
revocation_list_size, revocation_list,
// Outputs
&enclave_mac, &enclave_public_key, &security_properties, &quote,
&quote_size,
// Retry logic
max_retry_count, retry_wait_in_secs
);
LOG(
enclave_id, status, context,
"process_challenger_details_and_generate_quote() = quote(size=%u)",
quote_size
);
// Create output objects.
jbyteArray _enclave_mac = env->NewByteArray(sizeof(sgx_mac_t));
env->SetByteArrayRegion(
_enclave_mac, 0, sizeof(sgx_mac_t), (jbyte*)&enclave_mac
);
jbyteArray _enclave_public_key = env->NewByteArray(
sizeof(sgx_ec256_public_t)
);
env->SetByteArrayRegion(
_enclave_public_key, 0, sizeof(sgx_ec256_public_t),
(jbyte*)&enclave_public_key
);
jbyteArray _security_properties = env->NewByteArray(
sizeof(sgx_ps_sec_prop_desc_t)
);
env->SetByteArrayRegion(
_security_properties, 0, sizeof(sgx_ps_sec_prop_desc_t),
(jbyte*)&security_properties
);
jbyteArray _quote = NULL;
// Free up memory.
if (NULL != quote) {
_quote = env->NewByteArray(quote_size);
env->SetByteArrayRegion(_quote, 0, quote_size, (jbyte*)quote);
free(quote);
} else {
_quote = env->NewByteArray(0);
}
free(revocation_list);
// Return QuoteResult(mac, publicKey, securityProperties, quote, status).
return env->NewObject(
klass, cid,
_enclave_mac, _enclave_public_key, _security_properties, _quote, status
);
}
NATIVE_WRAPPER(jobject, verifyAttestationResponse)
(JNIEnv *env, jobject, jlong enclave_id, jint context,
jbyteArray message, jbyteArray cmac, jbyteArray secret,
jbyteArray gcm_iv, jbyteArray gcm_mac)
{
// FindClass/GetMethodID both throw an exception upon failure, so we don't
// need to perform any further NULL-checks.
jclass klass = env->FindClass(KLASS("VerificationResult"));
jmethodID cid = env->GetMethodID(klass, "<init>", "([BJJ)V");
if (cid == NULL) { return NULL; }
// Get buffer sizes.
size_t message_size = env->GetArrayLength(message);
size_t cmac_size = env->GetArrayLength(cmac);
size_t secret_size = env->GetArrayLength(secret);
size_t gcm_mac_size = env->GetArrayLength(gcm_mac);
// Allocate buffers.
uint8_t *_message = (uint8_t*)malloc(message_size);
uint8_t *_cmac = (uint8_t*)malloc(cmac_size);
uint8_t *_secret = (uint8_t*)malloc(secret_size);
uint8_t *_gcm_iv = (uint8_t*)malloc(SGX_AESGCM_IV_SIZE);
uint8_t *_gcm_mac = (uint8_t*)malloc(gcm_mac_size);
// Length of secret is preserved during encryption, but prepend header.
size_t _sealed_secret_size = sizeof(sgx_sealed_data_t) + secret_size;
uint8_t *_sealed_secret = (uint8_t*)malloc(_sealed_secret_size);
// Check if we ran out of memory.
if (NULL == _message || NULL == _cmac || NULL == _secret ||
NULL == _gcm_iv || NULL == _gcm_mac || NULL == _sealed_secret) {
free(_message);
free(_cmac);
free(_secret);
free(_gcm_iv);
free(_gcm_mac);
free(_sealed_secret);
return NULL;
}
// Marshal inputs.
env->GetByteArrayRegion(message, 0, message_size, (jbyte*)_message);
env->GetByteArrayRegion(cmac, 0, cmac_size, (jbyte*)_cmac);
env->GetByteArrayRegion(secret, 0, secret_size, (jbyte*)_secret);
env->GetByteArrayRegion(gcm_iv, 0, SGX_AESGCM_IV_SIZE, (jbyte*)_gcm_iv);
env->GetByteArrayRegion(gcm_mac, 0, gcm_mac_size, (jbyte*)_gcm_mac);
// Verify the attestation response received from the service provider.
sgx_status_t cmac_status = SGX_SUCCESS;
sgx_status_t status = verify_attestation_response(
enclave_id, context, _message, message_size, _cmac, cmac_size,
_secret, secret_size, _gcm_iv, _gcm_mac, gcm_mac_size, _sealed_secret,
&_sealed_secret_size, &cmac_status
);
// Free temporary allocations.
free(_message);
free(_cmac);
free(_secret);
free(_gcm_iv);
free(_gcm_mac);
// Marshal outputs.
jbyteArray sealed_secret;
if (NULL != _sealed_secret) {
sealed_secret = env->NewByteArray(_sealed_secret_size);
env->SetByteArrayRegion(
sealed_secret, 0, _sealed_secret_size, (jbyte*)_sealed_secret
);
free(_sealed_secret);
} else {
sealed_secret = env->NewByteArray(0);
}
// Return VerificationResult(sealedSecret, cmacValidationStatus, status).
return env->NewObject(klass, cid, sealed_secret, cmac_status, status);
}
NATIVE_WRAPPER(jlong, unsealSecret)
(JNIEnv *env, jobject, jlong enclave_id, jbyteArray sealed_secret)
{
// Check if we've actually got a sealed secret to unseal.
uint8_t *_sealed_secret = NULL;
size_t sealed_secret_size = env->GetArrayLength(sealed_secret);
if (0 == sealed_secret_size) {
return SGX_ERROR_INVALID_PARAMETER;
}
// Allocate buffer.
_sealed_secret = (uint8_t*)malloc(sealed_secret_size);
// Check if we ran out of memory.
if (NULL == _sealed_secret) {
return 0;
}
// Marshal inputs.
env->GetByteArrayRegion(
sealed_secret, 0, sealed_secret_size, (jbyte*)_sealed_secret
);
// Try to unseal the secret.
sgx_status_t result = unseal_secret(
enclave_id, _sealed_secret, sealed_secret_size
);
// Free temporary allocations.
free(_sealed_secret);
return result;
}

View File

@ -0,0 +1,212 @@
package net.corda.sgx.attestation
import net.corda.sgx.attestation.entities.QuoteType
import net.corda.sgx.attestation.service.ISVHttpClient
import net.corda.sgx.bridge.EnclaveConfiguration
import net.corda.sgx.bridge.attestation.NativeAttestationEnclave
import net.corda.sgx.enclave.SgxStatus
import net.corda.sgx.sealing.SecretManager
import org.hamcrest.CoreMatchers.anyOf
import org.hamcrest.CoreMatchers.containsString
import org.junit.Assert.*
import org.junit.After
import org.junit.Test
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import kotlin.test.assertEquals
import kotlin.test.assertFails
@Suppress("KDocMissingDocumentation")
class AttestationManagerTests {
private companion object {
@JvmStatic
private val log: Logger = LoggerFactory
.getLogger(AttestationManagerTests::class.java)
}
private val usePlatformServices = false
private lateinit var manager: AttestationManager
private fun enclave() = NativeAttestationEnclave(
EnclaveConfiguration.path,
usePlatformServices
)
private fun enclaveAndManager(
name: String
): Pair<AttestationEnclave, AttestationManager> {
val enclave = enclave()
manager = AttestationManager(
enclave = enclave,
usePlatformServices = usePlatformServices,
secretManager = SecretManager(enclave),
isvClient = ISVHttpClient(name = name)
)
return Pair(enclave, manager)
}
@After
fun cleanup() {
manager.cleanUp()
}
@Test
fun `step 1 - can initialize attestation process`() {
// No interaction with ISV.
val (_, manager) = enclaveAndManager("Step1")
val challenge = manager.requestChallenge()
manager.initialize(challenge)
}
@Test
fun `step 2 - can request challenge provisioning`() {
// Request challenge from ISV.
val (_, manager) = enclaveAndManager("Step2")
val challenge = manager.requestChallenge()
assertNotEquals(0, challenge.nonce.length)
}
@Test
fun `step 3 - can send extended group identifier`() {
// Send MSG0.
val (_, manager) = enclaveAndManager("Step3")
val challenge = manager.requestChallenge()
manager.initialize(challenge)
manager.sendExtendedGroupIdentifier()
}
@Test
fun `step 4a - can retrieve public key and group identifier`() {
// No interaction with ISV.
val (enclave, manager) = enclaveAndManager("Step4a")
val challenge = manager.requestChallenge()
manager.initialize(challenge)
val (publicKey, groupId) = enclave.getPublicKeyAndGroupIdentifier()
assertNotEquals(0, publicKey.bytes.size)
assertNotEquals(0, groupId)
}
@Test
fun `step 4b - can send public key and group identifier`() {
// Send MSG1.
val (_, manager) = enclaveAndManager("Step4b")
val challenge = manager.requestChallenge()
manager.initialize(challenge)
manager.sendPublicKeyAndGroupIdentifier()
}
@Test
fun `step 5 - can receive challenger details`() {
// Send MSG1 and receive MSG2.
val (_, manager) = enclaveAndManager("Step5")
val challenge = manager.requestChallenge()
manager.initialize(challenge)
val details = manager.sendPublicKeyAndGroupIdentifier()
assertEquals(1, details.keyDerivationFunctionIdentifier)
assertEquals(QuoteType.LINKABLE, details.quoteType)
}
@Test
fun `step 6a - can generate quote`() {
// Send MSG1, receive MSG2, and generate MSG3.
val (enclave, manager) = enclaveAndManager("Step6a")
val challenge = manager.requestChallenge()
manager.initialize(challenge)
val details = manager.sendPublicKeyAndGroupIdentifier()
val quote = enclave
.processChallengerDetailsAndGenerateQuote(details)
assertNotNull(quote.publicKey)
assertNotEquals(0, quote.publicKey.bytes.size)
assertNotNull(quote.messageAuthenticationCode)
assertNotEquals(0, quote.messageAuthenticationCode.size)
assertNotNull(quote.payload)
assertNotEquals(0, quote.payload.size)
assertNotNull(quote.securityProperties)
assertNotEquals(0, quote.securityProperties.size)
}
@Test
fun `step 6b - can generate and submit quote`() {
// Request challenge from ISV, send MSG1, receive MSG2, and send MSG3.
val (_, manager) = enclaveAndManager("Step6b")
val challenge = manager.requestChallenge()
manager.initialize(challenge)
val details = manager.sendPublicKeyAndGroupIdentifier()
assertEquals(64, details.signature.size)
val response = manager.generateAndSubmitQuote(challenge, details)
assertThat(response.quoteStatus.name, anyOf(
containsString("OK"),
// If the attestation request to IAS returns
// GROUP_OUT_OF_DATE, the machine needs a microcode
// upgrade. In this case, it's up to the challenger
// whether or not to trust the content of the quote.
containsString("GROUP_OUT_OF_DATE")
))
}
@Test
fun `step 7a - can verify attestation response`() {
// Request challenge from ISV, send MSG0 and MSG1, receive MSG2, send
// MSG3, and receive MSG4.
val sealingHeaderSize = 560
val (_, manager) = enclaveAndManager("Step7a")
val challenge = manager.requestChallenge()
manager.initialize(challenge)
val details = manager.sendPublicKeyAndGroupIdentifier()
val response = manager.generateAndSubmitQuote(challenge, details)
val (macStatus, sealedSecret) =
manager.verifyAttestationResponse(response)
assertEquals(SgxStatus.SUCCESS, macStatus)
assertNotNull(sealedSecret)
assertEquals(
sealingHeaderSize + response.secret.size,
sealedSecret.size
)
}
@Test
fun `step 7b - cannot verify attestation response with invalid IV`() {
// Request challenge from ISV, send MSG0 and MSG1, receive MSG2, send
// MSG3, and receive MSG4.
val (_, manager) = enclaveAndManager("Step7b")
val challenge = manager.requestChallenge()
manager.initialize(challenge)
val details = manager.sendPublicKeyAndGroupIdentifier()
val response = manager.generateAndSubmitQuote(challenge, details)
response.secretIV[0] = ((response.secretIV[0] + 1) % 255).toByte()
assertFails { manager.verifyAttestationResponse(response) }
}
@Test
fun `step 8 - can seal and reuse provisioned secret`() {
// Request challenge from ISV, send MSG0 and MSG1, receive MSG2, send
// MSG3, and receive MSG4. Seal the secret and then unseal it again.
val sealingHeaderSize = 560
val (enclave, manager) = enclaveAndManager("Step8")
val challenge = manager.requestChallenge()
manager.initialize(challenge)
val details = manager.sendPublicKeyAndGroupIdentifier()
val response = manager.generateAndSubmitQuote(challenge, details)
val (macStatus, sealedSecret) =
manager.verifyAttestationResponse(response)
assertEquals(SgxStatus.SUCCESS, macStatus)
assertNotNull(sealedSecret)
assertEquals(
sealingHeaderSize + response.secret.size,
sealedSecret.size
)
val unsealingResult = enclave.unseal(sealedSecret)
assertEquals(SgxStatus.SUCCESS, unsealingResult)
val invalidSecret = sealedSecret.copyOf(sealedSecret.size - 10) +
byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
val unsealingResultInvalid = enclave.unseal(invalidSecret)
assertNotEquals(SgxStatus.SUCCESS, unsealingResultInvalid)
}
}

View File

@ -0,0 +1,12 @@
package net.corda.sgx.bridge
object EnclaveConfiguration {
private val dir: String = System.getProperty("corda.sgx.enclave.path")
/**
* The path of the signed attestation enclave binary.
*/
val path: String = "$dir/corda_sgx_ra_enclave.so"
}

View File

@ -0,0 +1,83 @@
package net.corda.sgx.attestation
import net.corda.sgx.attestation.entities.AttestationResult
import net.corda.sgx.attestation.entities.Quote
import net.corda.sgx.attestation.service.ChallengerDetails
import net.corda.sgx.enclave.ECKey
import net.corda.sgx.enclave.Enclave
import net.corda.sgx.enclave.SgxException
import net.corda.sgx.enclave.SgxStatus
import net.corda.sgx.sealing.SealedSecret
import net.corda.sgx.system.GroupIdentifier
/**
* Enclave used in remote attestation.
*/
interface AttestationEnclave : Enclave {
/**
* The platform services offer an architectural enclave which provides a
* trusted time source and a monotonic counter, which in turn can be used
* for replay protection during nonce generation and for securely
* calculating the length of time for which a secret shall be valid.
*/
val usePlatformServices: Boolean
/**
* Create a context for the remote attestation and key exchange process.
*
* @param challengerKey The elliptic curve public key of the challenger
* (NIST P-256 elliptic curve).
*
* @throws SgxException If unable to create context.
*/
fun initializeKeyExchange(challengerKey: ECKey)
/**
* Finalize the remote attestation and key exchange process.
*/
fun finalizeKeyExchange()
/**
* Get the public key of the application enclave, based on NIST P-256
* elliptic curve, and the identifier of the EPID group to which the
* platform belongs.
*/
fun getPublicKeyAndGroupIdentifier(): Pair<ECKey, GroupIdentifier>
/**
* Process the response from the challenger and generate a quote for the
* final step of the attestation process.
*
* @param challengerDetails Details received from the challenger.
*/
fun processChallengerDetailsAndGenerateQuote(
challengerDetails: ChallengerDetails
): Quote
/**
* Verify the attestation response received from the service provider.
*
* @param attestationResult The received attestation response.
*
* @return A pair of (1) the outcome of the validation of the CMAC over
* the security manifest, and (2) the sealed secret, if successful.
*
* @throws SgxException If unable to verify the response and seal the
* secret.
*/
fun verifyAttestationResponse(
attestationResult: AttestationResult
): Pair<SgxStatus, SealedSecret>
/**
* Attempt to unseal a secret inside the enclave and report the outcome of
* the operation.
*
* @param sealedSecret The sealed secret provisioned by the challenger.
*
* @return A status code indicative of the outcome of the operation.
*/
fun unseal(sealedSecret: SealedSecret): SgxStatus
}

View File

@ -0,0 +1,289 @@
package net.corda.sgx.attestation
import net.corda.sgx.attestation.entities.*
import net.corda.sgx.attestation.service.ChallengerDetails
import net.corda.sgx.attestation.service.ISVClient
import net.corda.sgx.enclave.SgxStatus
import net.corda.sgx.sealing.SealedSecret
import net.corda.sgx.sealing.SecretManager
import org.slf4j.Logger
import org.slf4j.LoggerFactory
/**
* Remote attestation flow.
*
* @property enclave The enclave used for the remote attestation process.
* @property secretManager The secret manager used to store and manage secrets.
* @property isvClient Client used to communicate with the remote attestation
* service provider.
* @property usePlatformServices Indication of whether to use platform services
* or not to. Platform services supplies a trusted time source in addition to a
* monotonic counter, which in combination can be used to protect against
* replay attacks during nonce generation and to securely set time validity of
* secrets.
*/
class AttestationManager @JvmOverloads constructor(
private val enclave: AttestationEnclave,
private val secretManager: SecretManager,
private val isvClient: ISVClient,
private val usePlatformServices: Boolean = false
) {
private companion object {
@JvmStatic
private val log: Logger = LoggerFactory
.getLogger(AttestationManager::class.java)
}
/**
* The backbone of the remote attestation flow. Ensure that the attestation
* context gets closed (if needed) upon completion.
*/
fun attest() {
val challenge = requestChallenge()
initialize(challenge)
try {
sendExtendedGroupIdentifier()
val details = sendPublicKeyAndGroupIdentifier()
val response = generateAndSubmitQuote(challenge, details)
val (status, secret) = verifyAttestationResponse(response)
if (status != SgxStatus.SUCCESS) {
throw AttestationException(
"Failed to validate CMAC manifest (${status.name})"
)
}
persistSecret(secret)
} finally {
cleanUp()
}
}
/**
* Ask service provider to provision challenge.
*/
fun requestChallenge(): Challenge {
// Request challenge from service provider.
try {
return isvClient.requestChallenge()
} catch (ex: Exception) {
log.error("Failed to request challenge.", ex)
throw ex
}
}
/**
* Activate enclave and initialize the key exchange and remote attestation
* flow.
*/
fun initialize(challenge: Challenge) {
// Make sure the system is SGX-enabled and that we have a running
// enclave to work with.
try {
enclave.activate()
} catch (ex: Exception) {
log.error("Failed to activate enclave.", ex)
throw ex
}
// If we have already been through an attestation, chances are that the
// existing sealed secret might still be valid. So, if we have a sealed
// secret, we first try to unseal it to see whether we need to
// re-attest or not.
if (!checkIfAttestationIsNeeded()) {
// The secret was already provisioned and is still valid, so we can
// relinquish control to the enclave to operate further on the
// provisioned data.
return
}
// Initialize the key exchange and remote attestation process. Platform
// services supplies a trusted time source in addition to a monotonic
// counter, which in combination can be used to protect against replay
// attacks during nonce generation and to securely set time validity of
// secrets.
try {
enclave.initializeKeyExchange(challenge.publicKey)
} catch (ex: Exception) {
log.error("Failed to initialize key exchange.", ex)
throw ex
}
}
/**
* Send extended group identifier to service provider.
*/
fun sendExtendedGroupIdentifier() {
// Next, we need to send our extended group identifier to let the
// service provider know what EPID group to use during remote
// attestation. This corresponds to message 0 in the Intel remote
// attestation flow.
val egid = try {
enclave.system.getExtendedGroupIdentifier()
} catch (ex: Exception) {
log.error("Failed to get extended group identifier.", ex)
throw ex
}
try {
if (!isvClient.validateExtendedGroupIdentifier(egid)) {
throw AttestationException("Extended group not accepted.")
}
} catch (ex: Exception) {
log.error("Failed validating extended group identifier.", ex)
throw ex
}
}
/**
* Send public key and group identifier, and receive details about the
* challenger.
*/
fun sendPublicKeyAndGroupIdentifier(): ChallengerDetails {
// Call into the Intel Provisioning Server to provision attestation
// key, if necessary. Otherwise, just go ahead and retrieve the
// public key and the group identifier.
val (publicKey, gid) = try {
enclave.getPublicKeyAndGroupIdentifier()
} catch (ex: Exception) {
log.error("Failed to get public key and group identifier.", ex)
throw ex
}
// Send our public key and our group identifier to the service
// provider, which in turn will check our details against the current
// version of the revocation list. The service provider will forward
// this list together with its service provider ID, signatures, quote
// type, etc. to us (the client). This request corresponds to message 1
// in the Intel remote attestation flow.
try {
return isvClient.sendPublicKeyAndGroupIdentifier(publicKey, gid)
} catch (ex: Exception) {
log.error("Failed sending PK and group identifier to ISV.", ex)
throw ex
}
}
/**
* Process response from the service provider, generate a quote and send
* it to the service provider to get it verified.
*/
fun generateAndSubmitQuote(
challenge: Challenge,
details: ChallengerDetails
): AttestationResult {
// Process response from the service provider and generate a quote.
val quote = try {
enclave.processChallengerDetailsAndGenerateQuote(details)
} catch (ex: Exception) {
log.error("Failed to process challenger details and generate " +
"quote.", ex)
throw ex
}
// Send the quote to the service provider, which in turn will verify
// the attestation evidence with the Intel attestation service.
return submitQuote(challenge, quote)
}
/**
* Send quote to service provider, which in turn will verify the
* attestation evidence with the Intel attestation service.
*/
fun submitQuote(
challenge: Challenge,
quote: Quote
): AttestationResult {
// Send the quote to the service provider, which in turn will verify
// the attestation evidence with the Intel attestation service.
val attestationResponse = try {
isvClient.submitQuote(challenge, quote)
} catch (ex: Exception) {
log.error("Failed to submit quote to ISV.", ex)
throw ex
}
if (!attestationResponse.isSuccessful) {
val status = attestationResponse.status
throw AttestationException("Failed to verify quote. $status")
}
return attestationResponse
}
/**
* Verify the received attestation response from service provider.
*/
fun verifyAttestationResponse(
response: AttestationResult
): Pair<SgxStatus, SealedSecret> {
if (response.quoteStatus == QuoteStatus.GROUP_OUT_OF_DATE) {
log.warn(QuoteStatus.GROUP_OUT_OF_DATE.description)
} else if (response.quoteStatus != QuoteStatus.OK) {
val code = response.quoteStatus
throw AttestationException("${code.description} (${code.name})")
}
if (response.secretHash.isEmpty()) {
throw AttestationException("No secret hash available")
}
// The attestation service is happy with the quote, and the remote
// attestation has been verified. Now, we need to verify the response,
// decrypt the secret and seal it in the enclave.
try {
return enclave.verifyAttestationResponse(response)
} catch (ex: Exception) {
log.error("Failed to verify attestation response.", ex)
throw ex
}
}
/**
* Finalize remote attestation and key exchange process.
*/
fun cleanUp() {
try {
// The secret has been provisioned, so we are now ready to
// relinquish control to the enclave to operate further on the
// provisioned data.
enclave.finalizeKeyExchange()
} catch (ex: Exception) {
log.error("Failed to finalize key exchange.", ex)
throw ex
}
}
/**
* Persist the secret sealed by the enclave.
*/
fun persistSecret(secret: SealedSecret) {
// The attestation response was verified, so we persist the secret.
try {
secretManager.persistSealedSecret(secret)
} catch (ex: Exception) {
log.error("Failed to persist sealed secret.", ex)
throw ex
}
}
/**
* Checks if we already have a sealed secret from a previous attestation
* that is still valid.
*/
private fun checkIfAttestationIsNeeded(): Boolean {
// If we do not have a secret at hand, we need to be provisioned one.
// This is accomplished by running through the attestation process. If
// we have already been provisioned a secret, that does not necessarily
// mean that we are validly attested. We also need to check that we can
// unseal said secret and consequently that the attestation has not
// expired.
return try {
!secretManager.isValid()
} catch (ex: Exception) {
log.warn("Failed to unseal and validate secret.", ex)
true
}
}
}

View File

@ -0,0 +1,6 @@
package net.corda.sgx.attestation.entities
/**
* Remote attestation context.
*/
typealias AttestationContext = Int

View File

@ -0,0 +1,8 @@
package net.corda.sgx.attestation.entities
/**
* Exception thrown during remote attestation.
*/
class AttestationException(
message: String
) : Exception(message)

View File

@ -0,0 +1,53 @@
package net.corda.sgx.attestation.entities
import net.corda.sgx.sealing.ProvisionedSecret
import java.time.LocalDateTime
/**
* The outcome of a remote attestation process.
*/
class AttestationResult(
/**
* THe status of the provided quote.
*/
val quoteStatus: QuoteStatus,
/**
* The received attestation result message.
*/
val attestationResultMessage: ByteArray,
/**
* The CMAC over the attestation result message.
*/
val aesCMAC: ByteArray,
/**
* Provisioned, encrypted secret if the attestation was successful.
*/
val secret: ProvisionedSecret,
/**
* The initialization vector used as part of the decryption.
*/
val secretIV: ByteArray,
/**
* The GCM MAC returned as part of the attestation response.
*/
val secretHash: ByteArray,
/**
* The end result of the remote attestation process.
*/
val status: AttestationStatus
) {
/**
* Result of the remote attestation.
*/
val isSuccessful: Boolean = status == AttestationStatus.SUCCESS
}

View File

@ -0,0 +1,20 @@
package net.corda.sgx.attestation.entities
/**
* The status of the remote attestation process.
*
* @property message A human readable representation of the state.
*/
enum class AttestationStatus(val message: String) {
/**
* The remote attestation was successful.
*/
SUCCESS("Remote attestation succeeded."),
/**
* The remote attestation failed.
*/
FAIL("Remote attestation failed."),
}

View File

@ -0,0 +1,21 @@
package net.corda.sgx.attestation.entities
import net.corda.sgx.enclave.ECKey
/**
* Remote attestation challenge from service provider.
*/
class Challenge(
/**
* A one-time number generated by the service provider and used
* throughout the attestation to protect against replay attacks.
*/
val nonce: String,
/**
* The long-term identity key of the challenger.
*/
val publicKey: ECKey
)

View File

@ -0,0 +1,38 @@
package net.corda.sgx.attestation.entities
import net.corda.sgx.enclave.ECKey
/**
* Signed report from the application enclave. The corresponds to message 3 in
* the Intel remote attestation flow.
*/
class Quote(
/**
* The 128-bit AES-CMAC generated by the application enclave. See
* [sgx_ra_msg3_t](https://software.intel.com/en-us/node/709238) for
* more details on its derivation.
*/
val messageAuthenticationCode: ByteArray,
/**
* The public elliptic curve key of the application enclave.
*/
val publicKey: ECKey,
/**
* Security property of the Intel SGX Platform Service. If the Intel
* SGX Platform Service security property information is not required
* in the remote attestation and key exchange process, this field will
* be all zeros. The buffer is 256 bytes long.
*/
val securityProperties: ByteArray,
/**
* Quote returned from sgx_get_quote. More details about how the quote
* is derived can be found in Intel's documentation:
* [sgx_ra_msg3_t](https://software.intel.com/en-us/node/709238)
*/
val payload: ByteArray
)

View File

@ -0,0 +1,71 @@
package net.corda.sgx.attestation.entities
/**
* The status of an enclave's quote as it has been processed by the Intel
* Attestation service.
*
* @property description A human-readable description of the status code.
*/
enum class QuoteStatus(val description: String) {
/**
* EPID signature of the ISV enclave quote was verified correctly and the
* TCB level of the SGX platform is up-to- date.
*/
OK("EPID signature of the ISV enclave quote was verified correctly and " +
"the TCB level of the SGX platform is up-to-date."),
/**
* EPID signature of the ISV enclave quote was invalid. The content of the
* quote is not trustworthy.
*/
SIGNATURE_INVALID("EPID signature of the ISV enclave quote was invalid."),
/**
* The EPID group has been revoked. When this value is returned, the
* revocation reason field of the Attestation Verification Report will
* contain a revocation reason code for this EPID group as reported in the
* EPID Group CRL. The content of the quote is not trustworthy.
*/
GROUP_REVOKED("The EPID group has been revoked."),
/**
* The EPID private key used to sign the quote has been revoked by
* signature. The content of the quote is not trustworthy.
*/
SIGNATURE_REVOKED("The EPID private key used to sign the quote has been " +
"revoked by signature."),
/**
* The EPID private key used to sign the quote has been directly revoked
* (not by signature). The content of the quote is not trustworthy.
*/
KEY_REVOKED("The EPID private key used to sign the quote has been " +
"directly revoked (not by signature)."),
/**
* SigRL version in ISV enclave quote does not match the most recent
* version of the SigRL. In rare situations, after SP retrieved the SigRL
* from IAS and provided it to the platform, a newer version of the SigRL
* is made available. As a result, the Attestation Verification Report will
* indicate SIGRL_VERSION_MISMATCH. SP can retrieve the most recent version
* of SigRL from the IAS and request the platform to perform remote
* attestation again with the most recent version of SigRL. If the platform
* keeps failing to provide a valid quote matching with the most recent
* version of the SigRL, the content of the quote is not trustworthy.
*/
SIGRL_VERSION_MISMATCH("SigRL version in ISV enclave quote does not " +
"match the most recent version of the SigRL."),
/**
* The EPID signature of the ISV enclave quote has been verified correctly,
* but the TCB level of SGX platform is outdated. The platform has not been
* identified as compromised and thus it is not revoked. It is up to the
* Service Provider to decide whether or not to trust the content of the
* quote.
*/
GROUP_OUT_OF_DATE("The EPID signature of the ISV enclave quote has " +
"been verified correctly, but the TCB level of SGX platform " +
"is outdated.")
}

View File

@ -0,0 +1,23 @@
package net.corda.sgx.attestation.entities
/**
* The type of quote used in the attestation.
*
* @property value The native value of the quote type.
*/
enum class QuoteType(val value: Short) {
/**
* Unlinkable is random value-based, meaning that having two quotes you
* cannot identify whether they are from the same source or not.
*/
UNLINKABLE(0),
/**
* Linkable is name-based, meaning that having two quotes you can identify
* if they come from the same enclave or not. Note that you can not
* determine which enclave it is though.
*/
LINKABLE(1)
}

View File

@ -0,0 +1,57 @@
package net.corda.sgx.attestation.service
import net.corda.sgx.attestation.entities.QuoteType
import net.corda.sgx.enclave.ECKey
/**
* Information retrieved from message 2 in the Intel remote attestation flow.
*/
class ChallengerDetails(
/**
* The public elliptic curve key of the challenger, based on the NIST
* P-256 elliptic curve.
*/
val publicKey: ECKey,
/**
* The identifier of the service provider, also known as SPID.
*/
val serviceProviderIdentifier: ByteArray,
/**
* Indicates the quote type, i.e., whether it is linkable or
* un-linkable.
*/
val quoteType: QuoteType,
/**
* The identifier of the key derivation function.
*/
val keyDerivationFunctionIdentifier: Short,
/**
* An ECDSA signature of (g_b||g_a), using the challenger's ECDSA
* private key corresponding to the public key specified in the
* sgx_ra_init function, where g_b is the public elliptic curve key of
* the challenger and g_a is the public key of application enclave,
* provided by the application enclave, in the remote attestation and
* key exchange message (message 1).
*/
val signature: ByteArray,
/**
* The 128-bit AES-CMAC generated by the service provider. See
* [sgx_ra_msg2_t](https://software.intel.com/en-us/node/709237)
* for more details on its derivation.
*/
val messageAuthenticationCode: ByteArray,
/**
* The Intel EPID signature revocation list certificate of the Intel
* EPID group identified by the group identifier in the remote
* attestation and key exchange message (message 1).
*/
val signatureRevocationList: ByteArray
)

View File

@ -0,0 +1,71 @@
package net.corda.sgx.attestation.service
import net.corda.sgx.attestation.entities.AttestationResult
import net.corda.sgx.attestation.entities.Challenge
import net.corda.sgx.attestation.entities.Quote
import net.corda.sgx.enclave.ECKey
import net.corda.sgx.system.ExtendedGroupIdentifier
import net.corda.sgx.system.GroupIdentifier
/**
* A proxy providing an interface for calling into required functionality of
* the remote attestation service. Communication with the service takes place
* over HTTPS.
*/
interface ISVClient {
/**
* Send request to the remote attestation service asking to get provisioned
* a challenge.
*
* @return A challenge from the attestation service.
*/
fun requestChallenge(): Challenge
/**
* Send request to the remote attestation service to validate the extended
* group identifier. This corresponds to message 0 in Intel's remote
* attestation flow.
*
* @param extendedGroupId The extended group identifier obtained from
* calling into the architectural enclave.
*
* @return True if the service successfully validated the identifier and is
* happy to proceed. False otherwise.
*/
fun validateExtendedGroupIdentifier(
extendedGroupId: ExtendedGroupIdentifier
): Boolean
/**
* Send request containing the application enclave's public key and the
* platform's EPID group identifier. This corresponds to message 1 in
* Intel's remote attestation flow.
*
* @param publicKey The application enclave's public key.
* @param groupIdentifier The platform's EPID group identifier.
*
* @return Details about the service provider, such as its public key, SPID
* and quote type.
*/
fun sendPublicKeyAndGroupIdentifier(
publicKey: ECKey,
groupIdentifier: GroupIdentifier
): ChallengerDetails
/**
* Send request containing the generated quote to the service provider so
* that they can verify the attestation evidence with Intel's attestation
* service.
*
* @param challenge The challenge received in the first step.
* @param quote The generated quote.
*
* @return The outcome of the remote attestation.
*/
fun submitQuote(
challenge: Challenge,
quote: Quote
): AttestationResult
}

View File

@ -0,0 +1,220 @@
package net.corda.sgx.attestation.service
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import net.corda.sgx.attestation.entities.*
import net.corda.sgx.attestation.service.messages.*
import net.corda.sgx.enclave.ECKey
import net.corda.sgx.system.ExtendedGroupIdentifier
import net.corda.sgx.system.GroupIdentifier
import net.corda.sgx.system.SgxSystem
import net.corda.sgx.system.value
import org.apache.http.HttpStatus.SC_OK
import org.apache.http.client.config.RequestConfig
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpPost
import org.apache.http.config.SocketConfig
import org.apache.http.entity.ContentType.APPLICATION_JSON
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClients
import org.apache.http.impl.conn.BasicHttpClientConnectionManager
import org.apache.http.util.EntityUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
/**
* Client used to talk to the Remote Attestation ISV.
*
* @property endpoint The base URL of the ISV service.
*/
open class ISVHttpClient(
private val endpoint: String = "http://localhost:${portNumber()}",
private val name: String = "Client"
) : ISVClient {
private companion object {
@JvmStatic
private val log: Logger = LoggerFactory
.getLogger(ISVHttpClient::class.java)
fun portNumber(): Int {
val portNumber = System.getenv("PORT") ?: "9080"
return Integer.parseUnsignedInt(portNumber)
}
}
private var httpClient: CloseableHttpClient
private val mapper = ObjectMapper().registerModule(JavaTimeModule())
init {
val httpRequestConfig = RequestConfig.custom()
.setConnectTimeout(5_000)
.setSocketTimeout(5_000)
.build()
val httpSocketConfig = SocketConfig.custom()
.setSoReuseAddress(true)
.setTcpNoDelay(true)
.build()
val connectionManager = BasicHttpClientConnectionManager()
.apply { socketConfig = httpSocketConfig }
httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(httpRequestConfig)
.build()
log.debug("$name : Talking to ISV on $endpoint")
}
override fun requestChallenge(): Challenge {
val request = HttpGet("$endpoint/attest/provision")
httpClient.execute(request).use { response ->
val body = EntityUtils.toString(response.entity)
log.debug("$name : Challenge <- ${response.statusLine}")
log.debug("$name : Challenge <- $body")
if (response.statusLine.statusCode < 200 ||
response.statusLine.statusCode >= 300)
{
throw AttestationException(response.statusLine.toString())
}
val challenge = read<ChallengeResponse>(body)
if (SC_OK != response.statusLine.statusCode
|| challenge.nonce.isEmpty()) {
throw AttestationException("Failed to request challenge")
}
return Challenge(
challenge.nonce,
ECKey.fromBytes(challenge.serviceKey)
)
}
}
override fun validateExtendedGroupIdentifier(
extendedGroupId: ExtendedGroupIdentifier
): Boolean {
val message0 = Message0(extendedGroupId.value)
val request = HttpPost("$endpoint/attest/msg0")
log.debug("$name : MSG0 -> ${mapper.writeValueAsString(message0)}")
request.entity = StringEntity(
mapper.writeValueAsString(message0),
APPLICATION_JSON
)
httpClient.execute(request).use { response ->
log.debug("$name : MSG0 <- ${response.statusLine}")
return SC_OK == response.statusLine.statusCode
}
}
override fun sendPublicKeyAndGroupIdentifier(
publicKey: ECKey,
groupIdentifier: GroupIdentifier
): ChallengerDetails {
val message1 = Message1(
ga = publicKey.bytes,
platformGID = groupIdentifier.value()
)
val request = HttpPost("$endpoint/attest/msg1")
log.debug("$name : MSG1 -> ${mapper.writeValueAsString(message1)}")
request.entity = StringEntity(
mapper.writeValueAsString(message1),
APPLICATION_JSON
)
httpClient.execute(request).use { response ->
val body = EntityUtils.toString(response.entity)
log.debug("$name : MSG2 <- ${response.statusLine}")
log.debug("$name : MSG2 <- $body")
if (response.statusLine.statusCode < 200 ||
response.statusLine.statusCode >= 300)
{
throw AttestationException(response.statusLine.toString())
}
val message2 = read<Message2>(body)
if (SC_OK != response.statusLine.statusCode) {
throw AttestationException("Invalid response from server")
}
message2.spid
val spid = hexStringToByteArray(message2.spid)
return ChallengerDetails(
publicKey = ECKey.fromBytes(message2.gb),
serviceProviderIdentifier = spid,
quoteType = if (message2.linkableQuote) {
QuoteType.LINKABLE
} else {
QuoteType.UNLINKABLE
},
keyDerivationFunctionIdentifier = message2.keyDerivationFuncId.toShort(),
signature = message2.signatureGbGa,
messageAuthenticationCode = message2.aesCMAC,
signatureRevocationList = message2.revocationList
)
}
}
override fun submitQuote(
challenge: Challenge,
quote: Quote
): AttestationResult {
val message3 = Message3(
aesCMAC = quote.messageAuthenticationCode,
ga = quote.publicKey.bytes,
quote = quote.payload,
securityManifest = quote.securityProperties,
nonce = challenge.nonce
)
val request = HttpPost("$endpoint/attest/msg3")
log.debug("$name : MSG3 -> ${mapper.writeValueAsString(message3)}")
request.entity = StringEntity(
mapper.writeValueAsString(message3),
APPLICATION_JSON
)
httpClient.execute(request).use { response ->
val body = EntityUtils.toString(response.entity)
log.debug("$name : MSG4 <- ${response.statusLine}")
for (header in response.allHeaders) {
log.trace("$name : MSG4 <- ${header.name} = ${header.value}")
}
log.debug("$name : MSG4 <- $body")
if (response.statusLine.statusCode < 200 ||
response.statusLine.statusCode >= 300)
{
throw AttestationException(response.statusLine.reasonPhrase)
}
val message4 = read<Message4>(body)
return AttestationResult(
quoteStatus = SgxSystem.quoteStatusFromString(message4.quoteStatus),
attestationResultMessage = message4.platformInfo ?: byteArrayOf(),
aesCMAC = message4.aesCMAC ?: byteArrayOf(),
secret = message4.secret,
secretHash = message4.secretHash,
secretIV = message4.secretIV,
status = if (SC_OK == response.statusLine.statusCode) {
AttestationStatus.SUCCESS
} else {
AttestationStatus.FAIL
}
)
}
}
private inline fun <reified T> read(value: String): T =
mapper.readValue(value, T::class.java) as T
private fun hexStringToByteArray(s: String): ByteArray {
val len = s.length
val data = ByteArray(len / 2)
var i = 0
while (i < len) {
val c1 = Character.digit(s[i], 16) shl 4
val c2 = Character.digit(s[i + 1], 16)
data[i / 2] = (c1 + c2).toByte()
i += 2
}
return data
}
}

View File

@ -0,0 +1,19 @@
package net.corda.sgx.attestation.service.messages
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonPropertyOrder
@Suppress("KDocMissingDocumentation")
@JsonPropertyOrder("nonce", "serviceKey")
@JsonInclude(NON_NULL)
class ChallengeResponse(
@param:JsonProperty("nonce")
val nonce: String,
@param:JsonProperty("serviceKey")
val serviceKey: ByteArray
)

View File

@ -0,0 +1,16 @@
package net.corda.sgx.attestation.service.messages
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonPropertyOrder
@Suppress("KDocMissingDocumentation")
@JsonPropertyOrder("extendedGID")
@JsonInclude(NON_NULL)
class Message0(
@param:JsonProperty("extendedGID")
val extendedGID: Int
)

View File

@ -0,0 +1,18 @@
package net.corda.sgx.attestation.service.messages
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonPropertyOrder
@Suppress("KDocMissingDocumentation")
@JsonPropertyOrder("ga", "platformGID")
class Message1(
// The client's 512 bit public DH key
@param:JsonProperty("ga")
val ga: ByteArray,
// Platform GID value from the SGX client
@param:JsonProperty("platformGID")
val platformGID: String
)

View File

@ -0,0 +1,39 @@
package net.corda.sgx.attestation.service.messages
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonPropertyOrder
@Suppress("KDocMissingDocumentation")
@JsonPropertyOrder(
"gb",
"spid",
"linkableQuote",
"keyDerivationFuncId",
"signatureGbGa",
"aesCMAC",
"revocationList"
)
class Message2(
// The server's 512 bit public DH key (Little Endian)
@param:JsonProperty("gb")
val gb: ByteArray,
@param:JsonProperty("spid")
val spid: String,
@param:JsonProperty("linkableQuote")
val linkableQuote: Boolean = true,
@param:JsonProperty("keyDerivationFuncId")
val keyDerivationFuncId: Int,
@param:JsonProperty("signatureGbGa")
val signatureGbGa: ByteArray, // Not ASN.1 encoded
@param:JsonProperty("aesCMAC")
val aesCMAC: ByteArray, // Not ASN.1 encoded
@param:JsonProperty("revocationList")
val revocationList: ByteArray
)

View File

@ -0,0 +1,28 @@
package net.corda.sgx.attestation.service.messages
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonPropertyOrder
@Suppress("KDocMissingDocumentation")
@JsonPropertyOrder("aesCMAC", "ga", "quote", "securityManifest", "nonce")
@JsonInclude(NON_NULL)
class Message3(
@param:JsonProperty("aesCMAC")
val aesCMAC: ByteArray, // Not ASN.1 encoded
// The client's 512 bit public DH key
@param:JsonProperty("ga")
val ga: ByteArray,
@param:JsonProperty("quote")
val quote: ByteArray,
@param:JsonProperty("securityManifest")
val securityManifest: ByteArray? = null,
@param:JsonProperty("nonce")
val nonce: String? = null
)

View File

@ -0,0 +1,72 @@
package net.corda.sgx.attestation.service.messages
import com.fasterxml.jackson.annotation.JsonFormat
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonPropertyOrder
import java.time.LocalDateTime
@Suppress("KDocMissingDocumentation")
@JsonPropertyOrder(
"reportID",
"quoteStatus",
"quoteBody",
"securityManifestStatus",
"securityManifestHash",
"platformInfo",
"aesCMAC",
"epidPseudonym",
"nonce",
"secret",
"secretHash",
"secretIV",
"timestamp"
)
@JsonInclude(NON_NULL)
class Message4(
@param:JsonProperty("reportID")
val reportID: String,
@param:JsonProperty("quoteStatus")
val quoteStatus: String,
@param:JsonProperty("quoteBody")
val quoteBody: ByteArray,
@param:JsonProperty("securityManifestStatus")
val securityManifestStatus: String? = null,
@param:JsonProperty("securityManifestHash")
val securityManifestHash: String? = null,
@param:JsonProperty("platformInfo")
val platformInfo: ByteArray? = null,
@param:JsonProperty("aesCMAC")
val aesCMAC: ByteArray? = null,
@param:JsonProperty("epidPseudonym")
val epidPseudonym: ByteArray? = null,
@param:JsonProperty("nonce")
val nonce: String? = null,
@param:JsonProperty("secret")
val secret: ByteArray,
@param:JsonProperty("secretHash")
val secretHash: ByteArray,
@param:JsonProperty("secretIV")
val secretIV: ByteArray,
@param:JsonProperty("timestamp")
@field:JsonFormat(
pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS",
timezone = "UTC"
)
val timestamp: LocalDateTime
)

View File

@ -0,0 +1,213 @@
package net.corda.sgx.bridge.attestation
import net.corda.sgx.attestation.AttestationEnclave
import net.corda.sgx.attestation.entities.AttestationContext
import net.corda.sgx.attestation.entities.AttestationException
import net.corda.sgx.attestation.entities.AttestationResult
import net.corda.sgx.attestation.entities.Quote
import net.corda.sgx.attestation.service.ChallengerDetails
import net.corda.sgx.bridge.enclave.NativeEnclave
import net.corda.sgx.bridge.wrapper.NativeWrapper
import net.corda.sgx.enclave.ECKey
import net.corda.sgx.enclave.SgxException
import net.corda.sgx.enclave.SgxStatus
import net.corda.sgx.sealing.*
import net.corda.sgx.system.GroupIdentifier
import net.corda.sgx.system.SgxSystem
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import kotlin.concurrent.withLock
/**
* Enclave used in remote attestation.
*/
class NativeAttestationEnclave @JvmOverloads constructor(
enclavePath: String,
override val usePlatformServices: Boolean = false
) : NativeEnclave(enclavePath, usePlatformServices), AttestationEnclave {
private companion object {
@JvmStatic
private val log: Logger = LoggerFactory
.getLogger(NativeAttestationEnclave::class.java)
}
private val maxRetryCount: Int = 5
private val retryDelayInSeconds: Int = 5
private var context: AttestationContext? = null
/**
* Create a context for the remote attestation and key exchange process.
*
* @param challengerKey The elliptic curve public key of the challenger
* (NIST P-256 elliptic curve).
*
* @throws SgxException If unable to create context.
*/
override fun initializeKeyExchange(challengerKey: ECKey) {
lock.withLock {
val result = NativeWrapper.initializeRemoteAttestation(
identifier,
usePlatformServices,
challengerKey.bytes
)
val status = SgxSystem.statusFromCode(result.result)
context = result.context
if (status != SgxStatus.SUCCESS) {
throw SgxException(status, identifier, context)
}
}
}
/**
* Clean up and finalize the remote attestation process.
*/
override fun finalizeKeyExchange() {
lock.withLock {
val oldContext = context
if (oldContext != null) {
val code = NativeWrapper.finalizeRemoteAttestation(
identifier,
oldContext
)
context = null
val status = SgxSystem.statusFromCode(code)
if (status != SgxStatus.SUCCESS) {
throw SgxException(status, identifier, oldContext)
}
}
}
}
/**
* Get the public key of the application enclave, based on NIST P-256
* elliptic curve, and the identifier of the EPID group the platform
* belongs to.
*/
override fun getPublicKeyAndGroupIdentifier(): Pair<ECKey, GroupIdentifier> {
lock.withLock {
val context = context
?: throw AttestationException("Not initialized.")
val result = NativeWrapper.getPublicKeyAndGroupIdentifier(
identifier,
context,
maxRetryCount,
retryDelayInSeconds
)
val status = SgxSystem.statusFromCode(result.result)
if (status != SgxStatus.SUCCESS) {
throw SgxException(status, identifier, context)
}
return Pair(
ECKey.fromBytes(result.publicKey),
result.groupIdentifier
)
}
}
/**
* Process the response from the challenger and generate a quote for the
* final step of the attestation process.
*
* @param challengerDetails Details from the challenger.
*/
override fun processChallengerDetailsAndGenerateQuote(
challengerDetails: ChallengerDetails
): Quote {
lock.withLock {
val context = context
?: throw AttestationException("Not initialized.")
val result = NativeWrapper.processServiceProviderDetailsAndGenerateQuote(
identifier,
context,
challengerDetails.publicKey.bytes,
challengerDetails.serviceProviderIdentifier,
challengerDetails.quoteType.value,
challengerDetails.keyDerivationFunctionIdentifier,
challengerDetails.signature,
challengerDetails.messageAuthenticationCode,
challengerDetails.signatureRevocationList.size,
challengerDetails.signatureRevocationList,
maxRetryCount,
retryDelayInSeconds
)
val status = SgxSystem.statusFromCode(result.result)
if (status != SgxStatus.SUCCESS) {
throw SgxException(status, identifier, context)
}
return Quote(
result.messageAuthenticationCode,
ECKey.fromBytes(result.publicKey),
result.securityProperties,
result.payload
)
}
}
/**
* Verify the attestation response received from the service provider.
*
* @param attestationResult The received attestation response.
*
* @return A pair of (1) the outcome of the validation of the CMAC over
* the security manifest, and (2) the sealed secret, if successful.
*
* @throws SgxException If unable to verify the response or seal the
* secret.
*/
override fun verifyAttestationResponse(
attestationResult: AttestationResult
): Pair<SgxStatus, SealedSecret> {
lock.withLock {
val context = context
?: throw AttestationException("Not initialized.")
val result = NativeWrapper.verifyAttestationResponse(
identifier,
context,
attestationResult.attestationResultMessage,
attestationResult.aesCMAC,
attestationResult.secret,
attestationResult.secretIV,
attestationResult.secretHash
)
val cmacValidationStatus = SgxSystem.statusFromCode(
result.cmacValidationStatus
)
if (cmacValidationStatus != SgxStatus.SUCCESS) {
if (attestationResult.aesCMAC.isEmpty()) {
log.warn("No CMAC available")
} else {
log.warn(
"Failed to validate AES-CMAC ({}).",
cmacValidationStatus.name
)
}
}
val status = SgxSystem.statusFromCode(result.result)
if (status != SgxStatus.SUCCESS) {
throw SgxException(status, identifier, context)
}
return Pair(cmacValidationStatus, result.secret)
}
}
/**
* Attempt to unseal a secret inside the enclave and report the outcome of
* the operation.
*/
override fun unseal(sealedSecret: SealedSecret): SgxStatus {
lock.withLock {
val result = NativeWrapper.unsealSecret(identifier, sealedSecret)
return SgxSystem.statusFromCode(result)
}
}
}

View File

@ -0,0 +1,99 @@
package net.corda.sgx.bridge.enclave
import net.corda.sgx.bridge.system.NativeSgxSystem
import net.corda.sgx.bridge.wrapper.LaunchToken
import net.corda.sgx.bridge.wrapper.NativeWrapper
import net.corda.sgx.bridge.wrapper.newLaunchToken
import net.corda.sgx.enclave.Enclave
import net.corda.sgx.enclave.EnclaveIdentifier
import net.corda.sgx.enclave.SgxStatus
import net.corda.sgx.system.SgxSystem
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
/**
* Representation of an enclave on an SGX-enabled system.
*
* @param enclavePath The path to the signed enclave binary.
* @param usePlatformServices Whether or not to leverage Intel's platform
* services (for replay protection in nonce generation, etc.).
*/
open class NativeEnclave @JvmOverloads constructor(
private val enclavePath: String,
private val usePlatformServices: Boolean = false
) : Enclave {
/**
* Lock for ensuring single entry of enclave calls.
*/
protected val lock: Lock = ReentrantLock()
private var enclaveId: EnclaveIdentifier = 0
private var launchToken: LaunchToken = newLaunchToken()
/**
* The SGX-enabled system on which this enclave is running.
*/
override val system: SgxSystem
get() = NativeSgxSystem()
/**
* The enclave identifier.
*/
override val identifier: EnclaveIdentifier
get() = enclaveId
/**
* Create enclave used for remote attestation, and consequently for secret
* sealing and unsealing.
*/
override fun create(): SgxStatus {
lock.withLock {
val result = NativeWrapper.createEnclave(
enclavePath, // The path to the signed enclave binary
usePlatformServices, // Whether to use Intel's services
launchToken // New or pre-existing launch token.
)
val status = SgxSystem.statusFromCode(result.result)
if (status == SgxStatus.ERROR_ENCLAVE_LOST) {
// If the enclave was lost, we need to destroy it. Not doing so
// will result in EPC memory leakage that could prevent
// subsequent enclaves from loading.
destroy()
}
enclaveId = result.identifier
launchToken = result.token
return status
}
}
/**
* Destroy enclave if running.
*/
override fun destroy(): Boolean {
lock.withLock {
if (enclaveId != 0L) {
// Only attempt to destroy enclave if one has been created
val result = NativeWrapper.destroyEnclave(enclaveId)
enclaveId = 0L
launchToken = newLaunchToken()
return result
}
return true
}
}
/**
* Check whether the enclave has been run before or not.
*/
override fun isFresh(): Boolean {
lock.withLock {
val nullByte = 0.toByte()
return identifier == 0L &&
(0 until launchToken.size).all { launchToken[it] == nullByte }
}
}
}

View File

@ -0,0 +1,41 @@
package net.corda.sgx.bridge.system
import net.corda.sgx.attestation.entities.AttestationException
import net.corda.sgx.bridge.wrapper.NativeWrapper
import net.corda.sgx.enclave.SgxException
import net.corda.sgx.enclave.SgxStatus
import net.corda.sgx.system.ExtendedGroupIdentifier
import net.corda.sgx.system.SgxDeviceStatus
import net.corda.sgx.system.SgxSystem
/**
* Query system properties of an SGX-enabled environment.
*/
class NativeSgxSystem : SgxSystem {
/**
* Check if the client platform is enabled for Intel SGX. The application
* must be run with administrator privileges to get the status
* successfully.
*
* @return The current status of the SGX device.
*/
override fun getDeviceStatus(): SgxDeviceStatus {
return SgxSystem.deviceStatusFromCode(NativeWrapper.getDeviceStatus())
}
/**
* Get the extended Intel EPID Group the client uses by default. The key
* used to sign a quote will be a member of the this group.
*/
override fun getExtendedGroupIdentifier(): ExtendedGroupIdentifier {
val result = NativeWrapper.getExtendedGroupIdentifier()
val status = SgxSystem.statusFromCode(result.result)
if (status != SgxStatus.SUCCESS) {
throw SgxException(status)
}
return SgxSystem.extendedGroupIdentifier(result.extendedGroupIdentifier)
?: throw AttestationException("Invalid extended EPID group")
}
}

View File

@ -0,0 +1,27 @@
package net.corda.sgx.bridge.wrapper
import net.corda.sgx.enclave.EnclaveIdentifier
import net.corda.sgx.enclave.SgxStatus
/**
* The result of a call to [NativeWrapper.createEnclave].
*/
class EnclaveResult(
/**
* The identifier of the created enclave, if any.
*/
val identifier: EnclaveIdentifier,
/**
* The launch token of the enclave.
*/
val token: LaunchToken,
/**
* The output status code of the enclave creation (of type [SgxStatus]
* downstream).
*/
val result: Long
)

View File

@ -0,0 +1,22 @@
package net.corda.sgx.bridge.wrapper
import net.corda.sgx.enclave.SgxStatus
import net.corda.sgx.system.ExtendedGroupIdentifier
/**
* The result of a call to [NativeWrapper.getExtendedGroupIdentifier].
*/
data class ExtendedGroupIdentifierResult(
/**
* The extended Intel EPID group identifier (of type
* [ExtendedGroupIdentifier] downstream).
*/
val extendedGroupIdentifier: Int,
/**
* The result of the operation (of type [SgxStatus] downstream).
*/
val result: Long
)

View File

@ -0,0 +1,21 @@
package net.corda.sgx.bridge.wrapper
import net.corda.sgx.attestation.entities.AttestationContext
import net.corda.sgx.enclave.SgxStatus
/**
* The result of a call to [NativeWrapper.initializeRemoteAttestation].
*/
data class InitializationResult(
/**
* The context returned from the call to sgx_ra_init().
*/
val context: AttestationContext,
/**
* The result of the operation (of type [SgxStatus] downstream).
*/
val result: Long
)

View File

@ -0,0 +1,13 @@
package net.corda.sgx.bridge.wrapper
/**
* An opaque type used to hold enclave launch information. Used by
* sgx_create_enclave to initialize an enclave. The license is generated by the
* launch enclave.
*/
typealias LaunchToken = ByteArray
/**
* Create a new launch token.
*/
fun newLaunchToken(): LaunchToken = ByteArray(1024)

View File

@ -0,0 +1,99 @@
package net.corda.sgx.bridge.wrapper
import net.corda.sgx.attestation.entities.AttestationContext
import net.corda.sgx.enclave.EnclaveIdentifier
import net.corda.sgx.sealing.ProvisionedSecret
import net.corda.sgx.sealing.SealedSecret
@Suppress("KDocMissingDocumentation")
internal object NativeWrapper {
init {
System.loadLibrary("corda_sgx_ra")
}
// enclave-management.cpp
@Throws(Exception::class)
external fun getDeviceStatus(): Int
@Throws(Exception::class)
external fun getExtendedGroupIdentifier(): ExtendedGroupIdentifierResult
@Throws(Exception::class)
external fun createEnclave(
path: String,
usePlatformServices: Boolean,
token: LaunchToken
): EnclaveResult
@Throws(Exception::class)
external fun destroyEnclave(
id: Long
): Boolean
// remote-attestation.cpp
@Throws(Exception::class)
external fun initializeRemoteAttestation(
enclaveIdentifier: EnclaveIdentifier,
usePlatformServices: Boolean,
challengerKey: ByteArray
): InitializationResult
@Throws(Exception::class)
external fun finalizeRemoteAttestation(
enclaveIdentifier: EnclaveIdentifier,
context: AttestationContext
): Long
@Throws(Exception::class)
external fun getPublicKeyAndGroupIdentifier(
enclaveIdentifier: EnclaveIdentifier,
context: AttestationContext,
maxRetryCount: Int,
retryWaitInSeconds: Int
): PublicKeyAndGroupIdentifier
@Throws(Exception::class)
external fun processServiceProviderDetailsAndGenerateQuote(
enclaveIdentifier: EnclaveIdentifier,
context: AttestationContext,
publicKey: ByteArray,
serviceProviderIdentifier: ByteArray,
quoteType: Short,
keyDerivationFunctionIdentifier: Short,
signature: ByteArray,
messageAuthenticationCode: ByteArray,
signatureRevocationSize: Int,
signatureRevocationList:ByteArray,
maxRetryCount: Int,
retryWaitInSeconds: Int
): QuoteResult
@Throws(Exception::class)
external fun verifyAttestationResponse(
enclaveIdentifier: EnclaveIdentifier,
context: AttestationContext,
attestationResultMessage: ByteArray,
aesCmac: ByteArray,
secret: ByteArray,
gcmIV: ByteArray,
gcmMac: ByteArray
): VerificationResult
// sealing.cpp
@Throws(Exception::class)
external fun sealSecret(
enclaveIdentifier: EnclaveIdentifier,
provisionedSecret: ProvisionedSecret
): SealingOperationResult
@Throws(Exception::class)
external fun unsealSecret(
enclaveIdentifier: EnclaveIdentifier,
sealedSecret: SealedSecret
): Long
}

View File

@ -0,0 +1,28 @@
package net.corda.sgx.bridge.wrapper
import net.corda.sgx.enclave.ECKey
import net.corda.sgx.enclave.SgxStatus
import net.corda.sgx.system.GroupIdentifier
/**
* The result of a call to [NativeWrapper.getPublicKeyAndGroupIdentifier].
*/
class PublicKeyAndGroupIdentifier(
/**
* The public elliptic curve key of the application enclave (of type
* [ECKey] downstream).
*/
val publicKey: ByteArray,
/**
* The identifier of the EPID group to which the enclave belongs.
*/
val groupIdentifier: GroupIdentifier,
/**
* The result of the operation (of type [SgxStatus] downstream).
*/
val result: Long
)

View File

@ -0,0 +1,45 @@
package net.corda.sgx.bridge.wrapper
import net.corda.sgx.enclave.ECKey
import net.corda.sgx.enclave.SgxStatus
/**
* The result of a call to
* [NativeWrapper.processServiceProviderDetailsAndGenerateQuote].
*/
class QuoteResult(
/**
* The 128-bit AES-CMAC generated by the application enclave. See
* [sgx_ra_msg3_t](https://software.intel.com/en-us/node/709238) for
* more details on its derivation.
*/
val messageAuthenticationCode: ByteArray,
/**
* The public elliptic curve key of the application enclave (of type
* [ECKey] downstream).
*/
val publicKey: ByteArray,
/**
* Security property of the Intel SGX Platform Service. If the Intel
* SGX Platform Service security property information is not required
* in the remote attestation and key exchange process, this field will
* be all zeros. The buffer is 256 bytes long.
*/
val securityProperties: ByteArray,
/**
* Quote returned from sgx_get_quote. More details about how the quote
* is derived can be found in Intel's documentation:
* [sgx_ra_msg3_t](https://software.intel.com/en-us/node/709238)
*/
val payload: ByteArray,
/**
* The result of the operation (of type [SgxStatus] downstream).
*/
val result: Long
)

View File

@ -0,0 +1,22 @@
package net.corda.sgx.bridge.wrapper
import net.corda.sgx.sealing.SealedSecret
import net.corda.sgx.sealing.SealingResult
/**
* The result of a call to [NativeWrapper.sealSecret].
*/
class SealingOperationResult(
/**
* The sealed secret, if any.
*/
val sealedSecret: SealedSecret,
/**
* The output result of the operation (of type [SealingResult]
* downstream).
*/
val result: Long
)

View File

@ -0,0 +1,28 @@
package net.corda.sgx.bridge.wrapper
import net.corda.sgx.enclave.SgxStatus
import net.corda.sgx.sealing.SealedSecret
/**
* The result of a call to [NativeWrapper.verifyAttestationResponse].
*/
class VerificationResult(
/**
* The sealed secret returned if the attestation result was
* successfully verified.
*/
val secret: SealedSecret,
/**
* The outcome of the validation of the CMAC over the attestation
* result message (of type [SgxStatus] downstream).
*/
val cmacValidationStatus: Long,
/**
* The result of the operation (of type [SgxStatus] downstream).
*/
val result: Long
)

View File

@ -0,0 +1,51 @@
@file:JvmName("Program")
package net.corda.sgx.cli
import net.corda.sgx.attestation.AttestationManager
import net.corda.sgx.attestation.service.ISVHttpClient
import net.corda.sgx.bridge.attestation.NativeAttestationEnclave
import net.corda.sgx.sealing.SecretManager
import org.slf4j.LoggerFactory
object Flow
fun main(args: Array<String>) {
val log = LoggerFactory.getLogger(Flow::class.java)
val dir = System.getProperty("corda.sgx.enclave.path")
val enclavePath = "$dir/corda_sgx_ra_enclave.so"
val enclave = NativeAttestationEnclave(enclavePath)
val isvClient = ISVHttpClient()
val secretManager = SecretManager(enclave)
val manager = AttestationManager(enclave, secretManager, isvClient)
log.info("Request challenge ...")
val challenge = manager.requestChallenge()
log.info("Initialize remote attestation context ...")
manager.initialize(challenge)
try {
log.info("Send public key and group identifier ...")
val details = manager.sendPublicKeyAndGroupIdentifier()
log.info("Process challenger details and generate quote ...")
val quote = enclave
.processChallengerDetailsAndGenerateQuote(details)
log.info("Submit generated quote ...")
val response = manager.submitQuote(challenge, quote)
log.info("Quote status = ${response.quoteStatus}")
log.info("Verify attestation response ...")
val (validMac, sealedSecret) =
manager.verifyAttestationResponse(response)
log.info("Has valid CMAC = $validMac")
log.info("Sealed secret size = ${sealedSecret.size}")
} catch (ex : Exception) {
log.error("Failed to complete remote attestation", ex)
} finally {
manager.cleanUp()
}
}

View File

@ -0,0 +1,42 @@
package net.corda.sgx.enclave
/**
* Public key based on NIST P-256 elliptic curve
*
* @param componentX The 256-bit X component of the key.
* @param componentY The 256-bit Y component of the key.
*/
class ECKey(
componentX: ByteArray,
componentY: ByteArray
) {
/**
* The bytes constituting the elliptic curve public key.
*/
val bytes: ByteArray = componentX.plus(componentY)
companion object {
/**
* Create a public key from a byte array.
*
* @param bytes The 64 bytes forming the NIST P-256 elliptic curve.
*/
fun fromBytes(bytes: ByteArray): ECKey {
if (bytes.size != 64) {
throw Exception("Expected 64 bytes, but got ${bytes.size}")
}
val componentX = bytes.copyOfRange(0, 32)
val componentY = bytes.copyOfRange(32, 64)
return ECKey(componentX, componentY)
}
}
}
/**
* Short-hand generator for supplying a constant X or Y component.
*/
fun ecKeyComponent(vararg bytes: Int) = bytes.map { it.toByte() }.toByteArray()

View File

@ -0,0 +1,86 @@
package net.corda.sgx.enclave
import net.corda.sgx.system.SgxSystem
import net.corda.sgx.system.SgxUnavailableException
/**
* Representation an enclave.
*/
interface Enclave {
/**
* The SGX-enabled system on which this enclave is running.
*/
val system: SgxSystem
/**
* The enclave identifier.
*/
val identifier: EnclaveIdentifier
/**
* Create enclave used for remote attestation, and consequently for secret
* sealing and unsealing.
*/
fun create(): SgxStatus
/**
* Destroy enclave if running.
*/
fun destroy(): Boolean
/**
* Destroy and re-create the enclave. This is normally done if the enclave
* is lost due to a power transition or similar events.
*/
fun recreate(): SgxStatus {
destroy()
return create()
}
/**
* Check whether the enclave has been run before or not.
*/
fun isFresh(): Boolean
/**
* Check whether an enclave has already been created and initialized.
* Otherwise, try to create required enclave or re-create one in the cases
* where an older one has been lost due to a power transition or similar.
*
* @throws SgxException If unable to create enclave.
* @throws SgxUnavailableException If SGX is unavailable or for some reason
* disabled.
*/
fun activate() {
// First, make sure SGX is available and that it is enabled. Under some
// circumstances, a reboot may be required to enable SGX. In either
// case, as long as the extensions aren't enabled, an
// [SgxUnavailableException] will be thrown.
system.ensureAvailable()
// If the enclave has already been created and is active, we are good
// to proceed.
var status = create()
if (status == SgxStatus.SUCCESS) {
return
}
// Check if an attestation enclave was previously created. If it was
// and it is no longer available, recreate one to the same
// specification. Note: Losing an enclave is normally the result of a
// power transition.
if (status == SgxStatus.ERROR_ENCLAVE_LOST) {
status = recreate()
if (status != SgxStatus.SUCCESS) {
throw SgxException(status, identifier)
}
return
}
// Some other error occurred, let's abort
throw SgxException(status, identifier)
}
}

View File

@ -0,0 +1,6 @@
package net.corda.sgx.enclave
/**
* The identifier of an enclave.
*/
typealias EnclaveIdentifier = Long

View File

@ -0,0 +1,39 @@
package net.corda.sgx.enclave
import net.corda.sgx.attestation.entities.AttestationContext
/**
* Exception raised whenever there's a problem creating, destroying or
* interacting with an enclave or SGX.
*
* @property status The status or outcome of an SGX operation.
* @property enclaveIdentifier The identifier of the enclave, if available.
* @property context The established remote attestation context, if available.
*/
class SgxException @JvmOverloads constructor(
val status: SgxStatus,
private val enclaveIdentifier: EnclaveIdentifier? = null,
private val context: AttestationContext? = null
) : Exception(status.message) {
/**
* Human readable representation of the exception.
*/
override fun toString(): String {
val message = super.toString()
val identifierString = if (enclaveIdentifier != null) {
"0x${java.lang.Long.toHexString(enclaveIdentifier)}"
} else {
"null"
}
val contextString = if (context != null) {
"0x${java.lang.Integer.toHexString(context)}"
} else {
"null"
}
return "$message (enclave=$identifierString, " +
"context=$contextString, status=${status.name})"
}
}

View File

@ -0,0 +1,291 @@
package net.corda.sgx.enclave
/**
* The status of an SGX operation
*
* @property code The native status code returned from the SGX API.
* @property message A human readable representation of the state.
*/
enum class SgxStatus(val code: Long, val message: String) {
/**
* Success.
*/
SUCCESS(0x0000, "Success"),
/**
* Unexpected error.
*/
ERROR_UNEXPECTED(0x0001, "Unexpected error"),
/**
* The parameter is incorrect.
*/
ERROR_INVALID_PARAMETER(0x0002, "The parameter is incorrect"),
/**
* Not enough memory is available to complete this operation.
*/
ERROR_OUT_OF_MEMORY(0x0003, "Not enough memory is available to complete this operation"),
/**
* Enclave lost after power transition or used in child process created by linux:fork().
*/
ERROR_ENCLAVE_LOST(0x0004, "Enclave lost after power transition or used in child process created by linux:fork()"),
/**
* SGX API is invoked in incorrect order or state.
*/
ERROR_INVALID_STATE(0x0005, "SGX API is invoked in incorrect order or state"),
/**
* The ECALL/OCALL index is invalid.
*/
ERROR_INVALID_FUNCTION(0x1001, "The ecall/ocall index is invalid"),
/**
* The enclave is out of TCS.
*/
ERROR_OUT_OF_TCS(0x1003, "The enclave is out of TCS"),
/**
* The enclave is crashed.
*/
ERROR_ENCLAVE_CRASHED(0x1006, "The enclave is crashed"),
/**
* The ECALL is not allowed at this time, e.g. ECALL is blocked by the dynamic entry table, or nested ECALL is not allowed during initialization.
*/
ERROR_ECALL_NOT_ALLOWED(0x1007, "The ECALL is not allowed at this time, e.g. ECALL is blocked by the dynamic entry table, or nested ECALL is not allowed during initialization"),
/**
* The OCALL is not allowed at this time, e.g. OCALL is not allowed during exception handling.
*/
ERROR_OCALL_NOT_ALLOWED(0x1008, "The OCALL is not allowed at this time, e.g. OCALL is not allowed during exception handling"),
/**
* The enclave is running out of stack.
*/
ERROR_STACK_OVERRUN(0x1009, "The enclave is running out of stack"),
/**
* The enclave image has one or more undefined symbols.
*/
ERROR_UNDEFINED_SYMBOL(0x2000, "The enclave image has one or more undefined symbols"),
/**
* The enclave image is not correct.
*/
ERROR_INVALID_ENCLAVE(0x2001, "The enclave image is not correct."),
/**
* The enclave identifier is invalid.
*/
ERROR_INVALID_ENCLAVE_ID(0x2002, "The enclave identifier is invalid."),
/**
* The signature is invalid.
*/
ERROR_INVALID_SIGNATURE(0x2003, "The signature is invalid"),
/**
* The enclave is signed as product enclave, and can not be created as debuggable enclave.
*/
ERROR_NDEBUG_ENCLAVE(0x2004, "The enclave is signed as product enclave, and can not be created as debuggable enclave"),
/**
* Not enough EPC is available to load the enclave.
*/
ERROR_OUT_OF_EPC(0x2005, "Not enough EPC is available to load the enclave"),
/**
* Cannot open SGX device.
*/
ERROR_NO_DEVICE(0x2006, "Cannot open SGX device"),
/**
* Page mapping failed in driver.
*/
ERROR_MEMORY_MAP_CONFLICT(0x2007, "Page mapping failed in driver"),
/**
* The metadata is incorrect.
*/
ERROR_INVALID_METADATA(0x2009, "The metadata is incorrect"),
/**
* Device is busy, mostly EINIT failed.
*/
ERROR_DEVICE_BUSY(0x200c, "Device is busy, mostly EINIT failed"),
/**
* Metadata version is inconsistent between uRTS and sgx_sign, or uRTS is incompatible with current platform.
*/
ERROR_INVALID_VERSION(0x200d, "Metadata version is inconsistent between uRTS and sgx_sign, or uRTS is incompatible with current platform"),
/**
* The target enclave 32/64 bit mode or SIM/HW mode is incompatible with the mode of current uRTS.
*/
ERROR_MODE_INCOMPATIBLE(0x200e, "The target enclave 32/64 bit mode or SIM/HW mode is incompatible with the mode of current uRTS"),
/**
* Cannot open enclave file.
*/
ERROR_ENCLAVE_FILE_ACCESS(0x200f, "Cannot open enclave file"),
/**
* The MiscSelct/MiscMask settings are not correct.
*/
ERROR_INVALID_MISC(0x2010, "The MiscSelct/MiscMask settings are not correct"),
/**
* Indicates verification error for reports, sealed data, etc.
*/
ERROR_MAC_MISMATCH(0x3001, "Indicates verification error for reports, sealed data, etc"),
/**
* The enclave is not authorized.
*/
ERROR_INVALID_ATTRIBUTE(0x3002, "The enclave is not authorized"),
/**
* The CPU SVN is beyond platform's CPU SVN value.
*/
ERROR_INVALID_CPUSVN(0x3003, "The CPU SVN is beyond platform's CPU SVN value"),
/**
* The ISV SVN is greater than the enclave's ISV SVN.
*/
ERROR_INVALID_ISVSVN(0x3004, "The ISV SVN is greater than the enclave's ISV SVN"),
/**
* The key name is an unsupported value.
*/
ERROR_INVALID_KEYNAME(0x3005, "The key name is an unsupported value"),
/**
* Indicates AESM didn't respond or the requested service is not supported.
*/
ERROR_SERVICE_UNAVAILABLE(0x4001, "Indicates AESM didn't respond or the requested service is not supported"),
/**
* The request to AESM timed out.
*/
ERROR_SERVICE_TIMEOUT(0x4002, "The request to AESM timed out"),
/**
* Indicates EPID blob verification error.
*/
ERROR_AE_INVALID_EPIDBLOB(0x4003, "Indicates EPID blob verification error"),
/**
* Enclave has no privilege to get launch token.
*/
ERROR_SERVICE_INVALID_PRIVILEGE(0x4004, "Enclave has no privilege to get launch token"),
/**
* The EPID group membership is revoked.
*/
ERROR_EPID_MEMBER_REVOKED(0x4005, "The EPID group membership is revoked"),
/**
* SGX needs to be updated.
*/
ERROR_UPDATE_NEEDED(0x4006, "SGX needs to be updated"),
/**
* Network connection or proxy settings issue is encountered.
*/
ERROR_NETWORK_FAILURE(0x4007, "Network connection or proxy settings issue is encountered"),
/**
* Session is invalid or ended by server.
*/
ERROR_AE_SESSION_INVALID(0x4008, "Session is invalid or ended by server"),
/**
* Requested service is temporarily not available.
*/
ERROR_BUSY(0x400a, "Requested service is temporarily not available"),
/**
* Monotonic Counter doesn't exist or has been invalidated.
*/
ERROR_MC_NOT_FOUND(0x400c, "Monotonic Counter doesn't exist or has been invalidated"),
/**
* Caller doesn't have access to specified VMC.
*/
ERROR_MC_NO_ACCESS_RIGHT(0x400d, "Caller doesn't have access to specified VMC"),
/**
* Monotonic counters are used up.
*/
ERROR_MC_USED_UP(0x400e, "Monotonic counters are used up"),
/**
* Monotonic counters exceeds quota limitation.
*/
ERROR_MC_OVER_QUOTA(0x400f, "Monotonic counters exceeds quota limitation"),
/**
* Key derivation function doesn't match during key exchange.
*/
ERROR_KDF_MISMATCH(0x4011, "Key derivation function doesn't match during key exchange"),
/**
* EPID provisioning failed due to platform not being recognized by backend server.
*/
ERROR_UNRECOGNIZED_PLATFORM(0x4012, "EPID provisioning failed due to platform not being recognized by backend server"),
/**
* Not privileged to perform this operation.
*/
ERROR_NO_PRIVILEGE(0x5002, "Not privileged to perform this operation"),
/**
* The file is in a bad state.
*/
ERROR_FILE_BAD_STATUS(0x7001, "The file is in a bad state, run sgx_clearerr to try and fix it"),
/**
* The KeyID field is all zeros, cannot re-generate the encryption key.
*/
ERROR_FILE_NO_KEY_ID(0x7002, "The KeyID field is all zeros, cannot re-generate the encryption key"),
/**
* The current file name is different then the original file name (not allowed due to potential substitution attack).
*/
ERROR_FILE_NAME_MISMATCH(0x7003, "The current file name is different then the original file name (not allowed due to potential substitution attack)"),
/**
* The file is not an SGX file.
*/
ERROR_FILE_NOT_SGX_FILE(0x7004, "The file is not an SGX file"),
/**
* A recovery file cannot be opened, so flush operation cannot continue (only used when no EXXX is returned).
*/
ERROR_FILE_CANT_OPEN_RECOVERY_FILE(0x7005, "A recovery file cannot be opened, so flush operation cannot continue (only used when no EXXX is returned)"),
/**
* A recovery file cannot be written, so flush operation cannot continue (only used when no EXXX is returned).
*/
ERROR_FILE_CANT_WRITE_RECOVERY_FILE(0x7006, "A recovery file cannot be written, so flush operation cannot continue (only used when no EXXX is returned)"),
/**
* When opening the file, recovery is needed, but the recovery process failed.
*/
ERROR_FILE_RECOVERY_NEEDED(0x7007, "When opening the file, recovery is needed, but the recovery process failed"),
/**
* fflush operation (to disk) failed (only used when no EXXX is returned).
*/
ERROR_FILE_FLUSH_FAILED(0x7008, "fflush operation (to disk) failed (only used when no EXXX is returned)"),
/**
* fclose operation (to disk) failed (only used when no EXXX is returned).
*/
ERROR_FILE_CLOSE_FAILED(0x7009, "fclose operation (to disk) failed (only used when no EXXX is returned)"),
}

View File

@ -0,0 +1,6 @@
package net.corda.sgx.sealing
/**
* Representation of a sealed secret.
*/
typealias SealedSecret = ByteArray

View File

@ -0,0 +1,10 @@
package net.corda.sgx.sealing
/**
* Exception raised whenever there is a problem with a sealing operation.
*
* @property status The status or outcome of the operation.
*/
class SealingException(
val status: SealingResult
) : Exception(status.message)

View File

@ -0,0 +1,21 @@
package net.corda.sgx.sealing
/**
* The outcome of a performed sealing operation.
*
* @property code The underlying status code.
* @property message A human readable representation of the state.
*/
enum class SealingResult(val code: Long, val message: String) {
/**
* Sealing was successful.
*/
SUCCESS(0, "Sealing was successful."),
/**
* Sealing was unsuccessful.
*/
FAIL(1, "Failed to seal secret."),
}

View File

@ -0,0 +1,6 @@
package net.corda.sgx.sealing
/**
* Representation of a provisioned secret.
*/
typealias ProvisionedSecret = ByteArray

View File

@ -0,0 +1,59 @@
package net.corda.sgx.sealing
import net.corda.sgx.attestation.AttestationEnclave
import net.corda.sgx.enclave.SgxStatus
/**
* Manager for storing and managing secrets.
*
* @property enclave The facilitating attestation enclave.
*/
open class SecretManager(
private val enclave: AttestationEnclave
) {
/**
* Check that an existing secret (if available) is valid and hasn't
* expired.
*/
fun isValid(): Boolean {
// First off, check whether we actually have access to a secret
if (!hasSecret()) {
return false
}
// Then, ensure that we can unseal the secret, that the lease has not
// expired, etc.
val result = unsealSecret()
return result == SgxStatus.SUCCESS
}
/**
* Retrieve sealed secret, or null if not available.
*/
open fun getSealedSecret(): SealedSecret? = null
/**
* Persist the sealed secret to disk or similar, for future use.
*
* @param sealedSecret The secret sealed to the enclave's context.
*/
open fun persistSealedSecret(sealedSecret: SealedSecret) { }
/**
* Check whether we have a secret persisted already.
*/
private fun hasSecret(): Boolean {
return getSealedSecret() != null
}
/**
* Check if we can unseal an existing secret.
*/
private fun unsealSecret(): SgxStatus {
val sealedSecret = getSealedSecret()
?: return SgxStatus.ERROR_INVALID_PARAMETER
return enclave.unseal(sealedSecret)
}
}

View File

@ -0,0 +1,15 @@
package net.corda.sgx.system
/**
* Extended Intel EPID group identifier.
*
* @property value The identifier for the extended EPID group.
*/
enum class ExtendedGroupIdentifier(val value: Int) {
/**
* Indicates that we are using Intel's Attestation Service.
*/
INTEL(0),
}

View File

@ -0,0 +1,13 @@
package net.corda.sgx.system
/**
* The Intel EPID group identifier.
*/
typealias GroupIdentifier = Int
/**
* Get the string representation of the group identifier.
*/
fun GroupIdentifier.value(): String = Integer
.toHexString(this)
.padStart(8, '0')

View File

@ -0,0 +1,62 @@
package net.corda.sgx.system
/**
* The status of the SGX device on the current machine.
*
* @property code The native status code returned from the SGX API.
* @property message A human readable representation of the state.
*/
enum class SgxDeviceStatus(val code: Int, val message: String) {
/**
* The platform is enabled for Intel SGX.
*/
ENABLED(0, "SGX device is available and enabled"),
/**
* This platform is disabled for Intel SGX. It is configured to be enabled
* after the next reboot.
*/
DISABLED_REBOOT_REQUIRED(1, "Rebooted required"),
/**
* The operating system does not support UEFI enabling of the Intel SGX
* device. If UEFI is supported by the operating system in general, but
* support for enabling the Intel SGX device does not exist, this function
* returns the more general [DISABLED].
*/
DISABLED_LEGACY_OS(2, "Operating system with EFI support required"),
/**
* This platform is disabled for Intel SGX. More details about the ability
* to enable Intel SGX are unavailable. There may be cases when Intel SGX
* can be enabled manually in the BIOS.
*/
DISABLED(3, "SGX device is not available"),
/**
* The platform is disabled for Intel SGX but can be enabled using the
* Software Control Interface.
*/
DISABLED_SCI_AVAILABLE(4, "Needs enabling using the SCI"),
/**
* The platform is disabled for Intel SGX but can be enabled manually
* through the BIOS menu. The Software Control Interface is not available
* to enable Intel SGX on this platform.
*/
DISABLED_MANUAL_ENABLE(5, "Needs enabling through the BIOS menu"),
/**
* The detected version of Windows 10 is incompatible with Hyper-V. Intel
* SGX cannot be enabled on the target machine unless Hyper-V is disabled.
*/
DISABLED_HYPERV_ENABLED(6, "Hyper-V must be disabled"),
/**
* Intel SGX is not supported by this processor.
*/
DISABLED_UNSUPPORTED_CPU(7, "SGX not supported by processor"),
}

View File

@ -0,0 +1,76 @@
package net.corda.sgx.system
import net.corda.sgx.attestation.entities.AttestationException
import net.corda.sgx.attestation.entities.QuoteStatus
import net.corda.sgx.enclave.SgxStatus
/**
* Query system properties of an SGX-enabled environment.
*/
interface SgxSystem {
companion object {
/**
* Get [SgxDeviceStatus] from numeric code.
*/
fun deviceStatusFromCode(code: Int): SgxDeviceStatus =
enumValues<SgxDeviceStatus>().first { it.code == code }
/**
* Get [SgxStatus] from numeric code.
*/
fun statusFromCode(code: Long): SgxStatus =
enumValues<SgxStatus>().first { it.code == code }
/**
* Get [ExtendedGroupIdentifier] from a numeric identifier.
*/
fun extendedGroupIdentifier(id: Int): ExtendedGroupIdentifier? =
enumValues<ExtendedGroupIdentifier>().
firstOrNull { it.value == id }
/**
* Get [QuoteStatus] from string.
*/
fun quoteStatusFromString(
code: String
): QuoteStatus {
return enumValues<QuoteStatus>()
.firstOrNull { it.name == code }
?: throw AttestationException(
"Invalid quote status code '$code'")
}
}
/**
* Check if the client platform is enabled for Intel SGX. The application
* must be run with administrator privileges to get the status
* successfully.
*
* @return The current status of the SGX device.
*/
fun getDeviceStatus(): SgxDeviceStatus
/**
* Get the extended Intel EPID Group the client uses by default. The key
* used to sign a quote will be a member of the this group.
*/
fun getExtendedGroupIdentifier(): ExtendedGroupIdentifier
/**
* Check if SGX is available and enabled in the current runtime
* environment.
*
* @throws SgxUnavailableException If SGX is unavailable or for some reason
* disabled.
*/
fun ensureAvailable() {
val status = getDeviceStatus()
if (status != SgxDeviceStatus.ENABLED) {
throw SgxUnavailableException(status)
}
}
}

View File

@ -0,0 +1,10 @@
package net.corda.sgx.system
/**
* Exception raised if SGX for some reason is unavailable on the system.
*
* @property status The status of the SGX device.
*/
class SgxUnavailableException(
val status: SgxDeviceStatus
) : Exception(status.message)

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration package="net.corda.sgx"
status="info">
<Properties>
<Property name="log-path">${sys:attestation.home}</Property>
<Property name="log-name">attestation-host</Property>
<Property name="archive">${sys:log-path}/archive</Property>
<Property name="consoleLogLevel">info</Property>
<Property name="defaultLogLevel">debug</Property>
</Properties>
<ThresholdFilter level="trace"/>
<Appenders>
<!-- Will generate up to 10 log files for a given day. During every rollover it will delete
those that are older than 60 days, but keep the most recent 10 GB -->
<RollingFile name="RollingFile-Appender"
fileName="${sys:log-path}/${log-name}.log"
filePattern="${archive}/${log-name}.%date{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="%date{ISO8601}{UTC}Z [%-5level] %c - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<DefaultRolloverStrategy min="1" max="10">
<Delete basePath="${archive}" maxDepth="1">
<IfFileName glob="${log-name}*.log.gz"/>
<IfLastModified age="60d">
<IfAny>
<IfAccumulatedFileSize exceeds="10 GB"/>
</IfAny>
</IfLastModified>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</Appenders>
<Loggers>
<Root level="${sys:defaultLogLevel}">
<AppenderRef ref="RollingFile-Appender"/>
</Root>
<Logger name="org.apache.http" level="warn"/>
</Loggers>
</Configuration>

View File

@ -0,0 +1,12 @@
package net.corda.sgx.bridge
object EnclaveConfiguration {
private val dir: String = System.getProperty("corda.sgx.enclave.path")
/**
* The path of the signed attestation enclave binary.
*/
val path: String = "$dir/corda_sgx_ra_enclave.so"
}

View File

@ -0,0 +1,53 @@
package net.corda.sgx.bridge.attestation
import net.corda.sgx.bridge.EnclaveConfiguration
import net.corda.sgx.enclave.ECKey
import net.corda.sgx.enclave.ecKeyComponent
import org.junit.Ignore
import org.junit.Test
@Suppress("KDocMissingDocumentation")
class NativeAttestationEnclaveTests {
// Dummy key used to initialize the key exchange
private val challengerKey: ECKey = ECKey(
ecKeyComponent(
0xC0, 0x8C, 0x9F, 0x45, 0x59, 0x1A, 0x9F, 0xAE,
0xC5, 0x1F, 0xBC, 0x3E, 0xFB, 0x4F, 0x67, 0xB1,
0x93, 0x61, 0x45, 0x9E, 0x30, 0x27, 0x10, 0xC4,
0x92, 0x0F, 0xBB, 0xB2, 0x69, 0xB0, 0x16, 0x39
),
ecKeyComponent(
0x5D, 0x98, 0x6B, 0x24, 0x2B, 0x52, 0x46, 0x72,
0x2A, 0x35, 0xCA, 0xE0, 0xA9, 0x1A, 0x6A, 0xDC,
0xB8, 0xEB, 0x32, 0xC8, 0x1C, 0x2B, 0x5A, 0xF1,
0x23, 0x1F, 0x6C, 0x6E, 0x30, 0x00, 0x96, 0x4F
)
)
@Test
fun `can initialize and finalize key exchange without platform services`() {
val enclave = NativeAttestationEnclave(EnclaveConfiguration.path, false)
enclave.activate()
enclave.initializeKeyExchange(challengerKey)
enclave.finalizeKeyExchange()
enclave.destroy()
}
// TODO Use PSE to protect against replay attacks
// We currently don't leverage the PSE to protect against replay attacks,
// etc., and it seems like the box doesn't communicate properly with it.
// Longer term, we should get this enabled, which will require further
// changes to the enclave code as well.
@Ignore("Requires platform services")
@Test
fun `can initialize and finalize key exchange with platform services`() {
val enclave = NativeAttestationEnclave(EnclaveConfiguration.path, true)
enclave.activate()
enclave.initializeKeyExchange(challengerKey)
enclave.finalizeKeyExchange()
enclave.destroy()
}
}

View File

@ -0,0 +1,92 @@
package net.corda.sgx.bridge.enclave
import net.corda.sgx.bridge.EnclaveConfiguration
import net.corda.sgx.bridge.attestation.NativeAttestationEnclave
import net.corda.sgx.enclave.SgxStatus
import org.junit.Assert.*
import org.junit.Test
@Suppress("KDocMissingDocumentation")
class NativeEnclaveTests {
@Test
fun `cannot create enclave without valid executable`() {
val enclave = NativeAttestationEnclave("/somewhere/over/the/rainbow")
assertTrue(enclave.isFresh())
assertEquals(SgxStatus.ERROR_ENCLAVE_FILE_ACCESS, enclave.create())
}
@Test
fun `can create enclave without platform services`() {
val enclave = NativeAttestationEnclave(EnclaveConfiguration.path, false)
assertTrue(enclave.isFresh())
assertEquals(SgxStatus.SUCCESS, enclave.create())
assertFalse(enclave.isFresh())
assertTrue(enclave.destroy())
}
@Test
fun `can create enclave with platform services`() {
val enclave = NativeAttestationEnclave(EnclaveConfiguration.path, true)
assertTrue(enclave.isFresh())
assertEquals(SgxStatus.SUCCESS, enclave.create())
assertFalse(enclave.isFresh())
assertTrue(enclave.destroy())
}
@Test
fun `can destroy existing enclave`() {
val enclave = NativeAttestationEnclave(EnclaveConfiguration.path)
assertEquals(SgxStatus.SUCCESS, enclave.create())
assertFalse(enclave.isFresh())
assertTrue(enclave.destroy())
assertTrue(enclave.isFresh())
}
@Test
fun `can detect destruction of non-existent enclave`() {
val enclave = NativeAttestationEnclave(EnclaveConfiguration.path)
assertTrue(enclave.isFresh())
assertTrue(enclave.destroy())
}
@Test
fun `can enter enclave that has already been created`() {
val enclave = NativeAttestationEnclave(EnclaveConfiguration.path)
assertTrue(enclave.isFresh())
assertEquals(SgxStatus.SUCCESS, enclave.create())
assertFalse(enclave.isFresh())
assertEquals(SgxStatus.SUCCESS, enclave.create())
assertFalse(enclave.isFresh())
assertTrue(enclave.destroy())
}
@Test
fun `can create same enclave twice`() {
val enclaveA = NativeAttestationEnclave(EnclaveConfiguration.path)
assertTrue(enclaveA.isFresh())
assertEquals(SgxStatus.SUCCESS, enclaveA.create())
assertFalse(enclaveA.isFresh())
val identifierA = enclaveA.identifier
val enclaveB = NativeAttestationEnclave(EnclaveConfiguration.path)
assertTrue(enclaveB.isFresh())
assertEquals(SgxStatus.SUCCESS, enclaveB.create())
assertFalse(enclaveB.isFresh())
val identifierB = enclaveB.identifier
assertNotEquals(identifierA, identifierB)
assertTrue(enclaveA.destroy())
assertTrue(enclaveB.destroy())
}
@Test
fun `can activate enclave`() {
val enclave = NativeAttestationEnclave(EnclaveConfiguration.path)
enclave.activate() // throws on failure
}
}

View File

@ -0,0 +1,24 @@
package net.corda.sgx.bridge.system
import net.corda.sgx.system.ExtendedGroupIdentifier
import net.corda.sgx.system.SgxDeviceStatus
import org.junit.Assert.assertEquals
import org.junit.Test
@Suppress("KDocMissingDocumentation")
class NativeSgxSystemTests {
@Test
fun `can retrieve device status`() {
val system = NativeSgxSystem()
assertEquals(SgxDeviceStatus.ENABLED, system.getDeviceStatus())
}
@Test
fun `can retrieve extended group identifier`() {
val system = NativeSgxSystem()
val identifier = system.getExtendedGroupIdentifier()
assertEquals(ExtendedGroupIdentifier.INTEL, identifier)
}
}

View File

@ -0,0 +1,24 @@
package net.corda.sgx.enclave
import org.junit.Assert.assertArrayEquals
import org.junit.Test
@Suppress("KDocMissingDocumentation")
class ECKeyTests {
@Test
fun `can get bytes from EC key instantiated from two empty components`() {
val ecKey = ECKey(byteArrayOf(), byteArrayOf())
assertArrayEquals(byteArrayOf(), ecKey.bytes)
}
@Test
fun `can get bytes from EC key instantiated from two components`() {
val ecKey1 = ECKey(byteArrayOf(1), byteArrayOf(2))
assertArrayEquals(byteArrayOf(1, 2), ecKey1.bytes)
val ecKey2 = ECKey(byteArrayOf(1, 2, 3, 4), byteArrayOf(5, 6, 7, 8))
assertArrayEquals(byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8), ecKey2.bytes)
}
}

View File

@ -0,0 +1,26 @@
package net.corda.sgx.enclave
import net.corda.sgx.system.SgxSystem
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Test
import kotlin.test.assertFails
@Suppress("KDocMissingDocumentation")
class SgxStatusTests {
@Test
fun `can retrieve status from valid integer`() {
assertEquals(SgxStatus.SUCCESS, SgxSystem.statusFromCode(0))
assertEquals(SgxStatus.ERROR_BUSY, SgxSystem.statusFromCode(0x400a))
assertEquals(SgxStatus.ERROR_FILE_CLOSE_FAILED, SgxSystem.statusFromCode(0x7009))
assertNotEquals(SgxStatus.ERROR_AE_INVALID_EPIDBLOB, SgxSystem.statusFromCode(0x4003 - 1))
}
@Test
fun `cannot retrieve status from invalid integer`() {
assertFails { SgxSystem.statusFromCode(-1) }
assertFails { SgxSystem.statusFromCode(0xfedc) }
}
}

View File

@ -0,0 +1,26 @@
package net.corda.sgx.system
import org.junit.Assert.assertEquals
import org.junit.Test
import kotlin.test.assertNull
@Suppress("KDocMissingDocumentation")
class ExtendedGroupIdentifierTests {
@Test
fun `can retrieve extended group identifier for Intel`() {
assertEquals(
ExtendedGroupIdentifier.INTEL,
SgxSystem.extendedGroupIdentifier(0)
)
}
@Test
fun `cannot retrieve other extended group identifiers`() {
assertNull(SgxSystem.extendedGroupIdentifier(-1))
assertNull(SgxSystem.extendedGroupIdentifier(1))
assertNull(SgxSystem.extendedGroupIdentifier(2))
assertNull(SgxSystem.extendedGroupIdentifier(10))
}
}

View File

@ -0,0 +1,22 @@
package net.corda.sgx.system
import org.junit.Assert.assertEquals
import org.junit.Test
import kotlin.test.assertFails
@Suppress("KDocMissingDocumentation")
class SgxDeviceStatusTests {
@Test
fun `can retrieve status from valid integer`() {
assertEquals(SgxDeviceStatus.ENABLED, SgxSystem.deviceStatusFromCode(0))
assertEquals(SgxDeviceStatus.DISABLED, SgxSystem.deviceStatusFromCode(3))
}
@Test
fun `cannot retrieve status from invalid integer`() {
assertFails { SgxSystem.deviceStatusFromCode(-1) }
assertFails { SgxSystem.deviceStatusFromCode(0xfedc) }
}
}

View File

@ -1,2 +1,3 @@
rootProject.name = 'remote-attestation'
include 'attestation-server'
include 'host'