Skip to content
Open
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
70 changes: 50 additions & 20 deletions core/capabilities/confidentialrelay/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,30 @@ func newMetrics() (*handlerMetrics, error) {
}, nil
}

// attestationValidatorFunc validates a TEE attestation document.
type attestationValidatorFunc func(attestation []byte, expectedUserData []byte, trustedMeasurements []byte) error
// AttestationValidator validates TEE attestation documents, with or without a
// custom CA root. Both the production Nitro validator and the insecure
// passthrough validator implement it, so the handler validates the same way
// regardless of which is configured.
type AttestationValidator interface {
ValidateAttestation(attestation, expectedUserData, trustedMeasurements []byte) error
ValidateAttestationWithRoots(attestation, expectedUserData, trustedMeasurements []byte, caRootsPEM string) error
}

// nitroValidator is the production validator backed by AWS Nitro.
type nitroValidator struct{}

func (nitroValidator) ValidateAttestation(attestation, expectedUserData, trustedMeasurements []byte) error {
return nitro.ValidateAttestation(attestation, expectedUserData, trustedMeasurements)
}

func (nitroValidator) ValidateAttestationWithRoots(attestation, expectedUserData, trustedMeasurements []byte, caRootsPEM string) error {
return nitro.ValidateAttestationWithRoots(attestation, expectedUserData, trustedMeasurements, caRootsPEM)
}

// NewAttestationValidator returns the production validator backed by AWS Nitro.
func NewAttestationValidator() AttestationValidator {
return nitroValidator{}
}

// Handler processes enclave relay requests from the gateway.
// It validates attestations and proxies requests to VaultDON or capability DONs.
Expand All @@ -103,13 +125,16 @@ type Handler struct {
lggr logger.Logger
metrics *handlerMetrics

// validateAttestation validates TEE attestation documents.
// Defaults to the Nitro validator; overridden in tests.
validateAttestation attestationValidatorFunc
limitsFactory limits.Factory
// validator validates TEE attestation documents.
validator AttestationValidator
// requireBFTQuorum selects the required signature quorum: when true the relay
// demands a Byzantine quorum of 2*F+1 unique signers, otherwise a crash-fault
// quorum of F+1.
requireBFTQuorum bool
limitsFactory limits.Factory
}

