Skip to content

Commit 58434e3

Browse files
committed
attestation: add insecure issuer and validator
1 parent 4fe5f05 commit 58434e3

5 files changed

Lines changed: 214 additions & 2 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2026 Edgeless Systems GmbH
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package insecure
5+
6+
import (
7+
"context"
8+
"log/slog"
9+
"net/http"
10+
"net/http/httptest"
11+
"testing"
12+
13+
"github.com/edgelesssys/contrast/internal/attestation"
14+
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
func TestIssueAndValidate(t *testing.T) {
19+
hostData := []byte("hostdata")
20+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
21+
assert.Equal(t, "/hostdata", r.URL.Path)
22+
_, err := w.Write(hostData)
23+
assert.NoError(t, err)
24+
}))
25+
defer server.Close()
26+
27+
issuer := &Issuer{hostdataURL: server.URL + "/hostdata", client: server.Client()}
28+
var reportData [64]byte
29+
copy(reportData[:], []byte("report-data"))
30+
31+
attDoc, err := issuer.Issue(context.Background(), reportData)
32+
require.NoError(t, err)
33+
34+
setter := &stubReportSetter{}
35+
validator := NewValidatorWithReportSetter(slog.Default(), setter, "insecure")
36+
require.NoError(t, validator.Validate(context.Background(), attDoc, reportData[:]))
37+
require.NotNil(t, setter.report)
38+
assert.Equal(t, hostData, setter.report.HostData())
39+
}
40+
41+
func TestValidateMismatchingReportData(t *testing.T) {
42+
validator := NewValidator(slog.Default(), "insecure")
43+
attDoc := []byte(`{"reportData":"AQ==","hostData":"Ag=="}`)
44+
45+
err := validator.Validate(context.Background(), attDoc, []byte{0x02})
46+
require.Error(t, err)
47+
assert.Contains(t, err.Error(), "reportData mismatch")
48+
}
49+
50+
func TestAttestationDocumentOID(t *testing.T) {
51+
assert.True(t, attestation.IsAttestationDocumentExtension(NewIssuer().OID()))
52+
}
53+
54+
type stubReportSetter struct {
55+
report attestation.Report
56+
}
57+
58+
func (s *stubReportSetter) SetReport(report attestation.Report) {
59+
s.report = report
60+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2025 Edgeless Systems GmbH
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
// Package insecure provides a fake aTLS issuer and validator for development
5+
// platforms without confidential computing hardware.
6+
package insecure
7+
8+
import (
9+
"context"
10+
"encoding/asn1"
11+
"encoding/json"
12+
"fmt"
13+
"io"
14+
"net/http"
15+
16+
"github.com/edgelesssys/contrast/internal/oid"
17+
)
18+
19+
// HostdataAddr is the address where the initdata-processor serves the
20+
// hostdata digest on insecure platforms.
21+
const HostdataAddr = "127.0.0.1:19629"
22+
23+
// HostdataURL is the full URL for fetching the hostdata digest.
24+
const HostdataURL = "http://" + HostdataAddr + "/hostdata"
25+
26+
// Issuer issues fake attestation documents for insecure (non-CC) platforms.
27+
//
28+
// It fetches the initdata digest from the local initdata-processor HTTP server
29+
// and packages it with the report data into a JSON attestation document.
30+
type Issuer struct {
31+
hostdataURL string
32+
client *http.Client
33+
}
34+
35+
// NewIssuer creates a new insecure issuer.
36+
func NewIssuer() *Issuer {
37+
return &Issuer{hostdataURL: HostdataURL, client: http.DefaultClient}
38+
}
39+
40+
// OID returns the OID for the insecure attestation.
41+
func (i *Issuer) OID() asn1.ObjectIdentifier {
42+
return oid.RawInsecureReport
43+
}
44+
45+
// Issue creates a fake attestation document containing the report data and
46+
// the initdata digest fetched from the local hostdata server.
47+
func (i *Issuer) Issue(ctx context.Context, reportData [64]byte) ([]byte, error) {
48+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, i.hostdataURL, nil)
49+
if err != nil {
50+
return nil, fmt.Errorf("creating hostdata request: %w", err)
51+
}
52+
resp, err := i.client.Do(req)
53+
if err != nil {
54+
return nil, fmt.Errorf("fetching hostdata from %q: %w", i.hostdataURL, err)
55+
}
56+
defer resp.Body.Close()
57+
if resp.StatusCode != http.StatusOK {
58+
return nil, fmt.Errorf("fetching hostdata: status %s", resp.Status)
59+
}
60+
hostData, err := io.ReadAll(resp.Body)
61+
if err != nil {
62+
return nil, fmt.Errorf("reading hostdata response: %w", err)
63+
}
64+
return json.Marshal(attestationDoc{
65+
ReportData: reportData[:],
66+
HostData: hostData,
67+
})
68+
}
69+
70+
// attestationDoc is the fake attestation document exchanged between issuer and validator.
71+
type attestationDoc struct {
72+
ReportData []byte `json:"reportData"`
73+
HostData []byte `json:"hostData"`
74+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2025 Edgeless Systems GmbH
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package insecure
5+
6+
import (
7+
"bytes"
8+
"context"
9+
"crypto/x509/pkix"
10+
"encoding/asn1"
11+
"encoding/json"
12+
"fmt"
13+
"log/slog"
14+
15+
"github.com/edgelesssys/contrast/internal/attestation"
16+
"github.com/edgelesssys/contrast/internal/oid"
17+
)
18+
19+
// Validator validates fake attestation documents from insecure (non-CC) platforms.
20+
type Validator struct {
21+
reportSetter attestation.ReportSetter
22+
logger *slog.Logger
23+
name string
24+
}
25+
26+
// NewValidator creates a new insecure validator.
27+
func NewValidator(log *slog.Logger, name string) *Validator {
28+
return &Validator{logger: log, name: name}
29+
}
30+
31+
// NewValidatorWithReportSetter creates a new insecure validator with a report setter callback.
32+
func NewValidatorWithReportSetter(log *slog.Logger, reportSetter attestation.ReportSetter, name string) *Validator {
33+
return &Validator{reportSetter: reportSetter, logger: log, name: name}
34+
}
35+
36+
// OID returns the OID for the insecure attestation.
37+
func (v *Validator) OID() asn1.ObjectIdentifier {
38+
return oid.RawInsecureReport
39+
}
40+
41+
// Validate verifies the fake attestation document and extracts the host data.
42+
func (v *Validator) Validate(_ context.Context, attDocRaw []byte, reportData []byte) error {
43+
var doc attestationDoc
44+
if err := json.Unmarshal(attDocRaw, &doc); err != nil {
45+
return fmt.Errorf("unmarshaling insecure attestation: %w", err)
46+
}
47+
if !bytes.Equal(doc.ReportData, reportData) {
48+
return fmt.Errorf("reportData mismatch: expected %x, got %x", reportData, doc.ReportData)
49+
}
50+
if v.reportSetter != nil {
51+
v.reportSetter.SetReport(report{hostData: doc.HostData})
52+
}
53+
return nil
54+
}
55+
56+
// String returns the validator's name.
57+
func (v *Validator) String() string {
58+
return v.name
59+
}
60+
61+
// report implements the [attestation.Report] interface for insecure platforms.
62+
type report struct {
63+
hostData []byte
64+
}
65+
66+
// HostData returns the initdata digest.
67+
func (r report) HostData() []byte {
68+
return r.hostData
69+
}
70+
71+
// ClaimsToCertExtension returns no extensions for insecure platforms.
72+
func (r report) ClaimsToCertExtension() ([]pkix.Extension, error) {
73+
return nil, nil
74+
}

internal/attestation/oid.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
)
1111

