Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
784 changes: 455 additions & 329 deletions Cargo.lock

Large diffs are not rendered by default.

29 changes: 11 additions & 18 deletions Documentation/docs/developer/ATTESTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- [Host Proxy Diagram](#host-proxy-diagram)
- [Transport Methods](#transport-methods)
- [Try for yourself](#try-for-yourself)
- [Try with Trustee KBS](#try-with-trustee-kbs)
<!--toc:end-->

## Background
Expand Down Expand Up @@ -53,35 +54,22 @@ tuple [MAJOR, MINOR, PATCH]. The current version is 0.1.0.
- `tee`: The TEE hardware architecture that SVSM is running on.

The proxy will then complete the negotiation phase with the remote attestation
server and reply with a list of negotiation parameters that must be included in
the attestation evidence.
server and reply with a challenge nonce that must be included in the attestation evidence.

A `NegotiationResponse` is sent from the proxy to SVSM.
An example `NegotiationResponse` is shown below.
```json
{
"challenge": "oFlY92ZdS3ymzxokYuDzxw==\",
"params": [
"EcPublicKeyBytes",
"Challenge"
]

}
```
- `challenge`: The challenge nonce returned by the remote attestation server
that will likely need to be hashed into the attestation evidence to ensure
freshness. Represented as variably-sized array of bytes.
- `params`: The negotiation parameters. Each `NegotiationParam` represents some
form of data that must be hashed into the attestation evidence. This hash will
be reconstructed by the remote attestation server when the evidence is presented
from SVSM.

The valid negotiation parameters are as follows:
- `Challenge`: The bytes represented in the `challenge`.
- `EcPublicKeyBytes`: The byte buffers of the public key's x and y coordinates
(in that order).

SVSM can then collect the attestation evidence (with the negotiation parameters
embedded within the report data) and continue to the attestation phase.
aproxy builds the entire KBS runtime JSON object, hashes it on the host using Sha384,
and returns the hash as a single challenge for SVSM to put in its report.

### Attestation Phase

Expand Down Expand Up @@ -146,7 +134,7 @@ example).

Valid evidence formats are the following:
- `Snp`: SEV-SNP
- `report`: Attestation report bytes.
- `attestation_report`: Attestation report bytes.
- `certs_buf`: Optional byte buffer representing SEV-SNP certificate chain
(ARK, ASK, VEK) set by the host hypervisor.

Expand Down Expand Up @@ -387,3 +375,8 @@ SEV-SNP machine with an SVSM-enabled kernel.
cd kbs-test
cargo run -- --measurement 9ef6c500d19addcd5937c6c8bd4e51b1893f048eea03d5407cfb0692c06615e3f6c044c667c32e520913d93234e836fe
```

## Try with Trustee KBS

You can also try with the official Trustee KBS and SVSM setup. See [KBS-SVSM](KBS-SVSM.md)

201 changes: 201 additions & 0 deletions Documentation/docs/developer/KBS-SVSM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# Attestation in SVSM using Trustee KBS

We need,
1. SVSM (with attest feature),
> git clone https://github.com/coconut-svsm/svsm.git
2. The aproxy on the host.
3. Trustee KBS server (For now, the KBS softhsm container, is not upstream)
> git clone https://github.com/armenon-rh/trustee.git

This document explains the steps to perform a complete attestation.

# Prerequisites & Architectural Overview
The Key Broker Service (KBS) handles confidential computing attestation and secret provisioning. Depending on your security requirements, it can be deployed in one of the two modes:

## Mode A: Resource Key-Value Storage Backend (Default)
Act as a direct storage repository for secrets.
- Flow: SVSM <--> aproxy <--> KBS (Resource kvstorage backend)
- Concept: The user provisions a secret into the KBS storage via kbs-client. SVSM later presents its attestation token to retrieve that raw secret.

## Mode B: PKCS#11 Plugin Backend (softHSM)
Offloads cryptographic operations and key management to a Software HSM.
- Flow: SVSM <--> aproxy <--> KBS(PKCS#11 plugin) <--> softHSM
- Concept: The user generates a 32-byte symmetric key, which softHSM wraps (encrypts) using its internal public key. The user uses the raw key to encrypt vTPM state, but implants the wrapped key into the metadata header. During boot, SVSM hands the wrapped key to KBS, which requests softHSM to unwrap it, passing back the raw key only after attestation.

# Step by Step implementation

## 1. Build the image

An Ubuntu based KBS container image that also has softHSM and opensc (pkcs11-tool to interact with softHSM) packages installed.

```bash
cd trustee/
podman build -t kbs-service -f kbs/docker/kbs-softhsm-pkcs11/Dockerfile .
```

## 2. Initialize Host storage directories

Create the required host workspaces to persist softHSM tokens and KBS resource assets

```bash
mkdir -p $HOME/kbs-workspace/softhsm_tokens
mkdir -p $HOME/kbs-workspace/kbs-storage
```

## 3. Configure Attestation Policy
Create the repository directory and establish the Open Policy Agent (OPA) Rego policy file that dictates KBS access rules.

```bash
mkdir -p kbs-workspace/repository/kbs && cd kbs-workspace/repository/kbs

cat <<EOF > resource-policy.rego
package policy

default allow = false

allow if {
true
}
EOF

cd -
```

## 4. Run the KBS container
Choose the configuration profile that fits your desired operational mode:

### Option A: Run as Resource Backend (KV Storage)

```bash
podman run -d \
--name kbs-pkcs11-service \
-p 8080:8080 \
-v $HOME/kbs-workspace/repository/kbs/resource-policy.rego:/etc/kbs/repository/kbs/resource-policy.rego:Z \
-v $HOME/kbs-workspace/kbs-storage/:/opt/confidential-containers/storage:Z \
kbs-service:latest /usr/local/bin/kbs --config-file /etc/kbs/kbs-config-resource.toml
```

### Option B: Run as PKCS#11 Plugin Backend
```bash
podman run -d \
--name kbs-resource-service \
-p 8080:8080 \
-v $HOME/kbs-workspace/softhsm_tokens/:/var/lib/softhsm/tokens:Z \
-v $HOME/kbs-workspace/repository/kbs/resource-policy.rego:/etc/kbs/repository/kbs/resource-policy.rego:Z \
-v $HOME/test/trustee/kbs/docker/kbs-softhsm-pkcs11/config_pkcs11.toml:/etc/kbs/kbs-config.toml \
kbs-service:latest
```

## 5. Generate Cryptographic assets
```bash
openssl rand 32 > rand.bin
echo "My secret" > secret.txt
```
The secret will be stored in kbs resource backend, whereas if we want to use the pkcs11 plugin then the key will be wrapped.

## 6. Provision or Wrap the cryptographic assets

### Option A: Store Secret in Resource Backend

```bash
make -C kbs cli FEATURES
./target/release/kbs-client --url http://0.0.0.0:8080 config set-resource --path default/sample/test --resource-file secret.txt
```
The secret will be store in KBS.

### Option B: Wrap Key via PKCS#11 Plugin
```bash
curl -X POST http://0.0.0.0:8080/kbs/v0/pkcs11/wrap-key \
-H "Content-Type: application/octet-stream" \
--data-binary @rand.bin \
--output wrapped_rand.bin
```
wrapped_rand.bin will contain the wrapped key

## 7. Use [tpm provisioner](https://github.com/armenon-rh/tpm_provisioner) to create an NVChip file

```bash
cd ../
git clone https://github.com/armenon-rh/tpm_provisioner.git && cd tpm_provisioner

podman build -t tpm-provisioner -f Dockerfile .

podman run -it --name tpm-lab tpm-provisioner

cargo run && verify

# Generate the ek public key from tpm, store it in ek.pub --> 1
tpm2_createek -T mssim:host=localhost,port=2321 -c ek.ctx -u ek.pub -G rsa

# print the public part of it the endorsement key --> 2
tpm2_print -t TPM2B_PUBLIC ek.pub

# Retrieve the certificate from nvram, in DER format --> 3
tpm2_nvread -T mssim:host=localhost,port=2321 -C o 0x1c00002 -o ek_cert.der

# Extract public key from certificate, in PEM format --> 4
openssl x509 -in ek_cert.der -inform DER -pubkey -noout > cert_key.pem

# print the public key, modulus will be printed --> 5
openssl rsa -pubin -in cert_key.pem -text -noout

# modulus in 2 and 5 should match!

# copy the important files to host
# ek_cert.der, ek_cert.pem, local_ca.pem, cert_key.pem, NVChip
podman cp tpm-lab:/app/tpm_provisioner/<file_name> /path/on/host/<file_name>

```

## 8. Use [vtpm-to-svsm-blk](https://github.com/armenon-rh/vtpm-to-svsm-blk) to create a virtio block image
- use rand.bin from step 5 as key for encryption param
- use wrapped_rand.bin from step 6 as wrapped key param
vtpm_state.img is ready, which has payload encrypted and a header with the wrapped key.

```bash
git clone https://github.com/armenon-rh/vtpm-to-svsm-blk.git && cd vtpm-to-svsm-blk

cargo build

cargo run -- -k rand.bin -w wrapped_rand.bin -s /path/on/host/<NVChip> -o /path/on/host/to/store/vtpm_state.img

# This will create a vTPM image, that can be attached to SVSM as a virtio block

```

## 9. Run Aproxy on the host

```bash
cd svsm/
make aproxy
./bin/aproxy --protocol kbs \
--url http://0.0.0.0:8080 \
--unix /tmp/svsm-proxy.sock \
--force > aproxy.log 2>&1 &
```

## 10. Launch the confidential guest

Assuming that qemu is compiled using the [svsm](https://github.com/coconut-svsm/qemu.git) branch and SVSM is compiled using the latest [OVMF.FD file](https://github.com/coconut-svsm/edk2.git), and the fedora image is built using [image builder](https://github.com/stefano-garzarella/snp-svsm-vtpm/blob/main/build-vm-image.sh)

```bash
./scripts/launch_guest.sh --aproxy /tmp/svsm-proxy.sock \
--qemu $HOME/test/qemu/bin/qemu-system-x86_64 \
--image $HOME/test/snp-svsm-vtpm/images/fedora-luks.qcow2 \
--state ../vtpm_state.img
```

Note: Make sure to add the option --pcd PcdUninstallMemAttrProtocol=TRUE while building edk2 for Fedora 42 images

```bash
export PYTHON3_ENABLE=TRUE
export PYTHON_COMMAND=python3
make -j16 -C BaseTools/
source ./edksetup.sh --reconfig
build -p OvmfPkg/OvmfPkgX64.dsc -a X64 \
-b DEBUG -t GCC \
-D DEBUG_ON_SERIAL_PORT \
-D DEBUG_VERBOSE \
-D TPM2_ENABLE
--pcd PcdUninstallMemAttrProtocol=TRUE
```
70 changes: 35 additions & 35 deletions kernel/src/attest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ use cocoon_tpm_utils_common::{
io_slices::{self, IoSlicesIterCommon as _},
};
use kbs_types::Tee;
pub use libaproxy::SecretRequest;
use libaproxy::*;
use serde::Serialize;
use sha2::{Digest, Sha512};
use zerocopy::{FromBytes, IntoBytes};

// TODO: Make the IO port configurable/discoverable or drop the support entirely.
Expand Down Expand Up @@ -128,20 +128,39 @@ impl TryFrom<Tee> for AttestationDriver<'_> {
}

impl AttestationDriver<'_> {
/// Extracts the public key coordinates as a TPM ECC point from the internal `EccKey`.
fn get_tpm_pub_key(&self) -> Result<TpmsEccPoint<'static>, AttestationError> {
let curve =
Curve::new(self.ecc.pub_key().get_curve_id()).map_err(AttestationError::Crypto)?;

let curve_ops = curve.curve_ops().map_err(AttestationError::Crypto)?;

self.ecc
.pub_key()
.to_tpms_ecc_point(&curve_ops)
.map_err(AttestationError::Crypto)
}

/// Attest SVSM's launch state by communicating with the attestation proxy.
pub fn attest(&mut self) -> Result<SecretSlice, SvsmError> {
pub fn attest(
&mut self,
secret_request: Option<SecretRequest>,
) -> Result<SecretSlice, SvsmError> {
let negotiation = self.negotiation()?;

Ok(self.attestation(negotiation)?)
Ok(self.attestation(negotiation, secret_request)?)
}

/// Send a negotiation request to the proxy. Proxy should reply with Negotiation parameters
/// that should be included in attestation evidence (e.g. through SEV-SNP's REPORT_DATA
/// mechanism).
fn negotiation(&mut self) -> Result<NegotiationResponse, AttestationError> {
let pub_key = self.get_tpm_pub_key()?;

let request = NegotiationRequest {
version: (0, 1, 0), // Only version supported at present.
tee: self.tee,
key: (self.ecc.pub_key().get_curve_id(), &pub_key).into(),
};

self.write(request)?;
Expand All @@ -153,23 +172,21 @@ impl AttestationDriver<'_> {
/// Send an attestation request to the proxy. Proxy should reply with attestation response
/// containing the status (success/fail) and an optional secret returned from the server upon
/// successful attestation.
fn attestation(&mut self, n: NegotiationResponse) -> Result<SecretSlice, AttestationError> {
let curve =
Curve::new(self.ecc.pub_key().get_curve_id()).map_err(AttestationError::Crypto)?;
fn attestation(
&mut self,
n: NegotiationResponse,
secret_request: Option<SecretRequest>,
) -> Result<SecretSlice, AttestationError> {
let pub_key = self.get_tpm_pub_key()?;

let pub_key = self
.ecc
.pub_key()
.to_tpms_ecc_point(&curve.curve_ops().map_err(AttestationError::Crypto)?)
.map_err(AttestationError::Crypto)?;

let evidence = evidence(&self.tee, hash(&n, &pub_key)?)?;
let evidence = evidence(&self.tee, prepare_report_data(&n)?)?;

let req = AttestationRequest {
tee: self.tee,
evidence,
challenge: n.challenge.clone(),
key: (self.ecc.pub_key().get_curve_id(), &pub_key).into(),
secret_request,
};

self.write(req)?;
Expand Down Expand Up @@ -397,26 +414,9 @@ fn evidence(tee: &Tee, hash: Vec<u8>) -> Result<AttestationEvidence, Attestation
Ok(evidence)
}

/// Hash the negotiation parameters from the attestation server for inclusion in the
/// attestation evidence.
fn hash(
n: &NegotiationResponse,
pub_key: &TpmsEccPoint<'static>,
) -> Result<Vec<u8>, AttestationError> {
let mut sha = Sha512::new();

for p in &n.params {
match p {
NegotiationParam::Challenge => {
sha.update(&n.challenge);
}
#[allow(irrefutable_let_patterns)]
NegotiationParam::EcPublicKeyBytes => {
sha.update(&*pub_key.x.buffer);
sha.update(&*pub_key.y.buffer);
}
}
}

try_to_vec(&sha.finalize()).or(Err(AttestationError::VecAlloc))
/// Take 48 byte negotiation challenge nonce from aproxy into 64 byte array required for the TEE attestation evidence report
fn prepare_report_data(n: &NegotiationResponse) -> Result<Vec<u8>, AttestationError> {
let mut report_data = [0u8; 64];
report_data[..48].copy_from_slice(&n.challenge);
Ok(report_data.to_vec())
}
Loading
Loading