mirror of
https://github.com/corda/corda.git
synced 2025-01-13 16:30:25 +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'
|
rootProject.name = 'remote-attestation'
|
||||||
include 'attestation-server'
|
include 'attestation-server'
|
||||||
|
include 'host'
|
||||||
|
Loading…
Reference in New Issue
Block a user