Skip to content
Merged
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
60 changes: 60 additions & 0 deletions internal/attestation/insecure/insecure_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2026 Edgeless Systems GmbH
// SPDX-License-Identifier: BUSL-1.1

package insecure

import (
"context"
"log/slog"
"net/http"
"net/http/httptest"
"testing"

"github.com/edgelesssys/contrast/internal/attestation"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestIssueAndValidate(t *testing.T) {
hostData := []byte("hostdata")
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/hostdata", r.URL.Path)
_, err := w.Write(hostData)
assert.NoError(t, err)
Comment thread
msanft marked this conversation as resolved.
}))
defer server.Close()

issuer := &Issuer{hostdataURL: server.URL + "/hostdata", client: server.Client()}
var reportData [64]byte
copy(reportData[:], []byte("report-data"))

attDoc, err := issuer.Issue(context.Background(), reportData)
require.NoError(t, err)

setter := &stubReportSetter{}
validator := NewValidatorWithReportSetter(slog.Default(), setter, "insecure")
require.NoError(t, validator.Validate(context.Background(), attDoc, reportData[:]))
require.NotNil(t, setter.report)
assert.Equal(t, hostData, setter.report.HostData())
}

func TestValidateMismatchingReportData(t *testing.T) {
validator := NewValidator(slog.Default(), "insecure")
attDoc := []byte(`{"reportData":"AQ==","hostData":"Ag=="}`)

err := validator.Validate(context.Background(), attDoc, []byte{0x02})
require.Error(t, err)
assert.Contains(t, err.Error(), "reportData mismatch")
}

func TestAttestationDocumentOID(t *testing.T) {
assert.True(t, attestation.IsAttestationDocumentExtension(NewIssuer().OID()))
}

type stubReportSetter struct {
report attestation.Report
}

func (s *stubReportSetter) SetReport(report attestation.Report) {
s.report = report
}
74 changes: 74 additions & 0 deletions internal/attestation/insecure/issuer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2025 Edgeless Systems GmbH
// SPDX-License-Identifier: BUSL-1.1

// Package insecure provides a fake aTLS issuer and validator for development
// platforms without confidential computing hardware.
package insecure

import (
"context"
"encoding/asn1"
"encoding/json"
"fmt"
"io"
"net/http"

"github.com/edgelesssys/contrast/internal/oid"
)

// HostdataAddr is the address where the initdata-processor serves the
// hostdata digest on insecure platforms.
const HostdataAddr = "127.0.0.1:19629"

// HostdataURL is the full URL for fetching the hostdata digest.
const HostdataURL = "http://" + HostdataAddr + "/hostdata"

// Issuer issues fake attestation documents for insecure (non-CC) platforms.
//
// It fetches the initdata digest from the local initdata-processor HTTP server
// and packages it with the report data into a JSON attestation document.
type Issuer struct {
hostdataURL string
client *http.Client
}

// NewIssuer creates a new insecure issuer.
func NewIssuer() *Issuer {
return &Issuer{hostdataURL: HostdataURL, client: http.DefaultClient}
}

// OID returns the OID for the insecure attestation.
func (i *Issuer) OID() asn1.ObjectIdentifier {
return oid.RawInsecureReport
}

// Issue creates a fake attestation document containing the report data and
// the initdata digest fetched from the local hostdata server.
func (i *Issuer) Issue(ctx context.Context, reportData [64]byte) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, i.hostdataURL, nil)
if err != nil {
return nil, fmt.Errorf("creating hostdata request: %w", err)
}
resp, err := i.client.Do(req)
if err != nil {
return nil, fmt.Errorf("fetching hostdata from %q: %w", i.hostdataURL, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("fetching hostdata: status %s", resp.Status)
}
hostData, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("reading hostdata response: %w", err)
}
return json.Marshal(attestationDoc{
ReportData: reportData[:],
HostData: hostData,
})
}

// attestationDoc is the fake attestation document exchanged between issuer and validator.
type attestationDoc struct {
ReportData []byte `json:"reportData"`
HostData []byte `json:"hostData"`
}
74 changes: 74 additions & 0 deletions internal/attestation/insecure/validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2025 Edgeless Systems GmbH
// SPDX-License-Identifier: BUSL-1.1

package insecure

import (
"bytes"
"context"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/json"
"fmt"
"log/slog"

"github.com/edgelesssys/contrast/internal/attestation"
"github.com/edgelesssys/contrast/internal/oid"
)

// Validator validates fake attestation documents from insecure (non-CC) platforms.
type Validator struct {
reportSetter attestation.ReportSetter
logger *slog.Logger
name string
}

// NewValidator creates a new insecure validator.
func NewValidator(log *slog.Logger, name string) *Validator {
return &Validator{logger: log, name: name}
}

// NewValidatorWithReportSetter creates a new insecure validator with a report setter callback.
func NewValidatorWithReportSetter(log *slog.Logger, reportSetter attestation.ReportSetter, name string) *Validator {
return &Validator{reportSetter: reportSetter, logger: log, name: name}
}

// OID returns the OID for the insecure attestation.
func (v *Validator) OID() asn1.ObjectIdentifier {
return oid.RawInsecureReport
}

// Validate verifies the fake attestation document and extracts the host data.
func (v *Validator) Validate(_ context.Context, attDocRaw []byte, reportData []byte) error {
var doc attestationDoc
if err := json.Unmarshal(attDocRaw, &doc); err != nil {
return fmt.Errorf("unmarshaling insecure attestation: %w", err)
}
if !bytes.Equal(doc.ReportData, reportData) {
return fmt.Errorf("reportData mismatch: expected %x, got %x", reportData, doc.ReportData)
}
if v.reportSetter != nil {
v.reportSetter.SetReport(report{hostData: doc.HostData})
}
return nil
}

// String returns the validator's name.
func (v *Validator) String() string {
return v.name
}

// report implements the [attestation.Report] interface for insecure platforms.
type report struct {
hostData []byte
}

// HostData returns the initdata digest.
func (r report) HostData() []byte {
return r.hostData
}

// ClaimsToCertExtension returns no extensions for insecure platforms.
func (r report) ClaimsToCertExtension() ([]pkix.Extension, error) {
return nil, nil
}
4 changes: 2 additions & 2 deletions internal/attestation/oid.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

// IsAttestationDocumentExtension checks whether the given OID corresponds to an attestation document extension
// supported by Contrast (i.e. TDX or SNP).
// supported by Contrast (i.e. TDX, SNP, or insecure).
func IsAttestationDocumentExtension(oid asn1.ObjectIdentifier) bool {
return oid.Equal(oids.RawTDXReport) || oid.Equal(oids.RawSNPReport)
return oid.Equal(oids.RawTDXReport) || oid.Equal(oids.RawSNPReport) || oid.Equal(oids.RawInsecureReport)
}
4 changes: 4 additions & 0 deletions internal/oid/oid.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ var RawSNPReport = asn1.ObjectIdentifier{1, 3, 9901, 2, 1}
// used by the aTLS issuer and validator.
var RawTDXReport = asn1.ObjectIdentifier{1, 3, 9901, 2, 2}

// RawInsecureReport is the OID for the insecure (non-CC) attestation,
// used on development platforms without CC hardware.
var RawInsecureReport = asn1.ObjectIdentifier{1, 3, 9901, 2, 99}

// WorkloadSecretOID is the root OID for the workloadSecretID report
// extension, added to the mesh certificates to allow verification
// and authorization based on the workloadSecretID.
Expand Down