4.1. Tutorial 1: Hello, World!

This tutorial explains how to build and run a simple “hello world” enclave. An enclave consists of an eapp and a runtime, but also needs the host that initializes and launches the enclave. Thus, each enclave source tree contains at least the host and eapp.

Before jumping into the tutorial, please complete Quick Start.

4.1.1. Prerequisite

The Eyrie runtime allows the enclave to be statically linked with libc, and will then support a few standard functions such as printf. This is not a secure I/O interface, but is useful for demos and benchmarking.

Set PATH to include RISC-V tools and KEYSTONE_SDK_DIR to point the absolute path to sdk directory.

export PATH=$PATH:<path to RISC-V tools>
export KEYSTONE_SDK_DIR=<path to SDK>

Let’s take a look at the example provided in Keystone SDK.

ls sdk/examples/hello

You can find two directories and a build script called vault.sh

4.1.2. vault.sh

vault.sh is a sample script that builds the enclave. See full documentation at vault.sh.

sdk/examples/hello/vault.sh

To build the enclave application package, run:

./vault.sh

4.1.3. Enclave Application: hello.c

Open hello.c file in sdk/exmamples/hello/eapp/. This is the source code of the enclave application.

#include <stdio.h>

int main()
{
  printf("hello, world!\n");
  return 0;
}

This is the standard C program that we will run isolated in an enclave.

4.1.4. Host Application: host.cpp

Open host.cpp in sdk/examples/hello/host/. This is the source code of the host application.

#include "keystone.h"
#include "edge_call.h"
int main(int argc, char** argv)
{
  Keystone enclave;
  Params params;

  params.setFreeMemSize(1024*1024);
  params.setUntrustedMem(DEFAULT_UNTRUSTED_PTR, 1024*1024);

  enclave.init(argv[1], argv[2], params);

  enclave.registerOcallDispatch(incoming_call_dispatch);
  edge_call_init_internals((uintptr_t) enclave.getSharedBuffer(),
      enclave.getSharedBufferSize());

  enclave.run();

  return 0;
}

keystone.h contains Keystone class which has several member functions to control the enclave.

Following code initializes the enclave memory with the eapp/runtime.

Keystone enclave;
Params params;
enclave.init(<eapp binary>, <runtime binary>, params);

Params class is defined in sdk/lib/host/include/params.h, and contains enclave paraeters such as the size of free memory and the address/size of the untrusted shared buffer. These parameters can be configured by following lines:

params.setFreeMemSize(1024*1024);
params.setUntrustedMem(DEFAULT_UNTRUSTED_PTR, 1024*1024);

In order to handle the edge calls (including system calls), the enclave must register the edge call handler and initialize the buffer addresses. This is done as following:

enclave.registerOcallDispatch(incoming_call_dispatch);
edge_call_init_internals((uintptr_t) enclave.getSharedBuffer(),
  enclave.getSharedBufferSize());

Finally, the host launches the enclave by

enclave.run();

4.1.5. Enclave Package

vault.sh also contains packaging commands using makeself. makeself generates a self-extracting archive with a start-up command. All files included in $PACKAGE_FILES are copied into a directory and archived with makeself. The final output is hello.ke which is an executable file for our enclave.

Since we set $OUTPUT_DIR to buildroot overlay directory $KEYSTONE_SDK_DIR/../buildroot_overlay/root/$NAME, running make image in the top-level directory (keystone) will generate the buildroot disk image containing the outputs.

# go to top-level keystone directory
make image

4.1.6. Deploying Enclave

Boot the machine with QEMU.

./scripts/run-qemu.sh

Insert the Keystone driver

# [inside QEMU]
insmod keystone-driver.ko

Deploy the enclave

# [inside QEMU]
./hello/hello.ke

You’ll see the enclave running!

Verifying archive integrity... All good.
Uncompressing Keystone vault archive  100%
hello, world!