Skip to main content

Module signed

Module signed 

Source
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 Content

Content 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-server accepts 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 Content that borrows the fields of its post, and a reader reads a Signed that owns them. A reader skips each field that it does not know.
SignError
Why a signature is not valid.

Constants§

TYP
The typ of a message signature. It is not dpop+jwt, so a proof never counts as a message signature, and a signature never counts as a proof.

Functions§

check
Checks that sig signs the kept bytes of payload, 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 payload by the device key key.

Type Aliases§

Content
What the signature of a message covers: the payload that the sender writes, with the fields of its post.
Signed
The fields of a signed payload, as a reader decodes them.