-
Notifications
You must be signed in to change notification settings - Fork 11
Write an enclave
- Complete the Running an enclave tutorial.
Use this tutorial to:
- Set up a new Conclave project using Conclave Init.
- Modify the files to create an implementation of the Conclave sample application which we explored in the previous tutorial.
The client is a command line app for simplicity. You can implement your client as a GUI app or integrate it with another program.
To set up a project:
- Download the Conclave Init tool.
wget https://repo1.maven.org/maven2/com/r3/conclave/conclave-init/1.3/conclave-init-1.3.jar -O conclave-init.jar
- Create a new Conclave project using Conclave Init. Note that you don't have to create a project directory beforehand.
java -jar conclave-init.jar \
--package com.example.tutorial \
--enclave-class-name ReverseEnclave \
--target <your project directory>
- Navigate to your project directory.
cd <your project directory>
- Modify the
enclave/build.gradle
file to specify the signing methods for each build type. You could keep your private key in a file for both debug and release enclaves. To hold your private keys in an offline system or HSM, configure theenclave/build.gradle
file as:
conclave {
productID = 1
revocationLevel = 0
// For simulation, we want to use the default signing type of dummyKey so
// we do not need to specify a configuration.
debug {
signingType = privateKey
signingKey = file("../signing/sample_private_key.pem")
}
release {
// To protect our release private key with an HSM, the enclave needs to be built in stages.
// Firstly, build the signing material:
// ./gradlew prepareForSigning -PenclaveMode="Release"
//
// Generate a signature from the signing material.
//
// Finally build the signed enclave:
// ./gradlew :host:bootJar -PenclaveMode="Release"
//
signingType = externalKey
signatureDate = new Date(1970, 0, 1)
mrsignerSignature = file("../signing/signature.bin")
mrsignerPublicKey = file("../signing/external_signing_public.pem")
}
}
The example configuration above specifies different signing configurations for each of the different build types.
- Simulation builds use the default dummy key.
- Debug builds use a private key stored in a file.
- Release builds use a private key managed by an external signing process.
- Create an RSA private key.
openssl genrsa -out my_private_key.pem -3 3072
You can learn more ways to generate signing keys here.
Create a new subclass of Enclave
Enclaves have an equivalent to a "main class", similar to standalone programs. This class must be a subclass of
Enclave
.
- Replace the contents of
/enclave/src/.../ReverseEnclave.java
with the following:
package com.example.tutorial.enclave;
import com.r3.conclave.enclave.Enclave;
/**
* Simply reverses the bytes that are passed in.
*/
public class ReverseEnclave extends Enclave {
private String reverse(String input) {
var builder = new StringBuilder(input.length());
for (var i = input.length() - 1; i >= 0; i--) {
builder.append(input.charAt(i));
}
return builder.toString();
}
@Override
protected void receiveMail(EnclaveMail mail, String routingHint) {
// First, decode mail body as a String.
var stringToReverse = new String(mail.getBodyAsBytes());
// Reverse it and re-encode to UTF-8 to send back.
var reversedEncodedString = reverse(stringToReverse).getBytes();
// Get the post office object for responding back to this mail and use it to encrypt our response.
var responseBytes = postOffice(mail).encryptMail(reversedEncodedString);
postMail(responseBytes, routingHint);
}
}
The enclave code has the following methods:
The first method in the sample enclave is the reverse
function, which reverses a string. When you write your own
apps, you can replace this method with the business logic to be run on a secure enclave.
The second method in the enclave implementation overrides the
receiveMail
method. Messages enter the
enclave through this method.
The host calls this method to send messages to the enclave and uses the Conclave Mail API for encryption and authentication of messages. The enclave in this tutorial uses the
postMail
method to reply to the host.
The host then sends the encrypted reply to the appropriate client. This tutorial handles transport using the built-in
conclave web host, which allows clients to send and receive Mail items via a simple REST API.
You can also use other methods of transport. See writing your own host for an example using plain TCP sockets.
The receiveMail
method in this example
works as follows:
- Extract the body bytes from the Mail object and interpret them as a string.
- Reverse the string using the
reverse
method. - Create a Mail object encrypted to the sender of the received Mail object, containing the reversed string.
- Use
postMail
to send the Mail item containing the reversed result back to the client.
Before a client can set up communication with an enclave, it must get its remote attestation object
EnclaveInstanceInfo
. Depending
on the host, a client can get the enclave's EnclaveInstanceInfo
using:
- A downloaded file.
- A REST endpoint, which is used in this tutorial.
The EnclaveInstanceInfo
has a
toString
function that will print out something like this:
Remote attestation for enclave F86798C4B12BE12073B87C3F57E66BCE7A541EE3D0DDA4FE8853471139C9393F:
- Mode: SIMULATION
- Code signer: 01280A6F7EAC8799C5CFDB1F11FF34BC9AE9A5BC7A7F7F54C77475F445897E3B
- Session signing key: 302A300506032B65700321000568034F335BE25386FD405A5997C25F49508AA173E0B413113F9A80C9BBF542
- Session encryption key: A0227D6D11078AAB73407D76DB9135C0D43A22BEACB0027D166937C18C5A7973
- Product ID: 1
- Revocation level: 0
Assessed security level at 2020-07-17T16:31:51.894697Z is INSECURE
- Enclave is running in simulation mode.
The hash in the first line is the measurement. This is a hash of the code of the enclave. It includes all
the Java code inside the enclave as a fat-JAR, and all the support and JVM runtime code. This hash changes any time
you alter the code of your enclave, the version of Conclave in use, or the mode (simulation/debug/release) of the
enclave. The clients can audit the enclave by comparing the value they get in the
EnclaveInstanceInfo
against what
the build process prints out.
!!!Note
You can get all this data using individual getters on the
[`EnclaveInstanceInfo`](api/-conclave%20-core/com.r3.conclave.common/-enclave-instance-info/index.html). So you
don't need to parse the output of `toString`.
An instance has a security assessment,
which changes when infrastructure vulnerabilities are discovered (even without any changes in the host or enclave).
This sample enclave isn't secure yet because it's running in simulation mode. An enclave can be SECURE
, STALE
,
or INSECURE
. An assessment of STALE
means a software/firmware/microcode update that improves security is
available. The client needs to define a time span by which the remote enclave operator must upgrade when the
security assessment turns STALE
.
An attestation doesn't expire. As the SGX ecosystem constantly changes, the client code needs to define a frequency
with which it expects the host code to refresh the
EnclaveInstanceInfo
. Currently,
the host code refreshes every time the enclave restarts.
Intel signs the remote attestation only in debug and release modes.
To run the host from the command line:
./gradlew host:bootJar
java -jar host/build/libs/host-mock.jar
Replace the client code at client/src/.../ReverseEnclaveClient.java
with:
package com.example.tutorial.client;
import com.r3.conclave.client.EnclaveClient;
import com.r3.conclave.client.web.WebEnclaveTransport;
import com.r3.conclave.common.EnclaveConstraint;
import com.r3.conclave.common.InvalidEnclaveException;
import com.r3.conclave.mail.EnclaveMail;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class ReverseEnclaveClient {
private static String DESCRIPTION = "Simple client that communicates with the ReverseEnclave using the web host.";
private static String USAGE_MESSAGE = "Usage: reverse-client ENCLAVE_CONSTRAINT STRING_TO_REVERSE\n" +
" ENCLAVE_CONSTRAINT: Enclave constraint which determines the enclave's identity and whether it's " +
"acceptable to use.\n" +
" STRING_TO_REVERSE: The string to send to the enclave to reverse.";
private static String REVERSE_HOST_URL = "http://localhost:8080";
public static void main(String... args) throws IOException, InvalidEnclaveException {
if (args.length != 2) {
System.out.println(DESCRIPTION);
System.out.println(USAGE_MESSAGE);
}
EnclaveConstraint constraint = EnclaveConstraint.parse(args[0]);
String stringToReverse = args[1];
callEnclave(constraint, stringToReverse);
}
public static void callEnclave(EnclaveConstraint constraint, String stringToReverse) throws IOException, InvalidEnclaveException {
try (WebEnclaveTransport transport = new WebEnclaveTransport(REVERSE_HOST_URL);
EnclaveClient client = new EnclaveClient(constraint)) {
client.start(transport);
}
}
}
To encrypt the response from the enclave to the client, the client needs a public key and a private key.
Conclave abstracts the complexity of dealing with private keys inside
EnclaveClient
.
The new EnclaveClient(constraint)
line in the above code also creates a new random Curve25519 private key.
To use an existing private key or to manually create a new random key, use the following code:
PrivateKey myKey = Curve25519PrivateKey.random();
EnclaveClient client = new EnclaveClient(myKey, constraint);
You can send Mail after connecting to the host web server and verifying the enclave. To send Mail, call
EnclaveClient.sendMail
and send the
serialized bytes of the request message. If the enclave responds back immediately with a Mail of its own, then that
is returned by sendMail
.
public static void callEnclave(EnclaveConstraint constraint, String stringToReverse) throws IOException, InvalidEnclaveException {
try (WebEnclaveTransport transport = new WebEnclaveTransport(REVERSE_HOST_URL);
EnclaveClient client = new EnclaveClient(constraint)) {
// Connect to the host and send the string to reverse
client.start(transport);
byte[] requestMailBody = stringToReverse.getBytes(StandardCharsets.UTF_8);
EnclaveMail responseMail = client.sendMail(requestMailBody);
// Parse and print out the response
String responseString = (responseMail != null) ? new String(responseMail.getBodyAsBytes()) : null;
System.out.println("Reversing `" + stringToReverse + "` gives `" + responseString + "`");
}
}
Conclave Mail represents the response from the enclave as an
EnclaveMail
object. The Mail body will contain
the encoded reversed string.
!!!Note
If you write your enclave such that it responds to the client later at some point, you can use the
[`pollMail`](api/-conclave%20-core/com.r3.conclave.client/-enclave-client/poll-mail.html) method to poll for
responses. It will return `null` if there aren't any.
- Open a command line window.
- Navigate to the project directory.
- Run the host:
java -jar host/build/libs/host-mock.jar
- Open another command line window.
- Navigate to the project directory.
- Build the client:
./gradlew :client:shadowJar
- Run the client:
java -jar client/build/libs/client.jar \
"S:0000000000000000000000000000000000000000000000000000000000000000 PROD:1 SEC:INSECURE" \
reverse-me
Please refer to Running your first enclave for expected output and other information on these commands.
There are two ways to test the enclave:
- Use a mock build of the enclave in tests defined as part of your enclave project.
- Integrate enclave tests in your host project.
Mock mode allows you to white box test your enclave by running it fully in-memory. As you don't need SGX hardware or a specific OS, it's an ideal method for cross-platform unit testing.
Conclave supports building and running tests within the enclave project itself. When you define tests as part of your enclave project, Conclave loads the enclave classes along with the tests. Conclave detects this configuration and automatically enables mock mode for the enclave and test host. You don't need to explicitly specify mock mode for your project.
To use this functionality, simply create an instance of the enclave as usual by calling
EnclaveHost.load
inside your test class.
EnclaveHost mockHost = EnclaveHost.load("com.r3.conclave.sample.enclave.ReverseEnclave");
mockHost.start(null, null);
Conclave detects the enclave class on the classpath and starts the enclave in mock mode. You
can obtain the enclave instance using the
EnclaveHost.mockEnclave
property,
like in this sample mock unit test.
ReverseEnclave reverseEnclave = (ReverseEnclave)mockHost.getMockEnclave();
To test your enclave on real SGX hardware or in a simulated SGX environment, you need to define your tests in a project separate from the enclave project. A suitable place for your tests is to define them as part of the host project tests.
Add the Conclave host as a testImplementation
dependency in the project where your tests are defined.
dependencies {
testImplementation "com.r3.conclave:conclave-host:$conclaveVersion"
}
To load and test the enclave on real hardware or in a simulated SGX environment, you need to load the enclave with
EnclaveHost.load
. By default, this runs the
tests in a simulated SGX environment and allows testing only within Linux. If you configure [EnclaveHost.load
] to
test on real hardware, you must test within Linux on an SGX-supported system.
@EnabledOnOs(OS.LINUX)
public class NativeTest {
private static EnclaveHost enclave;
@BeforeAll
static void startup() throws EnclaveLoadException {
enclave = EnclaveHost.load("com.r3.conclave.sample.enclave.ReverseEnclave");
enclave.start(new AttestationParameters.DCAP(), null, null);
}
}
The test class is annotated with @EnabledOnOs(OS.LINUX)
. This is from
JUnit 5 to ensure that the
native test is run only on a Linux environment.
Here is a sample native test for your reference.
To execute the test using a simulation enclave, run:
./gradlew host:test
To switch to a debug enclave and test on real secure hardware, use the -PenclaveMode
flag:
./gradlew -PenclaveMode=debug host:test
To use a mock enclave and test on a non-Linux platform, remove @EnabledOnOs(OS.LINUX)
and run:
./gradlew -PenclaveMode=mock host:test
Note
To run the tests in a simulated SGX environment on a non-Linux machine, you can use Docker, which manages Linux VMs for you. See the system requirements and instructions for compiling and running the host.