Documentation
¶
Index ¶
- Constants
- func BuildSigStructure(protectedBytes, payload []byte) ([]byte, error)
- func ComputeLeafHash(data []byte) [32]byte
- func ComputeNodeHash(left, right [32]byte) [32]byte
- func ComputeSigStructureDigest(protectedBytes, payload []byte) ([32]byte, error)
- func GenerateHeaders(receipt, statusToken []byte) http.Header
- func MatchesIdentityCert(payload *StatusTokenPayload, fingerprint [32]byte) bool
- func MatchesServerCert(payload *StatusTokenPayload, fingerprint [32]byte) bool
- func VerifyMerkleInclusion(eventBytes []byte, leafIndex, treeSize uint64, hashPath [][32]byte, ...) error
- func WalkInclusionPath(eventBytes []byte, leafIndex, treeSize uint64, hashPath [][32]byte) ([32]byte, error)
- type AgentStatus
- type CertEntry
- type CertType
- type Checkpoint
- type CheckpointError
- type CheckpointErrorType
- type CheckpointFetcher
- type Client
- type ClockFunc
- type CoseError
- type CoseErrorType
- type HTTPClient
- func (c *HTTPClient) FetchCheckpoint(ctx context.Context) ([]byte, error)
- func (c *HTTPClient) FetchReceipt(ctx context.Context, agentID string) ([]byte, error)
- func (c *HTTPClient) FetchRootKeys(ctx context.Context) ([]string, error)
- func (c *HTTPClient) FetchStatusToken(ctx context.Context, agentID string) ([]byte, error)
- type HTTPClientOption
- type HeaderSupplier
- type HeaderSupplierOption
- type Headers
- type KeyLookup
- type KeyStore
- type MergeResult
- type MerkleError
- type MerkleErrorType
- type MockClient
- func (m *MockClient) FetchCheckpoint(_ context.Context) ([]byte, error)
- func (m *MockClient) FetchReceipt(_ context.Context, agentID string) ([]byte, error)
- func (m *MockClient) FetchRootKeys(_ context.Context) ([]string, error)
- func (m *MockClient) FetchStatusToken(_ context.Context, agentID string) ([]byte, error)
- func (m *MockClient) WithCheckpoint(note []byte) *MockClient
- func (m *MockClient) WithError(key string, err error) *MockClient
- func (m *MockClient) WithReceipt(agentID string, receipt []byte) *MockClient
- func (m *MockClient) WithRootKeys(keys []string) *MockClient
- func (m *MockClient) WithStatusToken(agentID string, token []byte) *MockClient
- type OutgoingHeaders
- type ParsedCoseSign1
- type ProtectedHeader
- type ReceiptCache
- type RefreshableKeyStore
- func (r *RefreshableKeyStore) CurrentSnapshot() *KeyStore
- func (r *RefreshableKeyStore) DoRefresh(ctx context.Context) error
- func (r *RefreshableKeyStore) Get(kid [4]byte) (*TrustedKey, error)
- func (r *RefreshableKeyStore) IsEmpty() bool
- func (r *RefreshableKeyStore) LastRefreshed() *time.Time
- func (r *RefreshableKeyStore) Len() int
- func (r *RefreshableKeyStore) RefreshIfCooldownElapsed(ctx context.Context) (bool, error)
- func (r *RefreshableKeyStore) StartBackgroundRefresh(ctx context.Context, interval time.Duration) context.CancelFunc
- func (r *RefreshableKeyStore) StartBackgroundRefreshDefault(ctx context.Context) context.CancelFunc
- type RefreshableKeyStoreOption
- type SignatureError
- type SignatureErrorType
- type StatusTokenCache
- type StatusTokenPayload
- type TokenError
- type TokenErrorType
- type TransportError
- type TransportErrorType
- type TrustedKey
- type VerifiedReceipt
- type VerifiedStatusToken
Constants ¶
const ( // HeaderReceipt is the HTTP header for SCITT receipts. HeaderReceipt = "X-SCITT-Receipt" // HeaderStatusToken is the HTTP header for ANS status tokens. HeaderStatusToken = "X-ANS-Status-Token" //nolint:gosec // G101: not credentials, this is a header name constant // MaxBase64HeaderSize is the maximum allowed Base64-encoded header size. // Derived from MaxCoseInputSize defined in cose.go. MaxBase64HeaderSize = (MaxCoseInputSize + 2) / 3 * 4 //nolint:mnd // standard base64 encoding formula: ceil(n/3)*4 )
const ( DefaultRefreshInterval = 24 * time.Hour DefaultOnDemandCooldown = 5 * time.Minute )
Default refresh configuration values.
const MaxCertArrayLen = 128
MaxCertArrayLen is the maximum number of entries allowed in cert arrays.
const MaxCheckpointNoteSize = 64 << 10
MaxCheckpointNoteSize is the maximum accepted size of a checkpoint note in bytes. A checkpoint is a three-line body plus a few signature lines; the bound keeps a hostile log from handing a verifier arbitrary amounts of input.
const MaxClockSkew = 10 * time.Minute
MaxClockSkew is the maximum clock-skew tolerance for status token expiry checks. Matches the cap in verify/options.go WithClockSkewTolerance.
const MaxCoseInputSize = 1 << 20
MaxCoseInputSize is the maximum allowed size for COSE_Sign1 input (1 MiB).
const MaxHashPathLen = 63
MaxHashPathLen is the maximum number of elements in a Merkle inclusion proof path. A SHA-256 Merkle tree can have at most 2^63 leaves, requiring at most 63 path nodes.
Variables ¶
This section is empty.
Functions ¶
func BuildSigStructure ¶
BuildSigStructure constructs the COSE Sig_structure1 for signing/verification. The structure is: ["Signature1", protectedBytes, externalAad, payload]
func ComputeLeafHash ¶
ComputeLeafHash computes SHA-256(0x00 || data) per RFC 9162 section 2.1.
func ComputeNodeHash ¶
ComputeNodeHash computes SHA-256(0x01 || left || right) per RFC 9162 section 2.1.
func ComputeSigStructureDigest ¶
ComputeSigStructureDigest builds the Sig_structure1 and returns its SHA-256 digest.
func GenerateHeaders ¶
GenerateHeaders encodes receipt and statusToken as Base64 and returns them as HTTP headers. Nil or empty slices are omitted from the result.
func MatchesIdentityCert ¶
func MatchesIdentityCert(payload *StatusTokenPayload, fingerprint [32]byte) bool
MatchesIdentityCert checks if any identity cert in the payload matches the given fingerprint. Uses constant-time comparison.
func MatchesServerCert ¶
func MatchesServerCert(payload *StatusTokenPayload, fingerprint [32]byte) bool
MatchesServerCert checks if any server cert in the payload matches the given fingerprint. Uses constant-time comparison.
func VerifyMerkleInclusion ¶
func VerifyMerkleInclusion(eventBytes []byte, leafIndex, treeSize uint64, hashPath [][32]byte, expectedRoot [32]byte) error
VerifyMerkleInclusion verifies an RFC 9162 Merkle inclusion proof by walking the path and comparing the computed root against the expected root using constant-time comparison.
Types ¶
type AgentStatus ¶
type AgentStatus string
AgentStatus represents an agent's operational status.
const ( // StatusActive indicates the agent is active and fully operational. StatusActive AgentStatus = "ACTIVE" // StatusWarning indicates the agent has warnings but is still operational. StatusWarning AgentStatus = "WARNING" // StatusDeprecated indicates the agent is deprecated but still allows connections. StatusDeprecated AgentStatus = "DEPRECATED" // StatusExpired indicates the agent's registration has expired (terminal). StatusExpired AgentStatus = "EXPIRED" // StatusRevoked indicates the agent's registration has been revoked (terminal). StatusRevoked AgentStatus = "REVOKED" )
func (AgentStatus) IsTerminal ¶
func (s AgentStatus) IsTerminal() bool
IsTerminal returns true if the status is a terminal state (no recovery).
func (AgentStatus) IsValidForConnection ¶
func (s AgentStatus) IsValidForConnection() bool
IsValidForConnection returns true if the status allows new connections.
type Checkpoint ¶ added in v0.1.19
type Checkpoint struct {
// Origin is the log's unique identifier, the first line of the checkpoint.
Origin string
// Size is the number of leaves in the tree the checkpoint commits to.
Size uint64
// RootHash is the Merkle tree root over the first Size leaves.
RootHash [32]byte
// KeyID is the 4-byte key hash of the trusted key whose signature verified.
KeyID [4]byte
}
Checkpoint is the verified content of a transparency log's signed checkpoint: the tree head the log committed to under a key the caller trusts.
func VerifyCheckpoint ¶ added in v0.1.19
func VerifyCheckpoint(note []byte, keys KeyLookup) (*Checkpoint, error)
VerifyCheckpoint parses a signed checkpoint note and verifies it against the trusted keys. The note has the shape golang.org/x/mod/sumdb/note produces:
<origin>\n <decimal tree size>\n <base64 root hash>\n [extension lines] \n — <signer name> <base64(key hash || signature)>\n [further signature lines]
Each signature line names its key by the 4-byte key hash that /root-keys advertises. Every line is examined, as golang.org/x/mod/sumdb/note.Open does, so the result does not depend on line order. For every line whose key the lookup knows, the signature is checked as ASN.1 DER ECDSA over SHA-256 of the body, then as fixed-width r||s; a verifying line must also carry a signer name equal to the note's origin and to that key's Name, or the note is rejected with SigErrIssuerMismatch. The checkpoint is accepted when at least one line verifies, and KeyID names the first such key. Lines for unknown keys are skipped. A line under a known key that does not verify is tolerated only when its payload is a compact JWS envelope, which the log emits under the same key hash as its ECDSA signature; any other failing line under a known key rejects the note with SigErrSignatureInvalid wherever it appears.
When no line verifies, the error is a *SignatureError: SigErrUnknownKeyID naming the first unknown key hash when the note carried one, with the lookup's error as Cause, so a caller can decide whether to refresh its keys and retry; SigErrSignatureInvalid otherwise. Structural problems are reported as *CheckpointError before any signature is examined.
A nil error proves that a key the caller trusts signed this tree head. It does not prove that the head extends the log's earlier heads or that other clients are shown the same head; both need consistency proofs or witnesses outside this package. RootHash is comparable with a receipt's RootHash only at equal tree size and when Origin equals the Name of the key that verified the receipt; Covers performs that comparison and states what it proves.
func (*Checkpoint) Covers ¶ added in v0.1.19
func (c *Checkpoint) Covers(r *VerifiedReceipt) error
Covers reports whether the checkpoint is the tree head a receipt's inclusion path leads to. It returns nil only when the receipt's TreeSize equals Size and the root the receipt walked to equals RootHash; the root comparison runs in constant time. Callers must separately confirm that the receipt's key names this log: the Name of the key that verified the receipt equals Origin.
A nil result proves that the log signed a tree head containing the receipt's event at that size, which the receipt's own signature does not establish (see VerifyReceipt). It does not prove that the log is honest or consistent: the checkpoint is signed by the same log that signed the receipt, so a log that shows different trees to different clients is detected only by comparing checkpoints across clients or with a witness.
A size mismatch is the normal outcome when the receipt was minted against an earlier checkpoint than the one the caller holds. A log may mint a receipt once, against the first checkpoint that covers its leaf, and return it unchanged afterwards, so the receipt's TreeSize stays fixed while /checkpoint advances. The receipt is not invalid; relating the two heads needs the checkpoint signed at the receipt's TreeSize or an RFC 6962 section 2.1.2 consistency proof, which this package does not verify.
type CheckpointError ¶ added in v0.1.19
type CheckpointError struct {
Type CheckpointErrorType
Message string
Cause error
}
CheckpointError represents a checkpoint note parsing failure or a receipt the checkpoint does not cover.
func (*CheckpointError) Error ¶ added in v0.1.19
func (e *CheckpointError) Error() string
Error implements the error interface.
func (*CheckpointError) Unwrap ¶ added in v0.1.19
func (e *CheckpointError) Unwrap() error
Unwrap returns the underlying cause.
type CheckpointErrorType ¶ added in v0.1.19
type CheckpointErrorType int
CheckpointErrorType represents the type of checkpoint note parsing error or checkpoint-to-receipt binding failure.
const ( // CheckpointErrOversizedInput indicates the note exceeds MaxCheckpointNoteSize. CheckpointErrOversizedInput CheckpointErrorType = iota // CheckpointErrMalformed indicates the note does not have the signed-note shape: // a body, a blank line, and one or more well-formed signature lines. CheckpointErrMalformed // CheckpointErrInvalidSize indicates the tree size line is not a decimal uint64. CheckpointErrInvalidSize // CheckpointErrInvalidRootHash indicates the root hash line is not the base64 of 32 bytes. CheckpointErrInvalidRootHash // CheckpointErrSizeMismatch indicates a receipt's tree size differs from the // checkpoint's. The receipt is not thereby invalid; see Checkpoint.Covers. CheckpointErrSizeMismatch // CheckpointErrRootMismatch indicates a receipt's root differs from the checkpoint's. CheckpointErrRootMismatch )
type CheckpointFetcher ¶ added in v0.1.19
CheckpointFetcher fetches a transparency log's latest signed checkpoint note, the raw text VerifyCheckpoint parses. Client implementations are not required to fetch checkpoints; *HTTPClient and *MockClient implement both interfaces.
type Client ¶
type Client interface {
FetchReceipt(ctx context.Context, agentID string) ([]byte, error)
FetchStatusToken(ctx context.Context, agentID string) ([]byte, error)
FetchRootKeys(ctx context.Context) ([]string, error)
}
Client defines the interface for fetching SCITT artifacts.
type ClockFunc ¶
ClockFunc returns the current time. Default: time.Now. Override for deterministic tests.
type CoseError ¶
type CoseError struct {
Type CoseErrorType
Message string
Cause error
}
CoseError represents a COSE_Sign1 structure or parsing failure.
type CoseErrorType ¶
type CoseErrorType int
CoseErrorType represents the type of COSE_Sign1 parsing error.
const ( // CoseErrOversizedInput indicates the input exceeds the maximum allowed size. CoseErrOversizedInput CoseErrorType = iota // CoseErrNotACoseSign1 indicates the data is not a valid COSE_Sign1 structure. CoseErrNotACoseSign1 // CoseErrCborDecode indicates a CBOR decoding failure. CoseErrCborDecode // CoseErrInvalidArrayLength indicates the COSE_Sign1 array does not have exactly 4 elements. CoseErrInvalidArrayLength // CoseErrInvalidSignatureLength indicates the signature is not exactly 64 bytes (P1363). CoseErrInvalidSignatureLength // CoseErrInvalidProtectedHeader indicates the protected header is malformed. CoseErrInvalidProtectedHeader // CoseErrInvalidUnprotectedHeader indicates the unprotected header or VDP is malformed. CoseErrInvalidUnprotectedHeader // CoseErrUnsupportedAlgorithm indicates an unsupported COSE algorithm. CoseErrUnsupportedAlgorithm // CoseErrMissingKid indicates the key ID (kid) is missing from the protected header. CoseErrMissingKid )
type HTTPClient ¶
type HTTPClient struct {
// contains filtered or unexported fields
}
HTTPClient is an HTTP-based implementation of Client.
func NewHTTPClient ¶
func NewHTTPClient(baseURL string, opts ...HTTPClientOption) (*HTTPClient, error)
NewHTTPClient creates a new HTTPClient. baseURL must use the https scheme unless WithAllowInsecureTransport is supplied. Returns an error if baseURL is malformed or uses a non-https scheme without the insecure opt-in.
func (*HTTPClient) FetchCheckpoint ¶ added in v0.1.19
func (c *HTTPClient) FetchCheckpoint(ctx context.Context) ([]byte, error)
FetchCheckpoint retrieves the log's latest signed checkpoint note.
func (*HTTPClient) FetchReceipt ¶
FetchReceipt retrieves the SCITT receipt for the given agent.
func (*HTTPClient) FetchRootKeys ¶
func (c *HTTPClient) FetchRootKeys(ctx context.Context) ([]string, error)
FetchRootKeys retrieves the SCITT root signing keys (newline-delimited C2SP key strings).
func (*HTTPClient) FetchStatusToken ¶
FetchStatusToken retrieves the status token for the given agent.
type HTTPClientOption ¶
type HTTPClientOption func(*HTTPClient)
HTTPClientOption configures an HTTPClient.
func WithAllowInsecureTransport ¶
func WithAllowInsecureTransport() HTTPClientOption
WithAllowInsecureTransport returns an option that permits a non-https baseURL. Use only for tests or loopback development — production must use https.
func WithHTTPClient ¶
func WithHTTPClient(client *http.Client) HTTPClientOption
WithHTTPClient returns an option that supplies a custom *http.Client. If the client has zero Timeout, defaultTimeout (30s) is applied.
func WithHeader ¶
func WithHeader(name, value string) HTTPClientOption
WithHeader returns an option that sets a single header (overwrites existing values for that name).
func WithHeaders ¶
func WithHeaders(headers http.Header) HTTPClientOption
WithHeaders returns an option that merges the given headers (appends values).
func WithTimeout ¶
func WithTimeout(d time.Duration) HTTPClientOption
WithTimeout returns an option that overrides the request timeout. Takes precedence over any timeout set on a WithHTTPClient client.
type HeaderSupplier ¶
type HeaderSupplier struct {
// contains filtered or unexported fields
}
HeaderSupplier fetches, verifies, caches, and base64-encodes SCITT proof headers for agent-side HTTP requests. Thread-safe.
func NewHeaderSupplier ¶
func NewHeaderSupplier(agentID string, client Client, keyStore *RefreshableKeyStore, opts ...HeaderSupplierOption) *HeaderSupplier
NewHeaderSupplier creates a HeaderSupplier backed by a RefreshableKeyStore.
func NewHeaderSupplierWithStaticKeys ¶
func NewHeaderSupplierWithStaticKeys(agentID string, client Client, keyStore *KeyStore, opts ...HeaderSupplierOption) *HeaderSupplier
NewHeaderSupplierWithStaticKeys creates a HeaderSupplier using a plain KeyStore wrapped in a static (no-refresh) RefreshableKeyStore.
func (*HeaderSupplier) CurrentHeaders ¶
func (s *HeaderSupplier) CurrentHeaders() *OutgoingHeaders
CurrentHeaders returns the current base64-encoded SCITT headers. On first call, triggers lazy initialization with a timeout. Returns empty headers if init fails or times out — never blocks indefinitely.
func (*HeaderSupplier) Healthy ¶
func (s *HeaderSupplier) Healthy() bool
Healthy returns true if initialized and no recent errors.
func (*HeaderSupplier) LastError ¶
func (s *HeaderSupplier) LastError() error
LastError returns the most recent fetch or verification error.
func (*HeaderSupplier) RefreshNow ¶
func (s *HeaderSupplier) RefreshNow(ctx context.Context) error
RefreshNow forces an immediate re-fetch and verification of receipt and status token.
func (*HeaderSupplier) StartAutoRefresh ¶
func (s *HeaderSupplier) StartAutoRefresh(ctx context.Context) context.CancelFunc
StartAutoRefresh spawns a background goroutine that refreshes at 50% of the remaining token TTL (min 10s) with ±10% jitter. Returns a cancel func.
type HeaderSupplierOption ¶
type HeaderSupplierOption func(*HeaderSupplier)
HeaderSupplierOption configures a HeaderSupplier.
func WithInitTimeout ¶
func WithInitTimeout(d time.Duration) HeaderSupplierOption
WithInitTimeout sets the timeout for the lazy initialization fetch.
func WithSupplierClock ¶
func WithSupplierClock(clock ClockFunc) HeaderSupplierOption
WithSupplierClock sets the clock function.
func WithSupplierClockSkew ¶
func WithSupplierClockSkew(d time.Duration) HeaderSupplierOption
WithSupplierClockSkew sets the clock skew tolerance for token verification. Negative values are clamped to 0. Values exceeding MaxClockSkew are clamped.
func WithSupplierLogger ¶
func WithSupplierLogger(logger *slog.Logger) HeaderSupplierOption
WithSupplierLogger sets the logger.
type Headers ¶
Headers holds the decoded SCITT-related HTTP headers.
func ExtractHeaders ¶
ExtractHeaders decodes SCITT-related headers from an HTTP response. Returns an empty Headers (not an error) when neither header is present.
type KeyLookup ¶
type KeyLookup interface {
Get(kid [4]byte) (*TrustedKey, error)
}
KeyLookup retrieves a trusted key by its 4-byte key ID.
type KeyStore ¶
type KeyStore struct {
// contains filtered or unexported fields
}
KeyStore is an immutable store of trusted ECDSA P-256 keys keyed by kid.
func NewKeyStore ¶
NewKeyStore parses a slice of C2SP key strings and returns an immutable key store. Returns an error if any key fails to parse or if duplicate key IDs are found.
func (*KeyStore) Get ¶
func (s *KeyStore) Get(kid [4]byte) (*TrustedKey, error)
Get looks up a trusted key by its 4-byte key ID.
func (*KeyStore) MergeFrom ¶
func (s *KeyStore) MergeFrom(keyStrings []string) (*KeyStore, MergeResult)
MergeFrom returns a new KeyStore containing all keys from s plus any successfully parsed keys from keyStrings. Existing keys (same kid) are preserved; duplicates and unparseable strings are skipped. The original store is never modified.
type MergeResult ¶
type MergeResult struct {
Added int
Skipped int // sum of SkippedUnparseable + SkippedDuplicate
SkippedUnparseable int // strings that failed ParseC2SPKey
SkippedDuplicate int // well-formed keys whose kid already existed
Collisions []string // kid hex strings that collided with a different name
}
MergeResult reports the outcome of a MergeFrom operation.
SkippedUnparseable counts input strings that failed to parse as C2SP keys. A non-zero value indicates a potentially malformed key server response and warrants operator attention.
SkippedDuplicate counts well-formed keys that collided with an existing kid in the store. This is benign during a re-scan of the same key set.
Skipped is the sum of both counters, retained for backward compatibility.
type MerkleError ¶
type MerkleError struct {
Type MerkleErrorType
Message string
Cause error
}
MerkleError represents an RFC 9162 inclusion proof verification failure.
func (*MerkleError) Error ¶
func (e *MerkleError) Error() string
Error implements the error interface.
func (*MerkleError) Unwrap ¶
func (e *MerkleError) Unwrap() error
Unwrap returns the underlying cause.
type MerkleErrorType ¶
type MerkleErrorType int
MerkleErrorType represents the type of Merkle proof verification error.
const ( // MerkleErrInvalidProof indicates the inclusion proof is structurally invalid. MerkleErrInvalidProof MerkleErrorType = iota // MerkleErrRootMismatch indicates the computed root does not match the expected root. MerkleErrRootMismatch )
type MockClient ¶
type MockClient struct {
// contains filtered or unexported fields
}
MockClient is a mock implementation of Client for testing.
func (*MockClient) FetchCheckpoint ¶ added in v0.1.19
func (m *MockClient) FetchCheckpoint(_ context.Context) ([]byte, error)
FetchCheckpoint returns the configured checkpoint note or error.
func (*MockClient) FetchReceipt ¶
FetchReceipt returns the configured receipt or error for the given agent.
func (*MockClient) FetchRootKeys ¶
func (m *MockClient) FetchRootKeys(_ context.Context) ([]string, error)
FetchRootKeys returns the configured root keys or error.
func (*MockClient) FetchStatusToken ¶
FetchStatusToken returns the configured status token or error for the given agent.
func (*MockClient) WithCheckpoint ¶ added in v0.1.19
func (m *MockClient) WithCheckpoint(note []byte) *MockClient
WithCheckpoint configures the checkpoint note response.
func (*MockClient) WithError ¶
func (m *MockClient) WithError(key string, err error) *MockClient
WithError configures an error for the given key. Use agentID for receipt/token errors, "root-keys" for root key errors, and "checkpoint" for checkpoint errors.
func (*MockClient) WithReceipt ¶
func (m *MockClient) WithReceipt(agentID string, receipt []byte) *MockClient
WithReceipt configures a receipt for the given agent ID.
func (*MockClient) WithRootKeys ¶
func (m *MockClient) WithRootKeys(keys []string) *MockClient
WithRootKeys configures the root keys response.
func (*MockClient) WithStatusToken ¶
func (m *MockClient) WithStatusToken(agentID string, token []byte) *MockClient
WithStatusToken configures a status token for the given agent ID.
type OutgoingHeaders ¶
OutgoingHeaders holds base64-encoded SCITT proof headers ready for HTTP transport.
func (*OutgoingHeaders) IsEmpty ¶
func (h *OutgoingHeaders) IsEmpty() bool
IsEmpty returns true when neither header has a value.
func (*OutgoingHeaders) String ¶
func (h *OutgoingHeaders) String() string
String implements fmt.Stringer for logging.
func (*OutgoingHeaders) ToHTTPHeaders ¶
func (h *OutgoingHeaders) ToHTTPHeaders() (http.Header, error)
ToHTTPHeaders converts to http.Header using GenerateHeaders. The raw bytes are reconstructed from the base64 strings — this round-trip is intentional to ensure consistency with the canonical GenerateHeaders encoding. Returns an error if either base64 field is malformed.
type ParsedCoseSign1 ¶
type ParsedCoseSign1 struct {
ProtectedBytes []byte // verbatim, never re-encoded
Protected ProtectedHeader
Unprotected cbor.RawMessage
Payload []byte
Signature []byte // exactly 64 bytes P1363
}
ParsedCoseSign1 holds the decoded fields of a COSE_Sign1 structure.
func ParseCoseSign1 ¶
func ParseCoseSign1(data []byte) (*ParsedCoseSign1, error)
ParseCoseSign1 parses a CBOR-encoded COSE_Sign1 structure.
This is a hand-rolled parser rather than using veraison/go-cose because:
- go-cose does not expose custom CBOR decode options (MaxNestedLevels, MaxArrayElements, MaxMapPairs) needed for DoS protection on untrusted input.
- Custom header fields (vds=395, CWT claims) require manual parsing of RawProtected regardless, negating most of go-cose's value.
- Verbatim ProtectedBytes must be preserved without re-encoding for correct ECDSA signature verification over the exact signed bytes.
type ProtectedHeader ¶
type ProtectedHeader struct {
Alg int64
Kid [4]byte
Vds *int64
ContentType *string
CwtIss *string
CwtIat *int64
}
ProtectedHeader holds the decoded COSE protected header fields.
type ReceiptCache ¶
type ReceiptCache struct {
// contains filtered or unexported fields
}
ReceiptCache is a thread-safe cache for verified SCITT receipts, keyed by agent ID. Eviction is FIFO on insertion order: the oldest inserted entries are dropped first when maxEntries is exceeded.
func NewReceiptCache ¶
func NewReceiptCache(ttl time.Duration, maxEntries int, clock ClockFunc) *ReceiptCache
NewReceiptCache creates a new ReceiptCache with the given TTL, max entries, and clock function.
func NewReceiptCacheWithDefaults ¶
func NewReceiptCacheWithDefaults() *ReceiptCache
NewReceiptCacheWithDefaults creates a new ReceiptCache with default settings (24h TTL, 1000 entries).
func (*ReceiptCache) Get ¶
func (c *ReceiptCache) Get(agentID string) (*VerifiedReceipt, bool)
Get retrieves a cached receipt by agent ID. Returns nil, false if missing or expired.
func (*ReceiptCache) Insert ¶
func (c *ReceiptCache) Insert(agentID string, receipt *VerifiedReceipt)
Insert adds a receipt to the cache. Overwrites any existing entry for the same agent ID. Evicts entries in FIFO order if the cache exceeds maxEntries.
func (*ReceiptCache) Invalidate ¶
func (c *ReceiptCache) Invalidate(agentID string)
Invalidate removes a specific entry from the cache.
func (*ReceiptCache) Len ¶
func (c *ReceiptCache) Len() int
Len returns the number of entries in the cache.
type RefreshableKeyStore ¶
type RefreshableKeyStore struct {
// contains filtered or unexported fields
}
RefreshableKeyStore wraps a KeyStore with snapshot-based reads, on-demand refresh gated by cooldown, and optional background refresh. It implements KeyLookup and is a drop-in replacement for *KeyStore.
func NewRefreshableKeyStore ¶
func NewRefreshableKeyStore(initial *KeyStore, client Client, opts ...RefreshableKeyStoreOption) *RefreshableKeyStore
NewRefreshableKeyStore creates a new RefreshableKeyStore wrapping the given initial KeyStore. If client is nil, the store operates in static mode where all refresh operations are no-ops.
func (*RefreshableKeyStore) CurrentSnapshot ¶
func (r *RefreshableKeyStore) CurrentSnapshot() *KeyStore
CurrentSnapshot returns the current immutable KeyStore snapshot.
func (*RefreshableKeyStore) DoRefresh ¶
func (r *RefreshableKeyStore) DoRefresh(ctx context.Context) error
DoRefresh fetches root keys from the client and merges them into the current snapshot. In static mode (client == nil) this is a no-op. Network errors do not update lastRefreshed and the existing snapshot is preserved.
func (*RefreshableKeyStore) Get ¶
func (r *RefreshableKeyStore) Get(kid [4]byte) (*TrustedKey, error)
Get looks up a trusted key by kid from the current snapshot. Implements KeyLookup.
func (*RefreshableKeyStore) IsEmpty ¶
func (r *RefreshableKeyStore) IsEmpty() bool
IsEmpty returns true if the current snapshot has no keys.
func (*RefreshableKeyStore) LastRefreshed ¶
func (r *RefreshableKeyStore) LastRefreshed() *time.Time
LastRefreshed returns when the last successful refresh occurred, or nil if never.
func (*RefreshableKeyStore) Len ¶
func (r *RefreshableKeyStore) Len() int
Len returns the number of keys in the current snapshot.
func (*RefreshableKeyStore) RefreshIfCooldownElapsed ¶
func (r *RefreshableKeyStore) RefreshIfCooldownElapsed(ctx context.Context) (bool, error)
RefreshIfCooldownElapsed performs an on-demand refresh only if enough time has elapsed since the last refresh. Concurrent callers are serialized by refreshGate — only the first caller fetches, others wait and get the updated snapshot. Returns true if a refresh was actually performed.
func (*RefreshableKeyStore) StartBackgroundRefresh ¶
func (r *RefreshableKeyStore) StartBackgroundRefresh(ctx context.Context, interval time.Duration) context.CancelFunc
StartBackgroundRefresh spawns a goroutine that periodically calls DoRefresh. Returns a cancel function to stop the background goroutine.
func (*RefreshableKeyStore) StartBackgroundRefreshDefault ¶
func (r *RefreshableKeyStore) StartBackgroundRefreshDefault(ctx context.Context) context.CancelFunc
StartBackgroundRefreshDefault starts background refresh with DefaultRefreshInterval (24h).
type RefreshableKeyStoreOption ¶
type RefreshableKeyStoreOption func(*RefreshableKeyStore)
RefreshableKeyStoreOption configures a RefreshableKeyStore.
func WithCooldown ¶
func WithCooldown(d time.Duration) RefreshableKeyStoreOption
WithCooldown sets the minimum interval between on-demand refreshes.
func WithRefreshClock ¶
func WithRefreshClock(clock ClockFunc) RefreshableKeyStoreOption
WithRefreshClock sets the clock function for the refreshable key store.
func WithRefreshLogger ¶
func WithRefreshLogger(logger *slog.Logger) RefreshableKeyStoreOption
WithRefreshLogger sets the logger for refresh operations.
type SignatureError ¶
type SignatureError struct {
Type SignatureErrorType
Message string
Kid [4]byte
Cause error
}
SignatureError represents an ECDSA verification or key management failure.
func (*SignatureError) Error ¶
func (e *SignatureError) Error() string
Error implements the error interface.
func (*SignatureError) Unwrap ¶
func (e *SignatureError) Unwrap() error
Unwrap returns the underlying cause.
type SignatureErrorType ¶
type SignatureErrorType int
SignatureErrorType represents the type of signature verification error.
const ( // SigErrSignatureInvalid indicates ECDSA signature verification failed. SigErrSignatureInvalid SignatureErrorType = iota // SigErrUnknownKeyID indicates the key ID was not found in the key store. SigErrUnknownKeyID // SigErrIssuerMismatch indicates the issuer claim does not match the key. SigErrIssuerMismatch // SigErrInvalidKeyFormat indicates the key material is not in the expected format. SigErrInvalidKeyFormat // SigErrKeyHashMismatch indicates the key ID does not match the key's SHA-256 prefix. SigErrKeyHashMismatch // SigErrInvalidPublicKey indicates the public key is invalid. SigErrInvalidPublicKey // SigErrUntrustedKeyDomain indicates a domain-restricted key was used outside its trust boundary. SigErrUntrustedKeyDomain )
type StatusTokenCache ¶
type StatusTokenCache struct {
// contains filtered or unexported fields
}
StatusTokenCache is a thread-safe cache for verified status tokens, keyed by agent ID. Expiration is per-entry, derived from the token's Payload.Exp claim. Eviction is FIFO on insertion order: the oldest inserted entries are dropped first when maxEntries is exceeded.
func NewStatusTokenCache ¶
func NewStatusTokenCache(maxEntries int, clock ClockFunc) *StatusTokenCache
NewStatusTokenCache creates a new StatusTokenCache with the given max entries and clock function.
func NewStatusTokenCacheWithDefaults ¶
func NewStatusTokenCacheWithDefaults() *StatusTokenCache
NewStatusTokenCacheWithDefaults creates a new StatusTokenCache with default settings (1000 entries).
func (*StatusTokenCache) Get ¶
func (c *StatusTokenCache) Get(agentID string) (*VerifiedStatusToken, bool)
Get retrieves a cached status token by agent ID. Returns nil, false if missing or expired.
func (*StatusTokenCache) Insert ¶
func (c *StatusTokenCache) Insert(agentID string, token *VerifiedStatusToken)
Insert adds a status token to the cache. Expiration is derived from token.Payload.Exp. Overwrites any existing entry for the same agent ID. Evicts entries in FIFO order if the cache exceeds maxEntries.
func (*StatusTokenCache) Invalidate ¶
func (c *StatusTokenCache) Invalidate(agentID string)
Invalidate removes a specific entry from the cache.
func (*StatusTokenCache) Len ¶
func (c *StatusTokenCache) Len() int
Len returns the number of entries in the cache.
type StatusTokenPayload ¶
type StatusTokenPayload struct {
AgentID string
AnsName string
Status AgentStatus
Iat int64
Exp int64
ValidIdentityCerts []CertEntry
ValidServerCerts []CertEntry
MetadataHashes map[string]string
}
StatusTokenPayload is the decoded payload of a verified status token.
type TokenError ¶
type TokenError struct {
Type TokenErrorType
Message string
Status AgentStatus
Exp int64
Now int64
Cause error
}
TokenError represents a status token payload or lifecycle failure.
func (*TokenError) Error ¶
func (e *TokenError) Error() string
Error implements the error interface.
func (*TokenError) Unwrap ¶
func (e *TokenError) Unwrap() error
Unwrap returns the underlying cause.
type TokenErrorType ¶
type TokenErrorType int
TokenErrorType represents the type of status token error.
const ( // TokenErrExpired indicates the status token has expired. TokenErrExpired TokenErrorType = iota // TokenErrMissingField indicates a required field is missing from the token payload. TokenErrMissingField // TokenErrPayloadInvalid indicates the token payload structure is invalid. TokenErrPayloadInvalid // TokenErrTerminalStatus indicates the agent has a terminal status. TokenErrTerminalStatus // TokenErrPayloadEmpty indicates the token payload is empty. TokenErrPayloadEmpty )
type TransportError ¶
type TransportError struct {
Type TransportErrorType
Message string
StatusCode int
// RetryAfter is the wait the server requested through a Retry-After header
// on a 503 or 429 response, capped at 24 hours; zero when the header was
// absent or malformed.
RetryAfter time.Duration
Cause error
}
TransportError represents an HTTP client or endpoint failure.
func (*TransportError) Error ¶
func (e *TransportError) Error() string
Error implements the error interface.
func (*TransportError) ShouldFallbackToBadge ¶
func (e *TransportError) ShouldFallbackToBadge() bool
ShouldFallbackToBadge returns true if this transport error is eligible for fallback to badge-based verification. Only transient/infrastructure errors are eligible — terminal agent states and decode errors are not.
func (*TransportError) Unwrap ¶
func (e *TransportError) Unwrap() error
Unwrap returns the underlying cause.
type TransportErrorType ¶
type TransportErrorType int
TransportErrorType represents the type of HTTP transport error.
const ( // TransportErrNotFound indicates the endpoint returned 404. TransportErrNotFound TransportErrorType = iota // TransportErrAgentTerminal indicates the agent is in a terminal state (410). TransportErrAgentTerminal // TransportErrNotSupported indicates SCITT is not supported (501). TransportErrNotSupported // TransportErrHTTPError indicates a generic HTTP error. TransportErrHTTPError // TransportErrBase64Decode indicates Base64 decoding of a response failed. TransportErrBase64Decode )
type TrustedKey ¶
TrustedKey holds a named ECDSA P-256 public key with its 4-byte key ID.
func ParseC2SPKey ¶
func ParseC2SPKey(s string) (*TrustedKey, error)
ParseC2SPKey parses a C2SP-formatted key string: "name+hex_kid+base64_spki_der".
type VerifiedReceipt ¶
type VerifiedReceipt struct {
TreeSize uint64
LeafIndex uint64
// RootHash is the Merkle tree root computed from walking the inclusion path.
// IMPORTANT: This value is NOT verified against any trusted tree head.
// Callers requiring tree-head attestation MUST compare this hash to a root
// obtained out-of-band (e.g., from a witness or monitor). The ECDSA signature
// over the leaf is authoritative for leaf-level trust; the walked root alone
// does not prove inclusion in any particular published tree.
RootHash [32]byte
EventBytes []byte
KeyID [4]byte
Iss *string
Iat *int64
}
VerifiedReceipt holds the verified fields extracted from a SCITT receipt.
func VerifyReceipt ¶
func VerifyReceipt(receiptBytes []byte, keys KeyLookup) (*VerifiedReceipt, error)
VerifyReceipt parses and verifies a COSE_Sign1 SCITT receipt.
Verification order:
- Parse COSE_Sign1 structure
- Validate vds == 1 (RFC 9162)
- Look up signing key by kid
- Verify ECDSA signature (before any issuer check)
- Verify issuer binding (after signature verification)
- Extract VDP (Verifiable Data Proofs) from unprotected header
- Walk Merkle inclusion path
A nil error proves that the key named by kid signed the protected header and the attached event (EventBytes), and that the unprotected inclusion proof is well-formed and walks from that event to RootHash. It does not prove that the event is in any tree the log published: tree size, leaf index and the path are not signed, and RootHash is compared with nothing here. A caller that needs inclusion in a published tree compares RootHash with a checkpoint signed at the same tree size (Checkpoint.Covers) or relates it to a later checkpoint with an RFC 6962 consistency proof, which this package does not build.
type VerifiedStatusToken ¶
type VerifiedStatusToken struct {
Payload StatusTokenPayload
KeyID [4]byte
}
VerifiedStatusToken holds the decoded payload and key ID of a verified status token.
func VerifyStatusToken ¶
func VerifyStatusToken(tokenBytes []byte, keys KeyLookup, clockSkew time.Duration) (*VerifiedStatusToken, error)
VerifyStatusToken verifies a COSE_Sign1 status token using the current time.
func VerifyStatusTokenAt ¶
func VerifyStatusTokenAt(tokenBytes []byte, keys KeyLookup, clockSkew time.Duration, now int64) (*VerifiedStatusToken, error)
VerifyStatusTokenAt verifies a COSE_Sign1 status token at the given unix timestamp.