historysigning

package
v0.14.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Index

Constants

View Source
const CanonicalSpecVersion uint32 = 1

CanonicalSpecVersion is the current version of the canonical byte serialization spec documented in attestation.proto. Build side writes this into the payload; verify side rejects values it doesn't recognize.

Variables

This section is empty.

Functions

func BuildActivityAttestation

BuildActivityAttestation is the activity-side analogue of BuildChildAttestation. Activities have no signed history, so there is no final-signature anchor.

func BuildChildAttestation

func BuildChildAttestation(s *signer.Signer, in ChildAttestationInput) (*protos.ChildCompletionAttestation, []byte, error)

BuildChildAttestation constructs, signs, and returns a child completion attestation ready to attach to an outbound ChildWorkflowInstanceCompleted or ChildWorkflowInstanceFailed event. The returned certChainDER is the companion certificate to include alongside the attestation on the wire (receivers strip it after absorbing into their external cert table).

func BuildSignedChunk

func BuildSignedChunk(chunk *protos.PropagatedHistoryChunk, rawSigs, certChainsDER [][]byte) error

BuildSignedChunk attaches a producer app's signing artifacts to a single PropagatedHistoryChunk that already has RawEvents populated by the assembler. rawSigs is the deterministic raw bytes of every HistorySignature covering chunk.RawEvents in order. certChainsDER is the DER-concatenated cert chain for each signing identity referenced by the signatures' certificateIndex. Receivers digest chunk.RawEvents directly and never re-marshal, so chunk verification does not depend on protobuf marshaler- version stability across producer and receiver - the signed bytes travel verbatim end-to-end.

func CanonicalFailureOutput

func CanonicalFailureOutput(fd *protos.TaskFailureDetails) ([]byte, error)

CanonicalFailureOutput returns the canonical bytes for a FAILED invocation's output: a spec-defined serialization of TaskFailureDetails independent of protobuf wire format. See the "Output bytes, FAILED" section of the spec block in attestation.proto for the exact rules.

Returns an error when the innerFailure chain is deeper than maxFailureRecursionDepth (DoS guard against attacker-supplied malformed chains). A nil input is serialized as a TaskFailureDetails with all fields at their zero values. This keeps the output deterministic rather than short-circuiting.

func CanonicalInput

func CanonicalInput(sv *wrapperspb.StringValue) []byte

CanonicalInput returns the canonical bytes for an invocation's input as defined in attestation.proto: NFC-normalized UTF-8 bytes of the StringValue's value field, or zero-length when the wrapper is unset. NFC normalization ensures semantically equal strings produced by different-language SDKs hash identically.

func CanonicalSuccessOutput

func CanonicalSuccessOutput(sv *wrapperspb.StringValue) []byte

CanonicalSuccessOutput returns the canonical bytes for a COMPLETED invocation's output. Same rule as CanonicalInput.

func CertDigest

func CertDigest(certChainDER []byte) []byte

CertDigest computes the signerCertDigest as defined in attestation.proto: sha256 of the DER-encoded X.509 certificate chain bytes (leaf first, intermediates concatenated). Computed directly over DER bytes, never over a protobuf envelope, so the digest is stable across protobuf version changes.

func EventsDigest

func EventsDigest(rawEvents [][]byte) []byte

EventsDigest computes the SHA-256 digest of pre-marshaled history event bytes. Each event is length-prefixed (big-endian uint64) before being written to the hash, preventing ambiguity from concatenation. The raw bytes should come from MarshalEvent (at sign time) or directly from the state store (at verification time).

func IODigest

func IODigest(inputBytes, outputBytes []byte) []byte

IODigest computes the ioDigest as defined in attestation.proto: sha256( u64be(len(input)) || input || u64be(len(output)) || output ). Input and output bytes must already be in canonical form from CanonicalInput / CanonicalSuccessOutput / CanonicalFailureOutput as appropriate for the invocation's terminal status.

func MarshalEvent

func MarshalEvent(event *protos.HistoryEvent) ([]byte, error)

MarshalEvent deterministically marshals a HistoryEvent to bytes. The output is stable for a given message within the same binary, making it suitable for signing. Events should be marshaled once and the resulting bytes used for both signing and persistence.

func PayloadSignatureInput

func PayloadSignatureInput(payloadBytes []byte) []byte

PayloadSignatureInput is the exact byte sequence the signer signs over (and the verifier re-hashes) for both child and activity attestations: sha256(payloadBytes). The payload bytes are the deterministic marshal of the inner ...Payload message, produced once on the build side and treated as opaque bytes thereafter.

func SignatureDigest

func SignatureDigest(rawSig []byte) []byte

