moonseal
Sealing a file for one or several readers, and opening it again.
pub(open) trait Sink {
fn write(Self, bytes~ : BytesView) -> Unit raise @spec.Refused
}
Where bytes go. It may be called with small pieces, and it is called in the order the file is written.
pub(open) trait Source {
fn read(Self, limit~ : Int32) -> Bytes raise @spec.Refused
}
Where bytes come from. A file, a socket, a buffer: anything that can hand over a run of bytes and say when it has no more.
read may return fewer bytes than it was asked for; an empty answer means the end. The reader in this library asks for a chunk at a time and keeps asking until it has what it needs or the source is spent.
pub struct BytesSink {
// private fields
}
A sink over a buffer, for the same reason.
pub fn BytesSink::bytes(Self) -> Bytes
Everything written so far, as one byte string.
pub fn BytesSink::new() -> Self
pub fn BytesSink::write(Self, bytes~ : BytesView) -> Unit raise @spec.Refused
pub struct BytesSource {
// private fields
}
A source over bytes that are already in memory, which is what a caller with a file in a buffer wants, and what the tests use.
pub fn BytesSource::new(BytesView) -> Self
pub fn BytesSource::read(Self, limit~ : Int32) -> Bytes raise @spec.Refused
pub fn drain(&Source) -> Bytes raise @spec.Refused
The whole of a source, which is what a caller does when the input is small enough to hold and they would rather see it than stream it.
pub fn open(Array[&@spec.Identity], BytesView) -> Bytes raise @spec.Refused
Open a file addressed to any one of identities.
The header is checked before the payload is touched: a stanza is opened to recover the file key, the MAC over the header is verified with it, and only then does a chunk get decrypted. A header that does not verify means somebody edited the list of who can read the file, and no part of the payload is worth looking at after that.
pub fn open_armored(Array[&@spec.Identity], StringView) -> Bytes raise @spec.Refused
Open an armored block.
pub fn open_stream(&Sink, Array[&@spec.Identity], &Source) -> Unit raise @spec.Refused
Open a file from a source, writing the plaintext to a sink as it is authenticated. Nothing is written until the header has verified.
pub fn open_stream_armored(&Sink, Array[&@spec.Identity], &Source) -> Unit raise @spec.Refused
Open an armored text block from a source.
pub fn seal(Array[&@spec.Recipient], BytesView, random~ : &@moonrand.Source) -> Bytes raise @spec.Refused
Seal plain for recipients, and give back the file.
The file key, every stanza's ephemeral material and the payload nonce come out of random, in that order: @system.source from moonrand/system in a program, a Replay of fixed bytes in a test that wants the same file twice. A source that fails is a refusal, and it arrives before the first byte is written.
How much a file draws: sixteen bytes for the file key, then thirty-two for each X25519 recipient and sixteen for each scrypt recipient, then sixteen for the payload nonce.
pub fn seal_armored(Array[&@spec.Recipient], BytesView, random~ : &@moonrand.Source) -> String raise @spec.Refused
The same as seal, written out as an armored text block.
pub fn seal_stream(&Sink, Array[&@spec.Recipient], &Source, random~ : &@moonrand.Source) -> Unit raise @spec.Refused
The same file, written a piece at a time: nothing larger than one chunk is ever held, which is what makes a file bigger than memory something this library can seal.
pub fn seal_stream_armored(&Sink, Array[&@spec.Recipient], &Source, random~ : &@moonrand.Source) -> Unit raise @spec.Refused
Seal to a sink, as an armored text block. The text is written in the same pieces the file is, so the whole of it never exists at once either.
spec
The contract the rest of this library is written against: the error type, the recipient stanza, and the two traits a recipient type implements.
pub(open) trait Identity {
fn unwrap(Self, stanza~ : Stanza) -> Bytes? raise Refused
}
A recipient type, from the decrypting side: given a stanza, either the file key or None for "not mine, try the next one".
A stanza of a type this identity does not implement is None, and so is one that is well formed but sealed to another key. A stanza that claims to be this type and is malformed is a refusal, because at that point the file is broken rather than addressed to somebody else.
pub(open) trait Recipient {
fn wrap(Self, file_key~ : BytesView, random~ : &@moonrand.Source) -> Stanza raise Refused
}
A recipient type, from the encrypting side: it takes the file key and wraps it into a stanza only the matching identity can unwrap.
random is where the ephemeral material comes from. It is passed rather than found so that the one source a caller chose serves every stanza of a file, and so that a Replay seals the same file twice.
pub(all) struct Stanza {
kind : String
args : Array[String]
body : Bytes
}
One recipient stanza: the type, the arguments after it, and the body that carries the file key wrapped for whoever holds the matching secret.
A recipient type is nothing more than a name, a list of arguments it defines the meaning of, and a body; the header carries them without understanding them, which is what lets a type this library has never heard of travel through it.
pub fn Stanza::new(kind~ : String, args~ : Array[String], body~ : Bytes) -> Self
pub(all) suberror Refused {
Header(at~ : Int32, want~ : String)
Stanza(want~ : String)
Mac
NoMatch
Payload(at~ : Int32)
Armor(at~ : Int32, want~ : String)
Key(want~ : Int32, got~ : Int32)
Random(@moonrand.Unavailable)
Limit(want~ : Int32, got~ : Int32)
} derive(Eq, @debug.Debug)
Why this library would not do what it was asked.
The variants are the places a file can stop being a file: the header does not say what the format says it says, a stanza of a known type does not either, the MAC over the header does not check out, no identity opened any stanza, a payload chunk did not authenticate, the text was not the armor it claimed to be, a key is not the size this format defines, the random source the caller handed in could not give what the file needs, or an input was longer than this library will read.
at is an offset in bytes from the start of the input the call was given.
pub fn draw(&@moonrand.Source, Int32) -> Bytes raise Refused
len bytes from random, or the reason it had none as a refusal.
A recipient type calls this for its ephemeral material, so that a failing source arrives as the one error type every call here raises.
stream
The payload: one long stream cut into 64 KiB chunks, each authenticated on its own.
pub struct Opener {
// private fields
}
A payload reader: holds the key, and checks that chunks arrive in order and that the last one says it is the last.
pub fn Opener::chunk(Self, cipher~ : BytesView, last~ : Bool) -> Bytes raise @spec.Refused
Open one chunk, or refuse it. The refusal carries the offset of the chunk from the start of the payload.
pub fn Opener::chunks(Self) -> Int32
How many chunks have been opened.
pub fn Opener::done(Self) -> Bool
Whether the payload ended: a payload is only over once a chunk said it was the last one, so a reader that runs out of bytes first has been handed a truncated file.
pub fn Opener::new(key~ : BytesView) -> Self raise @spec.Refused
pub struct Sealer {
// private fields
}
A payload writer: holds the key, and remembers which chunk comes next.
pub fn Sealer::chunk(Self, plain~ : BytesView, last~ : Bool) -> Bytes
Seal one chunk. last marks the end of the payload; a payload of no bytes at all is still one chunk — an empty one — whose tag is the only thing standing between an empty file and a truncated one.
pub fn Sealer::chunks(Self) -> Int32
How many chunks have been sealed, which is how many the payload will hold.
pub fn Sealer::new(key~ : BytesView) -> Self raise @spec.Refused
A sealer over a payload key of thirty-two bytes.
pub let chunk : Int32
How much plaintext one chunk holds.
pub let nonce : Int32
How many bytes of nonce precede the first chunk. It is drawn once per file, and the payload key is derived from it, which is what makes two files sealed under the same recipients differ from the first byte.
pub let tag : Int32
How many bytes a chunk grows by, which is the size of an authentication tag.
pub fn payload_key(file_key~ : BytesView, nonce~ : BytesView) -> Bytes
The payload key: the file key, salted with the payload nonce.
armor
ASCII armor: the same file as text, for the places a binary file cannot go.
pub struct Decoder {
// private fields
}
A block read a piece at a time.
pub fn Decoder::finish(Self) -> Bytes raise @spec.Refused
The end of the text: whatever was left of a last line without its line feed, and a check that the block was closed.
pub fn Decoder::new() -> Self
pub fn Decoder::push(Self, StringView) -> Unit raise @spec.Refused
Feed text. Complete lines are decoded as they arrive; what has been decoded so far is available from take.
pub fn Decoder::take(Self) -> Bytes
What has been decoded so far, and clear it.
pub struct Encoder {
// private fields
}
A block written a piece at a time, so that a file larger than memory can be armored.
pub fn Encoder::finish(Self) -> String
The rest of the block: the last, shorter line if there is one, and the line that ends it. An empty input writes no body line at all, which is a block that carries no bytes rather than a block that carries an empty line.
pub fn Encoder::new() -> Self
pub fn Encoder::write(Self, BytesView) -> String
Take some bytes and give back the lines that are now complete. Nothing comes back until at least 48 bytes have arrived, which is exactly one line.
pub let label : String
The label between the dashes, which is also the name this format is known by to anything that reads PEM blocks.
pub fn decode(StringView) -> Bytes raise @spec.Refused
A block back into bytes, or a refusal. See the package comment for what is allowed around the block and what is not.
pub fn encode(BytesView) -> String
A block around bytes, as text. Every line ends in a line feed, including the last one, so that the result survives being concatenated.
pub fn head() -> String
The line that opens a block, with its line feed.
x25519
The recipient type everyone uses: a public key per reader.
pub struct Identity {
// private fields
}
A secret key.
pub fn Identity::code(Self) -> String
The written form, AGE-SECRET-KEY-1…. Uppercase, which is how bech32 spells a secret so that it is hard to mistake for a public one.
pub fn Identity::generate(random~ : &@moonrand.Source) -> Self raise @spec.Refused
A new secret key, drawn from random. A secret key is thirty-two random bytes and nothing else; X25519 clamps them when it uses them.
pub fn Identity::new(BytesView) -> Self raise @spec.Refused
From thirty-two bytes of secret key.
pub fn Identity::of(StringView) -> Self raise @spec.Refused
From its written form, AGE-SECRET-KEY-1….
pub fn Identity::public(Self) -> Recipient
The public half, which is what a sender needs.
pub fn Identity::unwrap(Self, stanza~ : @spec.Stanza) -> Bytes? raise @spec.Refused
pub struct Recipient {
// private fields
}
A public key, as the thing you seal to.
pub fn Recipient::code(Self) -> String
The written form, age1….
pub fn Recipient::key(Self) -> BytesView
The raw key, for comparing one recipient with another.
pub fn Recipient::new(BytesView) -> Self raise @spec.Refused
From thirty-two bytes of public key.
pub fn Recipient::of(StringView) -> Self raise @spec.Refused
From its written form, age1…. The case of the string carries no meaning: bech32 allows either, and the checksum is taken over the lowercase one.
pub fn Recipient::wrap(Self, file_key~ : BytesView, random~ : &@moonrand.Source) -> @spec.Stanza raise @spec.Refused
pub let size : Int32
How long a public key, a secret key and a share are. X25519 has no other size.
scrypt
The recipient type for a passphrase: no keys, no key exchange, just a lot of memory.
pub struct Identity {
// private fields
}
A passphrase, from the decrypting side.
pub fn Identity::new(StringView, max_log_n? : Int32) -> Self
An identity for a passphrase, and the largest work factor it will agree to run. A file is allowed to ask for a high one; a reader is allowed to say no.
pub fn Identity::unwrap(Self, stanza~ : @spec.Stanza) -> Bytes? raise @spec.Refused
pub struct Recipient {
// private fields
}
A passphrase, from the encrypting side.
pub fn Recipient::log_n(Self) -> Int32
The work factor this recipient writes into the file.
pub fn Recipient::new(StringView, log_n? : Int32) -> Self
A recipient for a passphrase, with the work factor it should use.
The work factor is a parameter rather than a constant because it is the one knob that trades a reader's time for a guesser's: raise it when the passphrase is weak or the file will outlive the hardware.
pub fn Recipient::wrap(Self, file_key~ : BytesView, random~ : &@moonrand.Source) -> @spec.Stanza raise @spec.Refused
pub let default_log_n : Int32
The work factor a file gets when nobody says otherwise: 2^18, which is 256 MiB of scratch space and about a second on the machine that wrote this.
pub let max_log_n : Int32
The largest work factor a reader will accept. Four gibibytes of scratch space is the point past which a file stops being something to open and starts being something to defend against.