func NewHandler(capRegistry core.CapabilitiesRegistry, executionHandlers *ExecutionHandlers, conn core.GatewayConnector, responseSigner relayResponseSigner, lggr logger.Logger, lf limits.Factory) (*Handler, error) {
func NewHandler(capRegistry core.CapabilitiesRegistry, executionHandlers *ExecutionHandlers, conn core.GatewayConnector, responseSigner relayResponseSigner, lggr logger.Logger, lf limits.Factory, validator AttestationValidator, requireBFTQuorum bool) (*Handler, error) {
if responseSigner == nil {
return nil, errors.New("response signer is required")
}
Expand All @@ -119,14 +144,15 @@ func NewHandler(capRegistry core.CapabilitiesRegistry, executionHandlers *Execut
}

h := &Handler{
capRegistry: capRegistry,
executionHandlers: executionHandlers,
gatewayConnector: conn,
responseSigner: responseSigner,
lggr: logger.Named(lggr, HandlerName),
metrics: m,
validateAttestation: nitro.ValidateAttestation,
limitsFactory: lf,
capRegistry: capRegistry,
executionHandlers: executionHandlers,
gatewayConnector: conn,
responseSigner: responseSigner,
lggr: logger.Named(lggr, HandlerName),
metrics: m,
validator: validator,
requireBFTQuorum: requireBFTQuorum,
limitsFactory: lf,
}
h.Service, h.eng = services.Config{
Name: HandlerName,
Expand Down Expand Up @@ -540,9 +566,9 @@ func (h *Handler) verifyAttestationHash(ctx context.Context, attestationB64 stri
for _, m := range measurements {
var err error
if caRootsPEM != "" {
err = nitro.ValidateAttestationWithRoots(attestationBytes, hash, m, caRootsPEM)
err = h.validator.ValidateAttestationWithRoots(attestationBytes, hash, m, caRootsPEM)
} else {
err = h.validateAttestation(attestationBytes, hash, m)
err = h.validator.ValidateAttestation(attestationBytes, hash, m)
}
if err == nil {
return nil
Expand Down Expand Up @@ -571,9 +597,13 @@ func (h *Handler) verifyWorkflowAuthorization(don capabilities.DON, params confi
return errors.New("missing signed compute requests")
}

// Match the enclave's own quorum: server.go requires config.F+1 unique signers where the
// config-tracker sets config.F = 2*don.F, i.e. 2*F+1.
threshold := 2*int(don.F) + 1
// Match the enclave's own quorum. With requireBFTQuorum the relay demands a
// Byzantine quorum of 2*F+1 unique signers; otherwise a crash-fault quorum of
// F+1 suffices.
threshold := int(don.F) + 1
if h.requireBFTQuorum {
threshold = 2*int(don.F) + 1
}

// The forwarded requests differ only in their signature; they all sign one shared
// ComputeRequest hash. Reconstruct that hash once and verify each signature over it.
Expand Down
8 changes: 4 additions & 4 deletions core/capabilities/confidentialrelay/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
jsonrpc "github.com/smartcontractkit/chainlink-common/pkg/jsonrpc2"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
"github.com/smartcontractkit/chainlink-common/pkg/settings/limits"
"github.com/smartcontractkit/chainlink-common/pkg/teeattestation/passthrough"
"github.com/smartcontractkit/chainlink-common/pkg/types/core"
"github.com/smartcontractkit/chainlink-common/pkg/workflows/host"
sdkpb "github.com/smartcontractkit/chainlink-protos/cre/go/sdk"
Expand Down Expand Up @@ -56,8 +57,6 @@ func makeCapabilityPayload(t *testing.T, inputs map[string]any) string {

const testAttestationB64 = "ZHVtbXktYXR0ZXN0YXRpb24=" // base64("dummy-attestation")

func noopValidator(_ []byte, _, _ []byte) error { return nil }

type mockGatewayConnector struct {
core.UnimplementedGatewayConnector
lastResp *jsonrpc.Response[json.RawMessage]
Expand Down Expand Up @@ -133,9 +132,10 @@ func newTestHandler(t *testing.T, registry core.CapabilitiesRegistry, gwConn cor
require.NoError(t, err)
key, err := p2pkey.NewV2()
require.NoError(t, err)
h, err := NewHandler(registry, &ExecutionHandlers{}, gwConn, newRelayResponseSigner(key), lggr, limits.Factory{Logger: lggr})
validator, err := passthrough.New()
require.NoError(t, err)
h, err := NewHandler(registry, &ExecutionHandlers{}, gwConn, newRelayResponseSigner(key), lggr, limits.Factory{Logger: lggr}, validator, true)
require.NoError(t, err)
h.validateAttestation = noopValidator
return h
}

Expand Down
8 changes: 7 additions & 1 deletion core/capabilities/confidentialrelay/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type Service struct {
peerID p2pkey.PeerID
lggr logger.Logger
limitsFactory limits.Factory
validator AttestationValidator
requireBFTQuorum bool

handler *Handler
}
Expand All @@ -44,6 +46,8 @@ func NewService(
peerID p2pkey.PeerID,
lggr logger.Logger,
limitsFactory limits.Factory,
validator AttestationValidator,
requireBFTQuorum bool,
) *Service {
s := &Service{
wrapper: wrapper,
Expand All @@ -53,6 +57,8 @@ func NewService(
peerID: peerID,
lggr: lggr,
limitsFactory: limitsFactory,
validator: validator,
requireBFTQuorum: requireBFTQuorum,
}
s.Service, s.eng = services.Config{
Name: "ConfidentialRelayService",
Expand All @@ -71,7 +77,7 @@ func (s *Service) start(ctx context.Context) error {
if err != nil {
return fmt.Errorf("failed to get p2p key for confidential relay signing: %w", err)
}
h, err := NewHandler(s.capRegistry, s.executionHandlers, conn, newRelayResponseSigner(key), s.lggr, s.limitsFactory)
h, err := NewHandler(s.capRegistry, s.executionHandlers, conn, newRelayResponseSigner(key), s.lggr, s.limitsFactory, s.validator, s.requireBFTQuorum)
if err != nil {
return err
}
Expand Down
5 changes: 5 additions & 0 deletions core/config/cre_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ type WorkflowFetcher interface {
// CREConfidentialRelay defines configuration for the confidential relay handler.
type CREConfidentialRelay interface {
Enabled() bool
// TrustEnclaves reports whether the relay should trust fake (non-Nitro)
// enclaves by relaxing TEE attestation validation. INSECURE; test-only.
TrustEnclaves() bool
// RequireBFTQuorum selects the required signature quorum
RequireBFTQuorum() bool
}

// CRELinking defines configuration for connecting to the CRE linking service
Expand Down
4 changes: 4 additions & 0 deletions core/config/docs/core.toml
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,10 @@ DebugMode = false # Default
[CRE.ConfidentialRelay]
# Enabled controls whether the confidential relay gateway handler should be configured.
Enabled = false # Default
# TrustEnclaves relaxes TEE attestation validation so the relay trusts fake (non-Nitro) enclaves. intended only for tests.
TrustEnclaves = false # Default
# RequireBFTQuorum selects the relay's signature quorum.
RequireBFTQuorum = false # Default

# Sharding holds settings for node sharding configuration.
[Sharding]
Expand Down
12 changes: 12 additions & 0 deletions core/config/toml/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2048,6 +2048,12 @@ type WorkflowFetcherConfig struct {
// validating enclave attestations and proxying capability requests.
type ConfidentialRelayConfig struct {
Enabled *bool `toml:",omitempty"`
// TrustEnclaves relaxes TEE attestation validation so the relay trusts
// fake (non-Nitro) enclaves. INSECURE; intended only for tests/E2E that run
// against the fake enclave environment.
TrustEnclaves *bool `toml:",omitempty"`
// RequireBFTQuorum selects the required signature quorum.
RequireBFTQuorum *bool `toml:",omitempty"`
}

// LinkingConfig holds the configuration for connecting to the CRE linking service
Expand Down Expand Up @@ -2111,6 +2117,12 @@ func (c *CreConfig) setFrom(f *CreConfig) {
if v := f.ConfidentialRelay.Enabled; v != nil {
c.ConfidentialRelay.Enabled = v
}
if v := f.ConfidentialRelay.TrustEnclaves; v != nil {
c.ConfidentialRelay.TrustEnclaves = v
}
if v := f.ConfidentialRelay.RequireBFTQuorum; v != nil {
c.ConfidentialRelay.RequireBFTQuorum = v
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion core/scripts/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ require (
github.com/smartcontractkit/chain-selectors v1.0.104
github.com/smartcontractkit/chainlink-automation v0.8.1
github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260618155522-3600f66e26cd
github.com/smartcontractkit/chainlink-common v0.11.2-0.20260625162847-bdf9e82b2f75
github.com/smartcontractkit/chainlink-common v0.11.2-0.20260629191530-0371c428a6cf
github.com/smartcontractkit/chainlink-common/keystore v1.2.0
github.com/smartcontractkit/chainlink-data-streams v0.1.15-0.20260522094612-5f9f748bd87a
github.com/smartcontractkit/chainlink-deployments-framework v0.111.1-0.20260612191326-e31c0ae4cd54
Expand Down
4 changes: 2 additions & 2 deletions core/scripts/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 15 additions & 3 deletions core/services/chainlink/config_cre.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,14 @@ func (c *creConfig) Linking() config.CRELinking {
}

type confidentialRelayConfig struct {
enabled bool
enabled bool
trustEnclaves bool
requireBFTQuorum bool
}

func (cr *confidentialRelayConfig) Enabled() bool { return cr.enabled }
func (cr *confidentialRelayConfig) Enabled() bool { return cr.enabled }
func (cr *confidentialRelayConfig) TrustEnclaves() bool { return cr.trustEnclaves }
func (cr *confidentialRelayConfig) RequireBFTQuorum() bool { return cr.requireBFTQuorum }

func (c *creConfig) ConfidentialRelay() config.CREConfidentialRelay {
if c.c.ConfidentialRelay == nil {
Expand All @@ -119,7 +123,15 @@ func (c *creConfig) ConfidentialRelay() config.CREConfidentialRelay {
if c.c.ConfidentialRelay.Enabled != nil {
enabled = *c.c.ConfidentialRelay.Enabled
}
return &confidentialRelayConfig{enabled: enabled}
trustEnclaves := false
if c.c.ConfidentialRelay.TrustEnclaves != nil {
trustEnclaves = *c.c.ConfidentialRelay.TrustEnclaves
}
requireBFTQuorum := false
if c.c.ConfidentialRelay.RequireBFTQuorum != nil {
requireBFTQuorum = *c.c.ConfidentialRelay.RequireBFTQuorum
}
return &confidentialRelayConfig{enabled: enabled, trustEnclaves: trustEnclaves, requireBFTQuorum: requireBFTQuorum}
}

func (c *creConfig) LocalSecretOverrides() map[string]map[string]string {
Expand Down
4 changes: 3 additions & 1 deletion core/services/chainlink/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,9 @@ func TestConfig_Marshal(t *testing.T) {
TLSEnabled: ptr(true),
},
ConfidentialRelay: &toml.ConfidentialRelayConfig{
Enabled: ptr(false),
Enabled: new(bool),
TrustEnclaves: new(bool),
RequireBFTQuorum: ptr(true),
},
}
full.Billing = toml.Billing{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ TLSEnabled = true

[CRE.ConfidentialRelay]
Enabled = false
TrustEnclaves = false
RequireBFTQuorum = false

[Billing]
URL = 'localhost:4319'
Expand Down
2 changes: 2 additions & 0 deletions core/services/chainlink/testdata/config-full.toml
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,8 @@ TLSEnabled = true

[CRE.ConfidentialRelay]
Enabled = false
TrustEnclaves = false
RequireBFTQuorum = true

[Billing]
URL = 'localhost:4319'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ TLSEnabled = true

[CRE.ConfidentialRelay]
Enabled = false
TrustEnclaves = false
RequireBFTQuorum = false

[Billing]
URL = 'localhost:4319'
Expand Down
12 changes: 12 additions & 0 deletions core/services/cre/cre.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/smartcontractkit/chainlink-common/pkg/settings/limits"
"github.com/smartcontractkit/chainlink-common/pkg/sqlutil"
"github.com/smartcontractkit/chainlink-common/pkg/storage"
"github.com/smartcontractkit/chainlink-common/pkg/teeattestation/passthrough"
commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
"github.com/smartcontractkit/chainlink-common/pkg/workflows/dontime"
"github.com/smartcontractkit/chainlink-evm/pkg/keys"
Expand Down Expand Up @@ -211,6 +212,15 @@ func (s *Services) newSubservices(
srvs = append(srvs, gatewayConnectorWrapper)

if cfg.CRE().ConfidentialRelay().Enabled() {
var attestationValidator confidentialrelay.AttestationValidator
if cfg.CRE().ConfidentialRelay().TrustEnclaves() {
attestationValidator, ierr = passthrough.New()
if ierr != nil {
return nil, fmt.Errorf("could not create passthrough attestation validator: %w", ierr)
}
} else {
attestationValidator = confidentialrelay.NewAttestationValidator()
}
relayService := confidentialrelay.NewService(
gatewayConnectorWrapper,
opts.CapabilitiesRegistry,
Expand All @@ -219,6 +229,8 @@ func (s *Services) newSubservices(
confidentialRelayPeerID(cfg, capCfg),
lggr,
opts.LimitsFactory,
attestationValidator,
cfg.CRE().ConfidentialRelay().RequireBFTQuorum(),
)
srvs = append(srvs, relayService)
}
Expand Down
2 changes: 2 additions & 0 deletions core/web/resolver/testdata/config-empty-effective.toml
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ TLSEnabled = true

[CRE.ConfidentialRelay]
Enabled = false
TrustEnclaves = false
RequireBFTQuorum = false

[Billing]
URL = 'localhost:4319'
Expand Down
2 changes: 2 additions & 0 deletions core/web/resolver/testdata/config-full.toml
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,8 @@ TLSEnabled = true

[CRE.ConfidentialRelay]
Enabled = false
TrustEnclaves = false
RequireBFTQuorum = true

[Billing]
URL = 'localhost:4319'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ TLSEnabled = true

[CRE.ConfidentialRelay]
Enabled = false
TrustEnclaves = false
RequireBFTQuorum = false

[Billing]
URL = 'localhost:4319'
Expand Down
2 changes: 1 addition & 1 deletion deployment/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ require (
github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc
github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7
github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260618155522-3600f66e26cd
github.com/smartcontractkit/chainlink-common v0.11.2-0.20260625162847-bdf9e82b2f75
github.com/smartcontractkit/chainlink-common v0.11.2-0.20260629191530-0371c428a6cf
github.com/smartcontractkit/chainlink-common/keystore v1.2.0
github.com/smartcontractkit/chainlink-data-streams v0.1.15-0.20260522094612-5f9f748bd87a
github.com/smartcontractkit/chainlink-deployments-framework v0.111.1-0.20260612191326-e31c0ae4cd54
Expand Down
4 changes: 2 additions & 2 deletions deployment/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading