Skip to content
Open
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
53 changes: 37 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 5 additions & 23 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = [".", "dummy-attestation-server"]
members = [".", "dummy-attestation-server", "attested-tls"]

[package]
name = "attested-tls-proxy"
Expand All @@ -11,47 +11,31 @@ repository = "https://github.com/flashbots/attested-tls-proxy"
keywords = ["attested-TLS", "CVM", "TDX"]

[dependencies]
attested-tls = { path = "attested-tls" }
tokio = { version = "1.48.0", features = ["full"] }
tokio-rustls = { version = "0.26.4", default-features = false, features = [
"ring",
] }
sha2 = "0.10.9"
x509-parser = "0.18.0"
thiserror = "2.0.17"
clap = { version = "4.5.51", features = ["derive", "env"] }
webpki-roots = "1.0.4"
rustls-pemfile = "2.2.0"
anyhow = "1.0.100"
pem-rfc7468 = { version = "0.7.0", features = ["std"] }
configfs-tsm = "0.0.2"
rand_core = { version = "0.6.4", features = ["getrandom"] }
dcap-qvl = "0.3.4"
hex = "0.4.3"
hyper = { version = "1.7.0", features = ["server", "http2"] }
hyper-util = "0.1.17"
http-body-util = "0.1.3"
bytes = "1.10.1"
http = "1.3.1"
serde_json = "1.0.145"
serde = "1.0.228"
base64 = "0.22.1"
reqwest = { version = "0.12.23", default-features = false, features = [
"rustls-tls-webpki-roots-no-provider",
] }
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.20", features = ["env-filter", "json"] }
parity-scale-codec = "3.7.5"
openssl = "0.10.75"
az-tdx-vtpm = { version = "0.7.4", optional = true }
tss-esapi = { version = "7.6.0", optional = true }
num-bigint = "0.4.6"
webpki = { package = "rustls-webpki", version = "0.103.8" }
time = "0.3.44"
once_cell = "1.21.3"
axum = "0.8.6"
tower-http = { version = "0.6.7", features = ["fs"] }
tokio-tungstenite = { version = "0.28.0", optional = true }
futures-util = { version = "0.3.31", optional = true }

rsa = { version = "0.9", default-features = false }
p256 = { version = "0.13.2", features = ["pkcs8"] }
Expand All @@ -62,15 +46,13 @@ pkcs8 = "0.10.2"
rcgen = "0.14.5"
tempfile = "3.23.0"
tdx-quote = { version = "0.0.5", features = ["mock"] }
attested-tls = { path = "attested-tls", features = ["test-helpers", "mock"] }

[features]
default = ["azure", "ws"]
default = ["azure"]

# Adds support for Microsoft Azure attestation generation and verification
azure = ["tss-esapi", "az-tdx-vtpm"]

# Adds websocket support
ws = ["tokio-tungstenite", "futures-util"]
azure = ["attested-tls/azure"]

[package.metadata.deb]
maintainer = "Flashbots Team <devops+ci@flashbots.net>"
Expand Down
64 changes: 64 additions & 0 deletions attested-tls/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
[package]
name = "attested-tls"
version = "0.0.1"
edition = "2024"
license = "MIT"
description = "A remote-attested TLS protocol for secure communication with CVM services"
repository = "https://github.com/flashbots/attested-tls-proxy"
keywords = ["attested-TLS", "CVM", "TDX"]

[dependencies]
tokio = { version = "1.48.0", features = ["full"] }
tokio-rustls = { version = "0.26.4", default-features = false, features = [
"ring",
] }
sha2 = "0.10.9"
x509-parser = "0.18.0"
thiserror = "2.0.17"
webpki-roots = "1.0.4"
anyhow = "1.0.100"
pem-rfc7468 = { version = "0.7.0", features = ["std"] }
configfs-tsm = "0.0.2"
rand_core = { version = "0.6.4", features = ["getrandom"] }
dcap-qvl = "0.3.4"
hex = "0.4.3"
http = "1.3.1"
serde_json = "1.0.145"
serde = "1.0.228"
base64 = "0.22.1"
reqwest = { version = "0.12.23", default-features = false, features = [
"rustls-tls-webpki-roots-no-provider",
] }
tracing = "0.1.41"
parity-scale-codec = "3.7.5"
openssl = "0.10.75"
az-tdx-vtpm = { version = "0.7.4", optional = true }
tss-esapi = { version = "7.6.0", optional = true }
num-bigint = "0.4.6"
webpki = { package = "rustls-webpki", version = "0.103.8" }
time = "0.3.44"
once_cell = "1.21.3"
tokio-tungstenite = { version = "0.28.0", optional = true }
futures-util = { version = "0.3.31", optional = true }

