Skip to content

Commit 5672cca

Browse files
fnerdmanfnerdmanFrieder Paape
authored
feat: adds dcap attestation type (#18)
* feat: adds dcap attestation type * chore: adds feature flag to write dcap quotes to disk * chore: adds security relevant validation logic to the DCAP attestation --------- Co-authored-by: fnerdman <frieder@flashbots.net> Co-authored-by: Frieder Paape <frieder@konvera.io>
1 parent 62a3843 commit 5672cca

File tree

8 files changed

+372
-2
lines changed

8 files changed

+372
-2
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/constellation/
22
/cvm-reverse-proxy
33
/measurements.json
4-
/build/
4+
/build/
5+
/quotes/

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Client
4646
- `--client-measurements`: optional path to JSON measurements enforced on the client
4747
- `--log-json`: log in JSON format (default: false)
4848
- `--log-debug`: log debug messages (default: false)
49+
- `--log-dcap-quote`: log dcap quotes to folder quotes/ (default: false)
4950
- `--help, -h`: show help
5051

5152

@@ -91,6 +92,7 @@ This repository contains a [dummy http server](./cmd/dummy-server/main.go) that
9192
- `--client-attestation-type`: type of attestation to present (none, azure-tdx) (default: "none")
9293
- `--log-json`: log in JSON format (default: false)
9394
- `--log-debug`: log debug messages (default: false)
95+
- `--log-dcap-quote`: log dcap quotes to folder quotes/ (default: false)
9496
- `--help, -h`: show help
9597

9698

@@ -122,6 +124,16 @@ The measurements are expected to be a JSON map, and multiple valid measurements
122124
The (single) validated measurement is json-marshalled and forwarded (returned in the case of client) as "X-Flashbots-Measurement" header, and the type of attestation as "X-Flashbots-Attestation-Type" header. For mapping attestation types to OIDs and issuers, see [internal/attestation/variant/variant.go](./internal/attestation/variant/variant.go).
123125
To only validate and forward the measurement (as opposed to also authorizing the measurement against an expected one), simply provide an empty expected measurements object.
124126

127+
### Debugging DCAP quote isses
128+
129+
If logging dcap quotes to disk is enabled, issues with the respective quotes can be investigated using [github.com/google/go-tdx-guest](https://github.com/google/go-tdx-guest)'s check tool
130+
```
131+
git clone https://github.com/google/go-tdx-guest
132+
cd go-tdx-guest
133+
go build tools/check/check.go
134+
./check -verbosity 2 -get_collateral true -in quotes/quote_received_20241010_121042.dat
135+
```
136+
125137
---
126138

127139
## Notes

cmd/proxy-client/main.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/flashbots/cvm-reverse-proxy/common"
1111
"github.com/flashbots/cvm-reverse-proxy/internal/atls"
1212
"github.com/flashbots/cvm-reverse-proxy/proxy"
13+
"github.com/flashbots/cvm-reverse-proxy/tdx"
1314
"github.com/urfave/cli/v2" // imports as package "cli"
1415
)
1516

@@ -57,6 +58,12 @@ var flags []cli.Flag = []cli.Flag{
5758
Value: true,
5859
Usage: "log debug messages",
5960
},
61+
&cli.BoolFlag{
62+
Name: "log-dcap-quote",
63+
EnvVars: []string{"LOG_DCAP_QUOTE"},
64+
Value: false,
65+
Usage: "log dcap quotes to folder quotes/",
66+
},
6067
}
6168

6269
func main() {
@@ -78,6 +85,7 @@ func runClient(cCtx *cli.Context) error {
7885
serverMeasurements := cCtx.String("server-measurements")
7986
logJSON := cCtx.Bool("log-json")
8087
logDebug := cCtx.Bool("log-debug")
88+
tdx.SetLogDcapQuote(cCtx.Bool("log-dcap-quote"))
8189

8290
verifyTLS := cCtx.Bool("verify-tls")
8391

cmd/proxy-server/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/flashbots/cvm-reverse-proxy/common"
1515
"github.com/flashbots/cvm-reverse-proxy/internal/atls"
1616
"github.com/flashbots/cvm-reverse-proxy/proxy"
17+
"github.com/flashbots/cvm-reverse-proxy/tdx"
1718
"github.com/urfave/cli/v2" // imports as package "cli"
1819
)
1920

@@ -75,6 +76,12 @@ var flags []cli.Flag = []cli.Flag{
7576
Value: true,
7677
Usage: "log debug messages",
7778
},
79+
&cli.BoolFlag{
80+
Name: "log-dcap-quote",
81+
EnvVars: []string{"LOG_DCAP_QUOTE"},
82+
Value: false,
83+
Usage: "log dcap quotes to folder quotes/",
84+
},
7885
}
7986

