Documentation
¶
Overview ¶
Package aci implements a client for the Dstack private-ai-gateway "aci/1" confidential-inference protocol. This file defines the attestation failure reasons and the typed errors the package returns.
Every attestation-chain failure is reported as the provider-neutral, fail-closed *llm.AttestationError (defined in pkg/llm); for those this package only supplies the reason strings and thin constructors. The one exported error type it does introduce is UnpinnedPolicyError, which is deliberately NOT an *llm.AttestationError: it is a pre-attestation configuration refusal (the supplied Policy pins nothing) raised before any chain runs, not an attestation result.
Index ¶
- Constants
- func Canonicalize(v Value) ([]byte, error)
- func CompactJSON(v Value) ([]byte, error)
- func New(baseURL, apiKey string, policy Policy, opts ...Option) (inference.Client, error)
- func Sha256Hex(v Value) (string, error)
- func Sha256HexBytes(b []byte) (string, error)
- func Sha256Raw(v Value) ([32]byte, error)
- func VerifyReceipt(receiptJSON []byte, verified *VerifiedReport, expect ReceiptExpect) error
- type Array
- type Attestation
- type Bool
- type Client
- type EventLogEntry
- type Evidence
- type Float
- type FloatNotAllowedError
- type FloatOutOfDomainError
- type Freshness
- type Int
- type InvalidUTF8Error
- type KeyCustody
- type KeyCustodyEntry
- type KeyEntry
- type Keyset
- type KeysetEndorsement
- type KeysetEpoch
- type NonFiniteFloatError
- type Null
- type Number
- type Object
- type Option
- type Policy
- type ProvenanceKey
- type PublicKey
- type Receipt
- type ReceiptEvent
- type ReceiptExpect
- type ReceiptSignature
- type Report
- type ServiceCapabilities
- type SourceProvenance
- type String
- type TLSBinding
- type Uint
- type UnpinnedPolicyError
- type Value
- type VerifiedReport
- type WorkloadIdentity
Constants ¶
const SupportedAPIVersion = "aci/1"
SupportedAPIVersion is the only Dstack ACI wire api_version this client speaks. A report declaring any other version is rejected (reasonUnsupportedAPIVersion) — the version-drift tripwire.
Variables ¶
This section is empty.
Functions ¶
func Canonicalize ¶
Canonicalize emits the constrained-JCS canonical UTF-8 encoding of v. Object keys are sorted by UTF-16 code units at every level; numbers must be integers. It returns a *FloatNotAllowedError if any number violates the integer rule.
func CompactJSON ¶
CompactJSON emits the compact, order-preserving JSON encoding of v, matching serde_json::to_vec (preserve_order) byte-for-byte for the body-hash profile. Object keys are emitted in insertion order; non-integer floats are emitted via the Float variant. It returns a *NonFiniteFloatError for a NaN/Inf Float, a *FloatNotAllowedError for a non-integer Number (only Float carries fractionals), an *InvalidUTF8Error for a malformed string/key, or a *nilValueError for a nil Value.
func New ¶
New builds a Client and returns it as the inference.Client it implements. baseURL is the gateway origin (e.g. https://gateway.example); apiKey is the bearer token; policy is the attestation acceptance allow-list. Defaults — a timed *http.Client (TLS >= 1.2), time.Now, the live DCAP quote verifier, a crypto/rand nonce source, and a session cache wrapping the production attest — are applied first, then options override. Order matters: the cache binds the clock, so WithNow (when supplied) must take effect before the default cache is built; we therefore apply options that may need the clock after seeding the defaults, and (re)build the default cache only if no WithAttestFunc replaced it.
New FAILS CLOSED on an unpinned policy: if policy pins no acceptance set and did not explicitly opt out via UnpinnedPolicy(), New returns (nil, *UnpinnedPolicyError) BEFORE any network object is constructed, so attestation can never be silently accepted against an empty allow-list.
func Sha256Hex ¶
Sha256Hex returns the canonical digest as "sha256:" + lowercase hex of the SHA-256 of the canonical encoding of v.
func Sha256HexBytes ¶
Sha256HexBytes returns "sha256:" + lowercase hex of the SHA-256 of the given bytes. It is the receipt body-hash function over an already-serialized compact body (sha256_hex in the Rust reference), distinct from Sha256Hex which hashes the CANONICAL (JCS) encoding of a Value. Callers pass CompactJSON(body) here.
func VerifyReceipt ¶
func VerifyReceipt(receiptJSON []byte, verified *VerifiedReport, expect ReceiptExpect) error
VerifyReceipt verifies a signed §9 receipt against an attested VerifiedReport and the caller's expectations, enforcing every mandatory binding fail-closed.
It accepts the receipt JSON either bare ({...}) or wrapped ({"receipt": {...}}) — when a top-level "receipt" object key is present it is unwrapped first. It then verifies the signature (Task 4.1) and, only on a valid signature, the identity / request-body / response-hash / upstream bindings. The first failing check wins: identity, body, and response misses (and the signature) return the fail-closed *llm.AttestationError with reason receipt_invalid; an absent or non-matching upstream.verified event returns reason upstream_unverified. The returned error wraps a typed cause and never carries secret material. It returns nil only when ALL mandatory bindings hold.
Types ¶
type Attestation ¶
type Attestation struct {
Vendor string `json:"vendor"`
TEEType string `json:"tee_type"`
Keyset Keyset `json:"workload_keyset"`
ReportDataHex string `json:"report_data"`
KeysetEndorsement KeysetEndorsement `json:"keyset_endorsement"`
SourceProvenance SourceProvenance `json:"source_provenance"`
Freshness Freshness `json:"freshness"`
Evidence Evidence `json:"evidence"`
}
Attestation is the report's attestation envelope: the workload keyset, the report_data the quote binds to, the keyset endorsement, build provenance, freshness window, and the TEE evidence (quote + event log + custody).
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the ACI confidential-inference client. It attests each model (cached), seals requests E2EE to the attested model key, POSTs them, opens the sealed response, and verifies the signed receipt before decoding — returning a provider-neutral *inference.Response only when every check passes.
It implements inference.Client. It is safe for concurrent use: the only mutable shared state is the session cache, which is internally synchronized.
Connection-binding note (fail-safe asymmetry): unlike the generic transport.Client — which binds one Endpoint and rejects a request whose Model.Provider/BaseURL differs with a pre-I/O *failure.ModelMismatchError — this client binds its gateway endpoint at construction (New's baseURL) and enforces model identity per request via TEE attestation. A provider/endpoint mismatch therefore surfaces as an *AttestationError (attestation cannot succeed against the wrong model/gateway), not a *failure.ModelMismatchError. This is fail-safe: the request is never sent when the check fails; only the error type differs.
func (*Client) Invoke ¶
Invoke runs the buffer-until-verified flow for a single non-streaming chat request and returns the decoded *inference.Response, or a typed error and a NIL response on ANY failure. See the file header for the ordered stages; the decode runs ONLY after VerifyReceipt passes, so a verification failure never yields a partial response.
func (*Client) Stream ¶
func (c *Client) Stream(ctx context.Context, req inference.Request) (*stream.StreamReader[content.Chunk], error)
Stream runs the buffer-until-verified flow for a streaming chat request. It buffers the FULL sealed SSE response, opens + verifies it (receipt signature + request body_hash + wire_hash + upstream + the E2EE-authenticated open of every delta), and ONLY THEN returns a *stream.StreamReader replaying the already-verified opened deltas. On ANY failure it returns a typed error and a NIL reader, so the caller observes ZERO chunks — no unverified delta is ever observable.
Streaming SKIPS the receipt cleartext_hash check (RespBodyCleartext is nil): the client only sees the sealed WIRE bytes, not the raw upstream SSE framing the gateway hashed for cleartext_hash, so it cannot reconstruct that preimage. wire_hash (over the exact wire bytes) plus the per-delta AEAD open — each delta AAD-bound to the attested gateway — carry content authenticity instead.
type EventLogEntry ¶
type EventLogEntry struct {
IMR uint32 `json:"imr"`
EventType uint32 `json:"event_type"`
Digest string `json:"digest"`
Event string `json:"event"`
EventPayload string `json:"event_payload"`
}
EventLogEntry is one parsed event-log record. evidence.event_log is a raw JSON-in-string at THIS layer; this type is modeled now so Task 2.5 can decode the log into it. The serde rename type -> EventType applies here (Go has no "type" field name and the wire key stays "event_type").
type Evidence ¶
type Evidence struct {
Quote string `json:"quote"`
QuoteReportData string `json:"quote_report_data"`
EventLog string `json:"event_log"`
VMConfig string `json:"vm_config"`
KeyCustody KeyCustody `json:"key_custody"`
DownstreamTLSBinding TLSBinding `json:"downstream_tls_binding"`
}
Evidence is the TEE evidence: the TDX quote and its report_data, the raw (double-encoded) event log and VM config strings, the KMS key-custody chain, and the downstream TLS binding. quote/event_log/vm_config stay raw strings at this layer; later tasks parse them.
type Float ¶
type Float float64
Float is a NON-integer JSON number used ONLY by the compact body serializer (CompactJSON), not by the JCS canonical path. Real chat-request bodies carry fractional sampling params (temperature/top_p, e.g. 0.7, 0.9), so the body hash must serialize floats; the constrained JCS profile forbids them. Float therefore lives in the shared Value union but is REJECTED by Canonicalize (it surfaces *FloatNotAllowedError there) and accepted only by CompactJSON. See body.go for the emission rules and their validated serde_json domain.
type FloatNotAllowedError ¶
type FloatNotAllowedError struct {
// Literal is the offending JSON number literal (e.g. "1.5", "1e5", "+1",
// "01"). It is a numeric token only and carries no secret material.
Literal string
}
FloatNotAllowedError reports a JSON number that is not an acceptable integer under the constrained JCS profile: a fraction, an exponent, a value outside the i64 ∪ u64 range, OR a literal that is not in canonical JSON-integer form (a leading '+' or a leading zero such as "01"). It carries the offending decimal literal so callers can inspect the cause; it is the Go analogue of the Rust CanonicalError variant FloatNotAllowed. It is an internal canonicalization failure, distinct from the protocol-level *llm.AttestationError reasons.
The grammar gate matters off the JSON-parse path: serde_json (and Go's json.Decoder) only ever produce grammar-valid number literals, so on the parse path the looser cases never occur. But a programmatically-constructed Number("+1") / Number("01") must fail closed rather than be silently normalized — a digest validator must reject anything it would not have emitted.
func (*FloatNotAllowedError) Error ¶
func (e *FloatNotAllowedError) Error() string
type FloatOutOfDomainError ¶
type FloatOutOfDomainError struct {
// Value is the shortest decimal form of the offending magnitude (e.g.
// "1e+16"). It is a numeric label and carries no payload bytes.
Value string
}
FloatOutOfDomainError reports a finite Float whose magnitude falls in the range where serde_json (ryu) emits EXPONENT form rather than plain decimal — |x| >= 1e16 or 0 < |x| < 1e-5. In that range serde's spelling (e.g. "1e+16", "1e-6") cannot be reproduced by Go's shortest-float emitter without re-deriving ryu's exact exponent rules, so this serializer REFUSES to emit such a value rather than risk a wrong body hash. The realistic sampling-parameter domain (temperature/top_p ∈ [0,2]) lies entirely inside the decimal-form window, so this never fires on a real body; it is the runtime form of Task 1.3's "blocker #2: STOP and report" rule — a value here must be re-pinned against a fresh Rust vector before its hash can be trusted.
It carries the offending value's shortest decimal form (a numeric label, no secret material) so the caller can identify it.
func (*FloatOutOfDomainError) Error ¶
func (e *FloatOutOfDomainError) Error() string
type Freshness ¶
Freshness is the report's validity window in Unix seconds. Both bounds fit int64 comfortably (they are wall-clock seconds, not the epoch sentinel).
type InvalidUTF8Error ¶
type InvalidUTF8Error struct {
// Where names the location of the fault: "string" for a String value or
// "object key" for a member key. It is a fixed label, never external data.
Where string
}
InvalidUTF8Error reports a String value or object key that is not valid UTF-8. The emitter writes string bytes verbatim while the key sort decodes them via []rune (which folds invalid sequences to U+FFFD); those two views diverge on malformed input, so a digest validator must reject it rather than risk an order/encoding mismatch. On the JSON-parse path this never occurs (json guarantees valid UTF-8), but Task 1.3 builds String values programmatically.
It deliberately does NOT echo the offending bytes (they may be arbitrary, possibly secret-adjacent payload); it reports only where the fault was found.
func (*InvalidUTF8Error) Error ¶
func (e *InvalidUTF8Error) Error() string
type KeyCustody ¶
type KeyCustody struct {
Provider string `json:"provider"`
Keys []KeyCustodyEntry `json:"keys"`
}
KeyCustody is the KMS custody record: which provider holds the workload keys and, per key, the signature chain proving custody back to the KMS root.
type KeyCustodyEntry ¶
type KeyCustodyEntry struct {
Role string `json:"role"`
Path string `json:"path"`
Purpose string `json:"purpose"`
Algo string `json:"algo"`
PublicKeyHex string `json:"public_key"`
SignatureChain []string `json:"signature_chain"`
}
KeyCustodyEntry is one custody record: the key's role/path/purpose, its algorithm and hex public key, and the signature chain (hex links) binding it to the KMS root. public_key -> PublicKeyHex applies the serde rename.
type KeyEntry ¶
type KeyEntry struct {
KeyID string `json:"key_id"`
Algo string `json:"algo"`
PublicKeyHex string `json:"public_key"`
}
KeyEntry is one entry in the receipt-signing and E2EE key lists: a key id, its algorithm, and the hex-encoded public key. The two lists share this shape, so they share the type. public_key -> PublicKeyHex applies the serde rename.
type Keyset ¶
type Keyset struct {
Identity WorkloadIdentity `json:"workload_identity"`
Epoch KeysetEpoch `json:"keyset_epoch"`
ReceiptSigningKeys []KeyEntry `json:"receipt_signing_keys"`
E2EEPublicKeys []KeyEntry `json:"e2ee_public_keys"`
TLSPublicKeys []TLSBinding `json:"tls_public_keys"`
}
Keyset is the workload's published key material: the workload identity key, the epoch (version + expiry), and the receipt/E2EE/TLS key lists the gateway uses to sign receipts, accept E2EE envelopes, and bind TLS endpoints.
type KeysetEndorsement ¶
KeysetEndorsement is the signature over the workload keyset by the KMS root. value -> ValueHex applies the serde rename.
type KeysetEpoch ¶
KeysetEpoch is the keyset's monotonic epoch and its expiry. NotAfter is uint64 because the fixture carries 2^64-1 (a "never expires" sentinel) which does not fit int64. Version is uint64 for symmetry with the unsigned epoch.
type NonFiniteFloatError ¶
type NonFiniteFloatError struct {
// Repr is the Go string form of the non-finite value ("NaN", "+Inf",
// "-Inf"). It is a fixed numeric label and carries no payload bytes.
Repr string
}
NonFiniteFloatError reports a Float value that is NaN or ±Inf. JSON has no representation for non-finite numbers (serde_json's Number cannot even hold them — Number::from_f64 returns None), so a non-finite Float can never appear in a real body and must fail closed rather than emit a non-JSON token. It is typed (per CLAUDE.md's no-bare-error rule) and carries the Go textual form of the offending value ("NaN", "+Inf", "-Inf") — a numeric label, never secret material.
func (*NonFiniteFloatError) Error ¶
func (e *NonFiniteFloatError) Error() string
type Number ¶
Number is an integer JSON number carried verbatim as the source decimal literal (e.g. produced by the parser, which defers the i64-then-u64 check to emit time). Canonicalize validates it: a non-integer or out-of-range literal yields a *FloatNotAllowedError, matching the Rust reference's as_i64/as_u64 fallthrough.
type Object ¶
type Object struct {
// contains filtered or unexported fields
}
Object is a JSON object stored as ordered key/value pairs in insertion order. JCS key sorting is applied by the emitter, not by this type, so the same Object can be emitted in insertion order by a non-sorting serializer.
func NewObject ¶
func NewObject() *Object
NewObject returns an empty Object ready for ordered Set calls.
func (*Object) KeyAt ¶
KeyAt returns the key of the i-th member in insertion order. It panics on an out-of-range index, matching slice-indexing semantics.
func (*Object) Set ¶
Set inserts or updates a (key, value) pair and returns the Object so calls can chain. It follows IndexMap / serde_json(preserve_order) semantics: a new key is appended (preserving insertion order); an existing key keeps its original position and has its value overwritten (last value wins). This guarantees an Object never holds duplicate keys, so the emitter can never produce invalid JSON — the same parity serde_json yields on the deserialize path. The canonical emitter is still responsible for the UTF-16 key SORT; Set only dedups.
type Option ¶
type Option func(*Client)
Option configures a Client at construction. Only the knobs production or tests actually use are exposed; nothing speculative.
func WithAttestFunc ¶
func WithAttestFunc(attest attestFunc) Option
WithAttestFunc overrides the per-model attestation the session cache wraps. Tests supply a fake that returns a synthetic *VerifiedReport, bypassing the real attestation chain (already covered by Phase-2 tests).
func WithHTTPDoer ¶
func WithHTTPDoer(d httpDoer) Option
WithHTTPDoer sets the HTTP transport (the gateway seam). Tests inject a fake gateway; production uses the default timed *http.Client.
func WithNonceFunc ¶
WithNonceFunc overrides the attestation nonce source (the report_data binding nonce). Production draws 32 crypto/rand bytes; tests can pin it.
func WithNow ¶
WithNow sets the wall clock used for seal/open timestamps and the session-cache TTL. Tests inject a fixed clock for determinism.
func WithQuoteVerifier ¶
func WithQuoteVerifier(v quoteVerifier) Option
WithQuoteVerifier overrides the DCAP quote verifier seam used by the live attest path (offline tests of the production attest wiring).
type Policy ¶
type Policy struct {
// AcceptedWorkloadIDs is the set of accepted workload_id strings
// ("sha256:<hex>"). Commonly left EMPTY: the workload_id is a digest of the
// keyset, which ROTATES with each keyset epoch, so pinning it would reject a
// legitimately rotated keyset. Trust is instead anchored by app-id +
// provenance + KMS-root custody (the fields below), which are stable across
// rotations. Populate this only to pin one exact keyset.
AcceptedWorkloadIDs map[string]struct{}
// AcceptedSourceProvenance is the set of accepted {repo_url, repo_commit}
// build-provenance pairs (rtmr.go's ProvenanceKey). Checked in step 5.
AcceptedSourceProvenance map[ProvenanceKey]struct{}
// AcceptedAppIDs is the set of accepted workload app-ids as lowercase hex of
// the app-id bytes (encoding/hex form). Checked in step 5.
AcceptedAppIDs map[string]struct{}
// AcceptedKMSRootPubKeys is the set of accepted KMS-root public keys as
// compressed-SEC1 hex. Checked in step 7 (key custody recovers the root and
// requires membership).
AcceptedKMSRootPubKeys map[string]struct{}
// contains filtered or unexported fields
}
Policy is the attestation acceptance allow-list set: which app-ids, source provenances, KMS roots, and workload_ids VerifyReport will accept. A nil/empty field skips its check ("when configured"); a non-empty field requires membership. At the LOW-LEVEL verifyReport (verify.go) a zero value Policy{} still accepts any genuine, quote-backed report (no allow-listing) — that is the mechanism. The PUBLIC entry points (New and VerifyReport both call requireAcceptable) instead FAIL CLOSED: an unpinned Policy is rejected with *UnpinnedPolicyError unless the caller opts in with UnpinnedPolicy(). Callers narrow trust by populating fields.
func UnpinnedPolicy ¶
func UnpinnedPolicy() Policy
UnpinnedPolicy returns a Policy that explicitly accepts any cryptographically genuine report WITHOUT allow-listing a workload. This is a deliberate, greppable opt-out of the fail-closed default; prefer a pinned Policy in production.
func (Policy) IsPinned ¶
IsPinned reports whether the policy pins at least one acceptance set. Any non-empty set means VerifyReport runs at least one allow-list check (steps 5/7/9), so the policy is not fail-open. AcceptedWorkloadIDs counts: a workload-ID-only policy is the strictest pin (one exact keyset digest).
type ProvenanceKey ¶
ProvenanceKey is the comparable allow-list key for source provenance: the {repo_url, repo_commit} pair. A Policy's AcceptedSourceProvenance set uses this exact key form so checkProvenancePolicy can do a single map lookup. It is a struct (not a joined string) so neither field can be confused with a delimiter inside the other. It is exported so callers outside this package (a provider's pinned-policy constructor) can populate AcceptedSourceProvenance directly.
type PublicKey ¶
PublicKey is an algorithm-tagged public key (algo + hex-encoded key bytes). public_key -> PublicKeyHex applies the serde rename; the wire key stays "public_key".
type Receipt ¶
type Receipt struct {
APIVersion string `json:"api_version"`
ReceiptID string `json:"receipt_id"`
ChatID *string `json:"chat_id"`
WorkloadID string `json:"workload_id"`
WorkloadKeysetDigest string `json:"workload_keyset_digest"`
Endpoint string `json:"endpoint"`
Method string `json:"method"`
ServedAt uint64 `json:"served_at"`
EventLog []ReceiptEvent `json:"event_log"`
Signature ReceiptSignature `json:"signature"`
}
Receipt is a parsed ACI §9 receipt: the per-request signed event log plus its metadata and the signature over the canonical projection. ChatID is a pointer because the wire field is nullable (Rust Option<String>): nil when absent/null, a string when present — the two project to different canonical bytes. ServedAt and ReceiptEvent.Seq are uint64 (the Rust u64 arm).
func ParseReceipt ¶
ParseReceipt decodes a §9 receipt from its wire JSON. It first decodes the fixed receipt fields, then re-decodes each event-log entry to split the fixed seq/type members from the free-form (type-specific) fields, which are retained as raw JSON for the canonical projection. Any stdlib decode failure is wrapped in a typed *receiptParseError.
type ReceiptEvent ¶
type ReceiptEvent struct {
Seq uint64 `json:"seq"`
EventType string `json:"type"`
Fields json.RawMessage `json:"-"`
}
ReceiptEvent is one event in the receipt's event log. The wire shape flattens seq and type to the top of the object alongside any type-specific fields; this struct keeps Fields as RAW JSON (an object), parsed into a jcs.Value only when building the canonical projection. EventType reads the wire key "type".
type ReceiptExpect ¶
type ReceiptExpect struct {
Endpoint string
Method string
Vendor string
ModelID string
ReqBody []byte
RespBodyCleartext []byte
RespWireBytes []byte
}
ReceiptExpect carries the caller-supplied expectations VerifyReceipt binds the receipt against. The request body is supplied as the ALREADY-COMPACT serde_json bytes (the caller — Task 5.2 — computes CompactJSON over the decrypted cleartext request body); VerifyReceipt hashes those bytes directly with Sha256HexBytes. The response cleartext and wire fields are likewise already-serialized bytes.
RespBodyCleartext is nil to SKIP the cleartext_hash check. The non-streaming caller (Invoke) supplies it, so cleartext_hash is enforced; the STREAMING caller (Stream — Task 5.3) passes nil, because the client only sees the sealed WIRE bytes and cannot reconstruct the raw upstream framing the gateway hashed for cleartext_hash. For streaming, wire_hash (over the exact wire bytes) plus the E2EE-authenticated open of each delta cover content authenticity instead.
RespWireBytes is nil to SKIP the wire_hash check (the wire-bytes hash is optional in the design doc). The two response-hash checks are independent: a nil preimage skips only its own hash. Vendor is the upstream provider TYPE (the design doc's "vendor" maps to the event's `provider` field); an empty Vendor skips the provider check, binding the upstream event on result + model_id alone.
type ReceiptSignature ¶
type ReceiptSignature struct {
Algo string `json:"algo"`
KeyID string `json:"key_id"`
ValueHex string `json:"value"`
}
ReceiptSignature is the receipt's signature block: the algorithm, the key id naming the attested receipt-signing key, and the hex signature value. ValueHex reads the wire key "value"; it is OMITTED from the canonical projection (it is the signed thing).
type Report ¶
type Report struct {
APIVersion string `json:"api_version"`
WorkloadID string `json:"workload_id"`
WorkloadKeysetDigest string `json:"workload_keyset_digest"`
Attestation Attestation `json:"attestation"`
ServiceCapabilities ServiceCapabilities `json:"service_capabilities"`
}
Report is a decoded Dstack ACI attestation report. It is the root of the model every Phase 2 verification step consumes.
func ParseReport ¶
ParseReport decodes a Dstack ACI report document into *Report and enforces the api_version tripwire.
It unmarshals the whole document first, THEN guards api_version: a malformed body therefore yields a typed *reportParseError (the decode failure) rather than being masked by a version check, and a well-formed body with the wrong version yields the fail-closed *llm.AttestationError carrying reasonUnsupportedAPIVersion (via errUnsupportedAPIVersion). On any error the returned *Report is nil. The bytes are untrusted external input; decoding is the validation boundary and is performed before the value is handed back.
type ServiceCapabilities ¶
type ServiceCapabilities struct {
SupportedE2EEVersions []string `json:"supported_e2ee_versions"`
}
ServiceCapabilities advertises which E2EE protocol versions the gateway supports. Versions are wire strings ("2").
type SourceProvenance ¶
type SourceProvenance struct {
RepoURL string `json:"repo_url"`
RepoCommit string `json:"repo_commit"`
ImageDigest *string `json:"image_digest"`
ImageProvenance *string `json:"image_provenance"`
}
SourceProvenance is the build provenance of the workload image. ImageDigest and ImageProvenance are null in the fixture, so both are optional pointers.
type TLSBinding ¶
TLSBinding binds a domain to the SHA-256 of its TLS SubjectPublicKeyInfo. It is reused by both tls_public_keys and evidence.downstream_tls_binding. spki_sha256 -> SPKISHA256Hex applies the serde rename.
type Uint ¶
type Uint uint64
Uint is an unsigned-integer JSON number (the u64 tail above the i64 range).
type UnpinnedPolicyError ¶
type UnpinnedPolicyError struct{}
UnpinnedPolicyError is returned by the public aci entry points when a Policy pins no acceptance set and did not explicitly opt into unpinned mode via UnpinnedPolicy(). Fail-secure: attestation refuses to run allow-list-free unless the caller asks for it. Like apiVersionMismatchError it uses a pointer receiver and is returned as *UnpinnedPolicyError so callers can errors.As it.
func (*UnpinnedPolicyError) Error ¶
func (e *UnpinnedPolicyError) Error() string
type Value ¶
type Value interface {
// contains filtered or unexported methods
}
Value is the sealed union of canonicalizable JSON values. The unexported marker method keeps the set of concrete types closed to this package.
func ParseBodyValue ¶
ParseBodyValue decodes a request-body JSON document into the ordered Value union, ACCEPTING non-integer floats (mapped to the Float variant). It is the body-hash counterpart to the strict, float-REJECTING ParseValue: bodies carry fractional sampling params that the JCS profile forbids, so the body path needs its own float-tolerant parse. Objects keep insertion order; integers go to Int/Uint (or stay as the verbatim Number where they exceed neither range); non-integer numbers become Float (the wire literal parsed to f64, exactly as serde_json does before re-emitting via ryu). All decoder failures are wrapped in the same typed *parseError; surplus tokens yield *trailingDataError.
Sharing the parse spine with ParseValue (rather than forking it) would couple the strict and tolerant policies; instead this is a small, separate parser that reuses only the leaf helpers, keeping each policy single-responsibility.
func ParseValue ¶
ParseValue decodes JSON bytes into the ordered Value union. Objects keep insertion order (Go maps would lose it), numbers are validated against the integer rule on the way in, and the only any in the package lives here at the json.Token boundary, immediately narrowed to a concrete Value. All stdlib decoder failures are wrapped in *parseError (Unwrap-able); the integer rule yields *FloatNotAllowedError; surplus tokens yield *trailingDataError.
type VerifiedReport ¶
type VerifiedReport struct {
// WorkloadID is the validated workload_id ("sha256:<hex>"), recomputed and
// matched in step 2.
WorkloadID string
// WorkloadKeysetDigest is the validated workload_keyset_digest
// ("sha256:<hex>"), recomputed and matched in step 2.
WorkloadKeysetDigest string
// Keyset is the full validated keyset: identity, epoch, and the
// receipt-signing / E2EE / TLS key lists Phase 3/4/5 consume. It is the value
// whose digest step 2 verified, so its keys are attestation-backed.
Keyset Keyset
}
VerifiedReport is the validated output of a passing attestation chain: the workload identity (workload_id + keyset digest) and the FULL validated keyset. Phase 3 (E2EE sealing) reads Keyset.E2EEPublicKeys; Phase 4 (receipt verification) reads Keyset.ReceiptSigningKeys; both get their key ids, algos, and public keys from the embedded Keyset, which is the exact value whose digest was recomputed and matched in step 2 (so the keys are the attested ones, not merely claimed). Returned only on a fully successful chain; nil on any failure.
func VerifyReport ¶
func VerifyReport(reportJSON []byte, nonce *string, now time.Time, policy Policy) (*VerifiedReport, error)
VerifyReport runs the full Dstack ACI attestation chain (steps 1–9) against reportJSON with the LIVE DCAP quote verifier and returns the validated *VerifiedReport, or the first failing step's typed *llm.AttestationError. nonce is the report_data binding nonce (nil if none was sent), now is the wall clock for the freshness check, and policy is the acceptance allow-list.
FAIL CLOSED. This is a public entry point, so the fail-closed policy gate runs FIRST: a policy that pins no acceptance set is rejected with *UnpinnedPolicyError BEFORE the chain runs and BEFORE any parse or network collateral fetch — unless the caller explicitly opts into genuineness-only verification by passing UnpinnedPolicy(). Supply a pinned Policy (a provider package ships a preset) or UnpinnedPolicy() to opt out; a bare Policy{} is refused.
On an acceptable policy it delegates to the unexported verifyReport with defaultQuoteVerifier, so step 4 fetches Intel collateral over the bounded HTTPS-only getter — meaning this entry point requires network access and cannot run fully offline. verifyReport remains the UNGUARDED low-level runner shared with the client's per-request attest path (already gated at construction by aci.New) and offline tests, which inject a fake quote seam.
type WorkloadIdentity ¶
type WorkloadIdentity struct {
PublicKey PublicKey `json:"public_key"`
Subject *string `json:"subject"`
}
WorkloadIdentity is the workload's identity key plus an optional subject. The fixture's subject is null, so Subject is a pointer that stays nil when absent.