rcgen = { version = "0.14.5", optional = true }
tdx-quote = { version = "0.0.5", features = ["mock"], optional = true }

[dev-dependencies]
rcgen = "0.14.5"
tempfile = "3.23.0"
tdx-quote = { version = "0.0.5", features = ["mock"] }

[features]
default = ["azure", "ws"]

# Adds support for Microsoft Azure attestation generation and verification
azure = ["tss-esapi", "az-tdx-vtpm"]

# Adds websocket support
ws = ["tokio-tungstenite", "futures-util"]

# Exposes helper functions for testing - do not enable in production as this allows dangerous configuration
test-helpers = ["rcgen"]

mock = ["tdx-quote"]
92 changes: 92 additions & 0 deletions attested-tls/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# attested-tls

### Measurements File

Accepted measurements for the remote party can be specified in a JSON file containing an array of objects, each of which specifies an accepted attestation type and set of measurements.

This aims to match the formatting used by `cvm-reverse-proxy`.

These objects have the following fields:
- `measurement_id` - a name used to describe the entry. For example the name and version of the CVM OS image that these measurements correspond to.
- `attestation_type` - a string containing one of the attestation types (confidential computing platforms) described below.
- `measurements` - an object with fields referring to the five measurement registers. Field names are the same as for the measurement headers (see below).

Example:

```JSON
[
{
"measurement_id": "dcap-tdx-example",
"attestation_type": "dcap-tdx",
"measurements": {
"0": {
"expected": "47a1cc074b914df8596bad0ed13d50d561ad1effc7f7cc530ab86da7ea49ffc03e57e7da829f8cba9c629c3970505323"
},
"1": {
"expected": "da6e07866635cb34a9ffcdc26ec6622f289e625c42c39b320f29cdf1dc84390b4f89dd0b073be52ac38ca7b0a0f375bb"
},
"2": {
"expected": "a7157e7c5f932e9babac9209d4527ec9ed837b8e335a931517677fa746db51ee56062e3324e266e3f39ec26a516f4f71"
},
"3": {
"expected": "e63560e50830e22fbc9b06cdce8afe784bf111e4251256cf104050f1347cd4ad9f30da408475066575145da0b098a124"
},
"4": {
"expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
}
}
]
```

The only mandatory field is `attestation_type`. If an attestation type is specified, but no measurements, *any* measurements will be accepted for this attestation type. The measurements can still be checked up-stream by the source client or target service using header injection described below. But it is then up to these external programs to reject unacceptable measurements.

If a measurements file is not provided, a single allowed attestation type **must** be specified using the `--allowed-remote-attestation-type` option. This may be `none` for cases where the remote party is not running in a CVM, but that must be explicitly specified.

### Attestation Types

These are the attestation type names used in the HTTP headers, and the measurements file, and when specifying a local attestation type with the `--client-attestation-type` or `--server-attestation-type` command line options.

- `auto` - detect attestation type (used only when specifying the local attestation type as a command-line argument)
- `none` - No attestation provided
- `dummy` - Forwards the attestation to a remote service (for testing purposes, not yet supported)
- `gcp-tdx` - DCAP TDX on Google Cloud Platform
- `azure-tdx` - TDX on Azure, with MAA (not yet supported)
- `qemu-tdx` - TDX on Qemu (no cloud platform)
- `dcap-tdx` - DCAP TDX (platform not specified)

## Protocol Specification

This is based on TLS 1.3.

The protocol name `flashbots-ratls/1` must be given in the TLS configuration for ALPN protocol negotiation during the TLS handshake. Future versions of this protocol will use incrementing version numbers, eg: `flashbots-ratls/2`.

### Attestation Exchange

Immediately after the TLS handshake, an attestation exchange is made. The server first provides an attestation message (even if it has the `none` attestation type). The client verifies, if verification is successful it also provides an attestation message and otherwise closes the connection. If the server cannot verify the client's attestation, it closes the connection.

Attestation exchange messages are formatted as follows:
- A 4 byte length prefix - a big endian encoded unsigned 32 bit integer
- A SCALE (Simple Concatenated Aggregate Little-Endian) encoded [struct](./src/attestation/mod.rs) with the following fields:
- `attestation_type` - a string with one of the attestation types (described above) including `none`.
- `attestation` - the actual attestation data. In the case of DCAP this is a binary quote report. In the case of `none` this is an empty byte array.

