mirror of
https://github.com/corda/corda.git
synced 2024-12-25 23:51:10 +00:00
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:
parent
65ccd2318f
commit
83d6a248a8
6
sgx-jvm/.gitignore
vendored
Normal file
6
sgx-jvm/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.idea/
|
||||
.vscode/
|
||||
obj/
|
||||
build/
|
||||
log/
|
||||
tags
|
45
sgx-jvm/remote-attestation/README.md
Normal file
45
sgx-jvm/remote-attestation/README.md
Normal 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`.
|
6
sgx-jvm/remote-attestation/enclave/.gitignore
vendored
Normal file
6
sgx-jvm/remote-attestation/enclave/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
*_t.c
|
||||
*_t.h
|
||||
*_t.o
|
||||
*_u.c
|
||||
*_u.h
|
||||
*_u.o
|
213
sgx-jvm/remote-attestation/enclave/Makefile
Normal file
213
sgx-jvm/remote-attestation/enclave/Makefile
Normal 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)
|
11
sgx-jvm/remote-attestation/enclave/config/debug.xml
Normal file
11
sgx-jvm/remote-attestation/enclave/config/debug.xml
Normal 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>
|
11
sgx-jvm/remote-attestation/enclave/config/release.xml
Normal file
11
sgx-jvm/remote-attestation/enclave/config/release.xml
Normal 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>
|
216
sgx-jvm/remote-attestation/enclave/enclave.cpp
Normal file
216
sgx-jvm/remote-attestation/enclave/enclave.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
104
sgx-jvm/remote-attestation/enclave/enclave.edl
Normal file
104
sgx-jvm/remote-attestation/enclave/enclave.edl
Normal 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
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
};
|
10
sgx-jvm/remote-attestation/enclave/enclave.lds
Normal file
10
sgx-jvm/remote-attestation/enclave/enclave.lds
Normal 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:
|
||||
*;
|
||||
};
|
84
sgx-jvm/remote-attestation/enclave/sgx.mk
Normal file
84
sgx-jvm/remote-attestation/enclave/sgx.mk
Normal 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)
|
72
sgx-jvm/remote-attestation/host/Makefile
Normal file
72
sgx-jvm/remote-attestation/host/Makefile
Normal 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)
|
82
sgx-jvm/remote-attestation/host/build.gradle
Normal file
82
sgx-jvm/remote-attestation/host/build.gradle
Normal 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(":")
|
||||
}
|
||||
}
|
1
sgx-jvm/remote-attestation/host/native/.gitignore
vendored
Normal file
1
sgx-jvm/remote-attestation/host/native/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
wrapper.hpp
|
115
sgx-jvm/remote-attestation/host/native/Makefile
Normal file
115
sgx-jvm/remote-attestation/host/native/Makefile
Normal 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)
|
95
sgx-jvm/remote-attestation/host/native/enclave-manager.cpp
Normal file
95
sgx-jvm/remote-attestation/host/native/enclave-manager.cpp
Normal 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;
|
||||
}
|
55
sgx-jvm/remote-attestation/host/native/enclave-manager.hpp
Normal file
55
sgx-jvm/remote-attestation/host/native/enclave-manager.hpp
Normal 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__ */
|
13
sgx-jvm/remote-attestation/host/native/jni.hpp
Normal file
13
sgx-jvm/remote-attestation/host/native/jni.hpp
Normal 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__ */
|
32
sgx-jvm/remote-attestation/host/native/logging.cpp
Normal file
32
sgx-jvm/remote-attestation/host/native/logging.cpp
Normal 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
|
||||
);
|
||||
}
|
32
sgx-jvm/remote-attestation/host/native/logging.hpp
Normal file
32
sgx-jvm/remote-attestation/host/native/logging.hpp
Normal 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__ */
|
285
sgx-jvm/remote-attestation/host/native/remote-attestation.cpp
Normal file
285
sgx-jvm/remote-attestation/host/native/remote-attestation.cpp
Normal 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;
|
||||
}
|
181
sgx-jvm/remote-attestation/host/native/remote-attestation.hpp
Normal file
181
sgx-jvm/remote-attestation/host/native/remote-attestation.hpp
Normal 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__ */
|
15
sgx-jvm/remote-attestation/host/native/sealing.cpp
Normal file
15
sgx-jvm/remote-attestation/host/native/sealing.cpp
Normal 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;
|
||||
}
|
24
sgx-jvm/remote-attestation/host/native/sealing.hpp
Normal file
24
sgx-jvm/remote-attestation/host/native/sealing.hpp
Normal 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__ */
|
384
sgx-jvm/remote-attestation/host/native/wrapper.cpp
Normal file
384
sgx-jvm/remote-attestation/host/native/wrapper.cpp
Normal 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, "e,
|
||||
"e_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;
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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"
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package net.corda.sgx.attestation.entities
|
||||
|
||||
/**
|
||||
* Remote attestation context.
|
||||
*/
|
||||
typealias AttestationContext = Int
|
@ -0,0 +1,8 @@
|
||||
package net.corda.sgx.attestation.entities
|
||||
|
||||
/**
|
||||
* Exception thrown during remote attestation.
|
||||
*/
|
||||
class AttestationException(
|
||||
message: String
|
||||
) : Exception(message)
|
@ -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
|
||||
|
||||
}
|
@ -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."),
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
)
|
@ -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
|
||||
|
||||
)
|
@ -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.")
|
||||
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
)
|
@ -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
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
)
|
@ -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
|
||||
|
||||
)
|
@ -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
|
||||
|
||||
)
|
@ -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
|
||||
|
||||
)
|
@ -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
|
||||
|
||||
)
|
@ -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
|
||||
|
||||
)
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
)
|
@ -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
|
||||
|
||||
)
|
@ -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
|
||||
|
||||
)
|
@ -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)
|
@ -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
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
)
|
@ -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
|
||||
|
||||
)
|
@ -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
|
||||
|
||||
)
|
@ -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
|
||||
|
||||
)
|
@ -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()
|
||||
}
|
||||
}
|
@ -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()
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
package net.corda.sgx.enclave
|
||||
|
||||
/**
|
||||
* The identifier of an enclave.
|
||||
*/
|
||||
typealias EnclaveIdentifier = Long
|
@ -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})"
|
||||
}
|
||||
|
||||
}
|
@ -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)"),
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package net.corda.sgx.sealing
|
||||
|
||||
/**
|
||||
* Representation of a sealed secret.
|
||||
*/
|
||||
typealias SealedSecret = ByteArray
|
@ -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)
|
@ -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."),
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package net.corda.sgx.sealing
|
||||
|
||||
/**
|
||||
* Representation of a provisioned secret.
|
||||
*/
|
||||
typealias ProvisionedSecret = ByteArray
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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),
|
||||
|
||||
}
|
@ -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')
|
@ -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"),
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
@ -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>
|
||||
|
@ -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"
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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) }
|
||||
}
|
||||
|
||||
}
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
@ -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) }
|
||||
}
|
||||
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
rootProject.name = 'remote-attestation'
|
||||
include 'attestation-server'
|
||||
include 'host'
|
||||
|
Loading…
Reference in New Issue
Block a user