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
72 changes: 72 additions & 0 deletions attestation-agent/agent-go/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# agent-go

`agent-go` is a pure-Go re-implementation of the guest-components
`attestation-agent` **library crate**
(`attestation-agent/attestation-agent`). It sits on top of
[`attester-go`](../attester-go) and exposes the attestation-agent service API to
Go programs, with no cgo and no external shared library.

## What it provides

The `AttestationAPIs` interface mirrors the Rust `AttestationAPIs` trait:

| Method | Description |
| --- | --- |
| `GetEvidence(runtimeData)` | TEE hardware-signed evidence (compact JSON) for the given runtime/report data. |
| `GetAdditionalEvidence(runtimeData)` | Evidence from additional (device) attesters; empty when none. |
| `GetToken(tokenType, additionalData)` | Attestation token from a remote service (`coco_as`). |
| `ExtendRuntimeMeasurement(domain, operation, content, registerIndex)` | Extend a runtime measurement register **and** record the event in the AA Event Log (AAEL). |
| `BindInitData(initData)` | Bind an init-data digest to the TEE. |
| `GetTeeType()` / `GetAdditionalTees()` | The detected primary / additional TEE types. |

Supporting packages, one per Rust module:

- `config` — parse the AA config (TOML or JSON) with the same defaults, plus
`aa_kbc_params` resolution from the environment / kernel command line.
- `eventlog` — the AAEL: TCG2 event encoding, extend-into-register, and a
write-ahead log (WAL) that makes the "extend register + append log" pair
crash-recoverable. The TCG2 event-data digest is byte-compatible with the Rust
implementation (locked by unit-test vectors).
- `initdata` — parse the Initdata TOML and compute its digest.
- `token` — obtain an attestation token (CoCoAS).

## Usage

```go
import "github.com/confidential-containers/guest-components/attestation-agent/agent-go"

aa, err := attestationagent.New(nil) // or .New(&path) to load a config file
if err != nil { /* ... */ }
if err := aa.Init(); err != nil { // enables the event log if configured
/* ... */
}

evidence, err := aa.GetEvidence([]byte("nonce"))
```

## Differences from the Rust crate

All of these follow the scope already established by `attester-go`:

- **No cgo / no shared library.** Every platform is reached through
`attester-go`'s native kernel interfaces. The default build is
`CGO_ENABLED=0`.
- **`instance_info` is not ported** (AA instance info / heartbeat). An
`[aa_instance]` section in an existing config file is ignored.
- **KBS token is not ported.** Obtaining a KBS token needs the KBS
background-check protocol (the Rust `kbs_protocol` crate: RCAR handshake + TEE
key pair), which has no Go port yet; `token.KbsTokenGetter.GetToken` returns
`token.ErrKbsNotImplemented`. The **CoCoAS** token is fully implemented.
- **No additional (device) attesters.** `attester-go` exposes none, so
`GetAdditionalEvidence` returns empty and `GetAdditionalTees` is empty.

## Testing

```
go test ./...
```

Unit tests cover the TCG2 digest vectors (byte-compatibility with the Rust
attestation-agent), the event-log extend + WAL crash-recovery paths (with an
in-memory attester), config TOML/JSON parsing and defaults, `aa_kbc_params`,
Initdata digest, token dispatch and CoCoAS URL resolution.
272 changes: 272 additions & 0 deletions attestation-agent/agent-go/agent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
// Copyright (c) 2024 Alibaba Cloud
//
// SPDX-License-Identifier: Apache-2.0

// Package attestationagent is a pure-Go re-implementation of the guest-components
// `attestation-agent` library crate (attestation-agent/attestation-agent). It
// provides the attestation procedures used by confidential containers on top of
// the pure-Go attester-go module:
//
// - GetToken: obtain an attestation token from a remote service (CoCoAS).
// - GetEvidence: obtain TEE hardware-signed evidence for given runtime data.
// - ExtendRuntimeMeasurement: extend a runtime measurement register and record
// the event in the AA Event Log (AAEL).
// - BindInitData: bind an init-data digest to the TEE.
//
// Differences from the Rust crate, all following the scope already established
// by attester-go:
//
// - No cgo and no external shared library: every platform is reached through
// attester-go's native kernel interfaces.
// - The `instance_info` feature (AA instance info / heartbeat) is not ported.
// - KBS token support is not ported (it needs a Go port of kbs_protocol); the
// CoCoAS token is fully implemented. See package token.
// - attester-go exposes no additional (device) attesters, so
// GetAdditionalEvidence always returns empty and GetAdditionalTees is empty.
package attestationagent

import (
"encoding/json"
"fmt"
"sync"

attester "github.com/confidential-containers/guest-components/attestation-agent/attester-go"

"github.com/confidential-containers/guest-components/attestation-agent/agent-go/config"
"github.com/confidential-containers/guest-components/attestation-agent/agent-go/eventlog"
"github.com/confidential-containers/guest-components/attestation-agent/agent-go/token"
)

// Re-exported attester types for convenience.
type (
// Tee identifies the confidential computing platform.
Tee = attester.Tee
// InitDataResult is the outcome of BindInitData.
InitDataResult = attester.InitDataResult
// Config is the AA configuration.
Config = config.Config
)

// AttestationAPIs is the service API surface of the attestation agent,
// mirroring the Rust `AttestationAPIs` trait.
type AttestationAPIs interface {
// GetToken obtains an attestation token of the given type. additionalData
// is forwarded to the CoCoAS token getter (pass nil for none).
GetToken(tokenType string, additionalData *string) ([]byte, error)

// GetEvidence returns TEE hardware-signed evidence that includes
// runtimeData, as compact JSON bytes.
GetEvidence(runtimeData []byte) ([]byte, error)

// GetAdditionalEvidence returns evidence from all additional attesters. It
// returns an empty slice when none are configured.
GetAdditionalEvidence(runtimeData []byte) ([]byte, error)

// ExtendRuntimeMeasurement extends a runtime measurement register with the
// event (domain, operation, content) and records it in the AAEL.
// registerIndex selects the target register; pass nil for the configured
// default.
ExtendRuntimeMeasurement(domain, operation, content string, registerIndex *uint64) error

// BindInitData binds an init-data digest to the current TEE.
BindInitData(initData []byte) (InitDataResult, error)

// GetTeeType returns the detected primary TEE type.
GetTeeType() Tee

// GetAdditionalTees returns the additional (device) TEE types.
GetAdditionalTees() []Tee
}

// AttestationAgent provides the attestation service. Create it with New and,
// before extending runtime measurements, call Init.
type AttestationAgent struct {
primaryTee Tee

configMu sync.RWMutex
config *config.Config

eventlogMu sync.Mutex
eventlog *eventlog.EventLog

initdata *string

primaryAttester attester.Attester
additionalAttesters map[Tee]attester.Attester
}

// Compile-time assertion that AttestationAgent satisfies AttestationAPIs.
var _ AttestationAPIs = (*AttestationAgent)(nil)

// New creates an AttestationAgent. If configPath is nil, a default
// configuration is used; otherwise the file (TOML or JSON) is loaded. Mirrors
// Rust AttestationAgent::new.
func New(configPath *string) (*AttestationAgent, error) {
var cfg *config.Config
var err error
if configPath != nil {
if cfg, err = config.Load(*configPath); err != nil {
return nil, err
}
} else {
if cfg, err = config.New(); err != nil {
return nil, err
}
}

primaryTee := attester.DetectTeeType()
primaryAttester, err := attester.New(primaryTee)
if err != nil {
return nil, err
}

// attester-go exposes no additional (device) attesters; the map stays empty.
return &AttestationAgent{
primaryTee: primaryTee,
config: cfg,
primaryAttester: primaryAttester,
additionalAttesters: map[Tee]attester.Attester{},
}, nil
}

// Init initializes the event log if it is enabled in the configuration.
// Mirrors Rust AttestationAgent::init (minus the dropped instance_info setup).
func (a *AttestationAgent) Init() error {
a.configMu.RLock()
enable := a.config.EventlogConfig.EnableEventlog
initPCR := a.config.EventlogConfig.InitPcr
a.configMu.RUnlock()

if !enable {
return nil
}

el, err := eventlog.New(a.primaryAttester, initPCR)
if err != nil {
return err
}
a.eventlogMu.Lock()
a.eventlog = el
a.eventlogMu.Unlock()
return nil
}