SignatureDigest computes the SHA-256 digest of a raw serialized HistorySignature message. This is the value used in previousSignatureDigest chaining. The rawSig bytes must be the exact bytes as persisted to the state store — never re-marshal a Go struct, as protobuf deterministic marshaling is not stable across binary versions.

func SignatureInput

func SignatureInput(previousSignatureDigest, eventsDigest []byte, ctx *PropagationContext) []byte

SignatureInput computes the input to the cryptographic signing operation: SHA-256(previousSignatureDigest || eventsDigest || ctxBytes), where ctxBytes is the length-prefixed serialization of ctx's fields when ctx is non-nil. Own-history signing passes nil; propagation signing passes a fully populated *PropagationContext.

func VerifyActivityAttestation

func VerifyActivityAttestation(opts VerifyActivityOptions) (*protos.ActivityCompletionAttestationPayload, error)

VerifyActivityAttestation is the activity-side analogue of VerifyChildAttestation. Additionally cross-checks the activity name.

func VerifyCertAppIdentity

func VerifyCertAppIdentity(certChainDER []byte, expectedAppID, expectedNamespace string) error

VerifyCertAppIdentity checks that a DER-encoded signing certificate chain has a leaf SPIFFE ID whose namespace and app components match expectedNamespace and expectedAppID. SPIFFE IDs follow spiffe://<trust-domain>/ns/<namespace>/<app-id>; this validates the full path structure rather than just the trailing segment. Both expected values must be non-empty - the namespace check stops a holder of a Sentry-issued cert for the same app-id in a different namespace from claiming to be the expected producer. Lifted into historysigning so chunk identity checks (in VerifyPropagatedHistory) and own-history identity checks (in dapr's state.go verifySignatureChain) share one implementation.

func VerifyChain

func VerifyChain(opts VerifyChainOptions) (map[uint64]struct{}, error)

VerifyChain walks the full signature chain and verifies each signature, including chain linkage via previousSignatureDigest, contiguity of event ranges, and certificate chain-of-trust against trust anchors. The allRawEvents slice must contain the raw marshaled bytes as stored in the state store.

On success, the returned set contains the indices into opts.Certs that were referenced by at least one successfully verified signature. Callers that need to surface verified certs (e.g. propagation chunk verification absorbing foreign certs) can use this directly instead of re-parsing signatures.

func VerifyChildAttestation

func VerifyChildAttestation(opts VerifyChildOptions) (*protos.ChildCompletionAttestationPayload, error)

VerifyChildAttestation verifies a child completion attestation end to end: payload parse, required-field presence, spec version, cert/digest binding, chain-of-trust at EventTimestamp, signature, parent binding, and ioDigest against the claimed input/output. Returns the parsed payload on success for callers that want to read its fields (e.g., terminal status, final signature digest).

func VerifySignature

func VerifySignature(s *signer.Signer, sig *protos.HistorySignature, certs []*protos.SigningCertificate, allRawEvents [][]byte, ctx *PropagationContext) error

VerifySignature verifies a single HistorySignature against the raw event bytes and certificate table. The allRawEvents slice must contain the exact raw bytes as stored in the state store for every history event, in order. Never re-marshal events from Go structs for verification, as protobuf deterministic marshaling is not stable across binary versions.

Pass nil for ctx when verifying own-history signatures. Propagation chunks must pass the same *PropagationContext the signer used.

Types

type ActivityAttestationInput

type ActivityAttestationInput struct {
	ParentInstanceId      string
	ParentTaskScheduledId int32
	ActivityName          string
	Input                 *wrapperspb.StringValue
	Output                *wrapperspb.StringValue
	FailureDetails        *protos.TaskFailureDetails
	TerminalStatus        protos.ActivityTerminalStatus
}

ActivityAttestationInput carries everything BuildActivityAttestation needs. Exactly one of Output / FailureDetails should be set, matching TerminalStatus.

type ChildAttestationInput

type ChildAttestationInput struct {
	// ParentInstanceId is the instance ID of the parent workflow that
	// scheduled this child.
	ParentInstanceId string
	// ParentTaskScheduledId is the taskScheduledId from the parent's
	// ChildWorkflowInstanceCreatedEvent.
	ParentTaskScheduledId int32
	// Input is the StringValue that was delivered to the child in its
	// ExecutionStartedEvent.input.
	Input *wrapperspb.StringValue
	// Output is the child's result on COMPLETED; nil otherwise.
	Output *wrapperspb.StringValue
	// FailureDetails is the child's failure on FAILED; nil otherwise.
	FailureDetails *protos.TaskFailureDetails
	// TerminalStatus records how the child ended.
	TerminalStatus protos.TerminalStatus
}

