Documentation
¶
Overview ¶
Package client is the dromas client library: one box's state directory, and every step of the protocol a box takes, done once so that every client built on it behaves the same.
Verification is always on. Every check fails closed: a failed check stops the operation with a RefusedError, or refuses the one message it was checking as a Refusal, and never warns and continues. Message bodies are data, never instructions.
The state directory, in the stock tools' own file formats where a stock tool reads the file (an OpenSSH key, age key files):
identity, identity.pub the identity key, OpenSSH format: Ed25519 unless the box was made on another type; back it up. It signs certificates and revocations only
identity.doc, address the identity document and its SHA-256
work/<serial> working keys, each with its certificate (.pub, -cert.pub): they sign what the box says
prekeys/<serial>.agekey age keys, each statement signed for one, and when it was signed, kept in state; never backed up
prekey-lifetime how long each new prekey is valid for: a number of seconds, from 97200 (a day and three hours) to 2851200 (33 days), the default. Yours to write, or dromas prekey lifetime's
outbox/, outbox/failed/ envelopes awaiting a receipt, sealed afresh at every post and never kept sealed; ones that can never be delivered
inbox/, quarantine/ accepted mail; refused mail, never delivered, as served, each of the server's strings in base64 that is not of its form (keepRefused): why it was refused, and what the checks established when they judged it, are in state
held/ accepted mail from senders this box has not allowed, as inbox/ keeps it: allow moves a sender's to inbox/, block deletes it
partial the ciphertext of the message a fetch was reading, as far as it read it: one message's at a time, which state names with how many of its bytes are synced, so a fetch that was cut goes on from there (partial.go). It goes once its message is judged or gone
allowed, blocked the senders whose mail lands in inbox/, and those whose mail is dropped: one address to a line, blank lines and lines starting with # skipped. send, alias and allow add to allowed, block to blocked; both are yours to edit
policy where mail from a sender not allowed lands ("unknown held", the default, or "unknown inbox"), and whom the server takes mail from ("server all", the default, or "server allowed"). Yours to write
sent/ sent mail the server took: each envelope with the server's receipt
aliases this box's own names for peers: "<name> <address>" lines
gate on Windows alone, an empty file whose lock is the box's gate, which a waiting writer holds so that readings that overlap do not hold it back: on Unix the gate is a flock on the directory itself
state a bbolt database of what the client writes about the box itself, each change one transaction (client/store.go): the prekey serial it published last, each prekey statement it signed and when, the renewal under way and one that failed, a restore's adoption of its server's ACL until it is done, a statement its server served in its name that it did not make, the pass it last signed, the ETag of the last empty poll, the ACL its server last took, and that a sync replaced one holding what no file of the box says until recv reports it, its own revocations once one verifies, what it pinned of each peer, the replay set and its floor, and why each message in quarantine/ was refused. It is the box's lock too: each command that writes the box holds it open from its start to its end, a second process waits for it, and one killed leaves nothing. A box directory on NFS is not supported
.gitignore "*": the box ignores itself in any repository it lands in
Index ¶
- Constants
- func Dir() (string, error)
- func IsID(s string) bool
- func Publish(ctx context.Context, opt Options, rec *protocol.Revocations) (protocol.Address, error)
- func RecvWithin(lifetime time.Duration) time.Duration
- func Revoke(ctx context.Context, opt Options, rec *protocol.Revocations) (protocol.Address, error)
- func ValidAlias(name string) error
- type APIError
- type Alias
- type Box
- func (b *Box) ACLPending(ctx context.Context) (bool, error)
- func (b *Box) Address() protocol.Address
- func (b *Box) Adopt(ctx context.Context, key string) (string, error)
- func (b *Box) Aliases() ([]Alias, error)
- func (b *Box) Allow(ctx context.Context, a protocol.Address) ([]Delivery, error)
- func (b *Box) Allowed() ([]protocol.Address, error)
- func (b *Box) Ask(ctx context.Context, a protocol.Address) (*Held, error)
- func (b *Box) Block(ctx context.Context, a protocol.Address) (int, error)
- func (b *Box) Blocked() ([]protocol.Address, error)
- func (b *Box) CertificatesRecord(ctx context.Context) (*protocol.Revocations, error)
- func (b *Box) Destination(s string) (protocol.Address, error)
- func (b *Box) Dir() string
- func (b *Box) Flush(ctx context.Context) ([]*Sent, error)
- func (b *Box) HeldMail() ([]HeldSender, error)
- func (b *Box) Identity() *protocol.Identity
- func (b *Box) InboxLine(id string) ([]byte, error)
- func (b *Box) KeepRevocation(ctx context.Context, rec *protocol.Revocations) (a protocol.Address, h *Held, err error)
- func (b *Box) Leave(ctx context.Context) error
- func (b *Box) Link() string
- func (b *Box) NotMade(ctx context.Context) (*NotMade, error)
- func (b *Box) Policy() (Policy, error)
- func (b *Box) PrekeyLifetime() (time.Duration, error)
- func (b *Box) Publish(ctx context.Context, rec *protocol.Revocations) (protocol.Address, error)
- func (b *Box) Quarantined(ctx context.Context) ([]Quarantined, error)
- func (b *Box) RebuildAllowed(ctx context.Context) (int, error)
- func (b *Box) Receive(ctx context.Context) (*Result, error)
- func (b *Box) Rekey(ctx context.Context, kt protocol.KeyType) (*protocol.Prekey, error)
- func (b *Box) RemoveAlias(ctx context.Context, name string) error
- func (b *Box) RemoveQuarantined(ctx context.Context, ids []string) ([]string, error)
- func (b *Box) RenewAndRevoke(ctx context.Context, kt protocol.KeyType) (*Remedy, error)
- func (b *Box) Resolve(ctx context.Context, peer protocol.Address) (*protocol.Prekey, error)
- func (b *Box) Revocation(ctx context.Context) (*protocol.Revocations, error)
- func (b *Box) Revoke(ctx context.Context, rec *protocol.Revocations) (protocol.Address, error)
- func (b *Box) RevokeEarlier(ctx context.Context) (*protocol.RevokedCertificates, error)
- func (b *Box) Send(ctx context.Context, to protocol.Address, body string) (*Sent, error)
- func (b *Box) SendJSON(ctx context.Context, to protocol.Address, body []byte, f Fields) (*Sent, error)
- func (b *Box) SendWith(ctx context.Context, to protocol.Address, body string, f Fields) (*Sent, error)
- func (b *Box) SetAlias(ctx context.Context, name string, a protocol.Address) error
- func (b *Box) SetPolicy(ctx context.Context, p Policy) error
- func (b *Box) SetPrekeyLifetime(ctx context.Context, d time.Duration) error
- func (b *Box) Status(ctx context.Context) (*Status, error)
- func (b *Box) SyncACL(ctx context.Context) error
- type Delivery
- type Fields
- type Held
- type HeldSender
- type NotMade
- type Options
- type Policy
- type Quarantined
- type Refusal
- type RefusedError
- type Remedy
- type Result
- type RevokedError
- type Sent
- type Status
Constants ¶
const ( RefusedDecrypt = "undecryptable" // not encrypted to a prekey this box holds, or not to exactly one recipient of a prekey's type RefusedReplay = "replay" // this ciphertext was already received RefusedID = "bad_id" // the server's id for it is not the SHA-256 of its ciphertext RefusedPrekeyType = "unsupported_prekey_type" // the recipient's prekey is an age recipient of a type this client cannot encrypt to )
Why a message was refused, as codes: with protocol's (bad_message, bad_envelope, bad_identity, bad_signature, reattributed, stale), a closed set; and why a send refused its recipient's prekey statement when nothing is wrong with it (RefusedPrekeyType). A refusal's words are this client's own and never quote the message, its claimed sender's text or the server's: whoever reads a refusal may be a model.
const ( RefusedRevoked = "revoked" RefusedCertificate = "certificate_revoked" )
RefusedRevoked is why mail from a revoked address is refused: this box holds a revocation of the sender that verifies. RefusedCertificate is why mail under a revoked certificate is: its sender's address goes on, and the identity has revoked the certificate the mail was signed under.
const ( // LandHeld and LandInbox are where mail from a sender the box has not // allowed lands: held/ (the default), or inbox/ as any other. LandHeld = "held" LandInbox = "inbox" // AdmitAll and AdmitAllowed are whom the box's server takes mail from: // every sender the box has not blocked (the default), or only those it // has allowed. AdmitAll = "all" AdmitAllowed = "allowed" )
The values of a box's policy.
const DefaultBase = "https://dromas.dev"
DefaultBase is the server every box talks to unless told otherwise.
const DefaultDrainTime = 15 * time.Minute
DefaultDrainTime is how long one Receive drains while bytes move, unless Options.DrainTime (recv --drain-time) says otherwise: the drain's bound in time, beside its others. At it a call under way is cut, and a fetch keeps what it read for the next Receive to go on from (partial.go), so a drain of large mail on a slow link completes over several (#2, #381).
const EchoAddress = "82be51e181471140db80904d4992a99383028c1fbc92e1f53614341def8c2225"
EchoAddress is the echo box dromas.dev runs (cmd/dromas-echo): send it anything, and it answers with a signed reply saying what it checked and carrying the body back. Its prekey is post-quantum. Mail to it is readable by dromas.dev's operator, who runs it.
const EchoAddressNoPQ = "518a0a89c533354f0d5a890599722a86d01b9b2d8c87549bb24c3276e819048c"
EchoAddressNoPQ is dromas.dev's second echo box, whose prekey is X25519 only: the one a sender on age 1.2 can reach (sh/dromas.sh; this client reaches both). It answers as EchoAddress does, and mail to it is as readable by dromas.dev's operator.
const MinPrekeyLifetime = renewAfter + time.Hour + renewTime + protocol.MaxSkew
MinPrekeyLifetime is the shortest a box's prekeys may be valid for: the day Receive waits to renew one (renewAfter), an hour of slip for when recv runs, renewTime, and protocol.MaxSkew for clocks, the hour a sender's may read ahead of the box's (PROTOCOL.md §3.4, §12.4). At it, an hourly recv renews in time.
Variables ¶
This section is empty.
Functions ¶
func Dir ¶
Dir returns the state directory of the box to use, as the stock recipe finds it: $DROMAS_HOME if set, otherwise ${XDG_CONFIG_HOME:-$HOME/.config}/dromas/${DROMAS_BOX:-default}, the XDG Base Directory Specification's home for configuration. It is never the working directory, which for a coding agent is a git repository.
func IsID ¶
IsID reports whether s is a message's id as this box names it: the SHA-256 of its ciphertext, as 64 lowercase hex. Each file inbox/, held/ and quarantine/ keep is named by one.
func Publish ¶
Publish publishes a record to the server opt names: a revocation of an address, a revocation of its certificates, or both. It needs no box and no key, only the record, so a box that is lost can still be revoked with a record made in advance (Box.Revocation). Each part is checked here first, and a record with a part that does not verify goes nowhere.
func RecvWithin ¶
RecvWithin is how often a box must run Receive for a prekey of the given lifetime to be renewed while every sender whose clock a server takes still takes it: Receive renews one once it is a day old, and the prekey then has renewTime and protocol.MaxSkew left, the room whoever makes a time-limited thing leaves (PROTOCOL.md §3.4).
func Revoke ¶
Revoke publishes a revocation to the server opt names: the address the record is for is revoked there, for good. It needs no box and no key, only the record, so a box that is lost can still be revoked with a record made in advance. The record is checked here first. It is Publish, of a record that must carry a revocation of the address.
func ValidAlias ¶
ValidAlias returns nil if name can be an alias, and otherwise why not.
Types ¶
type APIError ¶
type APIError struct {
Status int
Problem protocol.Problem
// RetryAfter is the server's Retry-After, or zero.
RetryAfter time.Duration
// Dated is the time in the answer's Date header, and Read this box's
// clock when the answer came: both zero unless the call carried a pass
// and the answer a Date. They are set side by side in the error for a
// pass or a statement refused for its time, and never adopted: a box
// checks everything against its own clock, and a server may lie about
// the time as about anything else.
Dated, Read time.Time
}
APIError is a non-2xx answer from the server. Problem is the RFC 9457 body when the server sent one with a code this client knows, for the status the code has, beside the type that is the code's own (PROTOCOL.md §11.3); otherwise its code is empty.
func (*APIError) Error ¶
Error is the status and the code, then what the code means, in this client's own words, with the values the error carries once each is checked to its type. Nothing the server wrote is in it: its detail, hint and title are for a person reading the body, and whoever reads this may be a model (PROTOCOL.md §11.3). A code this client does not know is not printed at all, since any text can be made to fit a code's grammar.
type Alias ¶
Alias is this box's own name for a peer's address: a word to send to in place of 64 hex characters. Aliases live in the box's aliases file, one "<name> <address>" line each, and never leave the box: neither the server nor the peer sees them. An alias is only as good as where its address came from.
type Box ¶
type Box struct {
// contains filtered or unexported fields
}
Box is one box: its state directory and the server it talks to. It is safe for concurrent use: a command or reading of one goroutine waits for another's, as one of another process does.
func Init ¶
Init creates a box in dir, or finishes an init that stopped half way: it makes the identity, the first working key with its certificate, and the first prekey (of type kt) if they are missing, and registers the box. The registration carries a pass the working key signs, so the box is in use at once. Run again on a finished box, it changes nothing.
func Open ¶
Open opens an existing box's state directory. A directory holding only the identity, a box restored from its backup, gets the box's other directories; Box.Rekey then gives it a working key and a prekey, and the lists its server's ACL holds when it has none of its own.
func (*Box) ACLPending ¶
ACLPending reports whether the box's files say an ACL other than the one its server last took: the next send, recv, allow or block sends it.
func (*Box) Adopt ¶
Adopt puts the box in the account an adopt key names, and returns the account's id as the server gives it. An account can lift the box's limits; the server can tell who pays for the box, and nothing else about its mail changes. Keep the key secret: anyone holding it can adopt a box into the account until it is spent or expires.
func (*Box) Aliases ¶
Aliases returns the box's aliases, sorted by name. A line of the aliases file that is not "<name> <address>" names nothing and is skipped.
func (*Box) Allow ¶
Allow allows a sender: its mail lands in inbox/ from now on, whatever the policy says, and what held/ keeps from it moves there. A sender the box blocks is refused, with nothing done: blocking wins. The moved messages are returned as Receive delivers them, oldest first, but for Plaintext and Signature, which the box never kept. Then the server is sent the box's ACL, if the change is one it holds (Box.SyncACL): an error from that leaves the sender allowed here.
func (*Box) Allowed ¶
Allowed returns the senders whose mail lands in inbox/ whatever the policy says: those the allowed file lists and those an alias names.
func (*Box) Ask ¶
Ask asks this box's server what it holds about a, keeps what verifies, and returns what the box then holds: a box may ask at any time, and whatever it has verified about an address it keeps. A send asks by itself (Box.SendWith), and so does a drain, of each sender (Box.Receive).
A server that answers that it holds nothing has answered, and that is its word: it can keep a record back, and nothing here tells. What the box holds already stands whatever a server answers. No answer is an error.
func (*Box) Block ¶
Block blocks a sender: its mail is dropped from now on, and its server refuses it, which deletes what the server holds from it; what held/ keeps from it is deleted. It returns how many held messages it deleted. Blocking wins over allowing. An error sending the server the box's ACL leaves the sender blocked here, and the next send, recv, allow or block sends it.
func (*Box) Blocked ¶
Blocked returns the senders whose mail this box drops, and its server refuses.
func (*Box) CertificatesRecord ¶
CertificatesRecord is the record of this box's own revocation of its certificates that it keeps, the one it published or was shown, or nil: what dromas cert publish-revoked sends again.
func (*Box) Destination ¶
Destination parses where to send: an address, a link to one on this box's server, or one of this box's aliases (see Box.SetAlias). A link to another server is refused, since a box sends only through its own.
func (*Box) Flush ¶
Flush posts again every message still in the outbox, each sealed afresh to what the box holds now, as a send seals it: a seal is made for one post and never kept. A message the server took whose answer was lost is posted again as a new ciphertext, which the server stores and counts again and its reader drops by its msg_id (PROTOCOL.md §12.2). It tries every entry; one that fails never stops the others, and the error names each that failed. The server is asked about each recipient once, as a send asks (Box.SendWith).
func (*Box) HeldMail ¶
func (b *Box) HeldMail() ([]HeldSender, error)
HeldMail returns the mail held/ keeps, by sender, the sender with the oldest first. It reads only held/, and calls no server.
func (*Box) InboxLine ¶
InboxLine returns the line inbox/<id>.json keeps: the message's checked fields, as recv printed them ([Box.keptLine]). An id that is not one is refused before any file is read.
func (*Box) KeepRevocation ¶
func (b *Box) KeepRevocation(ctx context.Context, rec *protocol.Revocations) (a protocol.Address, h *Held, err error)
KeepRevocation takes a record someone handed over, of this box's own address or of anyone's: a revocation of the address, a revocation of its certificates, or both. Each part is checked, and what verifies is kept. It is the answer to a server that hides one: the record proves itself wherever it came from. A record with a part that does not verify is refused whole, and nothing of it is kept: whoever handed it over is told, where a server's answer that does not verify counts as nothing shown.
It returns the address and what this box then holds about it. A revocation of certificates through no higher a serial than the one held changes nothing.
func (*Box) Link ¶
Link returns the box's link, <server>/to/<address>: what to give whoever should write to it.
func (*Box) NotMade ¶
NotMade is the statement the box's server served in its name, at the box's last read of itself, that the box did not make, or nil.
func (*Box) Policy ¶
Policy reads the box's policy file: "<setting> <value>" lines, "unknown held" or "unknown inbox", and "server all" or "server allowed", each at most once, with blank lines and lines starting with "#" skipped. A file with any other line is an error, never a default: a policy that cannot be read stops what depends on it until it is put right.
func (*Box) PrekeyLifetime ¶
PrekeyLifetime is how long each new prekey of this box is valid for: the seconds its prekey-lifetime file names, or protocol.PrekeyLifetime when it has none. A file that names anything but a lifetime from MinPrekeyLifetime to protocol.PrekeyLifetime is an error, never read as the default: the box renews no prekey until it is put right.
func (*Box) Publish ¶
Publish is Publish as this box: to its own server, under its lock. It keeps nothing of the record (see Box.KeepRevocation), but what the server shows when it answers that it holds as much already, once that verifies, as of every answer (proved).
func (*Box) Quarantined ¶
func (b *Box) Quarantined(ctx context.Context) ([]Quarantined, error)
Quarantined lists quarantine/, by id: each message it keeps, with what the box's state keeps of why it was refused. A message with no record there, or one this client did not write, has the code "unknown" and nothing from it. Each value is held to its form again as it is read.
func (*Box) RebuildAllowed ¶
RebuildAllowed adds back to the allowed file every address the box has an alias for or has sent mail to, as sent/ names them, and returns how many it added. What the file lists stays. It is for a box whose allowed file was lost: send, alias and allow keep it otherwise.
func (*Box) Receive ¶
Receive drains the box: every page, every message, up to the drain's limits, its time among them (Options.DrainTime). Each message is judged by one gate (see [Box.judge]); only one that passes every check lands, where the box's say over its sender puts it: inbox/ for a sender it allowed, held/ for one it has not (unless its policy lands that mail in inbox/ too), and nowhere for one it blocked. Refused mail goes to quarantine/. All of it is acked. A transient error stops the drain and leaves the rest for the next one. It holds the box's lock throughout ([Box.hold]): a second Receive, or any other command that writes the box, waits for it.
First it renews the prekey if the current one is more than a day old, so a box received at least monthly never lapses. Once that renewal is taken, a read of the ACL its server holds is owed, and the first Receive that can make it makes it, renewing or not, and replaces one the box did not send (aclAt, checkACL). A drain that empties the box then deletes the retired prekeys no mail can still need (see [Box.dropRetired]): all but the newest. And once the server has answered the drain's list, so has taken a pass dated by this machine's clock, the prekeys whose deletion time has come by that pass's date go too.
A box that holds its own revocation receives nothing.
func (*Box) Rekey ¶
Rekey publishes a NEW prekey, of type kt, or of the current prekey's type if kt is empty (post-quantum, init's default, when there is none to read), signed by a NEW working key under a new certificate. The three share one number, which this machine's clock gives (protocol.SerialAt). If an earlier Rekey signed one and the server did not take it, that one is sent again, unchanged, until it can no longer be sent (PROTOCOL.md §12.4): it has expired or does not verify by this clock, its certificate began less than an hour before the clock or is at or below a serial the identity revoked, it was made for longer than the box's lifetime now allows or has no record of when it was made, this box no longer holds its key or its working key, or another type is asked for. A key is signed once, so no key is encrypted to for longer than its lifetime, at most 33 days or the box's shorter one, whatever the server does with what it was sent. It needs only the identity, so it also recovers a box that lost its prekeys or its working key, and such a box needs no record of the numbers the lost machine used; one that lost its allowed, blocked and policy files too takes them from the ACL its server holds (adoptPending). The upload carries a pass under the new certificate, and the server then takes no pass under an older one. Box.Receive renews the prekey once it is more than a day old, so a box received at least monthly never lapses; Rekey is for a new key type, for recovery, and for boxes that are only ever sent from.
It holds the box's lock, so two runs never make the same serial's key at once, and it refuses a served serial below the highest this box has published (a rollback), whose next serial's key was already published. A prekey with an error is one the server took, and a fault of this box after that.
func (*Box) RemoveAlias ¶
RemoveAlias removes a name. The peer's pinned identity and mail stay, and it stays allowed: un-naming a peer is not blocking it.
func (*Box) RemoveQuarantined ¶
RemoveQuarantined deletes refused messages from quarantine/, each with what the box's state keeps of it, and returns the ids it deleted; with no ids it deletes them all. An id that is not one, or that quarantine/ does not hold, is refused before anything is deleted.
It holds the box's lock from start to end ([Box.hold]), as every command that writes the box does.
func (*Box) RenewAndRevoke ¶
RenewAndRevoke is the remedy for a box whose working key may have been taken, with the address kept: a revocation, signed by the identity, of every certificate before a new one, then a new prekey under a new working key and that new certificate.
It reads the box once at its server, for what is held of the box's own revocation of its certificates. An answer it cannot use stops nothing: the revocation still goes to the server (PROTOCOL.md §12.6). It sends nothing an earlier renewal left: whoever copied the box while one waited holds its working key too.
The revocation is published first, once, through the serial below the number this machine's clock gives, or through the highest one held if that is higher. A server that holds a higher one it did not show answers conflict: the box reads itself once more, keeps that one once it verifies, and goes on above it, or stops there with nothing renewed when it shows none that covers what this one would have. Only then is the new certificate numbered, one above the revocation, so it is never revoked as it is made, and the renewal is sent, once. Once a server has taken the revocation it takes nothing under the certificates it revokes, the one the box was using among them: whoever holds that working key can no longer spend the box's calls, and the renewal that follows spends none of the box's counts, the calls a taker spent before the revocation among them (PROTOCOL.md §10.2, §11.9; dromas-dev/dromas-server#84). While the box's server holds the revocation and not the renewal, the box takes no mail there and signs none, and Box.Receive and Box.Rekey send the renewal again.
It returns no Remedy where it stopped before it published anything, a box shown its own revocation on the way among them, which is its RevokedError alone; otherwise the Remedy says what each publish met, and an error beside it is a fault of this box after a server took the revocation or the renewal, or the box's own revocation, shown at the renewal.
func (*Box) Resolve ¶
Resolve fetches a peer's identity and current prekey and verifies them. It returns the prekey only when every check passes; until then there is nothing to encrypt to.
- The identity is the pinned one, or at first contact the served one, which must hash to the address. A pin always wins over what the server sends later.
- The prekey statement must be signed by a working key that identity certified, under a certificate valid now, in the peer's prekey namespace, and name the peer. One whose key is an age recipient of a type this client cannot encrypt to is refused with its own code, RefusedPrekeyType.
- It may not be ordered before one already seen (rollback): under an earlier certificate, or under the same one with a lower serial. A later certificate outranks any serial under an earlier one, so a box that moved to a new certificate is followed whatever serial it publishes.
- Its certificate may not be one the peer's identity has revoked, by the revocation this box holds, and it may not have expired.
- A peer this box holds a revocation of is refused before anything is asked: a RevokedError, whatever the server now serves for it.
The identity is pinned, and the serials recorded, only after every check passed: nothing is kept of a peer whose statement did not verify. It holds the box's lock ([Box.hold]).
func (*Box) Revocation ¶
Revocation returns this box's revocation: its identity document and the identity's signature over the fixed statement. It publishes nothing. The record is what Revoke sends, and it can be made long before it is needed and kept somewhere other than the box. A box that holds its own revocation returns the one it holds, and signs nothing (sign).
func (*Box) RevokeEarlier ¶
RevokeEarlier revokes every certificate this box's identity made before the one in use: it signs a revocation of them, publishes it, and keeps it. Box.RenewAndRevoke is the whole remedy for a box whose working key may have been taken, and is what "dromas cert revoke-earlier" runs; this is its second half alone, for a box that has renewed already.
It revokes through the serial below the one in use, as the box holds it once it has read itself at its server (known), which records a renewal the server took whose answer the box never had. A box numbers a certificate by its clock (protocol.SerialAt), so that covers every certificate made before this one on any machine, whether this box knows of it or not: one a lost machine made and never published is among them, unless that machine's clock read ahead of this one's (PROTOCOL.md §5); a later run covers that one.
It returns the revocation, or nil when the certificate in use has serial 1, which nothing is below. A revocation with an error is one the server took, and a fault of this box after that: the box keeps it at its next read of itself (#383). Mail this box sent under those certificates, and that its reader has not read yet, is refused too: nobody can tell it from a thief's.
func (*Box) Send ¶
Send sends one logical message with none of the optional fields: see Box.SendWith.
func (*Box) SendJSON ¶
func (b *Box) SendJSON(ctx context.Context, to protocol.Address, body []byte, f Fields) (*Sent, error)
SendJSON is Box.SendWith with a body that is a JSON value, given as one JSON text, where SendWith's is text: an object, say, arrives as that object and not as a string holding it. It is refused whole, with nothing written, when its reader would refuse it (protocol.NewJSONEnvelope has the rules), and sent compact, as written.
func (*Box) SendWith ¶
func (b *Box) SendWith(ctx context.Context, to protocol.Address, body string, f Fields) (*Sent, error)
SendWith sends one logical message: its envelope is built once and kept in the outbox, then sealed for the post: dated at the post, signed for that peer only by this box's working key, and encrypted with that signature to the peer's verified prekey. On 409 stale_prekey it seals the SAME envelope to the newer prekey, once. If the post fails, the envelope stays in the outbox: retry with Box.Flush, never by sending again, or the peer gets it twice. Once the server has it, the envelope and the server's receipt are kept in sent/.
Writing to a peer allows it: its replies land in inbox/, and the box's server is sent the box's ACL, when it changed, before anything is posted (Box.SyncACL). One the server does not take holds the message in the outbox only when the ACL it last took does not admit the peer's reply, which it would refuse for good; otherwise the message is posted, and Sent.ACLErr says why the ACL was not taken (#180).
Before anything is posted the server is asked what it holds about the peer: a revocation of its address, or of its certificates. What verifies is kept, and the send is checked against what the box then holds. With no answer nothing is posted, and the message stays in the outbox.
The signature is inside the encryption, so the server is handed none: it knows the sender from the pass the post carries. Every message is padded, after the signature, so that the server sees its size only to within a block (protocol.PaddedSize).
Nothing is written or sent when its reader would refuse the envelope for its form (PROTOCOL.md §3.3, §12.3 check 3): a noncharacter in the body or the subject is refused here, before the outbox.
It holds the box's lock from start to end ([Box.hold]), as every command that writes the box does.
func (*Box) SetAlias ¶
SetAlias names a peer's address, and allows it: its mail lands in inbox/ (Box.Allow). A name that already names another address is refused, never moved: remove it first, so no send goes to a peer other than the one the name meant when it was learned.
func (*Box) SetPolicy ¶
SetPolicy writes the box's policy file. The server learns what it changes at the box's next send, recv, allow or block (Box.SyncACL).
func (*Box) SetPrekeyLifetime ¶
SetPrekeyLifetime writes the box's prekey-lifetime file: each prekey made from now on is valid for d, a whole number of seconds from MinPrekeyLifetime to protocol.PrekeyLifetime. The prekey in use keeps its own; Receive renews it at once if it was made for longer.
func (*Box) Status ¶
Status reads the box's own files and its state, read-only: it waits while a command writes the box, and changes nothing.
func (*Box) SyncACL ¶
SyncACL sends the box's server the ACL the box's files say, unless that is the one it took last, which is the case for a box that has blocked nobody and lets every sender reach the server: such a box sends none, and its server holds none. Each one sent replaces whatever the server holds, whoever wrote it, and carries no If-Match (PROTOCOL.md §11.14): the box's files are what its ACL is, so what another machine of the box sent is replaced, not merged, and a precondition would only add a read and a 412 to end the same way. One the server answers box_changed (the box changed while it handled the request) is sent again as it is, once. The server's answer must be the ACL sent, and is kept in the box's state. It reads nothing of the server first, but for a restore's adoption of its server's ACL (adoptPending), so it does not see an ACL another writer put there since: Box.Receive reads that at each renewal (checkACL).
type Delivery ¶
type Delivery struct {
ID string // the SHA-256 of its ciphertext, as computed here
Envelope *protocol.Envelope
// Line is the envelope as ONE line of JSON, built by this client from the
// checked fields (the identity document dropped), as kept in inbox/.
// Print or pass on this, never Plaintext, whose bytes the sender chose.
Line []byte
// Plaintext is the envelope's line exactly as the sender signed it, and
// Signature the signature block that verified over it: together they
// show anyone that this sender wrote this to this box. The client keeps
// neither on disk.
Plaintext []byte
Signature []byte
// contains filtered or unexported fields
}
Delivery is a message that passed every check.
type Fields ¶
type Fields struct {
Subject string
// InReplyTo is the msg_id of the message this one answers, as its
// envelope carried it: never the id the server lists a message by.
InReplyTo string
// Supersedes is the msg_id of an earlier message of this box's that this
// one replaces. The earlier one is not altered: the recipient decides.
Supersedes string
}
Fields are an envelope's optional fields: the sender's own words about a message, encrypted with it, so the server sees none of them.
type Held ¶
type Held struct {
// Revoked is true when the box holds a revocation of the address: it is
// ended, for good.
Revoked bool
// Certificates is the highest revocation of the address's certificates
// the box holds, or nil. The address goes on.
Certificates *protocol.RevokedCertificates
}
Held is what a box holds about an address: what that address's identity signed about it, verified here, and kept. It is what a send to the address and a read of its mail are checked against, with no call to anyone.
type HeldSender ¶
type HeldSender struct {
From protocol.Address `json:"from"`
// IDs name its messages in held/, oldest first by sent_at: held/<id>.json
// is the line recv would have printed.
IDs []string `json:"ids"`
FirstSentAt string `json:"first_sent_at"`
LastSentAt string `json:"last_sent_at"`
}
HeldSender is the mail held/ keeps from one sender.
type NotMade ¶
type NotMade struct {
Serial uint64 `json:"serial"`
CertificateSerial uint64 `json:"certificate_serial"`
// OwnCertificate is set when the statement is under a certificate this
// box made: one that certifies a working key it holds.
OwnCertificate bool `json:"own_certificate"`
// At is this machine's clock at that read.
At string `json:"at"`
}
NotMade is a prekey statement the box's server served in its name at the box's last read of itself, verified under its identity, that the box did not make: none it keeps. Under a certificate this box made, whoever made it holds that certificate's working key, from a copy of the box's directory, which holds the identity too; under one it did not, the identity certified someone else's working key, as on the lost machine a box restored from its identity alone meets. Either way the box recommends revoking the address (PROTOCOL.md §12.4), and recv renews nothing above it: Rekey, which its owner runs, does (#74).
type Options ¶
type Options struct {
// Base is the server's base URL; DefaultBase if empty. It is https, or
// http only to a server on this machine ([checkServerURL]).
Base string
// HTTPClient makes the requests; if nil, a client that cuts a request
// after a minute, so a server that stops answering cannot hold the box's
// lock for ever: a send or a fetch of a ciphertext once a minute passes
// with no byte of it moving, the wait for its answer's head among them,
// with no bound on the whole, so a message of any size moves on a slow
// link (#381); the rest of such a request, a send's answer once its
// ciphertext is sent and a fetch's answer that is not its ciphertext, and
// any other request, after a minute in all. A client
// given here is used as it is, its own timeouts its choice, and none of
// those bounds is added. The box uses a copy of it that follows no
// redirect ([followNone]).
HTTPClient *http.Client
// Now is the clock; time.Now if nil.
Now func() time.Time
// IdentityType is the type of identity key Init makes for a new box:
// protocol.IdentityEd25519 if empty, and that is the one to use. Every
// client verifies an Ed25519 identity; a box of any other type can
// neither send to nor receive from a box whose client does not verify
// that type. An existing identity key is used as it is, whatever this
// says.
IdentityType string
// SenderKeyTypes is what the box declares with each prekey it
// publishes: the identity key types of the senders it takes mail from
// (protocol.AcceptsSender). Nil: every type this client verifies, which
// is the one to use. The one entry protocol.AnySender is for a box that
// would not show which types it verifies: the server then takes mail
// for it from a sender of any type, and this client refuses what it
// cannot verify.
SenderKeyTypes []string
// Waiting, if not nil, is told when a command finds the box's lock held
// by another command of the box, once, before it waits for it: a box's
// commands write its files one at a time.
Waiting func()
// DrainTime is how long one Receive drains while bytes move, holding the
// box's lock: past it the drain stops, as at its other limits
// (Result.More), and a fetch under way keeps what it read for the next
// Receive to go on from. Zero is DefaultDrainTime.
DrainTime time.Duration
}
Options configures a Box. The zero value is ready to use.
type Policy ¶
type Policy struct {
// Unknown is where mail from a sender the box has not allowed lands:
// LandHeld or LandInbox.
Unknown string `json:"unknown"`
// Server is whom the box's server takes mail from: AdmitAll or
// AdmitAllowed.
Server string `json:"server"`
}
Policy is what a box's policy file says. A box with none, or a line left out, has the default.
func DefaultPolicy ¶
func DefaultPolicy() Policy
DefaultPolicy is the policy of a box whose policy file says nothing: mail from a sender the box has not allowed is held, and the server refuses only senders the box has blocked.
type Quarantined ¶
type Quarantined struct {
ID string `json:"id"`
Code string `json:"code"`
Reason string `json:"reason,omitempty"`
From string `json:"from,omitempty"`
ReceivedAt string `json:"received_at,omitempty"`
JudgedAt string `json:"judged_at,omitempty"`
SignedBy string `json:"signed_by,omitempty"`
ClaimsIdentity string `json:"claims_identity,omitempty"`
CertificateSerial uint64 `json:"certificate_serial,omitempty"`
CertificateBegan string `json:"certificate_began,omitempty"`
CertificateEnds string `json:"certificate_ends,omitempty"`
SentAt string `json:"sent_at,omitempty"`
MsgID string `json:"msg_id,omitempty"`
}
Quarantined is a refused message in quarantine/, in this client's words: its id, the code it was refused for and why, the sender and the time the server gave, and what this client's checks established about it when it judged it (keptFindings), each only when it has its form. Nothing else of what the server or the sender wrote is in it.
type Refusal ¶
type Refusal struct {
ID string
// Code says which check failed: one of this package's Refused codes or
// protocol's. Reason says it in this client's own words, which never
// quote the message.
Code string
Reason string
// Findings is what the checks established about it before it was
// refused (protocol.Findings): kept with it in quarantine/. Where the box
// holds a revocation of the address that signed it, or of its
// certificate, it is the clock and the identity alone, with Signed false,
// as for a signature that does not verify (#265).
Findings *protocol.Findings
}
Refusal is a message that failed a check. It was quarantined, never delivered, and acked so that it leaves the server. It never blocks its claimed sender: a forgery may be the operator framing them.
type RefusedError ¶
type RefusedError struct {
// Code is the refusal's code, for a check that has one of its own:
// RefusedPrekeyType. Otherwise it is empty.
Code string
Reason string
}
RefusedError is a check that failed. The operation stopped there: nothing was encrypted, sent, delivered or recorded past the failed check.
func (*RefusedError) Error ¶
func (e *RefusedError) Error() string
type Remedy ¶
type Remedy struct {
// Below is the serial of the certificate this call made, numbered once
// the revocation was final. Every certificate below it is revoked where
// the revocation was taken.
Below uint64
// Prekey is the new prekey, if the server took it.
Prekey *protocol.Prekey
// NotRenewed is why the server did not take it, or nil.
NotRenewed error
// Revoked is the revocation of those certificates, if a server took it,
// whether or not this box kept it: one it did not keep is the call's
// error, a fault of this box after the server took it (#383).
Revoked *protocol.RevokedCertificates
// NotRevoked is why the box's server did not take the revocation, or
// nil. One it holds higher, shown and verified, is taken: Revoked is that
// one.
NotRevoked error
// Unread is why the box's own server's read of it could not be used (no
// answer, a statement that does not verify, or one ordered before what
// the box published), or nil. The remedy went on: the certificate was
// numbered from what the box knows.
Unread error
}
A Remedy is what Box.RenewAndRevoke did.
type Result ¶
type Result struct {
// Delivered is the mail that landed in inbox/: from a sender the box
// allowed, or from any sender under "unknown inbox".
Delivered []Delivery
// Held is mail that passed every check from a sender the box has not
// allowed, kept in held/ under "unknown held" ([Box.Allow] moves it to
// inbox/). Blocked names mail from a sender the box blocked, dropped.
// Both were acked.
Held []Delivery
Blocked []string
// Duplicates are re-encrypted retries of messages already delivered:
// acked, not delivered again.
Duplicates []string
Refused []Refusal
// More reports that the drain stopped at one of its limits (see
// maxDrainMessages), its time among them (Options.DrainTime), or because
// the server paced it: the rest is still on the server for the next
// Receive, which goes on from what a fetch the time cut kept.
More bool
// Paced is how long the server asked the box to wait before taking out
// more, when it paced the drain (a 429 fetch_paced or refetch_spent with
// Retry-After, on a fetch); zero otherwise.
Paced time.Duration
// PollAfter is the server's poll interval, in seconds: the list's
// Retry-After.
PollAfter int64
// Renewed is the prekey this Receive published, the current one being
// more than a day old; nil if none was due.
Renewed *protocol.Prekey
// RenewErr is why a due renewal failed. The drain went ahead, and a
// later Receive tries again, at most hourly.
RenewErr error
// ACLErr is why the box's ACL was not sent where it differs from the one
// its server last took ([Box.SyncACL]), or from one another writer put
// there, which the renewal's read then kept as taken (checkACL). The drain
// went ahead, and the next send or Receive sends it.
ACLErr error
// ACLReplaced reports that the read of the ACL the box's server holds,
// owed since a renewal recv began was taken and made by this Receive
// (aclAt), found an ACL other than the one this box last sent it, which
// another machine of the box, whoever holds its pass or the server itself
// wrote, and that the box sent its own in its place (checkACL); or that a
// sync since the last Receive replaced an ACL a restore adopted that held
// what no file of the box says, or one another writer put that an owed
// read kept and could not replace then (syncACL; #366, #377).
ACLReplaced bool
// NotMade is a statement the box's server served in its name, at the
// box's last read of itself, that the box did not make, or nil
// ([NotMade]).
NotMade *NotMade
// contains filtered or unexported fields
}
Result is what one Receive did.
func (*Result) Again ¶
Again is how long the box waits before its next Receive: the server's poll interval, or a paced drain's wait when that is the later, since the next Receive begins with a list, which is a poll (PROTOCOL.md §11.11, §11.5). Sooner would spend the box's polls, and be refused once they are spent, where the poll interval lets the server's pace of new mail refill meanwhile (#117).
type RevokedError ¶
type RevokedError struct {
Address protocol.Address
// Own is true when the address is this box's.
Own bool
}
RevokedError is an address this box holds a revocation of. The revocation verified here, so it is not the server's word: nothing is sent to that address or taken from it again, and nothing a server says later undoes it.
func (*RevokedError) Error ¶
func (e *RevokedError) Error() string
type Sent ¶
type Sent struct {
To protocol.Address
MsgID string
protocol.Receipt
// ACLErr is why the box's ACL, changed since its server last took one,
// was not sent ([Box.SyncACL]): the post went ahead, since the ACL the
// server last took admits the recipient's reply. The next send or recv
// sends it.
ACLErr error
}
Sent is a message the server took: the box it went to, the msg_id its envelope carries, and the server's receipt for it, checked against the ciphertext posted. Its ID is the server's id for the message, the SHA-256 of the ciphertext; MsgID names the message itself, on every re-encryption.
type Status ¶
type Status struct {
Address protocol.Address `json:"address"`
// PrekeySerial and PrekeyNotAfter are the prekey this box published
// last: zero and empty if it holds none.
PrekeySerial uint64 `json:"prekey_serial"`
PrekeyNotAfter string `json:"prekey_not_after"`
// PrekeyMade is when that prekey was signed, by the record its client
// kept: empty if there is none, and the next recv renews it.
PrekeyMade string `json:"prekey_made,omitempty"`
// PrekeyLifetime is how long each new prekey is valid for, in seconds:
// the box's prekey-lifetime file, or 33 days. Zero if the file names no
// lifetime, and the box then renews no prekey until it is put right.
PrekeyLifetime int64 `json:"prekey_lifetime"`
// CertificateSerial and CertificateNotAfter are the certificate of the
// working key in use, the one made with that prekey: zero and empty if
// the box holds none it can sign with.
CertificateSerial uint64 `json:"certificate_serial"`
CertificateNotAfter string `json:"certificate_not_after"`
// RenewalPendingSince is when this box signed the oldest new prekey it
// still holds and has not seen published: empty if every renewal went
// through.
RenewalPendingSince string `json:"renewal_pending_since,omitempty"`
Inbox int `json:"inbox"`
Sent int `json:"sent"`
Outbox int `json:"outbox"`
OutboxFailed int `json:"outbox_failed"`
Quarantine int `json:"quarantine"`
// Held is the mail held/ keeps from senders this box has not allowed.
Held int `json:"held"`
// Policy is what the box's policy file says, or the default.
Policy Policy `json:"policy"`
// Revoked is true when the box holds a revocation of its own address
// that verifies: the address is finished.
Revoked bool `json:"revoked,omitempty"`
// PrekeyNotMade is a statement the box's server served in its name, at
// its last read of itself, that it did not make, or nil ([NotMade]).
PrekeyNotMade *NotMade `json:"prekey_not_made,omitempty"`
// Attention says, in words, each thing that needs a look. It is empty
// when nothing does.
Attention []string `json:"attention"`
}
Status is what a box can say of itself from its own files, with no call to the server: what a wrapper reads to learn that the box needs a look.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
sign
Package sign holds a box's keys: its identity, which signs certificates and revocations and nothing else, and its working keys, which sign what the box says (PROTOCOL.md §5).
|
Package sign holds a box's keys: its identity, which signs certificates and revocations and nothing else, and its working keys, which sign what the box says (PROTOCOL.md §5). |
|
taken
Package taken is a reading of a box's clock that a server took: the date of a pass it checked, on a call it answered, the pass's not_after less protocol.PassLifetime.
|
Package taken is a reading of a box's clock that a server took: the date of a pass it checked, on a call it answered, the pass's not_after less protocol.PassLifetime. |