8087
var log *slog.Logger
@@ -102,6 +109,8 @@ func runServer(cCtx *cli.Context) error {
102109
clientMeasurements := cCtx.String("client-measurements")
103110
logJSON := cCtx.Bool("log-json")
104111
logDebug := cCtx.Bool("log-debug")
112+
tdx.SetLogDcapQuote(cCtx.Bool("log-dcap-quote"))
113+
105114
serverAttestationTypeFlag := cCtx.String("server-attestation-type")
106115

107116
certFile := cCtx.String("tls-certificate")

measurements.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,22 @@
2121
"9": {
2222
"expected": "c9f429296634072d1063a03fb287bed0b2d177b0a504755ad9194cffd90b2489"
2323
}
24+
},
25+
"dcap-tdx-example": {
26+
"0": {
27+
"expected": "5d56080eb9ef8ce0bbaf6bdcdadeeb06e7c5b0a4d1ec16be868a85a953babe0c5e54d01c8e050a54fe1ca078372530d2"
28+
},
29+
"1": {
30+
"expected": "4216e925f796f4e282cfa6e72d4c77a80560987afa29155a61fdc33adb80eab0d4112abd52387e5e25a60deefb8a5287"
31+
},
32+
"2": {
33+
"expected": "4274fefb79092c164000b571b64ecb432fa2357adb421fd1c77a867168d7d7f7fe82796d1eba092c7bab35cf43f5ec55"
34+
},
35+
"3": {
36+
"expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
37+
},
38+
"4": {
39+
"expected": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
40+
}
2441
}
2542
}

proxy/atls_config.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import (
1212

1313
"github.com/flashbots/cvm-reverse-proxy/internal/atls"
1414
azure_tdx "github.com/flashbots/cvm-reverse-proxy/internal/attestation/azure/tdx"
15+
dcap_tdx "github.com/flashbots/cvm-reverse-proxy/tdx"
1516
"github.com/flashbots/cvm-reverse-proxy/internal/attestation/measurements"
1617
"github.com/flashbots/cvm-reverse-proxy/internal/attestation/variant"
18+
"github.com/flashbots/cvm-reverse-proxy/internal/cloud/cloudprovider"
1719
"github.com/flashbots/cvm-reverse-proxy/internal/config"
1820
)
1921

@@ -22,16 +24,19 @@ type AttestationType string
2224
const (
2325
AttestationNone AttestationType = "none"
2426
AttestationAzureTDX AttestationType = "azure-tdx"
27+
AttestationDCAPTDX AttestationType = "dcap-tdx"
2528
)
2629

27-
const AvailableAttestationTypes string = "none, azure-tdx"
30+
const AvailableAttestationTypes string = "none, azure-tdx, dcap-tdx"
2831

2932
func ParseAttestationType(attestationType string) (AttestationType, error) {
3033
switch attestationType {
3134
case string(AttestationNone):
3235
return AttestationNone, nil
3336
case string(AttestationAzureTDX):
3437
return AttestationAzureTDX, nil
38+
case string(AttestationDCAPTDX):
39+
return AttestationDCAPTDX, nil
3540
default:
3641
return AttestationType(""), errors.New("invalid attestation-type passed in")
3742
}
@@ -43,6 +48,8 @@ func CreateAttestationIssuer(log *slog.Logger, attestationType AttestationType)
4348
return nil, nil
4449
case AttestationAzureTDX:
4550
return azure_tdx.NewIssuer(log), nil
51+
case AttestationDCAPTDX:
52+
return dcap_tdx.NewIssuer(log), nil
4653
default:
4754
return nil, errors.New("invalid attestation-type passed in")
4855
}
@@ -73,6 +80,14 @@ func CreateAttestationValidators(log *slog.Logger, attestationType AttestationTy
7380
validators = append(validators, azure_tdx.NewValidator(attConfig, AttestationLogger{Log: log}))
7481
}
7582
return []atls.Validator{NewMultiValidator(validators)}, nil
83+
case AttestationDCAPTDX:
84+
validators := []atls.Validator{}
85+
for _, measurement := range parsedMeasurements {
86+
attConfig := &config.QEMUTDX{Measurements: measurements.DefaultsFor(cloudprovider.QEMU, variant.QEMUTDX{})}
87+
attConfig.SetMeasurements(measurement)
88+
validators = append(validators, dcap_tdx.NewValidator(attConfig, AttestationLogger{Log: log}))
89+
}
90+
return []atls.Validator{NewMultiValidator(validators)}, nil
7691
default:
7792
return nil, errors.New("invalid attestation-type passed in")
7893
}
@@ -86,6 +101,12 @@ func ExtractMeasurementsFromExtension(ext *pkix.Extension, v variant.Variant) (m
86101
return nil, errors.New("could not parse measurements from raw attestations document")
87102
}
88103
return measurements, nil
104+
case variant.QEMUTDX{}:
105+
measurements, err := dcap_tdx.ParseDcapTDXAttestationMeasurementsRaw(ext.Value)
106+
if err != nil {
107+
return nil, errors.New("could not parse measurements from raw attestations document")
108+
}
109+
return measurements, nil
89110
default:
90111
return nil, errors.New("unsupported ATLS variant")
91112
}