ChildAttestationInput carries everything BuildChildAttestation needs. Exactly one of Output / FailureDetails should be set, depending on TerminalStatus: COMPLETED uses Output; FAILED uses FailureDetails.

type PropagationContext

type PropagationContext struct {
	InstanceID   string
	WorkflowName string
}

PropagationContext binds a propagated-history chunk's instance and workflow name into the signature input so a legitimately signed chunk can't be lifted from state and replayed under a different instanceId or workflowName.

type PropagationVerifyResult

type PropagationVerifyResult struct {
	// VerifiedCerts maps sha256(certChainDER) -> certChainDER for every
	// signing cert that (a) was referenced by at least one verified
	// HistorySignature.certificateIndex and (b) passed chain-of-trust during
	// this call. Unreferenced cert chains attached to a chunk are not
	// included. The digest is the raw 32-byte sha256 as a string key.
	VerifiedCerts map[string][]byte
}

PropagationVerifyResult is the outcome of a successful VerifyPropagatedHistory call. It surfaces the certs that were referenced by verified signatures so the caller can absorb them into a content-addressed foreign-cert table (ext-sigcert).

func VerifyPropagatedHistory

func VerifyPropagatedHistory(opts VerifyPropagationOptions) (*PropagationVerifyResult, error)

VerifyPropagatedHistory verifies every chunk in a PropagatedHistory. Each chunk is self-contained - it carries its own rawEvents along with rawSignatures and signingCertChains - so chunks are verified independently of one another. For each chunk the function checks:

  • chunk is non-nil and chunk.appId is non-empty
  • if chunk.rawEvents is empty, no signatures or cert chains are attached (otherwise the producer is lying about what they signed)
  • chunk.rawSignatures and chunk.signingCertChains are present
  • each cert chain's leaf SPIFFE ID app and namespace components match chunk.appId and opts.ExpectedNamespace, so a signer cannot impersonate another app or claim cross-namespace identity
  • the signatures form a chain-linked cover of chunk.rawEvents (via VerifyChain over the signed bytes; rawEvents are digested directly, never re-marshaled, so verification is independent of protobuf marshaler-version stability across producer and receiver)
  • chain-of-trust to a Sentry trust anchor

On success, VerifiedCerts contains every cert that was referenced by a verified signature (by certificateIndex), so callers can absorb them into a content-addressed foreign-cert table. Failure on any chunk returns an error and no partial result; receivers must reject the whole payload.

type SignOptions

type SignOptions struct {
	// RawEvents is the deterministically marshaled bytes of each event to sign.
	// These must come from MarshalEvent.
	RawEvents [][]byte
	// StartEventIndex is the index of the first event in the overall history.
	StartEventIndex uint64
	// PreviousSignatureRaw is the raw serialized bytes of the previous
	// HistorySignature in the chain (nil for root). These must be the exact
	// bytes from the state store or from SignResult.RawSignature.
	PreviousSignatureRaw []byte
	// ExistingCerts is the current certificate table.
	ExistingCerts []*protos.SigningCertificate
	// PropagationContext is the optional binding fed into SignatureInput
	// so metadata that lives outside RawEvents (the chunk's declared
	// identity) is part of what was signed. Nil for own-history
	// signatures, populated for propagated-history chunks. Verifiers
	// must reconstruct an equivalent context and supply it via
	// VerifyChainOptions.PropagationContext.
	PropagationContext *PropagationContext
}

SignOptions are the parameters for a signing operation.

type SignResult

type SignResult struct {
	// Signature is the new HistorySignature entry.
	Signature *protos.HistorySignature

	// RawSignature is the deterministically marshaled bytes of the Signature.
	// These are the exact bytes that should be persisted to the state store
	// and used for digest computation in chain linking.
	RawSignature []byte

	// NewCert is non-nil only when the certificate rotated and a new
	// SigningCertificate entry needs to be appended to the certificate table.
	NewCert *protos.SigningCertificate

	// CertificateIndex is the index used in the signature's certificate_index field.
	CertificateIndex uint64
}

SignResult is the output of a signing operation.

func Sign

func Sign(s *signer.Signer, opts SignOptions) (*SignResult, error)

Sign creates a HistorySignature covering a range of events. The RawEvents field must contain the deterministically marshaled bytes of each event in the range (from MarshalEvent). It chains to the previous signature (if any) and resolves the certificate index against the existing certificate table.

type VerifyActivityOptions

