diff --git a/sgx-jvm/.gitignore b/sgx-jvm/.gitignore
new file mode 100644
index 0000000000..2d9cc6c751
--- /dev/null
+++ b/sgx-jvm/.gitignore
@@ -0,0 +1,6 @@
+.idea/
+.vscode/
+obj/
+build/
+log/
+tags
diff --git a/sgx-jvm/remote-attestation/README.md b/sgx-jvm/remote-attestation/README.md
new file mode 100644
index 0000000000..60ce64c6be
--- /dev/null
+++ b/sgx-jvm/remote-attestation/README.md
@@ -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`.
diff --git a/sgx-jvm/remote-attestation/enclave/.gitignore b/sgx-jvm/remote-attestation/enclave/.gitignore
new file mode 100644
index 0000000000..4652f2b807
--- /dev/null
+++ b/sgx-jvm/remote-attestation/enclave/.gitignore
@@ -0,0 +1,6 @@
+*_t.c
+*_t.h
+*_t.o
+*_u.c
+*_u.h
+*_u.o
diff --git a/sgx-jvm/remote-attestation/enclave/Makefile b/sgx-jvm/remote-attestation/enclave/Makefile
new file mode 100644
index 0000000000..491879e28c
--- /dev/null
+++ b/sgx-jvm/remote-attestation/enclave/Makefile
@@ -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)
diff --git a/sgx-jvm/remote-attestation/enclave/config/debug.xml b/sgx-jvm/remote-attestation/enclave/config/debug.xml
new file mode 100644
index 0000000000..36465f4b20
--- /dev/null
+++ b/sgx-jvm/remote-attestation/enclave/config/debug.xml
@@ -0,0 +1,11 @@
+
+ 0
+ 0
+ 0x40000
+ 0x100000
+ 1
+ 1
+ 0
+ 0
+ 0xFFFFFFFF
+
diff --git a/sgx-jvm/remote-attestation/enclave/config/release.xml b/sgx-jvm/remote-attestation/enclave/config/release.xml
new file mode 100644
index 0000000000..d694143ccf
--- /dev/null
+++ b/sgx-jvm/remote-attestation/enclave/config/release.xml
@@ -0,0 +1,11 @@
+
+ 0
+ 0
+ 0x40000
+ 0x100000
+ 1
+ 1
+ 1
+ 0
+ 0xFFFFFFFF
+
diff --git a/sgx-jvm/remote-attestation/enclave/enclave.cpp b/sgx-jvm/remote-attestation/enclave/enclave.cpp
new file mode 100644
index 0000000000..47228c0318
--- /dev/null
+++ b/sgx-jvm/remote-attestation/enclave/enclave.cpp
@@ -0,0 +1,216 @@
+#include
+#include
+#include
+
+#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 . 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;
+}
+
+}
diff --git a/sgx-jvm/remote-attestation/enclave/enclave.edl b/sgx-jvm/remote-attestation/enclave/enclave.edl
new file mode 100644
index 0000000000..6019bf6256
--- /dev/null
+++ b/sgx-jvm/remote-attestation/enclave/enclave.edl
@@ -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
+ );
+
+ };
+
+};
diff --git a/sgx-jvm/remote-attestation/enclave/enclave.lds b/sgx-jvm/remote-attestation/enclave/enclave.lds
new file mode 100644
index 0000000000..f15b9561c3
--- /dev/null
+++ b/sgx-jvm/remote-attestation/enclave/enclave.lds
@@ -0,0 +1,10 @@
+corda_sgx_ra_enclave.so
+{
+ global:
+ enclave_entry;
+ g_global_data_sim;
+ g_global_data;
+ g_peak_heap_used;
+ local:
+ *;
+};
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/enclave/sgx.mk b/sgx-jvm/remote-attestation/enclave/sgx.mk
new file mode 100644
index 0000000000..cafc3b0e19
--- /dev/null
+++ b/sgx-jvm/remote-attestation/enclave/sgx.mk
@@ -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)
diff --git a/sgx-jvm/remote-attestation/host/Makefile b/sgx-jvm/remote-attestation/host/Makefile
new file mode 100644
index 0000000000..fab45e1cf5
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/Makefile
@@ -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)
diff --git a/sgx-jvm/remote-attestation/host/build.gradle b/sgx-jvm/remote-attestation/host/build.gradle
new file mode 100644
index 0000000000..8307f092ec
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/build.gradle
@@ -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(":")
+ }
+}
diff --git a/sgx-jvm/remote-attestation/host/native/.gitignore b/sgx-jvm/remote-attestation/host/native/.gitignore
new file mode 100644
index 0000000000..8762f1c2da
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/native/.gitignore
@@ -0,0 +1 @@
+wrapper.hpp
diff --git a/sgx-jvm/remote-attestation/host/native/Makefile b/sgx-jvm/remote-attestation/host/native/Makefile
new file mode 100644
index 0000000000..064bfc4039
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/native/Makefile
@@ -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)
diff --git a/sgx-jvm/remote-attestation/host/native/enclave-manager.cpp b/sgx-jvm/remote-attestation/host/native/enclave-manager.cpp
new file mode 100644
index 0000000000..11ef5365ae
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/native/enclave-manager.cpp
@@ -0,0 +1,95 @@
+#include
+
+#include
+#include
+#include
+
+#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;
+}
diff --git a/sgx-jvm/remote-attestation/host/native/enclave-manager.hpp b/sgx-jvm/remote-attestation/host/native/enclave-manager.hpp
new file mode 100644
index 0000000000..81396b6ccb
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/native/enclave-manager.hpp
@@ -0,0 +1,55 @@
+#ifndef __ENCLAVE_MANAGER_H__
+#define __ENCLAVE_MANAGER_H__
+
+#include
+#include
+
+/**
+ * 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__ */
diff --git a/sgx-jvm/remote-attestation/host/native/jni.hpp b/sgx-jvm/remote-attestation/host/native/jni.hpp
new file mode 100644
index 0000000000..302efee1d0
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/native/jni.hpp
@@ -0,0 +1,13 @@
+#ifndef __JNI_HPP__
+#define __JNI_HPP__
+
+#include
+
+#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__ */
diff --git a/sgx-jvm/remote-attestation/host/native/logging.cpp b/sgx-jvm/remote-attestation/host/native/logging.cpp
new file mode 100644
index 0000000000..a798565038
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/native/logging.cpp
@@ -0,0 +1,32 @@
+#include
+
+#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
+ );
+}
diff --git a/sgx-jvm/remote-attestation/host/native/logging.hpp b/sgx-jvm/remote-attestation/host/native/logging.hpp
new file mode 100644
index 0000000000..cc24228edc
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/native/logging.hpp
@@ -0,0 +1,32 @@
+#ifndef __LOGGING_HPP__
+#define __LOGGING_HPP__
+
+#include
+
+#include
+#include
+
+#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__ */
diff --git a/sgx-jvm/remote-attestation/host/native/remote-attestation.cpp b/sgx-jvm/remote-attestation/host/native/remote-attestation.cpp
new file mode 100644
index 0000000000..e25354aca9
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/native/remote-attestation.cpp
@@ -0,0 +1,285 @@
+#include
+#include
+
+#include
+
+#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;
+}
diff --git a/sgx-jvm/remote-attestation/host/native/remote-attestation.hpp b/sgx-jvm/remote-attestation/host/native/remote-attestation.hpp
new file mode 100644
index 0000000000..caebbd3750
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/native/remote-attestation.hpp
@@ -0,0 +1,181 @@
+#ifndef __REMOTE_ATTESTATION_H__
+#define __REMOTE_ATTESTATION_H__
+
+#include
+#include
+#include
+
+/**
+ * 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__ */
diff --git a/sgx-jvm/remote-attestation/host/native/sealing.cpp b/sgx-jvm/remote-attestation/host/native/sealing.cpp
new file mode 100644
index 0000000000..be64121190
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/native/sealing.cpp
@@ -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;
+}
diff --git a/sgx-jvm/remote-attestation/host/native/sealing.hpp b/sgx-jvm/remote-attestation/host/native/sealing.hpp
new file mode 100644
index 0000000000..2ba607aa3f
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/native/sealing.hpp
@@ -0,0 +1,24 @@
+#ifndef __SEALING_H__
+#define __SEALING_H__
+
+#include
+#include
+
+/**
+ * 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__ */
diff --git a/sgx-jvm/remote-attestation/host/native/wrapper.cpp b/sgx-jvm/remote-attestation/host/native/wrapper.cpp
new file mode 100644
index 0000000000..441eddf6fe
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/native/wrapper.cpp
@@ -0,0 +1,384 @@
+#include
+#include
+
+#include
+#include
+
+#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, "", "(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, "", "(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, "", "(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, "", "([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, "", "([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, "", "([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;
+}
diff --git a/sgx-jvm/remote-attestation/host/src/integration-test/kotlin/net/corda/sgx/attestation/AttestationManagerTests.kt b/sgx-jvm/remote-attestation/host/src/integration-test/kotlin/net/corda/sgx/attestation/AttestationManagerTests.kt
new file mode 100644
index 0000000000..0c45a0fcac
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/integration-test/kotlin/net/corda/sgx/attestation/AttestationManagerTests.kt
@@ -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 {
+ 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)
+ }
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/integration-test/kotlin/net/corda/sgx/bridge/EnclaveConfiguration.kt b/sgx-jvm/remote-attestation/host/src/integration-test/kotlin/net/corda/sgx/bridge/EnclaveConfiguration.kt
new file mode 100644
index 0000000000..8654de00fd
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/integration-test/kotlin/net/corda/sgx/bridge/EnclaveConfiguration.kt
@@ -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"
+
+}
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/AttestationEnclave.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/AttestationEnclave.kt
new file mode 100644
index 0000000000..0d37d99575
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/AttestationEnclave.kt
@@ -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
+
+ /**
+ * 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
+
+ /**
+ * 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
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/AttestationManager.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/AttestationManager.kt
new file mode 100644
index 0000000000..0d32dc29f6
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/AttestationManager.kt
@@ -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 {
+ 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
+ }
+ }
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/AttestationContext.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/AttestationContext.kt
new file mode 100644
index 0000000000..d765eb1f50
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/AttestationContext.kt
@@ -0,0 +1,6 @@
+package net.corda.sgx.attestation.entities
+
+/**
+ * Remote attestation context.
+ */
+typealias AttestationContext = Int
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/AttestationException.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/AttestationException.kt
new file mode 100644
index 0000000000..31f6ccaaa8
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/AttestationException.kt
@@ -0,0 +1,8 @@
+package net.corda.sgx.attestation.entities
+
+/**
+ * Exception thrown during remote attestation.
+ */
+class AttestationException(
+ message: String
+) : Exception(message)
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/AttestationResult.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/AttestationResult.kt
new file mode 100644
index 0000000000..5212c07ba6
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/AttestationResult.kt
@@ -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
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/AttestationStatus.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/AttestationStatus.kt
new file mode 100644
index 0000000000..9b4414274c
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/AttestationStatus.kt
@@ -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."),
+
+}
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/Challenge.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/Challenge.kt
new file mode 100644
index 0000000000..7179a5e75e
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/Challenge.kt
@@ -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
+
+)
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/Quote.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/Quote.kt
new file mode 100644
index 0000000000..ab3f63d4bd
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/Quote.kt
@@ -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
+
+)
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/QuoteStatus.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/QuoteStatus.kt
new file mode 100644
index 0000000000..0f80107749
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/QuoteStatus.kt
@@ -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.")
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/QuoteType.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/QuoteType.kt
new file mode 100644
index 0000000000..f6f5f04659
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/entities/QuoteType.kt
@@ -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)
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/ChallengerDetails.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/ChallengerDetails.kt
new file mode 100644
index 0000000000..c181c93aec
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/ChallengerDetails.kt
@@ -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
+
+)
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/ISVClient.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/ISVClient.kt
new file mode 100644
index 0000000000..6ec22508b8
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/ISVClient.kt
@@ -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
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/ISVHttpClient.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/ISVHttpClient.kt
new file mode 100644
index 0000000000..31b4fe8850
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/ISVHttpClient.kt
@@ -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(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(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(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 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
+ }
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/ChallengeResponse.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/ChallengeResponse.kt
new file mode 100644
index 0000000000..794342c79a
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/ChallengeResponse.kt
@@ -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
+
+)
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/Message0.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/Message0.kt
new file mode 100644
index 0000000000..a7b8176e35
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/Message0.kt
@@ -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
+
+)
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/Message1.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/Message1.kt
new file mode 100644
index 0000000000..06a0d25f35
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/Message1.kt
@@ -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
+
+)
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/Message2.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/Message2.kt
new file mode 100644
index 0000000000..33baba78b9
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/Message2.kt
@@ -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
+
+)
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/Message3.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/Message3.kt
new file mode 100644
index 0000000000..0680b69a88
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/Message3.kt
@@ -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
+
+)
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/Message4.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/Message4.kt
new file mode 100644
index 0000000000..350230b283
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/attestation/service/messages/Message4.kt
@@ -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
+
+)
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/attestation/NativeAttestationEnclave.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/attestation/NativeAttestationEnclave.kt
new file mode 100644
index 0000000000..976a6ffdc1
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/attestation/NativeAttestationEnclave.kt
@@ -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 {
+ 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 {
+ 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)
+ }
+ }
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/enclave/NativeEnclave.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/enclave/NativeEnclave.kt
new file mode 100644
index 0000000000..a984e065b4
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/enclave/NativeEnclave.kt
@@ -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 }
+ }
+ }
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/system/NativeSgxSystem.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/system/NativeSgxSystem.kt
new file mode 100644
index 0000000000..5ea99aff51
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/system/NativeSgxSystem.kt
@@ -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")
+ }
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/EnclaveResult.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/EnclaveResult.kt
new file mode 100644
index 0000000000..793ea125b7
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/EnclaveResult.kt
@@ -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
+
+)
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/ExtendedGroupIdentifierResult.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/ExtendedGroupIdentifierResult.kt
new file mode 100644
index 0000000000..8ce9ab123f
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/ExtendedGroupIdentifierResult.kt
@@ -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
+
+)
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/InitializationResult.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/InitializationResult.kt
new file mode 100644
index 0000000000..b186072ac8
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/InitializationResult.kt
@@ -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
+
+)
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/LaunchToken.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/LaunchToken.kt
new file mode 100644
index 0000000000..93ffe933f2
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/LaunchToken.kt
@@ -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)
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/NativeWrapper.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/NativeWrapper.kt
new file mode 100644
index 0000000000..00c985bdce
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/NativeWrapper.kt
@@ -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
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/PublicKeyAndGroupIdentifier.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/PublicKeyAndGroupIdentifier.kt
new file mode 100644
index 0000000000..1028bb97bb
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/PublicKeyAndGroupIdentifier.kt
@@ -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
+
+)
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/QuoteResult.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/QuoteResult.kt
new file mode 100644
index 0000000000..40d3375f53
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/QuoteResult.kt
@@ -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
+
+)
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/SealingOperationResult.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/SealingOperationResult.kt
new file mode 100644
index 0000000000..d038746082
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/SealingOperationResult.kt
@@ -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
+
+)
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/VerificationResult.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/VerificationResult.kt
new file mode 100644
index 0000000000..88c2263d19
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/bridge/wrapper/VerificationResult.kt
@@ -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
+
+)
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/cli/Program.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/cli/Program.kt
new file mode 100644
index 0000000000..06e162dc34
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/cli/Program.kt
@@ -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) {
+ 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()
+ }
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/enclave/ECKey.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/enclave/ECKey.kt
new file mode 100644
index 0000000000..a99590272a
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/enclave/ECKey.kt
@@ -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()
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/enclave/Enclave.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/enclave/Enclave.kt
new file mode 100644
index 0000000000..15362a866d
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/enclave/Enclave.kt
@@ -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)
+ }
+
+}
+
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/enclave/EnclaveIdentifier.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/enclave/EnclaveIdentifier.kt
new file mode 100644
index 0000000000..64d67a777c
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/enclave/EnclaveIdentifier.kt
@@ -0,0 +1,6 @@
+package net.corda.sgx.enclave
+
+/**
+ * The identifier of an enclave.
+ */
+typealias EnclaveIdentifier = Long
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/enclave/SgxException.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/enclave/SgxException.kt
new file mode 100644
index 0000000000..ef03af6feb
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/enclave/SgxException.kt
@@ -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})"
+ }
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/enclave/SgxStatus.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/enclave/SgxStatus.kt
new file mode 100644
index 0000000000..8c04266397
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/enclave/SgxStatus.kt
@@ -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)"),
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/sealing/SealedSecret.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/sealing/SealedSecret.kt
new file mode 100644
index 0000000000..bd28cc2289
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/sealing/SealedSecret.kt
@@ -0,0 +1,6 @@
+package net.corda.sgx.sealing
+
+/**
+ * Representation of a sealed secret.
+ */
+typealias SealedSecret = ByteArray
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/sealing/SealingException.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/sealing/SealingException.kt
new file mode 100644
index 0000000000..e64fa4590d
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/sealing/SealingException.kt
@@ -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)
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/sealing/SealingResult.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/sealing/SealingResult.kt
new file mode 100644
index 0000000000..d5213e023b
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/sealing/SealingResult.kt
@@ -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."),
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/sealing/Secret.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/sealing/Secret.kt
new file mode 100644
index 0000000000..a0a1195c43
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/sealing/Secret.kt
@@ -0,0 +1,6 @@
+package net.corda.sgx.sealing
+
+/**
+ * Representation of a provisioned secret.
+ */
+typealias ProvisionedSecret = ByteArray
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/sealing/SecretManager.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/sealing/SecretManager.kt
new file mode 100644
index 0000000000..8048535889
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/sealing/SecretManager.kt
@@ -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)
+ }
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/system/ExtendedGroupIdentifier.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/system/ExtendedGroupIdentifier.kt
new file mode 100644
index 0000000000..d036995889
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/system/ExtendedGroupIdentifier.kt
@@ -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),
+
+}
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/system/GroupIdentifier.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/system/GroupIdentifier.kt
new file mode 100644
index 0000000000..7d35c8d2aa
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/system/GroupIdentifier.kt
@@ -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')
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/system/SgxDeviceStatus.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/system/SgxDeviceStatus.kt
new file mode 100644
index 0000000000..6d7f8fe608
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/system/SgxDeviceStatus.kt
@@ -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"),
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/system/SgxSystem.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/system/SgxSystem.kt
new file mode 100644
index 0000000000..262d551fd5
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/system/SgxSystem.kt
@@ -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().first { it.code == code }
+
+ /**
+ * Get [SgxStatus] from numeric code.
+ */
+ fun statusFromCode(code: Long): SgxStatus =
+ enumValues().first { it.code == code }
+
+ /**
+ * Get [ExtendedGroupIdentifier] from a numeric identifier.
+ */
+ fun extendedGroupIdentifier(id: Int): ExtendedGroupIdentifier? =
+ enumValues().
+ firstOrNull { it.value == id }
+
+ /**
+ * Get [QuoteStatus] from string.
+ */
+ fun quoteStatusFromString(
+ code: String
+ ): QuoteStatus {
+ return enumValues()
+ .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)
+ }
+ }
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/system/SgxUnavailableException.kt b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/system/SgxUnavailableException.kt
new file mode 100644
index 0000000000..fea9b82279
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/kotlin/net/corda/sgx/system/SgxUnavailableException.kt
@@ -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)
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/host/src/main/resources/log4j2.xml b/sgx-jvm/remote-attestation/host/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000..431d635d46
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/main/resources/log4j2.xml
@@ -0,0 +1,50 @@
+
+
+
+
+ ${sys:attestation.home}
+ attestation-host
+ ${sys:log-path}/archive
+ info
+ debug
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/bridge/EnclaveConfiguration.kt b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/bridge/EnclaveConfiguration.kt
new file mode 100644
index 0000000000..62c87c2e86
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/bridge/EnclaveConfiguration.kt
@@ -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"
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/bridge/attestation/NativeAttestationEnclaveTests.kt b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/bridge/attestation/NativeAttestationEnclaveTests.kt
new file mode 100644
index 0000000000..73f9bc405d
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/bridge/attestation/NativeAttestationEnclaveTests.kt
@@ -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()
+ }
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/bridge/enclave/NativeEnclaveTests.kt b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/bridge/enclave/NativeEnclaveTests.kt
new file mode 100644
index 0000000000..9a8bab83ea
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/bridge/enclave/NativeEnclaveTests.kt
@@ -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
+ }
+
+}
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/bridge/system/NativeSgxSystemTests.kt b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/bridge/system/NativeSgxSystemTests.kt
new file mode 100644
index 0000000000..2b26b3dda2
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/bridge/system/NativeSgxSystemTests.kt
@@ -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)
+ }
+
+}
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/enclave/ECKeyTests.kt b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/enclave/ECKeyTests.kt
new file mode 100644
index 0000000000..5cbb26eab1
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/enclave/ECKeyTests.kt
@@ -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)
+ }
+
+}
\ No newline at end of file
diff --git a/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/enclave/SgxStatusTests.kt b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/enclave/SgxStatusTests.kt
new file mode 100644
index 0000000000..5a5268866b
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/enclave/SgxStatusTests.kt
@@ -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) }
+ }
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/system/ExtendedGroupIdentifierTests.kt b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/system/ExtendedGroupIdentifierTests.kt
new file mode 100644
index 0000000000..246452520d
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/system/ExtendedGroupIdentifierTests.kt
@@ -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))
+ }
+
+}
diff --git a/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/system/SgxDeviceStatusTests.kt b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/system/SgxDeviceStatusTests.kt
new file mode 100644
index 0000000000..8e8a8a4f66
--- /dev/null
+++ b/sgx-jvm/remote-attestation/host/src/test/kotlin/net/corda/sgx/system/SgxDeviceStatusTests.kt
@@ -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) }
+ }
+
+}
diff --git a/sgx-jvm/remote-attestation/settings.gradle b/sgx-jvm/remote-attestation/settings.gradle
index 45be9e2c58..ebd2861e7a 100644
--- a/sgx-jvm/remote-attestation/settings.gradle
+++ b/sgx-jvm/remote-attestation/settings.gradle
@@ -1,2 +1,3 @@
rootProject.name = 'remote-attestation'
include 'attestation-server'
+include 'host'