Documentation
¶
Overview ¶
Package space implements the primitives of ATProto Spaces (permissioned data), following @atproto/space in bluesky-social/atproto PR #5187 at 5b95b2f2: the set hash, signed space repo commits, space tokens, HTTP message signatures and the space repo CAR format.
Index ¶
- Constants
- Variables
- func ComputeCommitMac(sharedSecret []byte, c Commit) []byte
- func ComputeRecordMac(sharedSecret []byte, uri string, c cid.Cid) []byte
- func CreateSpaceSig(key atcrypto.PrivateKey, authorization, audience string) (string, []byte, error)
- func CreateSpaceSigHeaders(key atcrypto.PrivateKey, authorization, audience string) (map[string]string, error)
- func CreateSpaceToken(typ TokenType, opts CreateTokenOpts, key atcrypto.PrivateKey) (string, error)
- func Decapsulate(authorKem *atcrypto.PrivateKeyP256, keyShare string) ([]byte, error)
- func FormatMacHeader(typ string, mac []byte) string
- func FormatRecordPath(collection, rkey string) string
- func FormatSetHashElement(collection, rkey string, c cid.Cid) string
- func FormatWantMacHeader(typ, keyShare string) string
- func IsP256DIDKey(didKey string) bool
- func JWTAlg(key any) (string, error)
- func ParseMacHeader(values []string, typ string) ([]byte, error)
- func ParseRecordPath(p string) (string, string, error)
- func ParseWantMacHeader(values []string, typ string) (string, error)
- func RepoStarMeta(c Commit) map[string]any
- func SerializeRepo(w io.Writer, commit Commit, records []SerializedRecord) error
- func SpaceHostAud(authority string) string
- func VerifyCommit(c Commit, p VerifyCommitParams) bool
- func VerifyCommitMac(sharedSecret []byte, c Commit, mac []byte) bool
- func VerifyCredentialSig(h http.Header, keyID string) error
- func VerifyJWTSignature(pub atcrypto.PublicKey, alg string, input, sig []byte) (bool, error)
- func VerifyRecord(uri, cidStr string, value map[string]any, p VerifyRecordParams) bool
- func VerifyRecordMac(sharedSecret []byte, uri string, c cid.Cid, mac []byte) bool
- type Commit
- type CommitCtx
- type CreateTokenOpts
- type Encapsulation
- type LexBytes
- type LtHash
- type MacError
- type ParsedToken
- type RecordRef
- type Ref
- type RepoCommit
- func (r *RepoCommit) Add(collection, rkey string, c cid.Cid) *RepoCommit
- func (r *RepoCommit) ApplyOp(op RepoOp) *RepoCommit
- func (r *RepoCommit) ApplyOps(ops []RepoOp) *RepoCommit
- func (r *RepoCommit) Matches(c Commit) bool
- func (r *RepoCommit) Remove(collection, rkey string, c cid.Cid) *RepoCommit
- func (r *RepoCommit) ToCommit(ctx CommitCtx) Commit
- type RepoOp
- type RepoStarReader
- type RepoVerificationError
- type SerializedRecord
- type SignatureError
- type Signer
- type SigningKeyFunc
- type StarParseError
- type StarReader
- type StarWriter
- type Token
- type TokenCnf
- type TokenError
- type TokenHeader
- type TokenPayload
- type TokenSpec
- type TokenType
- type VerifiedRecord
- type VerifiedRepo
- type VerifyCommitParams
- type VerifyRecordParams
- type VerifyRepoParams
- type VerifyTokenOpts
Constants ¶
const ( // CredentialScheme is the Authorization scheme a space credential is // presented with. CredentialScheme = "Atproto-Space-Credential" // HeaderSpaceAudience names the audience DID a credential request is for. HeaderSpaceAudience = "Atproto-Space-Audience" )
const ( CommitMacType = "atproto-space-repo" RecordMacType = "atproto-space-record" RecordMacVersion = 1 // HeaderWantMac is the request header asking for a MAC. HeaderWantMac = "Atproto-Space-Want-Mac" // HeaderMac is the response header carrying it. HeaderMac = "Atproto-Space-Mac" )
const ( // ClockSkewSec is the allowance applied to exp, and to a credential's iat. ClockSkewSec = 5 // CredentialMaxAgeSec bounds a credential's lifetime. CredentialMaxAgeSec = 3600 )
const CommitVersion = 1
CommitVersion is the only commit version this package understands.
const (
// LtHashStateBytes is the size of a persisted set hash state.
LtHashStateBytes = ltHashLanes * 2
)
const MaxRecordSize = 1024 * 1024
MaxRecordSize is the largest DRISL encoding a space record may have.
const RepoStarType = "atproto-space-repo"
RepoStarType is the $type of a space repo archive's metadata.
Variables ¶
var StarMagic = []byte{0x2a, 0x6c, 0x01, 0x00}
StarMagic opens every STAR archive.
var TokenSpecs = map[TokenType]TokenSpec{ TokenDelegation: {Typ: "atproto-space-delegation+jwt", Kid: "#atproto", ExpiresInSec: 60, RequireAud: true, SingleUse: true}, TokenCredential: {Typ: "atproto-space-credential+jwt", Kid: "#atproto", ExpiresInSec: 600, RequireCnf: true}, TokenClientAttestation: {Typ: "atproto-client-attestation+jwt", ExpiresInSec: 60, RequireAud: true, SingleUse: true}, }
TokenSpecs follows SPACE_TOKEN_TYPES in @atproto/space. Credentials carry no audience: HTTP message signatures bind each use to an audience DID.
Functions ¶
func ComputeCommitMac ¶ added in v0.14.1
ComputeCommitMac is
context = "atproto-space-repo-v1" || uint16be(len(space)) || space || uint16be(len(repo)) || repo || uint16be(len(rev)) || rev mac = HMAC-SHA256(shared_secret, context || data)
func ComputeRecordMac ¶ added in v0.14.1
ComputeRecordMac is
context = "atproto-space-record-v1" || uint16be(len(uri)) || uri mac = HMAC-SHA256(shared_secret, context || record_cid_bytes)
func CreateSpaceSig ¶ added in v0.13.1
func CreateSpaceSig(key atcrypto.PrivateKey, authorization, audience string) (string, []byte, error)
CreateSpaceSig signs a request authorized by a space credential, covering the credential (authorization, which must use the Atproto-Space-Credential scheme) and its audience DID. It returns the signature-input inner list and the compact signature.
func CreateSpaceSigHeaders ¶ added in v0.13.1
func CreateSpaceSigHeaders(key atcrypto.PrivateKey, authorization, audience string) (map[string]string, error)
CreateSpaceSigHeaders returns the authorization, audience and signature headers for a space request, keyed by lowercase header name.
func CreateSpaceToken ¶
func CreateSpaceToken(typ TokenType, opts CreateTokenOpts, key atcrypto.PrivateKey) (string, error)
CreateSpaceToken mints and signs a space token.
func Decapsulate ¶ added in v0.14.1
func Decapsulate(authorKem *atcrypto.PrivateKeyP256, keyShare string) ([]byte, error)
Decapsulate derives, on the repo host, the shared secret for a syncer's key_share using the author's KEM private key.
func FormatMacHeader ¶ added in v0.14.1
FormatMacHeader is the Atproto-Space-Mac response header, e.g. type="atproto-space-repo", ver=1, mac=:AAECAwQF...:.
func FormatRecordPath ¶ added in v0.13.1
FormatRecordPath is "{collection}/{rkey}".
func FormatSetHashElement ¶
FormatSetHashElement is the element a record contributes to its repo's set hash: "{collection}/{rkey}/{cid}". It stays injective because the outer two fields (an NSID and a base32 CID) never contain a slash.
func FormatWantMacHeader ¶ added in v0.14.1
FormatWantMacHeader is the Atproto-Space-Want-Mac request header, e.g. type="atproto-space-repo", ver=1, key_share="did:key:zDna...".
func IsP256DIDKey ¶ added in v0.14.1
IsP256DIDKey reports whether didKey is a P-256 did:key, the only kind a space credential can be bound to.
func JWTAlg ¶ added in v0.13.1
JWTAlg is the JWT alg for a key: ES256 for P-256, ES256K for secp256k1.
func ParseMacHeader ¶ added in v0.14.1
ParseMacHeader parses a response header of the given type, returning its MAC.
func ParseRecordPath ¶ added in v0.13.1
ParseRecordPath splits "{collection}/{rkey}".
func ParseWantMacHeader ¶ added in v0.14.1
ParseWantMacHeader parses a request header of the given type, returning its key_share. values are every field line of the header.
func RepoStarMeta ¶ added in v0.14.1
RepoStarMeta is a commit as a repo archive's metadata.
func SerializeRepo ¶
func SerializeRepo(w io.Writer, commit Commit, records []SerializedRecord) error
SerializeRepo writes a space repo archive. Records must be in strict path order, as the archive requires; it fails at the first that isn't.
func SpaceHostAud ¶ added in v0.13.1
SpaceHostAud is the audience a delegation token and a client attestation name: the authority acting as space host, which is its PDS like any other atproto account.
func VerifyCommit ¶
func VerifyCommit(c Commit, p VerifyCommitParams) bool
VerifyCommit checks a commit against the MAC its response carried. Once it passes, Data is trusted as the author's claim about their repo, which is what makes Matches meaningful.
func VerifyCommitMac ¶ added in v0.14.1
VerifyCommitMac checks a commit MAC in constant time.
func VerifyCredentialSig ¶ added in v0.14.1
VerifyCredentialSig verifies the signature on a request authorized by a space credential, against the key the credential is bound to (its cnf.kid).
func VerifyJWTSignature ¶ added in v0.13.1
VerifyJWTSignature checks a compact, low-S JWT signature, refusing an alg that does not match the key.
func VerifyRecord ¶ added in v0.14.1
func VerifyRecord(uri, cidStr string, value map[string]any, p VerifyRecordParams) bool
VerifyRecord checks a getRecord response against the MAC it carried: it must be the record requested, the MAC must cover its uri and cid, and its value must encode to that cid.
Types ¶
type Commit ¶ added in v0.14.1
type Commit struct {
Ver int64
Repo string
Space string
Rev string
// Data is the sha256 digest of the repo's LtHash state.
Data []byte
}
Commit is a space repo commit (com.atproto.space.defs#commit). It carries no signature: the repo host authenticates it to each syncer with a MAC in the Atproto-Space-Mac response header, so a leaked commit proves nothing to a third party about what the author wrote.
func ParseRepoStarMeta ¶ added in v0.14.1
ParseRepoStarMeta reads a commit from a repo archive's metadata.
func (Commit) MarshalJSON ¶ added in v0.14.1
MarshalJSON encodes a commit as com.atproto.space.defs#commit.
func (*Commit) UnmarshalJSON ¶ added in v0.14.1
type CommitCtx ¶
type CommitCtx struct {
Space string `json:"space"`
Repo string `json:"repo"`
Rev string `json:"rev"`
}
CommitCtx is which repo, in which space, at which revision a commit describes.
type CreateTokenOpts ¶ added in v0.13.1
type CreateTokenOpts struct {
Iss string
Sub string
Aud string
// KeyID is the did:key the credential is bound to (cnf.kid).
KeyID string
// ExpiresInSec overrides the type's default lifetime.
ExpiresInSec *int64
// Kid is the header kid, only for a client attestation. Other token types
// always use #atproto.
Kid string
// Now overrides the clock.
Now time.Time
}
type Encapsulation ¶ added in v0.14.1
type Encapsulation struct {
}
Encapsulation is a syncer's side of one request: the ephemeral public key to send as key_share, and the secret it shares with the repo host.
func Encapsulate ¶ added in v0.14.1
func Encapsulate(authorKemKey string) (*Encapsulation, error)
Encapsulate generates a fresh ephemeral keypair and derives a shared secret against the author's #atproto_kem key (a P-256 did:key). Use a new one for every request.
type LexBytes ¶ added in v0.13.1
type LexBytes []byte
LexBytes is the atproto JSON encoding of bytes: {"$bytes": base64}.
func (LexBytes) MarshalJSON ¶ added in v0.13.1
func (*LexBytes) UnmarshalJSON ¶ added in v0.13.1
type LtHash ¶
type LtHash struct {
// contains filtered or unexported fields
}
LtHash is a homomorphic set hash. Each element expands (BLAKE3 XOF) to 1024 little-endian u16 lanes, which are summed into the state mod 2^16, so the state depends only on the current multiset, not on insertion order.
func LtHashFromState ¶
LtHashFromState resumes a set hash from a persisted state. An empty state yields the empty hash.
type MacError ¶ added in v0.14.1
MacError is a missing or malformed MAC header, an unsupported MAC type or version, or an invalid key (BadSpaceMac).
type ParsedToken ¶ added in v0.13.1
ParsedToken is a structurally valid token with its signing input, before any signature check.
func ParseSpaceToken ¶
func ParseSpaceToken(typ TokenType, jwt string) (*ParsedToken, error)
ParseSpaceToken validates a token's structure, without checking its signature. That is as far as a client attestation goes here, since its key comes from the client's JWKS rather than a DID document.
type Ref ¶ added in v0.13.1
Ref names a space: at://{authority}/space/{spaceType}/{skey}.
func (Ref) HostAud ¶ added in v0.13.1
HostAud is the audience the authority answers to as space host.
type RepoCommit ¶
type RepoCommit struct {
SetHash *LtHash
}
RepoCommit tracks a space repo's contents as a set hash.
func RepoCommitFromRecords ¶
func RepoCommitFromRecords(records []RecordRef) *RepoCommit
RepoCommitFromRecords folds in a set of records.
func RepoCommitFromState ¶ added in v0.13.1
func RepoCommitFromState(state []byte) (*RepoCommit, error)
RepoCommitFromState resumes from a persisted set hash state (empty for nil).
func (*RepoCommit) Add ¶
func (r *RepoCommit) Add(collection, rkey string, c cid.Cid) *RepoCommit
func (*RepoCommit) ApplyOp ¶ added in v0.13.1
func (r *RepoCommit) ApplyOp(op RepoOp) *RepoCommit
func (*RepoCommit) ApplyOps ¶ added in v0.13.1
func (r *RepoCommit) ApplyOps(ops []RepoOp) *RepoCommit
func (*RepoCommit) Matches ¶
func (r *RepoCommit) Matches(c Commit) bool
Matches reports whether this repo's contents match a commit. Verify the commit first: on its own this says nothing about authenticity.
func (*RepoCommit) Remove ¶
func (r *RepoCommit) Remove(collection, rkey string, c cid.Cid) *RepoCommit
func (*RepoCommit) ToCommit ¶ added in v0.14.1
func (r *RepoCommit) ToCommit(ctx CommitCtx) Commit
ToCommit builds a commit over the current contents.
type RepoOp ¶ added in v0.13.1
RepoOp is one oplog entry: a create has no Prev, a delete no Cid, an update both.
type RepoStarReader ¶ added in v0.14.1
type RepoStarReader struct {
Commit Commit
// contains filtered or unexported fields
}
RepoStarReader is an archive whose commit has verified against the response's MAC. Next streams its records, folding each into a running set hash, and only the io.EOF that ends a fully read archive shows the records match the commit, so drain it before trusting what it returned.
func VerifyRepoStar ¶ added in v0.14.1
func VerifyRepoStar(r io.Reader, params VerifyRepoParams) (*RepoStarReader, error)
VerifyRepoStar verifies an archive's commit and returns a reader for its records.
func (*RepoStarReader) Next ¶ added in v0.14.1
func (r *RepoStarReader) Next() (*VerifiedRecord, error)
Next returns the next record, or io.EOF once the archive has ended and its records match the commit.
type RepoVerificationError ¶ added in v0.13.1
type RepoVerificationError struct{ Message string }
RepoVerificationError is a space repo or record that does not verify.
func (*RepoVerificationError) Error ¶ added in v0.13.1
func (e *RepoVerificationError) Error() string
type SerializedRecord ¶
SerializedRecord is a record's DRISL encoding.
func SerializeRecord ¶
func SerializeRecord(collection, rkey string, record map[string]any) (SerializedRecord, error)
SerializeRecord encodes a record (atproto data model) as DRISL.
type SignatureError ¶ added in v0.13.1
SignatureError is a request signature failure (BadSpaceSignature).
func (*SignatureError) Error ¶ added in v0.13.1
func (e *SignatureError) Error() string
func (*SignatureError) Unwrap ¶ added in v0.13.1
func (e *SignatureError) Unwrap() error
type SigningKeyFunc ¶ added in v0.13.1
SigningKeyFunc resolves the did:key an issuer signs with. forceRefresh asks for a fresh resolution, after a cached key failed to verify.
type StarParseError ¶ added in v0.14.1
type StarParseError struct{ Message string }
StarParseError is an archive that doesn't parse.
func (*StarParseError) Error ¶ added in v0.14.1
func (e *StarParseError) Error() string
type StarReader ¶ added in v0.14.1
StarReader reads an archive's pairs lazily, after its header.
type StarWriter ¶ added in v0.14.1
type StarWriter struct {
// contains filtered or unexported fields
}
StarWriter writes an archive's pairs after its header.
func WriteStar ¶ added in v0.14.1
WriteStar writes the magic bytes and the metadata, which must be a map with a string $type, as canonical DRISL.
func (*StarWriter) WritePair ¶ added in v0.14.1
func (sw *StarWriter) WritePair(key, value []byte) error
WritePair writes one key/value pair.
type Token ¶ added in v0.13.1
type Token struct {
Header TokenHeader
Payload TokenPayload
}
func VerifySpaceToken ¶
func VerifySpaceToken(typ TokenType, jwt string, opts VerifyTokenOpts) (*Token, error)
VerifySpaceToken parses a token, checks its timing, audience and subject, and verifies its signature against the issuer's key, retrying once with a freshly resolved key in case the key rotated.
type TokenError ¶ added in v0.13.1
TokenError is a token failure with an XRPC-style code.
func (*TokenError) Error ¶ added in v0.13.1
func (e *TokenError) Error() string
type TokenHeader ¶ added in v0.13.1
type TokenPayload ¶ added in v0.13.1
type TokenPayload struct {
Iss string `json:"iss"`
Sub string `json:"sub"`
Aud string `json:"aud,omitempty"`
Cnf *TokenCnf `json:"cnf,omitempty"`
Iat int64 `json:"iat"`
Exp int64 `json:"exp"`
Jti string `json:"jti"`
}
TokenPayload's field order matches the reference's JSON output. Sub is the space URI, or the client_id for a client attestation.
type TokenSpec ¶ added in v0.13.1
type TokenSpec struct {
Typ string
Kid string
ExpiresInSec int64
RequireAud bool
RequireCnf bool
SingleUse bool
}
TokenSpec describes how a token type is minted and checked.
type VerifiedRecord ¶
type VerifiedRepo ¶
type VerifiedRepo struct {
Commit Commit
Records []VerifiedRecord
}
VerifiedRepo is a fully read and verified repo archive.
func VerifyRepoStarFull ¶ added in v0.14.1
func VerifyRepoStarFull(r io.Reader, params VerifyRepoParams) (*VerifiedRepo, error)
VerifyRepoStarFull verifies an archive and collects its records.
type VerifyCommitParams ¶ added in v0.14.1
VerifyCommitParams names the space and repo that were requested, with the request's shared secret and the MAC from its response header.
type VerifyRecordParams ¶ added in v0.14.1
VerifyRecordParams names the record that was requested, with the request's shared secret and the MAC from its response header.
type VerifyRepoParams ¶
type VerifyRepoParams = VerifyCommitParams
VerifyRepoParams is VerifyCommitParams for a repo archive.
type VerifyTokenOpts ¶ added in v0.13.1
type VerifyTokenOpts struct {
GetSigningKey SigningKeyFunc
// Aud and Sub, when set, must match the token's.
Aud string
Sub string
// Now overrides the clock.
Now time.Time
}