Section 2: OTP
We address the issue of how to authorize the execution of confidential applications by a user: a user triggers the execution of a confidential program. How can we ensure that the user is authorized to do so?
Of course, one uses standard mechanisms to authenticate users (e.g., SSO) and authorize the start of a confidential application. Adversaries with root access might impersonate users and circumvent this standard authentication and authorization. We want to prevent an adversary with root access from starting a confidential application. We give some motivation why this might be required below.
We use SCONE policies to require additional authorization. In our reference solution of this section, we assume that we can set up the policies using a trusted computer. For example, we use a newly installed, on-premise computer that is only used to create or update policies. If one uses the computer for other tasks, one might want to run this code in an enclave. We come back to this in a later section.
Background Info
This section is a deep dive to learn
- how to authorize application starts,
- how to automate confidential workflows, and
- how to solve specific technical issues that appear in the automation of workflows.
We also provide you with a reference solution. If you get stuck or run out of time, maybe, look at the reference solution to get some inspiration. Also, if you have some solution / adding, send us a pull request.
To simplify the execution of our solution inside enclaves and to use some more modern scripting (i.e., which means we exclude bash
), we use 'rust-script' in this section for our reference solution. One can convert this code to run as compiled program.
If you do not want to automate the solution, you might simply use the Scone CLI to solve the assignments. For learning how to automate confidential workflows, it should be sufficient to study the rust scripts
. These scripts use Scone-related Rust
crates. For a deep dive, you could study how these crates are implemented.
Motivation
We focus on using OTPs (One Time Passwords) to authorize the start of confidential applications. You can look at an OTP demo for an example of how to use OTPs in the context of SCONE.
To motivate the use of OTPs, let us look at a use case. We want to sign a container image with the help of private/public key pair. To protect the private key pair, we want to generate the key pair inside an enclave and keep the private key inside the enclaves.
The first approach would be to run a confidential signer program, which signs container images inside an enclave and always encrypts the private key. SCONE CAS generates the key pair. Its SCONE CAS policy grants the signer program access to the key pair: After a successful attestation, the signer can read the private key from an injected file.
Starting the confidential signer CLI program is limited to certain users. Note that an adversary with root access can impersonate any user. For example, sometimes using the su
command is sufficient. Therefore, an adversary might use signer to sign any image. Therefore, there is no need to know the private key.
We cannot only rely on the operating system's access control mechanisms to restrict access to authorized users. To limit the use of signer by adversaries, we can use OTPs. Each authorized user has access to authenticators. An adversary could log the OTPs but cannot use the OTPs since it is for single use only. To generate the OTP, an authorized user can use a vanilla authenticator.
In this section, we will focus on authenticating users with the help of OTPs. You will see that we assume to have a trusted computer to bootstrap. For example, a laptop that you only use for performing critical tasks. If you worry about this assumption, we will generalize this solution in later sections. Later, we will address the following aspects:
- How to use this approach to protect a signer program:
- we show how to protect an existing program and convert it into a confidential program,
- that protects its keys, and
-
that provides access control via OTPs.
-
How to protect the
SCONE CLI
: - access control: we will show how to add OTPs to access the SCONE CLI, and
-
confidentiality of keys: to run the SCONE CLI inside of enclaves to protect the CLI private key,
-
How to add a Role-Based Access Control (RBAC) using OTPs:
-
RBAC can be independent of the operating system and entirely under the control of an application owner,
-
How to run simple scripts inside of enclaves:
- only needing to provide a single OTP to start the script
- protecting the secrets processed by the script
Things to learn in this section
In the different assignments of this section, you will also learn how to solve some generic issues:
-
How to execute a command at most one time. This can easily be extended to n times and reused for other programs.
-
How to create a volume that is transparently encrypted. The integrity, confidentiality, and consistency of the files stored in this volume are protected. In this example, the focus is on ensuring consistency, i.e., we always read the latest data that was written.
-
How only an authorized and authenticated user can run a given program. Using an authenticator, a user can authenticate themself with an OTP.
You will learn more specific things related to OTPs:
-
How to add a new authenticator. If a user wants to move to a new authenticator, the user needs to get access to the QR code for the new authenticator - using an OTP from an existing authenticator.
-
rolling forward. Sometimes, users might lose access to their authenticators. For example, a user's smartphone has been lost or stolen. If the smartphone has a hardware failure, we might have set up a backup authenticator. However, if the smartphone is stolen, a user wants to register a new secret. Hence, we need to generate a new QR code to initialize a new authenticator. We need to authorize the user without having access to the old authenticator. We achieve this by falling back to the SCONE CLI.
Assignments
We split this section into a sequence of assignments.
Assignment 1: Confidential QR code generator program
We use Time-based One Time Passwords (TOTP), which means that the one uses a secret and the current time to generate One Time Passwords (OTP). RFC6238 introduces TOTP.
TOTP passwords could be stolen as regular passwords. They are, however, only valid for at most 30 seconds. We will learn that SCONE CAS accepts an OTP only once. This means that after a user has used an OTP, neither the user nor an adversary can use this OTP anymore. Therefore, a user would get an error message that this OTP was already used if the adversary is faster than the user of sending the OTP.
An adversary could trick an authorized user into revealing a valid OTP using some phishing attack. We, therefore, recommend that one does not use a single secret (a.k.a. account) to authenticate a user. Instead, we recommend that a user allocates an account for each role or task of the user. Each account has a unique secret and hence, produces a unique sequence of OTPs.
In a nutshell, a user should only type in an OTP when wanting to perform a specific task. An adversary cannot use this OTP to perform a different task - since each task requires different OTPs. In practice, one still needs to balance the number of distinct OTP accounts a user needs to manage.
Problem description
Typically, one uses an authenticator program like FreeOTP
or Microsoft Authenticator
running on a smartphone. The authenticator needs to learn a secret to generate valid TOTPs. The standard way is to scan a QR code.
The first assignment is to generate a QR code for a given OTP secret. Our objective is to create a confidential program, otpqr
, that gets the secret via an environment variable and then writes a file that contains the QR code as a SVG. To display the QR code, one can use an SVG viewer.
Output
We assume that a user has access to a computer that can - at least temporarily - be trusted. Still, our objective is that the OTP secret nor the SVG is visible in the terminal log. Instead, it should be written to an output file.
To ensure that the computer is trusted, the user might use a dedicated computer, check that no other users are logged in, no other programs are running, perform a secure and measured boot, etc.
After scanning the QR code with their authenticator, the user deletes the file using some secure file delete function like shred
:
shred -n 3 -z -u qrcode.svg
Rust Support
Create a program - actually, a command-line utility - that generates the QR code for a given secret. In this step, we do native programming. In assignment two, we transform this native program into a confidential program that executes in an enclave.
Use your favorite programming language to build this program. Therefore, in our reference solution, we write this program in Rust
. Rust provides an external crate google_authenticator
, which permits generating SVGs - or URLs - to add accounts to an authenticator.
You can pass the required parameters like the secret and the account name via command-line arguments or environment variables. In our reference solution, we pass everything via environment variables.
Single Execution Only
We want to ensure that one can generate the SVG with the QR code only once. After the first time one generates the QR code, one cannot just execute the program otpqr
a second time to regenerate the QR code again.
To do so, otpqr
could first generate a file with a given path, say, single_run/once
. If the file already exists, otpqr
exits with an error since otpqr
had already generated the QR code.
If the file single_run/once
has not yet existed, otpqr
generates the QR code and stores it in the output file. An adversary with root access could delete this file. However, when one uses the SCONE file shield, the runtime of otpqr
would detect that file single_run/once
was removed!
When running otpqr
inside an enclave, otpqr
might typically abort during attestation. For some more sophisticated attacks that remove the file after otpqr
has already started, the runtime will detect this when creating the file single_run/once
. In this case, the program aborts.
To enforce this consistency of the files, i.e., one reads the latest version that was written, we need to enable the SCONE file shield for file single_run/once
. We address this in the following two assignments:
- Assignment 2: we need to run this program inside of an enclave, and
- Assignment 3: we need to add a policy.
Assignment 2: Build a container image
To execute Rust programs inside of enclaves, SCONE provides a cross-compiler for Rust. With the help of this image, you can build your program with scone cargo
.
Build a container image containing the cross-compiled program you created in task 1. Use a multistage build to generate a minimal image:
- in the first stage, you use the SCONE Rust cross-compiler image (
) to build your application with cargo build --release
. If you use a different programming language, you might need another container image.
in the second stage, you use the binary created in the first stage and copy it to a smaller container image.
- In the case of
Rust
, we create a statically linked binary which does not have any external dependencies. Hence, it is OK to execute in an Alpine-based container despite being built using an Ubuntu-based image.
Provide a simple script, say, build_image.sh
that builds the container image with the help of a Dockerfile.
Assignment 3: Generate a policy
Now that we have a container image to deploy a confidential program, the next assignment is to create a SCONE policy (a.k.a., session) for SCONE to be able to attest otpqr
.
To attest otpqr
, you need to specify an attestation section that defines how to attest and verify this service. Typically, we use templates for this. In the reference solution, we use handlebars that might look like this:
services:
- name: otpqr
attestation:
- mrenclave:
- {{mrenclave}}
The attestation of a service can involve multiple factors. Measurement-based attestation uses MrEnclave
, which is the expected initial state of the enclave. One can also verify that the enclave was signed by a certain MrSigner
.
In this assignment, we want to use measurement-based attestation. Therefore, we need to determine MrEnclave
of otpqr
. One can achieve this by setting environment variable SCONE_HASH to 1 before executing otpqr
. The output might look like this:
bb79c2851d6f205694314bc03f91e0acba4fd39ba24aaea7b34fa9a78fe6c531
Transparent Encryption of a Volume
To ensure one can execute the QR code generator only once, otpqr
creates a file /root/single_run/once
. While this file does not contain any secrets, we need to ensure consistency, i.e., if this file was created and then deleted by an adversary, we will detect this.
To ensure that /root/single_run/once
is persistent, one typically would ensure that this file is stored in some persistent Docker or Kubernetes volume. We activate the SCONE file system shield as follows. We define a volume as part of the SCONE policy:
volumes:
- name: single_run
export:
- session: {{session2}}
This encrypted volume can be shared with other policies by exporting this volume to other policies. In this example, the volume is shared with the policy referred to by {{session2}}
.
This volume can be mapped at a given path:
images:
- name: otpqr_image
volumes:
- name: single_run
path: /root/single_run
Each service is associated with an image. In this case, we associate otpqr
with image otpqr_image
as follows:
services:
- name: otpqr
image_name: otpqr_image
As seen in the last section, we can create and update policies using the SCONE CLI. As part of our reference solution, we provide a Rust crate scone_cli
to create and update policies.
Assignment 4: Scripting
Write a script - in your favorite scripting language - with commands:
create
: to create a policy foroptqr
and upload this policy toscone-cas.cf
,gen_qr_code
to generate a QR code. This can only be executed once after a create or roll-back - even if you delete the file/root/single_run/once
.
Add an account to your favorite authenticator using the emitting QR code.
The reference implementation uses a rust-script
and the above-mentioned scone-cli
crate to execute these steps.
Assignment 5: Add a new authenticator
Extend your script from assignment 4 by adding a new command:
add-authenticator
: adds a new authenticator, i.e., issues a new QR code. This command asks you for the current OTP, and only if a valid OTP is provided a QR code is generated.
To do so, we add a new option to otpqr
that drops the check if the QR code was already generated. To do so, define an argument or an environment variable that indicates to otpqr
that it does not need to check the file /root/single_run/once
.
In our reference solution, we define an environment variable OTP_RESET
, which is maybe a misnomer. If this is set, otpqr
does not check the /root/single_run/once
during this execution.
You need to define a policy such that otpqr
with this option OTP_RESET
is only defined if the correct OTP was supplied. Note that the OTP can be checked by SCONE during attestation (see 2FA with TOTP).
You need to enable this in a policy's security
section. You can define this as follows:
security:
attestation:
one_time_password_shared_secret: {{secret}}
The script itself defines the secret. In the future, we will extend the session language such that s
- to permit a shared secret per service, and
- to use secrets generated by the CAS in the secret section.
Currently, all services in a policy will need to provide a valid OTP during startup, command add-authenticator
cannot use the same policy as gen_qr_code
(which cannot require an OTP since no shared secret does exist yet).
One appends the OTP to the SCONE_CONFIG_ID
, i.e., to the name of the session and the service name. For example, add-authenticator
might execute otpqr
inside of a container as follows.
docker run -it --rm -w "/root" -v "$PWD:/root" -e SCONE_CAS_ADDR=scone-cas.cf -e SCONE_CONFIG_ID={{session2}}/otpqr@703357 otpqr:scone /bin/otpqr
If one uses the wrong OTP or it takes too long for otpqr
- which runs in a docker container - to startup, you will get an error code like this:
[SCONE|FATAL] src/process/init.c:476:__scone_prepare_secure_config(): Could not initialize enclave state: Attestation failed
Caused by: CAS sent an attestation/configuration error: Failed to retrieve service attestation configuration
Caused by: The given One-time Password is invalid. Please try again.
Note: Connecting to CAS at scone-cas.cf (port 18765) using service ID mjSKNz1VaDYFFHNJnBmP5/otpqr-reset/otpqr@703357
Security
So why does this approach prevent an adversary from getting the QR code using add-authenticator
?
-
First, the adversary runs
otpqr
in the session you defined foradd-authenticator
. This means thatotpqr
will only startup if a valid and unique OTP is provided. If the adversary already knows how to generate valid OTPs, there would be no need to calladd-authenticator
. -
Second, the adversary might execute
gen_qr_code
and tries to set the argument/environment variable (OTP_RESET
) to ignore file/root/single_run/once
. Note that arguments are passed from the policy tootpqr
only. Any arguments/environment variables passed by the adversary are ignored. Since we do not setOTP_RESET
in the policy forgen_qr_code
, an adversary cannot get to the QR code in this way. -
Third, the adversary might try to run
otpqr
in the context of a different policy. In this policy, the adversary might setOTP_RESET
. However, the adversary would need to know the OTP secret to generate the correct QR code. Again, the objective of the adversary is to learn this secret.
Note that we assumed that we execute the script on a trusted host. In case we want to relax this assumption, i.e., run the script on an untrusted host, there are a few exciting security issues that we address in later sections of this tutorial:
- the state of the script is stored in plain text,
- the script is not confidential, i.e., an adversary could steal secrets during the execution,
- the scone CLI is not sufficiently protected against unauthorized use, and it does not protect its secrets.
To solve these open issues, one runs the script and the Scone CLI inside the enclaves. A more difficult problem is establishing trust in our script when running on a host that we do not trust.
Assignment 6: Roll Forward
The task of this assignment is to implement a roll-forward
command. It replaces the OTP secret with a new one. After executing this command, one needs to update, i.e., remove and then add the OTP accounts previously added with the QR code generated by 'gen-qr-code' or 'add-authenticator'.
Command roll-forward
addresses two issues. First, let us consider that an authenticator was lost or is inaccessible. We might not have set up a spare authenticator. Therefore, we cannot use add-authenticator
to add a new authenticator. We need a method
- to invalidate the existing authenticators,
- to create a new OTP secret, and
- add new authenticators.
Second, let us consider that somebody erased the files in the volume /root/single_run/once
. In this case, otpqr
will fail during startup since the volume is inconsistent. We want to be able to recover from this.
This assignment assumes that access to the Scone CLI is protected by only being available on a trusted host. In this case, we update the policy to
- define a new OTP secret, and
- we replace the volume with a new volume that is not yet initialized
Note that one could also add an option that creates a new volume without creating a new OTP secret. In this case, one might protect this by requiring an OTP to replace the volume.
To replace a volume with a new one, we can use handlebars in the policy templates:
volumes:
- name: single_run_{{volume_version}}
export:
- session: {{session2}}
images:
- name: otpqr_image
volumes:
- name: single_run_{{volume_version}}
path: /root/single_run
Solution
Note the reference solution is available at https://github.com/scontain/Exercise/tree/main/Section_OTP.