// SetInitdataToml records the init-data TOML as the state of this AA instance,
// mirroring Rust set_initdata_toml.
func (a *AttestationAgent) SetInitdataToml(initdataToml string) {
a.initdata = &initdataToml
}

// Config returns the current configuration (read-locked copy of the pointer).
func (a *AttestationAgent) Config() *config.Config {
a.configMu.RLock()
defer a.configMu.RUnlock()
return a.config
}

// GetToken implements AttestationAPIs.
func (a *AttestationAgent) GetToken(tokenType string, additionalData *string) ([]byte, error) {
tt, err := token.ParseTokenType(tokenType)
if err != nil {
return nil, fmt.Errorf("Unsupported token type: %w", err)
}

a.configMu.RLock()
tokenConfigs := a.config.TokenConfigs
initdata := a.initdata
a.configMu.RUnlock()

switch tt {
case token.TokenTypeKbs:
if tokenConfigs.Kbs == nil {
return nil, fmt.Errorf("kbs token config not configured in config file")
}
return token.NewKbs(tokenConfigs.Kbs).GetToken(initdata)
case token.TokenTypeCoCoAS:
if tokenConfigs.CoCoAS == nil {
return nil, fmt.Errorf("coco_as token config not configured in config file")
}
return token.NewCoCoAS(tokenConfigs.CoCoAS).GetToken(additionalData)
default:
return nil, fmt.Errorf("Unsupported token type: %s", tokenType)
}
}

// GetEvidence implements AttestationAPIs.
func (a *AttestationAgent) GetEvidence(runtimeData []byte) ([]byte, error) {
evidence, err := a.primaryAttester.GetEvidence(runtimeData)
if err != nil {
return nil, err
}
// evidence is already compact JSON, matching the Rust
// evidence.to_string().into_bytes().
return []byte(evidence), nil
}

// GetAdditionalEvidence implements AttestationAPIs.
func (a *AttestationAgent) GetAdditionalEvidence(runtimeData []byte) ([]byte, error) {
if len(a.additionalAttesters) == 0 {
// No additional attesters configured, return empty evidence.
return []byte{}, nil
}

evidence := make(map[Tee]json.RawMessage, len(a.additionalAttesters))
for tee, att := range a.additionalAttesters {
ev, err := att.GetEvidence(runtimeData)
if err != nil {
return nil, err
}
evidence[tee] = json.RawMessage(ev)
}
out, err := json.Marshal(evidence)
if err != nil {
return nil, fmt.Errorf("Failed to serialize additional evidence: %w", err)
}
return out, nil
}

// ExtendRuntimeMeasurement implements AttestationAPIs.
func (a *AttestationAgent) ExtendRuntimeMeasurement(domain, operation, content string, registerIndex *uint64) error {
a.eventlogMu.Lock()
defer a.eventlogMu.Unlock()

if a.eventlog == nil {
return fmt.Errorf("Extend eventlog not enabled when launching!")
}

pcr := a.defaultPCR()
if registerIndex != nil {
pcr = *registerIndex
}

event, err := eventlog.NewEvent(domain, operation, content)
if err != nil {
return err
}
return a.eventlog.ExtendEntry(event, pcr)
}

func (a *AttestationAgent) defaultPCR() uint64 {
a.configMu.RLock()
defer a.configMu.RUnlock()
return a.config.EventlogConfig.InitPcr
}

// BindInitData implements AttestationAPIs.
func (a *AttestationAgent) BindInitData(initData []byte) (InitDataResult, error) {
return a.primaryAttester.BindInitData(initData)
}

// GetTeeType implements AttestationAPIs.
func (a *AttestationAgent) GetTeeType() Tee {
return a.primaryTee
}

// GetAdditionalTees implements AttestationAPIs.
func (a *AttestationAgent) GetAdditionalTees() []Tee {
tees := make([]Tee, 0, len(a.additionalAttesters))
for tee := range a.additionalAttesters {
tees = append(tees, tee)
}
return tees
}
Loading
Loading