Skip to content

Scone Signer Example

We show how to sconify a native, dynamically linked program into a confidential program. With term native we refer to the fact that the program was compiled to run in vanilla Linux environment. A confidential program is built to run in a trusted execution environment (TEE) like Intel SGX.

A confidential program can run in

  • debug mode: during development one can use debug mode to be able to attach with the help of a debugger to the program and inspect the content of the program while running inside a TEE
  • production mode: in production mode, the CPU prevents all access to the program's TEE.

In this section, we show how to transform an existing native program in a confidential program.

  • Given the native program, we transform (aka sconify) this program into a confidential application using binary transformation,
  • we generate a signer key (see identity.pem) to sign the enclave
  • we sign the program to be able to run in debug (alternatively, one could enable production) mode
  • We create a policy to attest the confidential program and provide the program with secrets:
  • in this example, we provide some environment variables and arguments only

Example Program

We create a simple, native C program.

cat > print-arg-env.c <<EOF
#include <stdio.h>

extern char **__environ;

int main (int argc, char **argv) {
    printf("argv:");
    for (int i = 0; i < argc; i++) {
        printf(" %s", argv[i]);
    }
    printf("\n");

    char** envp = __environ;
    printf("environ:\n");
    while (*envp != NULL) {
        printf("%s\n", *envp);
        envp++;
    }
    return 0;
}
EOF

We also create a new signer key for signing the confidential applications as follows:

openssl genrsa -3 -out "identity.pem" 3072

We create a policy template for debug mode as follows (see also Example].

cat > session.yaml <<EOF
name: \$SESSION
version: 0.3

security:
  attestation:
    tolerate: [debug-mode,hyperthreading, outdated-tcb]
    ignore_advisories: "*"
    one_time_password_shared_secret: \$OTPSECRET

volumes:
  - name: my_encrypted_volume

images:
  - name: my_image
    volumes:
      - name: my_encrypted_volume
        path: /volume

services:
   - name: print-arg-env
     image_name: my_image
     mrenclaves: [\$MRENCLAVE]
     command: ./print-arg-env need to pass your OTP
     environment:
        SCONE_MODE: hw
        env1: version
        env2: 3
        env3: otp-variant
     pwd: /
EOF

We create a script to upload this policy as part of a Dockerfile:

cat > upload_policy.sh <<EOF
#!/usr/bin/env bash

# 
export SCONE_HEAP=200M
export SCONE_STACK=1M
export SCONE_ALLOW_DLOPEN=0
export SCONE_MODE=hw
export SCONE_SYSLIBS=1
export SCONE_FSPF_MUTABLE=0
export SCONE_LOG=debug
export SCONE_MIN_HEAP=20M
export SCONE_TCS=8
export SCONE_MPROTECT=0
export SCONE_XFRM=3
export SCONE_ISVSVN=0
export SCONE_ISVPRODID=0
export SCONE_EXTENSIONS_PATH=""

export MRENCLAVE=\$(SCONE_HASH=1 print-arg-env)
echo MRENCLAVE=\$MRENCLAVE
envsubst < session.yaml > session2.yaml
export SCONE_CAS_ADDR=scone-cas.cf
scone cas attest \$SCONE_CAS_ADDR --only_for_testing-trust-any --only_for_testing-debug  --only_for_testing-ignore-signer -C -G -S
export PREDECESSOR2=\$(scone session create session2.yaml)
cat session2.yaml
EOF
chmod a+x upload_policy.sh

We create a random session name as follows:

export SESSION=sconified_program_$RANDOM_$RANDOM
echo Session=$SESSION

We protect the execution of this program with a OTP (One Time Password). We store the secret in environment variable OTP:

export OTPSECRET=... # base32 encoded without trailing =
export SCONE_LAS_ADDR=172.30.0.1 # define the address of the LAS

Let us build everything with the help of a multi-stage Dockerfile, i.e.,

  • the native program inside a container using a vanilla Alpine Linux container image
  • sconifying the program, i.e. performing a binary transformation of the program to run inside an enclave and signing the image
  • upload the policy for the image to a public CAS
cat > Dockerfile <<EOF
FROM registry.scontain.com/sconecuratedimages/crosscompilers:alpine3.15 as crosscompiler

FROM registry.scontain.com/sconecuratedimages/crosscompilers:alpine3.15 as signer

FROM alpine as builder

COPY --from=signer /usr/local/bin/scone-signer /bin/scone-signer
COPY --from=crosscompiler /opt/scone/lib /opt/scone/lib
COPY identity.pem /identity.pem
COPY print-arg-env.c /work/print-arg-env.c
COPY upload_policy.sh /work/upload_policy.sh

ENV SCONE_HEAP=200M
ENV SCONE_STACK=1M
ENV SCONE_ALLOW_DLOPEN=0
ENV SCONE_MODE=hw
ENV SCONE_SYSLIBS=1
ENV SCONE_VERSION=1
ENV SCONE_FSPF_MUTABLE=0
ENV SCONE_LOG=debug
ENV SCONE_MIN_HEAP=20M
ENV SCONE_TCS=8
ENV SCONE_MPROTECT=0
ENV SCONE_XFRM=3
ENV SCONE_ISVSVN=0
ENV SCONE_ISVPRODID=0
ENV SCONE_EXTENSIONS_PATH=""

RUN apk update && apk upgrade \
 && apk add build-base \
 && cd /work \
 && gcc print-arg-env.c -o print-arg-env \
 && scone-signer sign --sconify --verbose --key /identity.pem --env /work/print-arg-env \
 && scone-signer info /work/print-arg-env
# && scone-signer sign --sconify --production  --key /identity.pem --env /work/print-arg-env

FROM registry.scontain.com/sconecuratedimages/crosscompilers as policy_uploader
ARG OTPSECRET
ARG SESSION
ARG SCONE_CAS_ADDR

COPY --from=builder /work/print-arg-env /bin/print-arg-env
COPY upload_policy.sh /work/upload_policy.sh
COPY session.yaml /work/session.yaml

RUN cd /work && ./upload_policy.sh

FROM alpine as app
ARG SESSION
ARG SCONE_LAS_ADDR

COPY --from=builder /work/print-arg-env /bin/print-arg-env
COPY --from=crosscompiler /opt/scone/lib /opt/scone/lib

ENV SCONE_CONFIG_ID=\$SESSION/print-arg-env
ENV SCONE_CAS_ADDR=scone-cas.cf
ENV SCONE_LAS_ADDR=\$SCONE_LAS_ADDR

ENV SCONE_HEAP=200M
ENV SCONE_STACK=1M
ENV SCONE_ALLOW_DLOPEN=0
ENV SCONE_MODE=hw
ENV SCONE_SYSLIBS=1
ENV SCONE_VERSION=1
ENV SCONE_FSPF_MUTABLE=0
ENV SCONE_LOG=debug
ENV SCONE_MIN_HEAP=20M
ENV SCONE_TCS=8
ENV SCONE_MPROTECT=0
ENV SCONE_XFRM=3
ENV SCONE_ISVSVN=0
ENV SCONE_ISVPRODID=0
ENV SCONE_EXTENSIONS_PATH=""

CMD sh -c "SCONE_CONFIG_ID=\$SCONE_CONFIG_ID@\$OTP /bin/print-arg-env"
EOF

We can now build our image as follows

docker build --build-arg SESSION="$SESSION" --build-arg OTPSECRET="$OTPSECRET" --build-arg SCONE_LAS_ADDR="$SCONE_LAS_ADDR" -t sconify_example .

We determine which SGX device to mount with function determine_sgx_device.

determine_sgx_device

To run the generated image, we need to first set the correct OTP using an authenticator:

export OTP=...

and then run the container:

docker run $MOUNT_SGXDEVICE --network=host -it --rm -e OTP=$OTP sconify_example:latest