SCALE is used by parity/substrate and was chosen because it is simple and actually matches the formatting used in TDX quotes. So it was already used as a dependency (via the [`dcap-qvl`](https://docs.rs/dcap-qvl) crate).

### Attestation Generation and Verification

Attestation input takes the form of a 64 byte array.

The first 32 bytes are the SHA256 hash of the encoded public key from the TLS leaf certificate of the party providing the attestation, DER encoded exactly as given in the certificate.

The remaining 32 bytes are exported key material ([RFC5705](https://www.rfc-editor.org/rfc/rfc5705)) from the TLS session. This must have the exporter label `EXPORTER-Channel-Binding` and no context data.

In the case of attestation types `dcap-tdx`, `gcp-tdx`, and `qemu-tdx`, a standard DCAP attestation is generated using the `configfs-tsm` linux filesystem interface. This means that this binary must be run with access to `/sys/kernel/config/tsm/report` which on many systems requires sudo.

When verifying DCAP attestations, the Intel PCS is used to retrieve collateral unless a PCCS url is provided via a command line argument. If expired TCB collateral is provided, the quote will fail to verify.

## Dependencies and feature flags

The `azure` feature, for Microsoft Azure attestation requires [tpm2](https://tpm2-software.github.io) to be installed. On Debian-based systems this is provided by [`libtss2-dev`](https://packages.debian.org/trixie/libtss2-dev), and on nix `tpm2-tss`.

This feature is enabled by default. For non-azure deployments you can compile without this requirement by specifying `--no-default-features`. But note that this is will disable both generation and verification of azure attestations.
File renamed without changes.
12 changes: 6 additions & 6 deletions src/attestation/dcap.rs → attested-tls/src/attestation/dcap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub async fn create_dcap_attestation(input_data: [u8; 64]) -> Result<Vec<u8>, At
}

/// Verify a DCAP TDX quote, and return the measurement values
#[cfg(not(test))]
#[cfg(not(any(test, feature = "mock")))]
pub async fn verify_dcap_attestation(
input: Vec<u8>,
expected_input_data: [u8; 64],
Expand All @@ -32,7 +32,7 @@ pub async fn verify_dcap_attestation(
}

/// Allows the timestamp to be given, making it possible to test with existing attestations
async fn verify_dcap_attestation_with_given_timestamp(
pub async fn verify_dcap_attestation_with_given_timestamp(
input: Vec<u8>,
expected_input_data: [u8; 64],
pccs_url: Option<String>,
Expand Down Expand Up @@ -62,7 +62,7 @@ async fn verify_dcap_attestation_with_given_timestamp(
Ok(measurements)
}

#[cfg(test)]
#[cfg(any(test, feature = "mock"))]
pub async fn verify_dcap_attestation(
input: Vec<u8>,
expected_input_data: [u8; 64],
Expand All @@ -77,7 +77,7 @@ pub async fn verify_dcap_attestation(
}

/// Create a mock quote for testing on non-confidential hardware
#[cfg(test)]
#[cfg(any(test, feature = "mock"))]
fn generate_quote(input: [u8; 64]) -> Result<Vec<u8>, QuoteGenerationError> {
let attestation_key = tdx_quote::SigningKey::random(&mut rand_core::OsRng);
let provisioning_certification_key = tdx_quote::SigningKey::random(&mut rand_core::OsRng);
Expand All @@ -91,7 +91,7 @@ fn generate_quote(input: [u8; 64]) -> Result<Vec<u8>, QuoteGenerationError> {
}

/// Create a quote
#[cfg(not(test))]
#[cfg(not(any(test, feature = "mock")))]
fn generate_quote(input: [u8; 64]) -> Result<Vec<u8>, QuoteGenerationError> {
configfs_tsm::create_tdx_quote(input)
}
Expand All @@ -116,7 +116,7 @@ pub enum DcapVerificationError {
SystemTime(#[from] std::time::SystemTimeError),
#[error("DCAP quote verification: {0}")]
DcapQvl(#[from] anyhow::Error),
#[cfg(test)]
#[cfg(any(test, feature = "mock"))]
#[error("Quote parse: {0}")]
QuoteParse(#[from] tdx_quote::QuoteParseError),
}
Expand Down
Loading