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