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
- Variables
- func Digest(env []byte) [32]byte
- func SealRequest(to *PublicKey, r Route, req *Request) ([]byte, error)
- func SealResponse(to *PublicKey, signer ed25519.PrivateKey, r Route, requestDigest [32]byte, ...) ([]byte, error)
- type Header
- type PrivateKey
- type PublicKey
- type Request
- type RequestMeta
- type Response
- type ResponseMeta
- type Route
Constants ¶
const ( KindRequest byte = 0x01 KindResponse byte = 0x02 )
Kinds.
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.
const SuiteHybridV1 uint16 = 0x0001
Suite is the algorithm suite of an envelope.
Variables ¶
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 SealRequest ¶
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 ¶
Header is what can be read from an envelope without opening it.
func PeekHeader ¶
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 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).
type PublicKey ¶
type PublicKey struct {
// contains filtered or unexported fields
}
PublicKey is the public half others seal to.
func ParsePublicKey ¶
ParsePublicKey reads a public key from Bytes.
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.
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 ¶
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.