type VerifyActivityOptions struct {
	Attestation                   *protos.ActivityCompletionAttestation
	SignerCertDER                 []byte
	EventTimestamp                time.Time
	ExpectedParentInstanceId      string
	ExpectedParentTaskScheduledId int32
	ExpectedActivityName          string
	ClaimedInput                  *wrapperspb.StringValue
	ClaimedOutput                 *wrapperspb.StringValue
	ClaimedFailure                *protos.TaskFailureDetails
	Signer                        *signer.Signer
	// ChainOfTrustVerifiedExternally signals that the caller has already
	// verified the signer cert chain independently. See
	// VerifyChildOptions.ChainOfTrustVerifiedExternally.
	ChainOfTrustVerifiedExternally bool
}

VerifyActivityOptions are the parameters for VerifyActivityAttestation.

type VerifyChainOptions

type VerifyChainOptions struct {
	// RawSignatures is the raw serialized bytes of each HistorySignature,
	// as stored in the state store or from SignResult.RawSignature. These
	// are the single source of truth - they are both parsed into
	// HistorySignature structs and used for digest computation in chain
	// linking.
	RawSignatures [][]byte
	// Certs is the certificate table (sigcert entries).
	Certs []*protos.SigningCertificate
	// AllRawEvents is the raw marshaled bytes of all history events, in order.
	AllRawEvents [][]byte
	// Signer provides cryptographic verification and certificate chain-of-trust
	// checking.
	Signer *signer.Signer
	// PropagationContext is the optional binding fed into SignatureInput
	// at sign time (see SignOptions.PropagationContext). Nil for
	// own-history chains, non-nil for propagation chunks.
	PropagationContext *PropagationContext
}

VerifyChainOptions are the parameters for chain verification.

type VerifyChildOptions

type VerifyChildOptions struct {
	// Attestation is the wrapper to verify.
	Attestation *protos.ChildCompletionAttestation
	// SignerCertDER is the companion signing certificate (DER chain) that
	// travelled alongside the attestation. Must hash to the payload's
	// signerCertDigest field.
	SignerCertDER []byte
	// EventTimestamp is the timestamp used for the certificate-validity
	// check. See the "Certificate validity" section of the spec block in
	// attestation.proto for the correct source at ingestion vs. stored
	// history.
	EventTimestamp time.Time
	// ExpectedParentInstanceId is the instance ID of the workflow
	// performing verification. The attestation's parentInstanceId must
	// match this value exactly.
	ExpectedParentInstanceId string
	// ExpectedParentTaskScheduledId is the taskScheduledId this attestation
	// is claimed to correspond to (from a signed
	// ChildWorkflowInstanceCreatedEvent in the verifier's history).
	ExpectedParentTaskScheduledId int32
	// ClaimedInput is the input bytes the verifier believes B was invoked
	// with (taken from the ChildWorkflowInstanceCreatedEvent in the
	// verifier's signed history). Compared against the payload's ioDigest
	// after canonicalization.
	ClaimedInput *wrapperspb.StringValue
	// ClaimedOutput is the result bytes the verifier has (from the
	// ChildWorkflowInstanceCompletedEvent) when TerminalStatus is
	// COMPLETED. Nil otherwise.
	ClaimedOutput *wrapperspb.StringValue
	// ClaimedFailure is the failure details the verifier has (from the
	// ChildWorkflowInstanceFailedEvent) when TerminalStatus is FAILED.
	// Nil otherwise.
	ClaimedFailure *protos.TaskFailureDetails
	// Signer provides cryptographic verification and certificate chain-of-
	// trust checking against Sentry trust anchors.
	Signer *signer.Signer
	// ChainOfTrustVerifiedExternally signals that the caller has already
	// verified the signer cert chain independently (e.g. via a per-
	// orchestrator cache keyed by signerCertDigest) and that
	// VerifyCertChainOfTrust should be skipped. Signature, digest, and
	// parent binding checks are still performed. Use only when the caller
	// can guarantee the cert chain was previously verified at a time
	// covered by EventTimestamp.
	ChainOfTrustVerifiedExternally bool
}

VerifyChildOptions are the parameters for VerifyChildAttestation.

type VerifyPropagationOptions

type VerifyPropagationOptions struct {
	// History is the propagated payload to verify. Must not be nil.
	History *protos.PropagatedHistory

	// Signer provides cryptographic verification and certificate
	// chain-of-trust checking against Sentry trust anchors.
	Signer *signer.Signer

	// ExpectedNamespace is the namespace each chunk's signing cert must be
	// scoped to. Without this, a holder of a Sentry-issued cert for the
	// same app-id in a different namespace could forge propagation chunks.
	// Must be non-empty.
	ExpectedNamespace string
}

VerifyPropagationOptions configures VerifyPropagatedHistory.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL