Skip to content

Commit 28665f2

Browse files
authored
Merge pull request #37 from flashbots/peg/readme-protocol-spec
Add protocol specification to README
2 parents cc8774f + 6bf1198 commit 28665f2

File tree

4 files changed

+131
-31
lines changed

4 files changed

+131
-31
lines changed

README.md

Lines changed: 112 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,75 @@
11

22
# `attested-tls-proxy`
33

4-
This is a work-in-progress crate designed to be an alternative to [`cvm-reverse-proxy`](https://github.com/flashbots/cvm-reverse-proxy).
5-
6-
It has three commands:
7-
- `server` - run a proxy server, which accepts TLS connections from a proxy client, sends an attestation and then forwards traffic to a target CVM service.
8-
- `client` - run a proxy client, which accepts connections from elsewhere, connects to and verifies the attestation from the proxy server, and then forwards traffic to it over TLS.
9-
- `get-tls-cert` - connects to a proxy-server, verify the attestation, and if successful write the server's PEM-encoded TLS certificate chain to standard out. This can be used to make subsequent connections to services using this certificate over regular TLS.
4+
This is a reverse HTTP proxy allowing a normal HTTP client to communicate with a normal HTTP server over a remote-attested TLS channel, by tunneling requests through a proxy-client and proxy-server.
105

6+
This work-in-progress crate is designed to be an alternative to [`cvm-reverse-proxy`](https://github.com/flashbots/cvm-reverse-proxy).
117
Unlike `cvm-reverse-proxy`, this uses post-handshake remote-attested TLS, meaning regular CA-signed TLS certificates can be used.
128

13-
This repo shares some code with [ameba23/attested-channels](https://github.com/ameba23/attested-channels) and may eventually be merged with that crate.
9+
The proxy-client, on starting, immediately connects to the proxy-server and an attestation-verification exchange is made. This attested-TLS channel is then re-used for all requests from that proxy-client instance.
10+
11+
It has three subcommands:
12+
- `attested-tls-proxy server` - run a proxy server, which accepts TLS connections from a proxy client, sends an attestation and then forwards traffic to a target CVM service.
13+
- `attested-tls-proxy client` - run a proxy client, which accepts connections from elsewhere, connects to and verifies the attestation from the proxy server, and then forwards traffic to it over TLS.
14+
- `attested-tls-proxy get-tls-cert` - connects to a proxy-server, verify the attestation, and if successful write the server's PEM-encoded TLS certificate chain to standard out. This can be used to make subsequent connections to services using this certificate over regular TLS.
15+
16+
### How it works
17+
18+
19+
This works as follows:
20+
1. The source HTTP client (eg: curl or a web browser) makes an HTTP request to a proxy-client instance running locally.
21+
2. The proxy-client forwards the request to a proxy-server instance over a remote-attested TLS channel.
22+
3. The proxy-server forwards the request to the target service over regular HTTP.
23+
4. The response from the target service is sent back to the source client, via the proxy-server and proxy-client.
24+
25+
One or both of the proxy-client and proxy-server may be running in a confidential environment and provide attestations which will be verified by the remote party. Verification is configured by a measurements file, and attestation generation is configured by specifying an attestation type when starting the proxy client or server.
26+
27+
### Measurements File
28+
29+
Accepted measurements for the remote party are specified in a JSON file containing an array of objects, each of which specifies an accepted attestation type and set of measurements.
30+
31+
This aims to match the formatting used by `cvm-reverse-proxy`.
1432

15-
## Measurement headers
33+
These object have the following fields:
34+
- `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.
35+
- `attestation_type` - a string containing one of the attestation types (confidential computing platforms) described below.
36+
- `measurements` - an object with fields referring to the five measurement registers. Field names are the same as for the measurement headers (see below).
1637

17-
When attestation is validated successfully, the following values are injected into the request / response headers:
38+
Example:
39+
40+
```JSON
41+
[
42+
{
43+
"measurement_id": "dcap-tdx-example",
44+
"attestation_type": "dcap-tdx",
45+
"measurements": {
46+
"0": {
47+
"expected": "47a1cc074b914df8596bad0ed13d50d561ad1effc7f7cc530ab86da7ea49ffc03e57e7da829f8cba9c629c3970505323"
48+
},
49+
"1": {
50+
"expected": "da6e07866635cb34a9ffcdc26ec6622f289e625c42c39b320f29cdf1dc84390b4f89dd0b073be52ac38ca7b0a0f375bb"
51+
},
52+
"2": {
53+
"expected": "a7157e7c5f932e9babac9209d4527ec9ed837b8e335a931517677fa746db51ee56062e3324e266e3f39ec26a516f4f71"
54+
},
55+
"3": {
56+
"expected": "e63560e50830e22fbc9b06cdce8afe784bf111e4251256cf104050f1347cd4ad9f30da408475066575145da0b098a124"
57+
},
58+
"4": {
59+
"expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
60+
}
61+
}
62+
}
63+
]
64+
```
65+
66+
If a path to this file is not given or it contains an empty array, **any** attestation type and **any** measurements will be accepted, **including no attestation**. 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 configurations.
67+
68+
### Measurement Headers
69+
70+
When attestation is validated successfully, the following headers are injected into the HTTP request / response making them available to the source client and/or target service.
71+
72+
These aim to match the header formatting used by `cvm-reverse-proxy`.
1873

1974
Header name: `X-Flashbots-Measurement`
2075

@@ -31,9 +86,54 @@ Header value:
3186

3287
Header name: `X-Flashbots-Attestation-Type`
3388

34-
Header value:
89+
Header value: an attestation type given as a string as described below.
90+
91+
### Attestation Types
3592

36-
One of `none`, `dummy`, `azure-tdx`, `qemu-tdx`, `gcp-tdx`.
93+
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.
3794

38-
These aim to match the header formatting used by `cvm-reverse-proxy`.
95+
- `none` - No attestation provided
96+
- `dummy` - Forwards the attestation to a remote service (for testing purposes, not yet supported)
97+
- `gcp-tdx` - DCAP TDX on Google Cloud Platform
98+
- `azure-tdx` - TDX on Azure, with MAA (not yet supported)
99+
- `qemu-tdx` - TDX on Qemu (no cloud platform)
100+
- `dcap-tdx` - DCAP TDX (platform not specified)
101+
102+
## Protocol Specification
103+
104+
A proxy-client client will immediately attempt to connect to the given proxy-server.
105+
106+
Proxy-client to proxy-server connections use TLS 1.3.
107+
108+
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`.
109+
110+
### Attestation Exchange
111+
112+
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.
113+
114+
Attestation exchange messages are formatted as follows:
115+
- A 4 byte length prefix - a big endian encoded unsigned 32 bit integer
116+
- A SCALE (Simple Concatenated Aggregate Little-Endian) encoded [struct](./src/attestation/mod.rs) with the following fields:
117+
- `attestation_type` - a string with one of the attestation types (described above) including `none`.
118+
- `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.
119+
120+
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).
121+
122+
### Attestation Generation and Verification
123+
124+
Attestation input takes the form of a 64 byte array.
125+
126+
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.
127+
128+
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.
129+
130+
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.
131+
132+
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.
133+
134+
### HTTP reverse proxy
135+
136+
Following a successful attestation exchange, the client can make HTTP requests using HTTP2, and the server will forward them to the target service.
137+
138+
As described above, the server will inject measurement data into the request headers before forwarding them to the target service, and the client will inject measurement data into the response headers before forwarding them to the source client.
39139

src/attestation/mod.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ const PCS_URL: &str = "https://api.trustedservices.intel.com";
2525

2626
/// This is the type sent over the channel to provide an attestation
2727
#[derive(Debug, Serialize, Deserialize, Encode, Decode)]
28-
pub struct AttesationPayload {
28+
pub struct AttestationExchangeMessage {
2929
/// What CVM platform is used (including none)
3030
pub attestation_type: AttestationType,
3131
/// The attestation evidence as bytes - in the case of DCAP this is a quote
3232
pub attestation: Vec<u8>,
3333
}
3434

35-
impl AttesationPayload {
35+
impl AttestationExchangeMessage {
3636
/// Given an attestation generator (quote generation function for a specific platform)
3737
/// return an attestation
3838
/// This also takes the certificate chain and exporter as they are given as input to the attestation
@@ -185,16 +185,16 @@ impl AttestationVerifier {
185185
/// Verify an attestation, and ensure the measurements match one of our accepted measurements
186186
pub async fn verify_attestation(
187187
&self,
188-
attestation_payload: AttesationPayload,
188+
attestation_exchange_message: AttestationExchangeMessage,
189189
cert_chain: &[CertificateDer<'_>],
190190
exporter: [u8; 32],
191191
) -> Result<Option<Measurements>, AttestationError> {
192-
let attestation_type = attestation_payload.attestation_type;
192+
let attestation_type = attestation_exchange_message.attestation_type;
193193

194194
let measurements = match attestation_type {
195195
AttestationType::DcapTdx => {
196196
verify_dcap_attestation(
197-
attestation_payload.attestation,
197+
attestation_exchange_message.attestation,
198198
cert_chain,
199199
exporter,
200200
self.pccs_url.clone(),
@@ -205,7 +205,7 @@ impl AttestationVerifier {
205205
if self.has_remote_attestion() {
206206
return Err(AttestationError::AttestationTypeNotAccepted);
207207
}
208-
if attestation_payload.attestation.is_empty() {
208+
if attestation_exchange_message.attestation.is_empty() {
209209
return Ok(None);
210210
} else {
211211
return Err(AttestationError::AttestationGivenWhenNoneExpected);

src/lib.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use tokio_rustls::{
2828
TlsAcceptor, TlsConnector,
2929
};
3030

31-
use crate::attestation::{AttesationPayload, AttestationVerifier};
31+
use crate::attestation::{AttestationExchangeMessage, AttestationVerifier};
3232

3333
/// This makes it possible to add breaking protocol changes and provide backwards compatibility.
3434
/// When adding more supported versions, note that ordering is important. ALPN will pick the first
@@ -204,7 +204,7 @@ impl ProxyServer {
204204
let remote_cert_chain = connection.peer_certificates().map(|c| c.to_owned());
205205

206206
// If we are in a CVM, generate an attestation
207-
let attestation = AttesationPayload::from_attestation_generator(
207+
let attestation = AttestationExchangeMessage::from_attestation_generator(
208208
&cert_chain,
209209
exporter,
210210
local_quote_generator,
@@ -225,14 +225,14 @@ impl ProxyServer {
225225
let mut buf = vec![0; length];
226226
tls_stream.read_exact(&mut buf).await?;
227227

228-
let remote_attestation_payload = AttesationPayload::decode(&mut &buf[..])?;
229-
let remote_attestation_type = remote_attestation_payload.attestation_type;
228+
let remote_attestation_message = AttestationExchangeMessage::decode(&mut &buf[..])?;
229+
let remote_attestation_type = remote_attestation_message.attestation_type;
230230

231231
// If we expect an attestaion from the client, verify it and get measurements
232232
let measurements = if attestation_verifier.has_remote_attestion() {
233233
attestation_verifier
234234
.verify_attestation(
235-
remote_attestation_payload,
235+
remote_attestation_message,
236236
&remote_cert_chain.ok_or(ProxyError::NoClientAuth)?,
237237
exporter,
238238
)
@@ -624,24 +624,24 @@ impl ProxyClient {
624624
let mut buf = vec![0; length];
625625
tls_stream.read_exact(&mut buf).await?;
626626

627-
let remote_attestation_payload = AttesationPayload::decode(&mut &buf[..])?;
628-
let remote_attestation_type = remote_attestation_payload.attestation_type;
627+
let remote_attestation_message = AttestationExchangeMessage::decode(&mut &buf[..])?;
628+
let remote_attestation_type = remote_attestation_message.attestation_type;
629629

630630
// Verify the remote attestation against our accepted measurements
631631
let measurements = attestation_verifier
632-
.verify_attestation(remote_attestation_payload, &remote_cert_chain, exporter)
632+
.verify_attestation(remote_attestation_message, &remote_cert_chain, exporter)
633633
.await?;
634634

635635
// If we are in a CVM, provide an attestation
636636
let attestation = if local_quote_generator.attestation_type() != AttestationType::None {
637-
AttesationPayload::from_attestation_generator(
637+
AttestationExchangeMessage::from_attestation_generator(
638638
&cert_chain.ok_or(ProxyError::NoClientAuth)?,
639639
exporter,
640640
local_quote_generator,
641641
)?
642642
.encode()
643643
} else {
644-
AttesationPayload::without_attestation().encode()
644+
AttestationExchangeMessage::without_attestation().encode()
645645
};
646646

647647
// Send our attestation (or zero bytes) prefixed with length
@@ -723,10 +723,10 @@ async fn get_tls_cert_with_config(
723723
let mut buf = vec![0; length];
724724
tls_stream.read_exact(&mut buf).await?;
725725

726-
let remote_attestation_payload = AttesationPayload::decode(&mut &buf[..])?;
726+
let remote_attestation_message = AttestationExchangeMessage::decode(&mut &buf[..])?;
727727

728728
let _measurements = attestation_verifier
729-
.verify_attestation(remote_attestation_payload, &remote_cert_chain, exporter)
729+
.verify_attestation(remote_attestation_message, &remote_cert_chain, exporter)
730730
.await?;
731731

732732
Ok(remote_cert_chain)

test-assets/measurements.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[
22
{
3-
"measurement_id": "dcap-tdx-dummy",
3+
"measurement_id": "dcap-tdx-example",
44
"attestation_type": "dcap-tdx",
55
"measurements": {
66
"0": {

0 commit comments

Comments
 (0)