1212
// IsAttestationDocumentExtension checks whether the given OID corresponds to an attestation document extension
13-
// supported by Contrast (i.e. TDX or SNP).
13+
// supported by Contrast (i.e. TDX, SNP, or insecure).
1414
func IsAttestationDocumentExtension(oid asn1.ObjectIdentifier) bool {
15-
return oid.Equal(oids.RawTDXReport) || oid.Equal(oids.RawSNPReport)
15+
return oid.Equal(oids.RawTDXReport) || oid.Equal(oids.RawSNPReport) || oid.Equal(oids.RawInsecureReport)
1616
}

internal/oid/oid.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ var RawSNPReport = asn1.ObjectIdentifier{1, 3, 9901, 2, 1}
1313
// used by the aTLS issuer and validator.
1414
var RawTDXReport = asn1.ObjectIdentifier{1, 3, 9901, 2, 2}
1515

16+
// RawInsecureReport is the OID for the insecure (non-CC) attestation,
17+
// used on development platforms without CC hardware.
18+
var RawInsecureReport = asn1.ObjectIdentifier{1, 3, 9901, 2, 99}
19+
1620
// WorkloadSecretOID is the root OID for the workloadSecretID report
1721
// extension, added to the mesh certificates to allow verification
1822
// and authorization based on the workloadSecretID.

0 commit comments

Comments
 (0)