tdx/issuer.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
Copyright (c) Edgeless Systems GmbH
3+
4+
SPDX-License-Identifier: AGPL-3.0-only
5+
*/
6+
7+
package tdx
8+
9+
import (
10+
"context"
11+
"encoding/json"
12+
"fmt"
13+
"os"
14+
"time"
15+
16+
"github.com/flashbots/cvm-reverse-proxy/internal/attestation"
17+
"github.com/flashbots/cvm-reverse-proxy/internal/attestation/variant"
18+
19+
"github.com/google/go-tdx-guest/client"
20+
)
21+
22+
var logDcapQuote bool
23+
24+
type tdxAttestationDocument struct {
25+
// RawQuote is the raw TDX quote.
26+
RawQuote []byte
27+
// UserData is the user data that was passed to the enclave and was included in the quote.
28+
UserData []byte
29+
}
30+
31+
// Issuer is the TDX attestation issuer.
32+
type Issuer struct {
33+
variant.QEMUTDX
34+
35+
log attestation.Logger
36+
}
37+
38+
// NewIssuer initializes a new TDX Issuer.
39+
func NewIssuer(log attestation.Logger) *Issuer {
40+
if log == nil {
41+
log = attestation.NOPLogger{}
42+
}
43+
return &Issuer{
44+
log: log,
45+
}
46+
}
47+
48+
func SetLogDcapQuote(logQuote bool) {
49+
logDcapQuote = logQuote
50+
if logDcapQuote {
51+
// Create local folder "quotes" if it doesn't exist
52+
if _, err := os.Stat("quotes"); os.IsNotExist(err) {
53+
os.Mkdir("quotes", os.ModePerm)
54+
}
55+
}
56+
}
57+
58+
// Issue issues a TDX attestation document.
59+
func (i *Issuer) Issue(_ context.Context, userData []byte, nonce []byte) (attDoc []byte, err error) {
60+
i.log.Info("Issuing attestation statement")
61+
defer func() {
62+
if err != nil {
63+
i.log.Warn(fmt.Sprintf("Failed to issue attestation document: %s", err))
64+
}
65+
}()
66+
67+
qp, err := client.GetQuoteProvider()
68+
if err != nil {
69+
return nil, fmt.Errorf("get quote provider: %w", err)
70+
}
71+
72+
var checkedUserData [64]byte
73+
copy(checkedUserData[:], attestation.MakeExtraData(userData, nonce))
74+
75+
quote, err := qp.GetRawQuote(checkedUserData)
76+
if err != nil {
77+
return nil, fmt.Errorf("generating quote: %w", err)
78+
}
79+
80+
err = writeRawQuoteToDisk(quote, true)
81+
if err != nil {
82+
return nil, fmt.Errorf("writing quote to disk: %w", err)
83+
}
84+
85+
rawAttDoc, err := json.Marshal(tdxAttestationDocument{
86+
RawQuote: quote,
87+
UserData: userData,
88+
})
89+
if err != nil {
90+
return nil, fmt.Errorf("marshaling attestation document: %w", err)
91+
}
92+
93+
return rawAttDoc, nil
94+
}
95+
96+
func writeRawQuoteToDisk(RawQuote []byte, isIssued bool) error {
97+
if !logDcapQuote {
98+
return nil
99+
}
100+
101+
timestamp := time.Now().Format("20060102_150405")
102+
quoteType := "issued"
103+
if !isIssued {
104+
quoteType = "received"
105+
}
106+
107+
fileName := fmt.Sprintf("quotes/quote_%s_%s.dat", quoteType, timestamp)
108+
file, err := os.Create(fileName)
109+
if err != nil {
110+
return err
111+
}
112+
defer file.Close()
113+
114+
_, err = file.Write(RawQuote)
115+
if err != nil {
116+
return err
117+
}
118+
119+
return nil
120+
}

0 commit comments

Comments
 (0)