Expand description
Signed messages (R195-R201).
§Design
Each message carries a signature from the device key of its sender:
the key that its token is bound to (see crate::dpop). A reader
checks the signature. So it finds a message that changed in storage,
or a message with a false sender, and it does not need to trust the
storage of the server.
The signature is a JWS with a detached payload (RFC 7515, appendix F). The message carries the payload next to the signature:
sig = <header>..<signature>
header {"typ":"riff-message","alg":"ES256","jwk":{public key}}
payload = base64url of the JSON of ContentContent is what the signature covers: the who and the lead mark
of the sender, the thread of the post, the to selectors, the body,
the kind and the time (R196).
The sender makes the payload one time. The server and each reader
keep its bytes unchanged, and never encode them again. A check runs
over the kept bytes (check), then decodes them (Signed). A
build skips a field of the payload that it does not know, so a new
field never stops a check (01M3T411N0HM699VJXW6RTVWKB).
§Rules
- A post to a thread signs that thread. A direct message has no thread when it is sent, so it signs no thread. Its one selector names the other session.
riff-serveraccepts a signed post only from the key of the token of its caller, and only with a time near its own time (R197).- A reader counts a message as verified only when the signature is
valid, covers the message as the reader got it, and comes from a
key of the user of the sender (R199). See
crate::wire::Message::verified. - ECDSA can give two valid signatures for the same bytes. So a copy
check compares
payload_hash, not the signature.
§Example
use riff_core::dpop::Key;
use riff_core::name::Who;
use riff_core::signed::Content;
use riff_core::wire::Kind;
let key = Key::generate();
let mike = Who::new("mike", Some("a6cf"))?;
let thread = "como-technologies/riff".parse()?;
let content = Content {
from: &mike,
thread: Some(&thread),
to: &[],
body: "ready",
lead: false,
kind: Kind::Message,
at_ms: 1_000,
};
let sig = content.sign(&key);
assert_eq!(content.verify(&sig).unwrap(), key.thumbprint());
// A changed body does not verify.
let changed = Content { body: "not ready", ..content };
assert!(changed.verify(&sig).is_err());
// The check of the kept payload gives the key and the fields.
let (jkt, signed) = riff_core::signed::check(&content.payload(), &sig).unwrap();
assert_eq!(jkt, key.thumbprint());
assert!(signed.covers(&content));Structs§
- Payload
- The fields of a signed payload (R196). One struct is the layout of
both sides (01M49W17GGV1K5FZEKJRPV0GNA): the sender writes a
Contentthat borrows the fields of its post, and a reader reads aSignedthat owns them. A reader skips each field that it does not know. - Sign
Error - Why a signature is not valid.
Constants§
- TYP
- The
typof a message signature. It is notdpop+jwt, so a proof never counts as a message signature, and a signature never counts as a proof.
Functions§
- check
- Checks that
sigsigns the kept bytes ofpayload, then decodes them. Gives the thumbprint of the key and the fields. It never encodes the payload again. - payload_
hash - The hash of a payload, in hex. Two copies of one message have the same hash.
- sign_
payload - The signature of the kept bytes
payloadby the device keykey.