Documentation
¶
Overview ¶
Package protocol holds the dromas v1 formats every client and the server share: addresses, identity documents, signed prekey statements, signature namespaces, the message envelope, and the error codes.
Every parser here is exact. A signed statement must match its format byte for byte, and an identity key must be a canonical, non-small-order Ed25519 point. Failing a check returns an error; nothing warns and carries on.
testdata/vectors holds files, made with the stock tools (ssh-keygen, sha256sum, age), that any implementation can check itself against.
Index ¶
- Constants
- Variables
- func AcceptsSender(declared []string, t string) bool
- func Armor(blob []byte) []byte
- func CertificateBegan(signature []byte) (time.Time, error)
- func CheckCursor(s string) error
- func CheckServerName(s string) error
- func ContentRange(from, length int64) string
- func Dearmor(armored []byte) ([]byte, error)
- func Examine(plaintext []byte, me Address, now time.Time) (*Signed, *Findings, error)
- func IdentityTypes() []string
- func IfMatch(lines []string, etag string) bool
- func IfNoneMatch(lines []string, etag string) bool
- func IsMsgID(s string) bool
- func LowerASCII(s string) string
- func Member(err error) string
- func MessageNamespace(b Address) string
- func NewMsgID() (string, error)
- func NewPass(signer ssh.Signer, a Address, server string, now time.Time) (authorization string, notAfter time.Time, err error)
- func PadBlock(size int) int
- func PaddedSize(size int) int
- func ParseQuery(raw string, params ...string) (map[string]string, error)
- func ParseTime(s string) (time.Time, error)
- func PassNamespace(a Address) string
- func PassStatement(a Address, server string, notAfter time.Time) []byte
- func Plaintext(line, signature []byte, t KeyType) (plaintext []byte, sealed int)
- func PollInterval(v string) (uint64, error)
- func PrekeyNamespace(a Address) string
- func ProfileNamespace(b Address) string
- func RangeAsk(from int64) string
- func RangeAsked(ranges, ifRange []string) (from int64, ok bool)
- func RevocationStatement(a Address) []byte
- func RevokeNamespace(a Address) string
- func RevokedCertsNamespace(a Address) string
- func SealedSize(t KeyType, plaintext int) int
- func SerialAt(now time.Time, above uint64) uint64
- func Sign(s ssh.Signer, message []byte, namespace string) ([]byte, error)
- func SignEnvelope(e *Envelope, signer ssh.Signer) (line, signature []byte, err error)
- func SignRevocation(identity ssh.Signer, a Address) ([]byte, error)
- func SignRevokedCertificates(identity ssh.Signer, r *RevokedCertificates) ([]byte, error)
- func SignatureBound(t string) int
- func Unmarshal(data []byte, v any) error
- func UnmarshalRequest(data []byte, v any) error
- func Verify(id *Identity, message, signature []byte, namespace string) error
- func VerifyPrekey(id *Identity, statement, signature []byte, now time.Time) (*Prekey, *Certificate, error)
- func VerifyPrekeyRecord(id *Identity, statement, signature []byte) (*Prekey, *Certificate, error)
- type ACL
- type ACLDocument
- type ACLList
- type AccountResponse
- type Address
- type AdoptRequest
- type BoxResponse
- type Bytes
- type Certificate
- func CheckCertificate(id *Identity, key ssh.PublicKey, now time.Time) (*Certificate, error)
- func CheckCertificateRecord(id *Identity, key ssh.PublicKey) (*Certificate, error)
- func NewCertificate(id *Identity, identity ssh.Signer, work ssh.PublicKey, serial uint64, ...) (*Certificate, error)
- func VerifyCertified(id *Identity, message, signature []byte, namespace string, now time.Time) (*Certificate, error)
- func (c *Certificate) Marshal() []byte
- func (c *Certificate) NotAfter() time.Time
- func (c *Certificate) PublicKey() ssh.PublicKey
- func (c *Certificate) Serial() uint64
- func (c *Certificate) Signer(work ssh.Signer) (ssh.Signer, error)
- func (c *Certificate) SignsMail(now time.Time) bool
- func (c *Certificate) WorkingKey() ssh.PublicKey
- type Code
- type Envelope
- func NewEnvelope(from, to Address, body string, now time.Time) (*Envelope, error)
- func NewJSONEnvelope(from, to Address, body []byte, now time.Time) (*Envelope, error)
- func OpenEnvelope(plaintext []byte, signer, me Address, now time.Time) (*Envelope, error)
- func OpenEnvelopeRecord(plaintext []byte, signer, me Address) (*Envelope, error)
- type EnvelopeError
- type Findings
- type Identity
- type KeyType
- type MessageInfo
- type MessageList
- type Order
- type Pass
- type Prekey
- type PrekeyRequest
- type Problem
- type QueryError
- type Quota
- type Receipt
- type RegisterRequest
- type Revocations
- type RevokedCertificates
- type SendQuery
- type Signed
- type Stanzas
- type Subject
Constants ¶
const ( PrekeySerialParam = "prekey_serial" CertificateSerialParam = "certificate_serial" )
The names of a send's query parameters.
const ( // CertificateKeyID is the key id of every v1 certificate. CertificateKeyID = "dromas-cert-v1" // CertificateLifetime is how long a client makes a certificate good for: // the 31 days it signs mail for (SignsMail), the longest month, so a // certificate made each month carries a box's mail until the next, and // then TTL, so that the last mail it signs still verifies when it is read. CertificateLifetime = 31*24*time.Hour + TTL // MaxCertificateLifetime is the longest a certificate may still have to // run when a verifier takes it: MaxSkew more than a client asks for, so // that one made by a clock ahead of the verifier's is not refused. It is // the prekey's rule (PrekeyLifetime, MaxPrekeyLifetime). MaxCertificateLifetime = CertificateLifetime + MaxSkew )
const ( // EnvelopeVersion is the envelope's "v". It changes only for a breaking // change, and a receiver rejects a version it does not know. EnvelopeVersion = 1 // TTL is how old a message may be, counted from its sent_at, which its // sender dates afresh at every post: 33 days, a prekey's longest life, so // a box that checks in at least monthly loses neither new mail nor held // mail. Every window of mail's age derives from it: the recipient's // sent_at window, how long a server keeps mail (Retention), the replay // set (SeenFor), how long the sender's outbox posts an entry, and the // latest a retired prekey can still be needed. TTL = 33 * 24 * time.Hour // PostTime is the room the maker of a time-limited thing leaves for a // post's own time, beside MaxSkew: what comes between the post's dating // and the server's receipt that no pass bounds, its seal and the second // the dating truncates. The request itself is bounded by its pass, which // a server takes only from a clock less than PassLifetime behind its own. PostTime = 2 * time.Minute // Retention is how long a server keeps a message after it received it: // the TTL less MaxSkew, the hour whoever makes a time-limited thing // leaves, and less PostTime (PROTOCOL.md §3.4, §13). A post's sent_at is // its sender's clock at the post, which a server takes from up to // MaxSkew apart from its own, so a reader whose clock a server takes // refuses as stale no message the server still serves. Retention = TTL - MaxSkew - PostTime // SeenFor is how long a reader keeps a message in its replay set, // counted from the message's sent_at: the TTL in which a retry of it may // still be posted, dated afresh, since an outbox posts an entry until it // was written TTL ago; the TTL that retry is fresh for; and MaxSkew, so a // clock that steps back by as much as two clocks a server takes are apart // reopens nothing (PROTOCOL.md §12.3 check 9). A step further back is the // floor's (client: seen-floor). SeenFor = 2*TTL + MaxSkew )
const ( RefusedMessage = "bad_message" // what was decrypted is not an envelope line and then a signature block RefusedEnvelope = "bad_envelope" // not UTF-8 JSON, one object with no key twice, version 1, with a body, a msg_id and a sent_at RefusedIdentity = "bad_identity" // the envelope carries no identity document, or one that breaks its format or its key checks RefusedKeyType = "unsupported_key_type" // the sender's identity is a well-formed document of a key type this client does not verify RefusedSignature = "bad_signature" // not signed for this box by a working key the sender's identity certified, under a certificate valid now RefusedAttribution = "reattributed" // its from is not the address of the identity that signed it, or its to is not this box RefusedStale = "stale" // its sent_at is older than TTL RefusedFuture = "future" // its sent_at is more than MaxSkew ahead of the reader's clock )
The checks an envelope can fail, as codes: a closed set, so that whoever reads a refusal reads words this code chose.
const ( // IdentityTag is line 1 of every v1 identity document. IdentityTag = "dromas-v1" // IdentityDocumentSize is the exact size of an ssh-ed25519 identity // document. Another key type's document has another size, as exact. IdentityDocumentSize = 93 )
const ( IdentityEd25519 = ssh.KeyAlgoED25519 IdentityP256 = ssh.KeyAlgoECDSA256 )
The identity key types this package defines: the SSH name on line 2 of an identity document. Every client verifies IdentityEd25519. Every other type is optional: a box of one is reached only by clients that verify it (PROTOCOL.md §4.1).
const ( // PassTag is line 1 of every pass statement. PassTag = "dromas-pass-v1" //nolint:gosec // G101: a protocol tag, public, no credential // PassScheme is the Authorization scheme a pass travels under. PassScheme = "dromas-pass" // PassLifetime is how long a client makes a pass good for. PassLifetime = 10 * time.Minute // MaxPassLifetime is the longest a pass may still have to run when a // server takes it. It is longer than PassLifetime so that a client whose // clock runs fast is not refused. MaxPassLifetime = time.Hour // MaxSkew is how far apart two clocks can be with everything here still // working: an hour. The pass sets it. A box is taken by a server while its // clock is less than PassLifetime slow and no more than MaxPassLifetime // less PassLifetime fast, by the server's, and any two such clocks are // less than MaxPassLifetime apart. Every check is exact at the verifier's // own clock, and whoever makes a time-limited thing leaves this much room: // a certificate starts MaxSkew before it is made (NewCertificate), a // server takes a prekey that ends up to MaxSkew past PrekeyLifetime // (MaxPrekeyLifetime), and a reader takes a sent_at up to MaxSkew ahead // (OpenEnvelope). PROTOCOL.md §3.4. MaxSkew = MaxPassLifetime )
const ( // PrekeyTag is line 1 of every v1 prekey statement. PrekeyTag = "dromas-prekey-v1" // PrekeyLifetime is how long a client makes a prekey statement valid for: // 31 days, the longest month, plus a day a renewal may wait and a day of // slip, so a box checked at least monthly never lapses. PrekeyLifetime = 33 * 24 * time.Hour // MaxPrekeyLifetime is the longest a prekey statement may still have to // run when a server takes it: MaxSkew more than a client asks for, so // that a client whose clock is ahead of the server's is not refused. It // is the pass's rule (PassLifetime, MaxPassLifetime). MaxPrekeyLifetime = PrekeyLifetime + MaxSkew // MaxSerial is the largest serial, of a prekey or a certificate, and the // largest a revocation of certificates runs through: MaxInteger, 2^53 − 1, // since an answer names a serial as a JSON number (stale_prekey's, a // conflict's), and RFC 7493 promises no reader holds a larger integer // exactly. MaxSerial = MaxInteger // TimeLayout is the one timestamp format in signed statements and the // envelope: RFC 3339, UTC, to the second. TimeLayout = "2006-01-02T15:04:05Z" )
const AnyAddress = "*"
AnyAddress is the ACL entry that matches every address, as "*" does in a Matrix room's server ACL. It is the only pattern: an entry is an address or this.
const AnySender = "any"
AnySender is the one entry of a declaration of sender key types that is no key type's name: the box does not say which types its client verifies, and a server makes no check for it.
const CiphertextContentType = "application/octet-stream"
CiphertextContentType is the media type of a message's ciphertext, the body of a send and of a fetch: the age file's exact bytes.
const LinkPath = "/to/"
LinkPath is where a server serves a page for each address: an address is shared as the link <server>/to/<address>, which tells whoever opens it what dromas is. A client takes the address from the link and never fetches the page.
const MaxCiphertext = 16 << 20
MaxCiphertext is the most bytes a message's ciphertext may be, in a send and in a fetch (PROTOCOL.md §11.10): every reader takes one of any size up to it, and a server's caps on a ciphertext are at most it. It is a whole number of its own padding blocks (PaddedSize), so a message that fits it bare fits it padded. A larger one is a new version of the protocol (§14).
const MaxDepth = 32
MaxDepth is how deeply a JSON text the protocol carries may nest objects and arrays, the outermost counted as one. It is well under every parser's own limit (jq 1.7 refuses 257, Go 10,001: both measured), so a text one reader takes is never one another's parser refuses.
const MaxInteger = 1<<53 - 1
MaxInteger is the largest integer a member the protocol defines may hold: 2^53 − 1, the largest RFC 7493 says every reader holds exactly. Serials are bounded by it, since an answer names one as a JSON number.
const MaxPassAuthorization = len(PassScheme+` address="", not_after="", signature=""`) + 64 + len(TimeLayout) + (MaxSignature+2)/3*4
MaxPassAuthorization is the longest an Authorization value carrying a pass can be in the one form a sender writes: its fixed parts, an address, a time, and the standard base64 of a signature of MaxSignature bytes. A pass is signed by an Ed25519 working key, under the certificate its identity signed, so its size is its identity's key type's; and ParsePass refuses a longer value, in any form, before it reads it, so before it knows the box (PROTOCOL.md §8). So the bound is the most any identity key type allows, and whitespace a sender adds counts toward it.
const MaxSignature = 4096
MaxSignature is the most bytes an armored signature may be for a box of any identity key type defined here: the largest of their bounds (SignatureBound). A field that carries one counts every byte it holds; in a message it is the block from the BEGIN line through the END line. Dearmor refuses a longer one before reading it, so every verifier here does, before it knows whose signature it is.
const (
// PadMinBlock is the smallest block, in bytes.
PadMinBlock = 1024
)
const ProblemContentType = "application/problem+json"
ProblemContentType is the media type of every error body (RFC 9457).
const RecordType = "revocation_record"
RecordType is the type a revocation record names itself by.
const RevokedCertsTag = "dromas-revoke-certs-v1"
RevokedCertsTag is line 1 of a revocation of certificates.
const TypePrefix = "tag:dromas.dev,2026-10-26:errors/"
TypePrefix begins every code's Problem Details "type": a tag URI (RFC 4151) that names the problem and resolves to nothing (PROTOCOL.md §11.3). The date is a whole day after dromas.dev was registered: RFC 4151 reads a month alone as its first day, before the registration. A tag is a name, never a location.
Variables ¶
var ( // ErrACL is a document that is not an ACL: no allow list, or an entry // that is neither an address nor AnyAddress. ErrACL = errors.New("not an ACL: an allow list, and entries that are each an address or *") // ErrACLTooLarge is an ACL with more entries in its two lists together // than the server takes. ErrACLTooLarge = errors.New("the ACL has more entries than this server takes") )
Errors of an ACL document.
var ( ErrCertificate = errors.New("bad certificate") ErrCertificateTime = errors.New("the certificate is not valid now") ErrCertificateNotBegun = fmt.Errorf("%w: it has not begun", ErrCertificateTime) ErrCertificateEnded = fmt.Errorf("%w: it has ended", ErrCertificateTime) )
Every certificate error wraps one of these. ErrCertificate: it is not a certificate of this identity, or it breaks a rule of the format, the limit on how far ahead it may end among them. ErrCertificateTime: it has not begun or it has ended, by the verifier's own clock. Which of the two it is, ErrCertificateNotBegun or ErrCertificateEnded, and each is an ErrCertificateTime.
var ( ErrIdentityFormat = errors.New("not an identity document") ErrIdentityKey = errors.New("bad identity key") ErrIdentityType = errors.New("an identity key type this client does not verify") )
Every ParseIdentity error wraps one of these: the document breaks its format, its key fails the key checks, or it is a well-formed document of a key type this package does not define. A server answers the first with identity_mismatch, the second with bad_key and the third with unsupported_key_type.
var ( ErrPass = errors.New("bad pass") ErrPassExpired = errors.New("the pass has expired") )
Every pass error wraps one of these. ErrPass: it is not a pass of this box for this server, or it breaks its format. ErrPassExpired: it has run out, and the box signs another.
var ErrJSON = errors.New("not a JSON text as the protocol reads one")
ErrJSON is a JSON text the protocol does not take. Its words are fixed: they never quote the text, whose author may be a server or a sender.
var ErrMemberValue = errors.New("a member holds a value the protocol does not take there")
ErrMemberValue is a JSON text with a member the protocol knows that holds a value it does not take there: a revocation record that names a type other than RecordType, or a list whose next_cursor is not a cursor. Its words name the member, and so does Member.
var ErrNotBase64 = errors.New("a byte field is not standard base64 of the exact bytes, in its one encoding")
ErrNotBase64 is returned when a byte field is not standard base64.
var ErrPrekeyType = errors.New("the prekey is an age recipient of a type this client cannot encrypt to")
ErrPrekeyType is a prekey statement whose key is a well-formed age recipient of a type this client cannot encrypt to: a Bech32 string whose human-readable part begins "age", and is neither X25519's "age" nor the hybrid's "age1pq". Nothing is wrong with such a statement: a type defined later takes no new tag, so a sender refuses it as unsupported_prekey_type, never as a statement that does not verify (PROTOCOL.md §14, §6).
var ErrQuery = errors.New("the query does not read as this call's")
ErrQuery is returned for a query a server refuses: a parameter it does not know, one given twice, or a value not of its form.
var ErrRevocation = errors.New("not a revocation of that address")
ErrRevocation is a revocation that does not verify: its document does not hash to the address, or its signature is not the identity's own over the statement.
var ErrRevokedCertificates = errors.New("not a revocation of that address's certificates")
ErrRevokedCertificates is a revocation of certificates that is malformed, is another address's, or is not signed by that address's identity itself.
var ErrUnknownMember = errors.New("the body has a member this call does not take")
ErrUnknownMember is a request body with a member its call does not take (PROTOCOL.md §3.3). Its words name the member, for the caller who sent it, and so does Member.
Functions ¶
func AcceptsSender ¶
AcceptsSender reports whether a box that declared the sender key types declared takes mail from a box whose identity is of type t, as far as the declaration says: IdentityEd25519 always, whatever was declared; any type if it declared AnySender; otherwise only a type it listed. A box that declared nothing takes IdentityEd25519 only (PROTOCOL.md §7).
func Armor ¶
Armor armors an SSHSIG blob as ssh-keygen does: base64 in lines of 70 characters between the BEGIN and END lines, ending in LF.
func CertificateBegan ¶
CertificateBegan returns when the certificate an armored signature carries began: the moment to verify a signature at when the question is whether the identity ever certified its key, and not whether it is good now. A box reading its own record asks that; nobody judging another box's word does.
func CheckCursor ¶
CheckCursor checks that s is a cursor as a list may hand one out. A client refuses a list whose next_cursor is not one.
func CheckServerName ¶
CheckServerName checks that s can stand in a pass as the server's name: the host of the server's URL, lowercase, with its port only when that is not the scheme's default (PROTOCOL.md §8).
func ContentRange ¶
ContentRange is the Content-Range of a fetch's 206 that answers the bytes from byte from of a ciphertext of length bytes: "bytes <from>-<last>/<length>", each number in digits with no leading zero, the one form a server writes and a reader takes (PROTOCOL.md §11.12).
func Dearmor ¶
Dearmor returns the SSHSIG blob inside an armored signature of at most MaxSignature bytes, as OpenSSH reads it (opensshReading). It reads the armor as "ssh-keygen -Y verify" does (OpenSSH's sshsig_dearmor): the BEGIN line first, ending in LF or CRLF, then base64 with any whitespace, then LF and the END line. One NUL may be the last byte before that LF, as sshbuf_dup_string takes it; a NUL anywhere else is refused. Anything after the END line is ignored; it is not part of the signature, but it counts toward the bound.
func Examine ¶
Examine is OpenSigned, and returns what it established about the message whether or not it passes: Findings, never nil.
func IdentityTypes ¶
func IdentityTypes() []string
IdentityTypes returns the identity key types this package verifies, the one every client must verify first.
func IfMatch ¶
IfMatch reports whether an If-Match field's condition is true for a page whose entity-tag is etag, "" for a page that carries none, as RFC 9110 §13.1.1 has an origin server evaluate it, and as PROTOCOL.md §11.2 reads it: no field is no condition; "*" as the whole value is true, a page with no tag included, since it is a current representation; a list of entity-tags, each read through its closing quote (cutETag) and empty elements taken (§5.6.1.2), is true when one of them is etag by the strong comparison (§8.8.3.2), which a weak tag never is; and a value of any other form is false: "*" among tags, a tag followed by anything but a comma, and an empty value, which lists no tag, among them. lines are the field's lines as a request carries them (http.Header.Values), read as one list (§5.3), as IfNoneMatch reads its field. net/http's checkIfMatch differs: it reads the first line alone, takes an empty one as no field, and is true at the first "*" or matching tag, whatever follows it.
func IfNoneMatch ¶
IfNoneMatch reports whether an If-None-Match field names the entity-tag etag, as RFC 9110 §13.1.2 has a server evaluate it: a value of "*" names any page, and a list of entity-tags names a page whose tag is one of them by the weak comparison (§8.8.3.2). Each tag of the list is read through its closing quote (cutETag), since a comma and a "*" are characters a tag may hold, and empty elements are taken (§5.6.1.2). A value of any other form, "*" among tags included, names nothing: the condition is true, and the page is answered in full. lines are the field's lines as a request carries them (http.Header.Values), read as one list (§5.3).
func IsMsgID ¶
IsMsgID reports whether s has the form of a msg_id, as a receiver checks one: what a sender may put in in_reply_to and supersedes.
func LowerASCII ¶
LowerASCII is s with A to Z made lowercase, and nothing else changed: the one fold every comparison of a protocol token in any case makes, in both repositories. A token is ASCII (RFC 9110 §5.6.2), and Unicode's case folding, strings.EqualFold's and strings.ToLower's, takes characters that are no token's for ASCII letters (U+017F folds to s, the Kelvin sign to k), so a value a token would refuse would read as one. The linter refuses strings.EqualFold (.golangci.yml; #268, #405).
func Member ¶
Member is the name of the member an ErrUnknownMember or an ErrMemberValue names, or "" for any other error: a server answers it as a member of its own, beside the words (PROTOCOL.md §11.3).
func MessageNamespace ¶
MessageNamespace is the namespace of a message sent TO box b, so a captured message cannot be re-sent to another box in the sender's name.
func NewPass ¶
func NewPass(signer ssh.Signer, a Address, server string, now time.Time) (authorization string, notAfter time.Time, err error)
NewPass makes box a's pass for server, good for PassLifetime from now, and returns it as the Authorization header's value. signer is the working key under its certificate (Certificate.Signer).
func PadBlock ¶
PadBlock returns the block a ciphertext of size bytes is padded to a multiple of: the smallest power of two that is at least size/16, and never under PadMinBlock.
func PaddedSize ¶
PaddedSize returns the smallest size, at least size, that is a multiple of its own block: what a ciphertext of size bytes is padded to. A size it returns is padded to itself.
func ParseQuery ¶
ParseQuery reads a query of the given parameters: each at most once, and none other (PROTOCOL.md §11.2). A parameter left out is not in the map.
func ParseTime ¶
ParseTime parses a timestamp in TimeLayout, exactly: RFC 3339, UTC, to the second, as in 2026-10-10T12:00:00Z.
func PassNamespace ¶
PassNamespace is the namespace of box a's passes.
func PassStatement ¶
PassStatement is the text a pass signs: the tag, the box's address, the server's name as the box calls it, and the moment the pass runs out, each line ending in LF.
func Plaintext ¶
Plaintext returns what to encrypt to one key of type t: the envelope's line, its signature, and the padding that makes the age file a whole number of blocks (pad.go); and that file's size. The caller encrypts what it returns and checks the file is that size.
func PollInterval ¶
PollInterval reads a list answer's Retry-After: the server's poll interval in seconds, delay-seconds in digits alone, as a 429's (PROTOCOL.md §11.11, §11.3). A list answer without one, or with one of another form, breaks its form. More digits than a uint64 holds read as the most it holds: a reader bounds the wait it takes.
func PrekeyNamespace ¶
PrekeyNamespace is the namespace of box a's prekey statements.
func ProfileNamespace ¶
ProfileNamespace is the namespace of box b's signed profile.
func RangeAsk ¶
RangeAsk is the Range a fetch sends to resume at byte from: one open-ended byte range, "bytes=<from>-" (RFC 9110 §14.1.2).
func RangeAsked ¶
RangeAsked reads a fetch's Range and If-Range fields as a server answers them (PROTOCOL.md §11.12): from is the first byte of the one open-ended byte range the request asks for, "bytes=N-" with N in digits, the unit in any ASCII case (RFC 9110 §14.1; LowerASCII), and ok is false for anything else, which a server answers with the whole body as RFC 9110 §14.2 lets it ignore a Range: no Range, two Range lines, several ranges, a closed range, a suffix range, a unit other than bytes, or a Range beside an If-Range, whose condition is false where nothing carries a validator, as no fetch does (§13.1.5). A number with more digits than an int64 holds asks from past any length. Whether from is below the ciphertext's length, as a range a server answers 206 must be, is the caller's to compare: it alone knows the length.
func RevocationStatement ¶
RevocationStatement is the fixed text that revokes box a forever. Whoever checks a revocation rebuilds it from the address, so it can be signed in advance and kept apart from the identity key. It carries no date, no reason and no number, and it never changes: a copy signed years ago still verifies.
func RevokeNamespace ¶
RevokeNamespace is the namespace of box a's revocation.
func RevokedCertsNamespace ¶
RevokedCertsNamespace is the namespace of box a's revocation of its certificates.
func SealedSize ¶
SealedSize returns the size of the age file that holds plaintext bytes encrypted to one key of type t.
func SerialAt ¶
SerialAt is the serial a client gives a certificate it makes at now, for a box whose highest serial it knows of is above, or 0 if it knows of none: the seconds since 1970 at its own clock, or one above that serial when the clock gives nothing higher (PROTOCOL.md §5).
An identity is a key, and a key keeps no record of what it signed: a box restored from its identity alone knows only what a server shows it, which may be an older record, and no server has seen a certificate that was made and not yet published. The clock is what such a box still has. Numbered by it, the certificate made later has the higher serial whichever machine made the earlier one, so the serials below a new certificate are those of every certificate made before it, known to its maker or not.
A verifier never reads a serial as a time. It compares two of them, and a higher one is later: so a clock that reads behind, or one that once read ahead, still gives a serial that rises, by one.
func Sign ¶
Sign signs message in namespace and returns the signature armored byte-for-byte as "ssh-keygen -Y sign" writes it.
func SignEnvelope ¶
SignEnvelope returns the envelope's line and the signature over it for its recipient, by signer: a working key under its certificate (Certificate.Signer). The namespace names the recipient, so the signature holds for that box only.
func SignRevocation ¶
SignRevocation signs the revocation of box a with its identity key. The statement is fixed text with no date in it, so the signature can be made long before it is used.
func SignRevokedCertificates ¶
func SignRevokedCertificates(identity ssh.Signer, r *RevokedCertificates) ([]byte, error)
SignRevokedCertificates signs a revocation of certificates with the identity key of its address.
func SignatureBound ¶
SignatureBound returns the most bytes an armored signature may be for a box whose identity is of key type t (PROTOCOL.md §3.2): what the box signs, by its identity or by a working key under a certificate its identity signed. Each type states its own, several times the largest that type makes, and a new type leaves the others where they are: were one bound to rise, a block padded past the old one would be refused by one reader and taken by another. A type not defined here has none, and every signature is over it.
func Unmarshal ¶
Unmarshal reads data, a JSON text the protocol carries, into v: the rules of PROTOCOL.md §3.3, then encoding/json/v2's with names matched exactly. A member v does not name is ignored, as a client ignores one in a response. A byte field (Bytes) is base64 in its one encoding, or ErrNotBase64. An integer field holds an integer from 0 to MaxInteger, written as one. JSON null is the same as leaving the member out.
func UnmarshalRequest ¶
UnmarshalRequest is Unmarshal for a request body, which only a server reads: a member v does not name is ErrUnknownMember.
func VerifyPrekey ¶
func VerifyPrekey(id *Identity, statement, signature []byte, now time.Time) (*Prekey, *Certificate, error)
VerifyPrekey verifies a prekey statement of the box whose identity is id, at now, and parses it. The signature must be by a working key that identity certified, under a certificate valid at now, in id's prekey namespace; the statement must name id's own address; and it must not outlive the certificate it is signed under. It returns the certificate too: whoever keeps the highest serial it has seen for the box compares. The key's type is checked last, so ErrPrekeyType is a statement good in every other way, and comes with the statement and its certificate, for a caller to hold to its own rules first (#167).
func VerifyPrekeyRecord ¶
func VerifyPrekeyRecord(id *Identity, statement, signature []byte) (*Prekey, *Certificate, error)
VerifyPrekeyRecord verifies a prekey statement as VerifyPrekey does, by every rule but its certificate's dates (CheckCertificateRecord). It answers whether the identity certified the key that signed the statement, and at which serials it stands, which is what a box asks of its own record (PROTOCOL.md §12.4): a box that was away for longer than a certificate lasts, or whose record its server took under a rule since changed, still has to read its serial to publish the next one. Nobody judging another box's prekey uses it.
Types ¶
type ACL ¶
ACL is a sender ACL in its checked form: who may send to a box. Deny is read first and then allow, as Matrix reads a room's server ACL, and a sender neither matches is refused. A box with no ACL takes mail from everyone: a nil *ACL is that.
func CheckACL ¶
func CheckACL(d *ACLDocument, maxEntries int) (*ACL, error)
CheckACL checks an ACL document as a server takes one, and returns it in its checked form. maxEntries bounds the entries of both lists together, AnyAddress counting as one and a repeat counting as often as it is written; 0 sets no bound.
func (*ACL) Admits ¶
Admits reports whether a box whose ACL is a takes mail from a sender: with no ACL, everyone; with one, a sender its deny list does not match and its allow list does.
func (*ACL) Document ¶
func (a *ACL) Document() ACLDocument
Document is the ACL as it travels, in its one form: each list as Entries writes it. A box with none answers GET with this of nil: allowing AnyAddress and denying nothing.
type ACLDocument ¶
ACLDocument is a box's sender ACL as it travels (PROTOCOL.md §11.14): the body of PUT /v1/<address>/acl, which replaces the box's ACL whole, and the answer to GET. It carries no version: the server's ETag of it is what a writer that read it names in If-Match. Allow is required, so that an ACL always says whom it lets in: a document with none is refused, where Matrix reads one as letting in nobody. Deny may be left out, for none. Write both lists, empty or not: an empty Allow that is left out is not "nobody" but a refusal.
type ACLList ¶
ACLList is one of an ACL's lists: AnyAddress, addresses, or both, the addresses sorted and each once.
func NewACLList ¶
NewACLList is the list of the given entries: AnyAddress or addresses, in any order and with repeats, which it drops.
func (ACLList) Entries ¶
Entries is the list as it travels: AnyAddress first if it is there, then the addresses in order. It is never nil, so an empty list is written as one.
type AccountResponse ¶
type AccountResponse struct {
Account string `json:"account"`
}
AccountResponse names the account a box now belongs to.
type Address ¶
type Address [32]byte
Address is a box's address: the SHA-256 of its identity document. Its text form is 64 lowercase hex characters, the only form clients may send.
func ParseAddress ¶
ParseAddress parses the text form of an address: exactly 64 lowercase hex characters. The recipient compares addresses as exact strings, so uppercase is an error, not something to fold.
func (Address) MarshalText ¶
MarshalText implements encoding.TextMarshaler.
func (*Address) UnmarshalText ¶
UnmarshalText implements encoding.TextUnmarshaler, with ParseAddress's rules.
type AdoptRequest ¶
type AdoptRequest struct {
Key string `json:"key"`
}
AdoptRequest is the body of PUT /v1/<address>/account (with a pass): an adopt key an account minted. The box joins that account, which may lift its limits.
type BoxResponse ¶
type BoxResponse struct {
Identity Bytes `json:"identity"`
Prekey Bytes `json:"prekey"`
PrekeySignature Bytes `json:"prekey_signature"`
// SenderKeyTypes is what the box declared with that prekey, on the
// server's word: nothing signs it. A server that misstates it can only
// stop a message, which it can do without it.
SenderKeyTypes []string `json:"sender_key_types,omitempty"`
// RevokedCertificates and RevokedCertificatesSignature are the
// identity's revocation of its certificates that the server holds, as a
// record carries them (Revocations).
RevokedCertificates Bytes `json:"revoked_certificates,omitempty"`
RevokedCertificatesSignature Bytes `json:"revoked_certificates_signature,omitempty"`
}
BoxResponse is GET /v1/<address>, the one read of a box: everything a sender needs before it posts (PROTOCOL.md §11.8). None of it is trusted until checked: the document against the address, the signature's certificate against the document, the statement against the certificate, and the revocation of certificates against the identity. A revoked address is answered 410 revoked with its record instead (Problem.Record).
func (*BoxResponse) Record ¶
func (b *BoxResponse) Record() *Revocations
Record is the revocation record that what b carries makes, or nil when it carries no revocation of certificates.
type Bytes ¶
type Bytes []byte
Bytes is a byte field: standard base64 on the wire, in its one encoding (RFC 4648 §3): padded, with nothing outside the alphabet, a line break included, and the unused bits of its last character zero. So a field has one text, and no two readers decode it two ways.
func (Bytes) MarshalJSON ¶
MarshalJSON implements json.Marshaler.
func (*Bytes) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler. JSON null leaves the field unset; anything but a string of base64 in its one encoding is ErrNotBase64. The string is the one encoding when encoding what it decodes to gives it back.
type Certificate ¶
type Certificate struct {
// contains filtered or unexported fields
}
Certificate is a working key's certificate that has passed CheckCertificate.
func CheckCertificate ¶
CheckCertificate checks that key is a certificate the identity id signed for a working key, and that it is valid at now. A certificate is checked against the verifier's own clock, never a time someone else states.
The rules: a user certificate with the key id CertificateKeyID, a serial of at least 1, exactly one principal, the box's address, and no critical option; for an Ed25519 working key that passes the identity's own key checks and is not the identity key; signed by the identity key, byte for byte; begun, not ended, and ending at most MaxCertificateLifetime from now. As the format says (draft-ietf-sshm-cert-01 §2.1.1): a critical option a verifier does not know is refused, and none is known here; an extension it does not know is ignored, and none is known here either; and what the reserved field holds is ignored.
func CheckCertificateRecord ¶
func CheckCertificateRecord(id *Identity, key ssh.PublicKey) (*Certificate, error)
CheckCertificateRecord checks key as CheckCertificate does, by every rule but its dates: that the identity id certified the working key, with every field in its form. It is what a box asks of its own record at its server (PROTOCOL.md §12.4): which serial it stands at, and whether its identity signed it. A certificate's dates bind what is signed under it, never a renewal past it, so a record its server took is never one its box cannot renew past for a date: one made for longer than the rule now allows, or begun earlier than a client dates one, or ended.
func NewCertificate ¶
func NewCertificate(id *Identity, identity ssh.Signer, work ssh.PublicKey, serial uint64, now time.Time) (*Certificate, error)
NewCertificate makes the certificate of serial for the working key work, signed by identity, the signer for id's key. It is what "ssh-keygen -s identity -I dromas-cert-v1 -n <address> -z <serial> -V -1h:+64d -O clear work.pub" writes: valid from MaxSkew before now to CertificateLifetime after now. It starts in the past so that a verifier whose clock is behind this one takes it at once; ssh-keygen's own default, a minute or two back, is not enough between machines that do not share a clock.
func VerifyCertified ¶
func VerifyCertified(id *Identity, message, signature []byte, namespace string, now time.Time) (*Certificate, error)
VerifyCertified verifies an armored signature over message, in namespace, made by a working key of the identity id. The signature carries the certificate: it is taken from there, checked at now (CheckCertificate), and the signature is verified under it. It returns the certificate, so a caller that keeps the highest serial it has seen can compare.
func (*Certificate) Marshal ¶
func (c *Certificate) Marshal() []byte
Marshal returns the certificate's wire bytes. Two certificates with one serial are the same certificate only if these are equal.
func (*Certificate) NotAfter ¶
func (c *Certificate) NotAfter() time.Time
NotAfter returns the first moment the certificate is no longer valid.
func (*Certificate) PublicKey ¶
func (c *Certificate) PublicKey() ssh.PublicKey
PublicKey returns the certificate as the key its signatures verify under.
func (*Certificate) Serial ¶
func (c *Certificate) Serial() uint64
Serial returns the certificate's serial. The identity gives each a higher one than the last; holding a certificate to that order is the caller's.
func (*Certificate) Signer ¶
Signer returns the signer whose signatures carry the certificate: work, the working key's own signer, under c.
func (*Certificate) SignsMail ¶
func (c *Certificate) SignsMail(now time.Time) bool
SignsMail reports whether mail may still be signed under the certificate at now: it has more than TTL left, so it outlasts the mail, which may wait that long to be read. One a client makes signs mail for its first 31 days.
func (*Certificate) WorkingKey ¶
func (c *Certificate) WorkingKey() ssh.PublicKey
WorkingKey returns the key the certificate is for.
type Code ¶
type Code string
Code is an API error's machine code, from the closed list below (PROTOCOL.md §11.3). A code names what a client does about the error: one call that can fail for causes a client meets differently has a code for each, and where the call itself tells them apart (a too_large on an ACL is entries, on a send bytes) one code serves.
const ( // 400 CodeBadRequest Code = "bad_request" // not JSON, a missing or unknown field, a bad cursor CodeBadAddress Code = "bad_address" // not 64 hex characters, or a from that is not lowercase CodeBadEncoding Code = "bad_encoding" // a byte field that is not base64 CodeIdentityMismatch Code = "identity_mismatch" // the identity does not hash to the address, or breaks its grammar CodeBadKey Code = "bad_key" // an identity key, certificate or prekey statement that is malformed or does not verify CodePrekeyNotNew Code = "prekey_not_new" // the prekey was published before: every prekey is a new key CodePrekeyTime Code = "prekey_time" // the statement or its certificate is not valid at the server's clock CodeUnsupportedKeyType Code = "unsupported_key_type" // an identity of a key type this server does not register CodeNotAge Code = "not_age" // the ciphertext is not an age file CodeWrongRecipientType Code = "wrong_recipient_type" // not exactly one stanza of a live prekey type CodeBadRevocation Code = "bad_revocation" // not a revocation of the address: the document does not hash to it, or the signature is not its identity's own // 401, with WWW-Authenticate CodePassExpired Code = "pass_expired" // the pass, or the certificate it is under, is not valid at the server's clock: sign another, retry CodeCertificateNotCurrent Code = "certificate_not_current" // the pass or statement is under a certificate that is neither the box's current one nor above it CodeCertificateRevoked Code = "certificate_revoked" // a certificate the box's identity revoked: which calls, PROTOCOL.md §8 and §11.4 // 403 CodeUnknownSender Code = "unknown_sender" // not registered CodeSenderRevoked Code = "sender_revoked" // YOUR box is revoked CodeSenderNotAllowed Code = "sender_not_allowed" // not on the box's allow list, or on its block list CodeSenderKeyType Code = "sender_key_type" // the box's client does not verify identities of YOUR box's key type CodeAdoptKeyRefused Code = "adopt_key_refused" // an adopt key that is spent, expired, revoked or not this server's // 404 CodeNoSuchMailbox Code = "no_such_mailbox" // never registered, or forgotten CodeNoSuchMessage Code = "no_such_message" // acked or expired: treat it as gone CodeNotRevoked Code = "not_revoked" // no revocation of this address is held here // 409 CodeConflict Code = "conflict" // what is held is newer, or another: a replay that is not byte-identical CodeBoxChanged Code = "box_changed" // the box changed while the request was handled: send it again as it is CodeStalePrekey Code = "stale_prekey" // a newer prekey exists: re-fetch, re-encrypt the same envelope CodePrekeyExpired Code = "prekey_expired" // the box's owner must publish a new prekey // 408 CodeRequestTimeout Code = "request_timeout" // the body did not arrive in the time the server waits for it: send it again // 410 CodeRevoked Code = "revoked" // the box you named is revoked, forever // 412 CodePreconditionFailed Code = "precondition_failed" // an If-Match the server evaluated false (RFC 9110 §13.1.1) // 413 CodeTooLarge Code = "too_large" // 429, all with Retry-After CodeRateLimited Code = "rate_limited" // a limit takes no more for now: which one is quota_id, and whom it counted against subject CodeFetchPaced Code = "fetch_paced" // the box has taken out as much new mail as it may for now: the rest waits CodeRefetchSpent Code = "refetch_spent" // the message was fetched before, and fetching again is spent for now CodePairFull Code = "pair_full" CodeMailboxFull Code = "mailbox_full" CodeMailboxBusy Code = "mailbox_busy" // the box has taken as much new mail as it takes for now CodeUnavailable Code = "unavailable" )
The error codes, grouped by HTTP status.
func (Code) Status ¶
Status returns the code's HTTP status, or 0 for a code this version does not know.
type Envelope ¶
type Envelope struct {
From Address
To Address
SentAt time.Time // to the second, UTC
// MsgID is a random UUID (version 4), made once per logical message and
// kept on every re-encryption, so a retry is never acted on twice.
MsgID string
// Body is the message: a JSON string, or any JSON value the sender sent
// as one (PROTOCOL.md §9.1).
Body json.RawMessage
Subject string // optional
InReplyTo string // optional: the msg_id replied to
Supersedes string // optional: a msg_id of the same sender this replaces
// Identity is the sender's identity document, exactly as hashed: it is
// how a recipient checks a sender it has never met without asking the
// server. From is its hash. A signed envelope always carries it; the line
// a client delivers is built without it.
Identity []byte
}
Envelope is a message: one line of JSON, which the sender's working key signs. It is encrypted to the recipient's prekey with that signature after it (signed.go), so the server sees neither.
func NewEnvelope ¶
NewEnvelope returns an envelope from one box to another with a string body, sent now, with a fresh msg_id.
func NewJSONEnvelope ¶
NewJSONEnvelope returns an envelope whose body is a JSON value, given as a JSON text: an object, an array, a number, true, false, null, or a string, which is the body NewEnvelope makes of that string's text. The body is held to the rules its reader holds it to inside the envelope (PROTOCOL.md §3.3, §12.3 check 3), and refused whole when it breaks one, never repaired: one JSON value and nothing after it, in UTF-8, with no name twice in any object, nested at most MaxDepth − 1 deep, no noncharacter, and no number past binary64. It is sent compact, its members in the order given and its strings and numbers as written, but that the line escapes U+2028 and U+2029, as encoding/json does in every string for JavaScript (RFC 8259 §12), which an envelope never is: the same value, and a reader verifies the line as it decrypted it (PROTOCOL.md §9.2). The script's jq writes them raw.
func OpenEnvelope ¶
OpenEnvelope parses an envelope and checks it, in this order: it is UTF-8 JSON, one object with no key twice; it has a body, and every member it reads is of its type (from and to are addresses, 64 lowercase hex); it is version 1, an integer written as one; its from is the VERIFIED signer and its to is this box (a signature holds for one recipient, so a mismatch is a message passed on to a box it was not written to, or one whose from names a box that did not sign it); it has a msg_id; and sent_at is no older than TTL at now, and no more than MaxSkew ahead of it. A null member is one left out (PROTOCOL.md §3.3), but for the body, whose null is a value.
Keys match exactly, as in jq and every other strict reader: "BODY" is an unknown key, ignored, never the body. No key twice, so no two readers can take different values from the envelope's fields. The body itself is the verified sender's own JSON, passed on as it is.
The caller has verified the signature over these same bytes (OpenSigned does both). Replay and duplicate checks need the caller's seen-set and are the caller's.
func OpenEnvelopeRecord ¶
OpenEnvelopeRecord checks an envelope as OpenEnvelope does, but for its sent_at, which it only parses: no clock is read. It is how a box reads back an envelope it made itself, in its outbox or its sent/ record, whose age is its own to judge. Nobody judging another box's mail uses it.
type EnvelopeError ¶
type EnvelopeError struct{ Code, Why string }
EnvelopeError is the check an envelope failed: one of the codes above and fixed words. It never quotes the envelope, whose text its sender chose: a refusal is read by whoever runs the client, and that may be a model.
func (*EnvelopeError) Error ¶
func (e *EnvelopeError) Error() string
type Findings ¶
type Findings struct {
// At is this machine's clock when it judged the message.
At time.Time
// Identity is the address the identity document the envelope carries
// hashes to. Anyone can copy a document into an envelope, so it is who
// signed only when Signed is true.
Identity *Address
// Signed reports that the signature over the line verifies, by a working
// key Identity certified, checked at that certificate's own start: so it
// holds of a certificate that has not begun, or has ended, by At. It says
// who made the message, not that it is good now.
Signed bool
// What follows is kept only when Signed, since before that it is
// anyone's numbers: the certificate's serial and the moments it begins
// and ends, and the envelope's sent_at and msg_id when each has its form.
Serial uint64
Began, Ends time.Time
SentAt time.Time
MsgID string
}
Findings is what Examine established about a message on the way to its verdict, kept with a refused one so that its owner can judge it later. Each is a value whose form was checked, or one computed here: nothing the sender wrote as text.
type Identity ¶
type Identity struct {
// contains filtered or unexported fields
}
Identity is a box's identity: its identity document, the key in it, and the address that document hashes to.
func IdentityOf ¶
IdentityOf builds the identity document for a public key of any type this package defines and parses it, with every check ParseIdentity makes.
func NewIdentity ¶
NewIdentity builds the identity document for an Ed25519 public key and parses it, with every check ParseIdentity makes.
func ParseIdentity ¶
ParseIdentity parses an identity document. It must be the two-line form every type shares, with the key in its one encoding, the wire key as it is written back once parsed, and the key must pass that type's checks: for Ed25519 a canonical point that is not of small order, which Go's crypto/ed25519 accepts, as OpenSSH's own code did before 10.6p1, so the check is explicit; for P-256 a point on the curve, which the library checks as it reads the key, as OpenSSH from 10.3p1 does.
A well-formed document of a type this package does not define is ErrIdentityType: nothing is wrong with it, and nothing it signed can be checked here.
func VerifyRevocation ¶
VerifyRevocation checks a revocation of address a: document must hash to a and be an identity document, and signature must be that identity's own over RevocationStatement(a), in RevokeNamespace(a). It returns the identity.
The signature is the identity key's itself. One made by a working key, which carries a certificate, is refused: a working key can be stolen without the identity, and must not be able to end the address.
An identity of a key type this build does not verify is ErrIdentityType, as from ParseIdentity: nothing it signed can be checked here, a revocation included. Everything else that fails is ErrRevocation.
type MessageInfo ¶
type MessageInfo struct {
ID string `json:"id"`
From string `json:"from"`
Size int64 `json:"size"`
ReceivedAt string `json:"received_at"`
ExpiresAt string `json:"expires_at"`
}
MessageInfo is one message in a list. From is the box whose pass the send carried, on the server's word: a recipient holds it to the sender it verifies inside the ciphertext. Size is the ciphertext's length in bytes: GET /v1/<address>/messages/<id> answers the ciphertext itself (CiphertextContentType), and nothing else.
type MessageList ¶
type MessageList struct {
Count int64 `json:"count"`
Messages []MessageInfo `json:"messages"`
NextCursor *string `json:"next_cursor"`
}
MessageList is GET /v1/<address>/messages: one page of metadata, oldest first. NextCursor is null on the last page, and otherwise a cursor (CheckCursor). The poll interval is no member: every list answer, a 304 among them, carries it in Retry-After (PollInterval).
type Order ¶
Order is where a prekey statement would stand in its box's order (PROTOCOL.md §5): its own serial, and the serial and the SHA-256 of the certificate its signature carries.
func PrekeyOrder ¶
PrekeyOrder reads a statement's Order and verifies nothing: not the signature, not the certificate, not the key. A server reads it to refuse, before any signature check, a statement it could not take even if it verified, and trusts it for nothing more: a statement that passes is verified in full after.
type Pass ¶
Pass is what an Authorization header carries: whose pass it is, when it runs out, and the working key's signature on the statement.
func ParsePass ¶
ParsePass parses an Authorization header's value, read as RFC 9110 §11 lets a recipient read credentials (PROTOCOL.md §8): the scheme in any case and one space or more, then the three parameters, address, not_after and signature, in any order, each name in any case and given once, with optional whitespace around each comma and each "=", empty list elements skipped (§5.6.1.2), and each value a token or a quoted-string. A quoted-string holding a backslash is refused: no value's alphabet holds a character that needs one. Each value is then held to its own form: 64 lowercase hex, a time as TimeLayout writes it, and padded standard base64 of the signature file. A sender writes the one form NewPass makes. Nothing is verified yet.
func (*Pass) Authorization ¶
Authorization is the pass in the one form a client writes it (PROTOCOL.md §8), whatever form it was read from, its signature too (armored): a server that remembers passes remembers each by this, so one pass written two ways is one pass (dromas-dev/dromas-server#69).
func (*Pass) Verify ¶
Verify checks the pass at now: that it is the pass of the box whose identity is id, made for server, not run out, running out at most MaxPassLifetime from now, and signed by a working key of that identity under a certificate valid at now. It returns the certificate: a server takes a pass only under the box's current one, and only for a name that is the server's own, which are the caller's to check.
func (*Pass) VerifyUnder ¶
func (p *Pass) VerifyUnder(sum [sha256.Size]byte, server string, now time.Time) (ssh.PublicKey, error)
VerifyUnder checks the pass against a certificate known by the SHA-256 of its bytes, sum: the one a caller has on record for the box. The pass must be for server, not run out, signed under exactly that certificate, which must name the box the pass names and must not have ended, and the working key's signature must verify. It returns the certificate.
It makes one signature check, and none for a pass under another certificate (ErrCertificate), so a pass that is not the box's is cheap to refuse. It does NOT check the identity's signature on the certificate, nor the certificate's other rules: that is CheckCertificate, which a caller makes on what this returns, as Verify does in one step.
type Prekey ¶
type Prekey struct {
Address Address // the box the key belongs to
Serial uint64 // strictly increasing under one certificate; a client gives it its certificate's serial
Key string // the age recipient: "age1pq1…" or "age1…"
NotAfter time.Time // the last moment the key may be encrypted to
}
Prekey is a prekey statement: the box's current age encryption key, signed by a working key its identity certified.
func ParsePrekey ¶
ParsePrekey parses a prekey statement. It must be exactly five LF-terminated lines with nothing after them, its serial a decimal integer with no leading zero and at most MaxSerial, its key an age recipient in canonical form that can be encrypted to (so no small-order X25519 point, in either key type), and its not_after a UTC timestamp to the second. A key that is a well-formed age recipient of another type is ErrPrekeyType.
func (*Prekey) CheckLifetime ¶
CheckLifetime checks the statement could be published at now: it has not expired, and expires at most MaxPrekeyLifetime after now.
func (*Prekey) Expired ¶
Expired reports whether the key may no longer be encrypted to at now. Senders refuse to encrypt to an expired prekey, with no grace.
type PrekeyRequest ¶
type PrekeyRequest struct {
Prekey Bytes `json:"prekey"`
PrekeySignature Bytes `json:"prekey_signature"`
// SenderKeyTypes is the declaration that comes with this prekey, in
// place of the one before: none, if it is left out.
SenderKeyTypes []string `json:"sender_key_types,omitempty"`
}
PrekeyRequest is the body of PUT /v1/<address>/prekey: a new prekey statement, signed by a working key the identity certified. The call carries a pass under that certificate, which is how a box moves to a new one.
type Problem ¶
type Problem struct {
Type string `json:"type"`
Title string `json:"title"`
Status int64 `json:"status"`
Detail string `json:"detail,omitempty"`
Code Code `json:"code"`
Hint string `json:"hint,omitempty"`
// Limit is the limit hit: the most bytes a body or a ciphertext may
// hold, or entries an ACL (too_large); how many a day a limit counted by
// the day takes (rate_limited with QuotaSends, QuotaRenewals,
// QuotaCertificates or QuotaCertificateRevocations).
Limit *uint64 `json:"limit,omitempty"`
// QuotaID is the limit a rate_limited names, and Subject whom it was
// counted against (Google's QuotaFailure.Violation). A client shows each
// only once it knows the value.
QuotaID Quota `json:"quota_id,omitempty"`
Subject Subject `json:"subject,omitempty"`
// Count is how many messages a full box holds, as its list counts them
// (mailbox_full).
Count *uint64 `json:"count,omitempty"`
// Unacked and OldestExpiresAt are how many messages from this sender the
// box holds unacked, and when the oldest of them expires (pair_full).
Unacked *int64 `json:"unacked,omitempty"`
OldestExpiresAt string `json:"oldest_expires_at,omitempty"`
// PrekeySerial and CertificateSerial are serials of a prekey and of a
// certificate: which ones each code names is PROTOCOL.md §11.3's.
PrekeySerial *uint64 `json:"prekey_serial,omitempty"`
CertificateSerial *uint64 `json:"certificate_serial,omitempty"`
// Through is the serial a revocation of certificates held runs through
// (conflict).
Through *uint64 `json:"through,omitempty"`
// KeyTypes are the identity key types a server registers
// (unsupported_key_type), and KeyType the one a box's client does not
// verify (sender_key_type).
KeyTypes []string `json:"key_types,omitempty"`
KeyType string `json:"key_type,omitempty"`
// Member is the request member or query parameter that is missing,
// unknown, given twice or not of its form (bad_request).
Member string `json:"member,omitempty"`
// Record is the revocation record of the address a 410 revoked names,
// which the read of a box always carries, and any other call's where its
// server answers from the record (PROTOCOL.md §11.3). A client checks it,
// and takes "revoked" on nothing less.
Record *Revocations `json:"record,omitempty"`
}
Problem is an error body: RFC 9457 Problem Details, with the extension members code and hint, and one member for each value the error carries (PROTOCOL.md §11.3). Every value in detail is also a member of its own (AEP-193), so a client needs nothing from detail, hint or title: they are a server's words, for a person reading the body, and a client shows none of them. A client says what the code means in its own words, with the members it has checked to their types.
func (*Problem) CodeAt ¶
CodeAt returns the problem's code if it counts in an answer of the given status: a code this version knows, under its own status, in a body whose type is that code's own (PROTOCOL.md §11.3). Otherwise it returns "", and the answer is read by its status alone: a body that names its problem twice and disagrees names none.
type QueryError ¶
type QueryError struct{ Param string }
QueryError is an ErrQuery that names the parameter at fault, which a 400 carries in member (PROTOCOL.md §11.2).
func (*QueryError) Error ¶
func (e *QueryError) Error() string
type Quota ¶
type Quota string
Quota is a limit a rate_limited names in quota_id, from the closed set below (PROTOCOL.md §11.5), as Google's QuotaFailure names one in its quota_id. Each is counted against one subject. A server's limit outside the set names none, and gives its subject alone.
const ( QuotaPolls Quota = "polls" // a box's polls QuotaPages Quota = "pages" // a box's list pages after the first QuotaCalls Quota = "calls" // a box's calls with its pass that no other limit counts QuotaSends Quota = "sends" // a box's sends, a number a day QuotaRenewals Quota = "renewals" // a box's renewals under its current certificate, a number a day QuotaCertificates Quota = "certificates" // its identity's new certificates, a number a day QuotaCertificateRevocations Quota = "certificate_revocations" // its identity's raises of its revocation of certificates, a number a day QuotaRegistrations Quota = "registrations" // registrations from an IP address: an IPv4 address or an IPv6 /64 QuotaRegistrationsRange Quota = "registrations_range" // registrations from an IPv6 /48 QuotaMisses Quota = "misses" // calls from an IP address that named no box or carried nothing that verified QuotaSendsBeforePass Quota = "sends_before_pass" // sends from an IP address, counted before their pass is read QuotaRequests Quota = "requests" // calls from an IP address that carry no pass the server has checked, registrations and renewals among them, and requests no route matches )
The quotas, by whom each is counted against.
type Receipt ¶
type Receipt struct {
ID string `json:"id"` // lowercase hex SHA-256 of the ciphertext
ReceivedAt string `json:"received_at"`
ExpiresAt string `json:"expires_at"`
}
Receipt is the answer to a send. A retry of the same ciphertext gets the original receipt.
type RegisterRequest ¶
type RegisterRequest struct {
Identity Bytes `json:"identity"`
Prekey Bytes `json:"prekey"`
PrekeySignature Bytes `json:"prekey_signature"`
// SenderKeyTypes declares the identity key types the box's client
// verifies: the senders the box takes mail from (AcceptsSender). It is
// not signed, and it stands until the box's next prekey replaces it.
SenderKeyTypes []string `json:"sender_key_types,omitempty"`
}
RegisterRequest is the body of PUT /v1/<address>: the identity document, and the first prekey statement with the signature over it of a working key the identity certified. The call carries a pass under that certificate.
type Revocations ¶
type Revocations struct {
// Type is RecordType, or nil when the record read had none.
Type *string `json:"type,omitempty"`
// Identity is the identity document of the address.
Identity Bytes `json:"identity"`
// Revocation is the identity's signature over RevocationStatement, in
// RevokeNamespace: the address is revoked, for good.
Revocation Bytes `json:"revocation_signature,omitempty"`
// RevokedCertificates is the address's revocation of its certificates
// through a serial, and RevokedCertificatesSignature the identity's
// signature over it (VerifyRevokedCertificates): the address goes on, and
// nothing signed under a revoked certificate is taken.
RevokedCertificates Bytes `json:"revoked_certificates,omitempty"`
RevokedCertificatesSignature Bytes `json:"revoked_certificates_signature,omitempty"`
}
Revocations is what a server holds about an address: the body of PUT /v1/<address>/revocations and the answer to GET of the same path (PROTOCOL.md §10.3). It proves itself: the document hashes to the address and the signature verifies under the document's key, so it is worth the same from any server or from a file a person handed over.
It is also the record a box's owner makes in advance and keeps apart from the box. It holds the identity document because a box that is lost no longer has one.
It names its own type, RecordType, in its member type, so that one JSON text is the record a person keeps, the body a server takes, the answer it gives, and the line dromas revocation make prints. Every writer writes it, in no set order (PROTOCOL.md §10.3); MarshalJSON writes it first, as the client prints every line's type. A reader takes a record without it, as the route says what the body is, and refuses one that names any other type (ErrMemberValue).
func (Revocations) MarshalJSON ¶
func (r Revocations) MarshalJSON() ([]byte, error)
MarshalJSON writes the record with its type first, whatever Type holds.
type RevokedCertificates ¶
RevokedCertificates is what an identity signs to revoke the certificates it made for its address, and keep the address (PROTOCOL.md §10.2): every certificate whose serial is Through or below. Nothing signed under one is taken, whenever it was signed. Through is its own order: whoever holds one keeps the highest it has verified, and one that is not higher changes nothing, so a certificate once revoked stays revoked.
func NewRevokedCertificates ¶
func NewRevokedCertificates(a Address, through uint64) (*RevokedCertificates, error)
NewRevokedCertificates builds box a's revocation of every certificate up to and including serial through, which is one a certificate may have.
func ParseRevokedCertificates ¶
func ParseRevokedCertificates(statement []byte) (*RevokedCertificates, error)
ParseRevokedCertificates parses a revocation of certificates exactly: the tag, a lowercase address and a serial from 1 to MaxSerial with no leading zero, each on a line ending in LF, and nothing else: no CR, no blank line, no byte after the last LF.
func VerifyRevokedCertificates ¶
func VerifyRevokedCertificates(a Address, document, statement, signature []byte) (*RevokedCertificates, error)
VerifyRevokedCertificates checks a revocation of address a's certificates: document must hash to a and be an identity document, signature must be that identity's own over statement, in RevokedCertsNamespace(a), and statement must be a revocation of certificates, in its exact form, that names a. As with a revocation of the address, a working key's signature is refused: a certificate cannot revoke another, or itself.
An identity of a key type this build does not verify is ErrIdentityType. Everything else that fails is ErrRevokedCertificates.
func (*RevokedCertificates) Revokes ¶
func (r *RevokedCertificates) Revokes(serial uint64) bool
Revokes reports whether a certificate of this serial is revoked.
func (*RevokedCertificates) Statement ¶
func (r *RevokedCertificates) Statement() []byte
Statement returns the revocation as it is signed: the tag, the address and the serial it revokes through, each on a line.
type SendQuery ¶
type SendQuery struct {
// PrekeySerial is the serial of the prekey encrypted to. When the box
// has a newer one the server answers stale_prekey, and the sender
// encrypts the same signed envelope again.
PrekeySerial uint64
// CertificateSerial is the serial of the certificate that prekey's
// statement is signed under. A prekey is named by the two together: a
// box's statements are ordered by certificate first, so a prekey serial
// can come again under a later certificate.
CertificateSerial uint64
}
SendQuery is the query of POST /v1/<recipient>/messages, whose body is an age file encrypted to the recipient's prekey. The sender is the box whose pass the call carries. The message's signature is inside the file, so the server is handed none.
func ParseSendQuery ¶
ParseSendQuery reads a send's query as a server reads it: each serial once, written as a statement writes one (a decimal integer from 1 to MaxSerial, with no leading zero), and no other parameter.
type Signed ¶
type Signed struct {
Envelope *Envelope
// Line is the envelope exactly as it was signed: the sender's bytes.
Line []byte
// Signature is the signature block exactly as it arrived.
Signature []byte
// Sender is the identity the envelope carries, whose address is From.
Sender *Identity
// Certificate is the one the signature was made under.
Certificate *Certificate
}
Signed is a message that passed OpenSigned.
func OpenSigned ¶
OpenSigned checks what a recipient decrypted, in this order, and returns the message only if every check passes. A failure is an *EnvelopeError with one of the Refused codes.
- It is an envelope line and then a signature block.
- The line is UTF-8 JSON, one object with no key twice.
- It carries an identity document: the exact format, a key that passes the key checks.
- Its from is that document's hash. A well-formed document of a key type this client does not verify is refused here, after its hash: with its own code, since nothing is wrong with it.
- The signature over the line, as decrypted, verifies in this box's message namespace, by a working key that identity certified, under a certificate valid at now.
- The envelope, in OpenEnvelope's order: a body and every member of its type, version 1, to is this box, a msg_id, and a sent_at no older than TTL and no more than MaxSkew ahead.
The identity document is read from the envelope before the signature is checked. That is sound: the document proves itself, since its hash is the address, and the signature must then verify under it.
type Stanzas ¶
type Stanzas struct {
// Native are the types of its native stanzas, those of a type a prekey
// has (PostQuantum, Classic), in the header's order.
Native []KeyType
// Other is set when it has a stanza that is neither native nor grease.
Other bool
}
Stanzas is how an age file's recipient stanzas stand to the protocol (PROTOCOL.md §11.10 step 7, §12.3 check 1). A server takes a send with exactly one native stanza, of the recipient's current prekey's type, beside grease, and refuses one with none, with two or more, or with a stanza of another type; a reader may refuse a file without exactly one native stanza, before any key agreement.
func ReadStanzas ¶
ReadStanzas reads the recipient stanzas of the age file ciphertext, and sorts each once, before anything is decrypted: the one reading of them a reader and a server make (dromas-client's oneNative, and a server's check of a send). The header is read by age itself, once (age.Decrypt, given only an identity that keeps the stanzas it is given and matches none, which ends at the header), so they are the stanzas age would try. A file whose header age does not read is an error, with no Stanzas beside it.
type Subject ¶
type Subject string
Subject is whom a rate_limited was counted against (subject).