Help us shape the future of Go! Take the 2026 Go Developer Survey

sealedmsg

package
v0.37.0 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package sealedmsg is the end-to-end envelope a sensor and the platform exchange through an intermediary they do not trust: a relay, an object store, any store-and-forward hop (openctem research 194 §4.3).

A request is the sensor's ordinary signed v2 request (method, path, headers including the RFC 9421 signature, body), sealed with HPKE (RFC 9180) to the platform's ingest key. A response is signed by the platform's response key, bound to the digest of the request envelope it answers, and sealed to the sensor's encryption key. An intermediary can delay or drop envelopes; it cannot read them, change them, swap a response between requests or sensors, or send one in the platform's name. Replays of a request are refused by the platform's RFC 9421 nonce check, as for a direct request; a response opens only for the request it names.

Wire format, version 1:

magic   4 bytes  "OCSE"
version 1 byte   0x01
kind    1 byte   0x01 request, 0x02 response
suite   2 bytes  0x0001: KEM ML-KEM-768+X25519, KDF HKDF-SHA256,
                 AEAD ChaCha20-Poly1305
key id  16 bytes first 16 bytes of SHA-256 of the recipient public key
sealed  HPKE base-mode single-shot output (encapsulated key || ciphertext)

info = "openctem-sealed/v1" 0x00 || the 24 header bytes ||
       u16 len || realm || u16 len || sensor id

The header and the route (realm, sensor) are bound through the HPKE info: an envelope moved to another sensor's mailbox, or relabeled, does not open.

Plaintext of a request: u32 len || meta JSON (RequestMeta) || body. Plaintext of a response: u32 len || meta JSON (ResponseMeta) || body || Ed25519 signature (64 bytes) over "openctem-sealed-response/v1" 0x00 || everything before the signature.

Stability: Experimental (docs/STABILITY.md).

Index

Constants

View Source
const (
	KindRequest  byte = 0x01
	KindResponse byte = 0x02
)

Kinds.

View Source
const (
	// MaxBody bounds the body of one envelope (the v2 request body cap).
	MaxBody = 16 << 20
	// MaxEnvelope bounds one envelope on the wire.
	MaxEnvelope = MaxBody + 64<<10
	// MaxMeta bounds the meta JSON.
	MaxMeta = 64 << 10
	// MaxHeaders bounds the header fields carried.
	MaxHeaders = 64
	// MaxResponseSkew is how far a response's issued_at may be from the
	// sensor's clock.
	MaxResponseSkew = 10 * time.Minute
)

Limits.

View Source
const SuiteHybridV1 uint16 = 0x0001

Suite is the algorithm suite of an envelope.

Variables

View Source
var (
	// ErrMalformed: not an envelope of this version, or a part out of
	// bounds.
	ErrMalformed = errors.New("sealedmsg: malformed envelope")
	// ErrUnknownKey: the envelope is for a key this side does not hold.
	ErrUnknownKey = errors.New("sealedmsg: envelope for an unknown key")
	// ErrOpen: the envelope does not open for this route and key (altered,
	// moved to another mailbox, or not for us).
	ErrOpen = errors.New("sealedmsg: envelope does not open")
	// ErrSignature: a response not signed by a pinned response key.
	ErrSignature = errors.New("sealedmsg: response signature not valid")
	// ErrBinding: a response for another request, sensor or realm, or out
	// of the time window.
	ErrBinding = errors.New("sealedmsg: response does not answer this request")
)

Errors.

Functions

func Digest

func Digest(env []byte) [32]byte

Digest is the SHA-256 of an envelope: what a response is bound to.

func SealRequest

func SealRequest(to *PublicKey, r Route, req *Request) ([]byte, error)

SealRequest seals an inner request to the platform's ingest key for the route. It assigns the ID and Created time when empty.

func SealResponse

func SealResponse(to *PublicKey, signer ed25519.PrivateKey, r Route, requestDigest [32]byte, resp *Response) ([]byte, error)

SealResponse signs a response with the platform's response key, binds it to the request envelope's digest and the route, and seals it to the sensor's encryption key.

Types

type Header struct {
	Kind  byte
	KeyID string
}

Header is what can be read from an envelope without opening it.

func PeekHeader

func PeekHeader(env []byte) (Header, error)

PeekHeader parses an envelope's header (an intermediary never needs it; the platform uses it to pick the ingest key during a rotation).

type PrivateKey

type PrivateKey struct {
	// contains filtered or unexported fields
}

PrivateKey is an encryption key pair (the sensor's, or the platform's ingest key).

func GenerateKey

func GenerateKey() (*PrivateKey, error)

GenerateKey makes a new key pair.

func ParsePrivateKey

func ParsePrivateKey(b []byte) (*PrivateKey, error)

ParsePrivateKey reads a key pair from Bytes.

func (*PrivateKey) Bytes

func (k *PrivateKey) Bytes() ([]byte, error)

Bytes serializes the private key (keep it sealed: localstore).

func (*PrivateKey) KeyID

func (k *PrivateKey) KeyID() string

KeyID is the hex key id.

func (*PrivateKey) Public

func (k *PrivateKey) Public() *PublicKey

Public returns the public key.

type PublicKey

type PublicKey struct {
	// contains filtered or unexported fields
}

PublicKey is the public half others seal to.

func ParsePublicKey

func ParsePublicKey(b []byte) (*PublicKey, error)

ParsePublicKey reads a public key from Bytes.

func (*PublicKey) Bytes

func (p *PublicKey) Bytes() []byte

Bytes serializes the public key.

func (*PublicKey) KeyID

func (p *PublicKey) KeyID() string

KeyID is the hex key id.

type Request

type Request struct {
	RequestMeta
	Body []byte
}

Request is an inner request.

func OpenRequest

func OpenRequest(keys []*PrivateKey, r Route, env []byte) (*Request, error)

OpenRequest opens a request envelope with one of the platform's ingest keys (several during a rotation). The returned request is still to be authenticated: the platform passes it to its ordinary v2 handling, which checks the RFC 9421 signature, nonce, clock and tenant.

func (*Request) ReplyTo

func (r *Request) ReplyTo() (*PublicKey, error)

ReplyTo is the key the answer to this request is sealed to.

type RequestMeta

type RequestMeta struct {
	// ID is a random request id (hex); the sensor matches responses to
	// outstanding requests by the envelope digest, the id helps logs.
	ID     string      `json:"id"`
	Method string      `json:"method"`
	Path   string      `json:"path"`
	Header http.Header `json:"header,omitempty"`
	// Created is when the sensor built the request (store-and-forward
	// windows are checked by the platform).
	Created time.Time `json:"created"`
	// ReplyKey is the sensor's encryption public key (PublicKey.Bytes):
	// the platform seals its answer to it. It travels inside the sealed
	// envelope, so only the sender chose it; the platform answers with
	// data only after the inner request's signature verified.
	ReplyKey []byte `json:"reply_key"`
}

RequestMeta is the inner request without its body.

type Response

type Response struct {
	Status   int
	Header   http.Header
	Body     []byte
	IssuedAt time.Time
}

Response is an inner response.

func OpenResponse

func OpenResponse(key *PrivateKey, responseKeys []ed25519.PublicKey, r Route, requestDigest [32]byte, env []byte, now time.Time) (*Response, error)

OpenResponse opens a response with the sensor's encryption key, checks the signature against the pinned response keys before parsing anything, and checks that it answers requestDigest for this route within MaxResponseSkew of now.

func OpenResponseAny

func OpenResponseAny(key *PrivateKey, responseKeys []ed25519.PublicKey, r Route, env []byte, now time.Time) (*Response, [32]byte, error)

OpenResponseAny is OpenResponse for a sensor with several requests outstanding: it returns the digest of the request the response answers, which the caller matches against its outstanding requests (a response for none of them is dropped).

type ResponseMeta

type ResponseMeta struct {
	// RequestDigest is the hex SHA-256 of the request envelope answered.
	RequestDigest string      `json:"request_digest"`
	Realm         string      `json:"realm"`
	SensorID      string      `json:"sensor_id"`
	Status        int         `json:"status"`
	Header        http.Header `json:"header,omitempty"`
	IssuedAt      time.Time   `json:"issued_at"`
}

ResponseMeta is the inner response without its body.

type Route

type Route struct {
	Realm    string
	SensorID string
}

Route is who an envelope travels for: the platform realm (one platform installation or cell) and the sensor. Both sides know it from their own context (the sensor from its pairing, the platform from the mailbox the relay ticket names), never from the envelope.

Jump to

Keyboard shortcuts

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