Crate signed_note

Source
Expand description

This crate defines notes as specified by the C2SP signed-note specification.

This file contains code ported from the original project note.

References:

§Signed Note

A note is text signed by one or more server keys (spec). The text should be ignored unless the note is signed by a trusted server key and the signature has been verified using the server’s public key.

A server’s public key is identified by a name, typically the “host[/path]” giving the base URL of the server’s transparency log. The syntactic restrictions on a name are that it be non-empty, well-formed UTF-8 containing neither Unicode spaces nor plus (U+002B).

A server signs texts using public key cryptography. A given server may have multiple public keys, each identified by a 32-bit ID of the public key. The key_id function computes the key ID as RECOMMENDED by the spec.

key ID = SHA-256(key name || 0x0A || signature type || public key)[:4]

A Note represents a text with one or more signatures. An implementation can reject a note with too many signatures (for example, more than 100 signatures).

The Note::from_bytes function parses a message and validates that the text and signatures are syntactically valid, and returns a Note.

The Note::new function accepts a text and an existing list of signatures and returns a Note.

A Signature represents a signature on a note, verified or not (spec).

The Signature::from_bytes function parses a note signature line and ensures that it is syntactically valid, returning a Signature.

The Signature::to_bytes function encodes a signature for inclusion in a note.

§Verifying Notes

A Verifier allows verification of signatures by one server public key. It can report the name of the server and the uint32 ID of the key, and it can verify a purported signature by that key.

The standard implementation of a Verifier is constructed by StandardVerifier::new starting from a verifier key, which is a plain text string of the form <name>+<id>+<keydata>.

A Verifiers allows looking up a Verifier by the combination of server name and key ID.

The standard implementation of a Verifiers is constructed by VerifierList from a list of known verifiers.

The Note::verify function attempts to verify the signatures on a note using the provided Verifiers, and returns the verified and unverified signatures.

§Signing Notes

A Signer allows signing a text with a given key. It can report the name of the server and the ID of the key and can sign a raw text using that key.

The standard implementation of a Signer is constructed by StandardSigner::new starting from an encoded signer key, which is a plain text string of the form PRIVATE+KEY+<name>+<id>+<keydata>. Anyone with an encoded signer key can sign messages using that key, so it must be kept secret. The encoding begins with the literal text PRIVATE+KEY to avoid confusion with the public server key. This format is not required by the C2SP spec.

The Note::add_sigs function adds new signatures to the note from the provided list of Signers.

§Signed Note Format

A signed note consists of a text ending in newline (U+000A), followed by a blank line (only a newline), followed by one or more signature lines of this form: em dash (U+2014), space (U+0020), server name, space, base64-encoded signature, newline (spec).

Signed notes must be valid UTF-8 and must not contain any ASCII control characters (those below U+0020) other than newline.

A signature is a base64 encoding of 4+n bytes.

The first four bytes in the signature are the uint32 key ID stored in big-endian order.

The remaining n bytes are the result of using the specified key to sign the note text (including the final newline but not the separating blank line).

The Note::to_bytes function encodes a note into signed note format.

§Generating Keys

There is only one key type, Ed25519 with algorithm identifier 1. New key types may be introduced in the future as needed, although doing so will require deploying the new algorithms to all clients before starting to depend on them for signatures.

The generate_key function generates and returns a new signer and corresponding verifier.

§Example

Here is a well-formed signed note:

If you think cryptography is the answer to your problem,
then you don't know what your problem is
— PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=

It can be constructed and displayed using:

use signed_note::{Note, StandardSigner};

let skey = "PRIVATE+KEY+PeterNeumann+c74f20a3+AYEKFALVFGyNhPJEMzD1QIDr+Y7hfZx09iUvxdXHKDFz";
let text = "If you think cryptography is the answer to your problem,\n\
            then you don't know what your problem is.\n";

let signer = StandardSigner::new(skey).unwrap();
let mut n = Note::new(text.as_bytes(), &[]).unwrap();
n.add_sigs(&[&signer]).unwrap();

let want = "If you think cryptography is the answer to your problem,\n\
            then you don't know what your problem is.\n\
            \n\
            — PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=\n";

assert_eq!(&n.to_bytes(), want.as_bytes());

The note’s text is two lines, including the final newline, and the text is purportedly signed by a server named “PeterNeumann”. (Although server names are canonically base URLs, the only syntactic requirement is that they not contain spaces or newlines).

If Note::verify is given access to a Verifiers including the Verifier for this key, then it will succeed at verifying the encoded message and returning the parsed Note:

use signed_note::{Note, StandardVerifier, VerifierList};

let vkey = "PeterNeumann+c74f20a3+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW";
let msg = "If you think cryptography is the answer to your problem,\n\
           then you don't know what your problem is.\n\
           \n\
           — PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=\n";

let verifier = StandardVerifier::new(vkey).unwrap();
let n = Note::from_bytes(msg.as_bytes()).unwrap();
let (verified_sigs, _) = n.verify(&VerifierList::new(vec![Box::new(verifier.clone())])).unwrap();

let got = format!("{} ({:08x}):\n{}", verified_sigs[0].name(), verified_sigs[0].id(), std::str::from_utf8(n.text()).unwrap());
let want = "PeterNeumann (c74f20a3):\n\
            If you think cryptography is the answer to your problem,\n\
            then you don't know what your problem is.\n";
assert_eq!(want, got);

You can add your own signature to this message by re-signing the note, which will produce a doubly-signed message.

§Sign and add signatures

use signed_note::{Note, StandardSigner, StandardVerifier, VerifierList};

let vkey = "PeterNeumann+c74f20a3+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW";
let msg = "If you think cryptography is the answer to your problem,\n\
           then you don't know what your problem is.\n\
           \n\
           — PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=\n";
let text = "If you think cryptography is the answer to your problem,\n\
            then you don't know what your problem is.\n";

let mut n = Note::from_bytes(msg.as_bytes()).unwrap();

let verifier = StandardVerifier::new(vkey).unwrap();
let (verified_sigs, unverified_sigs) = n.verify(&VerifierList::new(vec![Box::new(verifier.clone())])).unwrap();
assert_eq!(verified_sigs.len(), 1);
assert!(unverified_sigs.is_empty());

struct ZeroRng;

impl rand_core::RngCore for ZeroRng {
    fn next_u32(&mut self) -> u32 {
        0
    }

    fn next_u64(&mut self) -> u64 {
        0
    }

    fn fill_bytes(&mut self, dest: &mut [u8]) {
        for byte in dest.iter_mut() {
            *byte = 0;
        }
    }

    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
        self.fill_bytes(dest);
        Ok(())
    }
}

impl rand_core::CryptoRng for ZeroRng {}

let (skey, _) = signed_note::generate_key(&mut ZeroRng{}, "EnochRoot");
let signer = StandardSigner::new(&skey).unwrap();
n.add_sigs(&[&signer]).unwrap();

let got = n.to_bytes();

let want = "If you think cryptography is the answer to your problem,\n\
           then you don't know what your problem is.\n\
           \n\
           — PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=\n\
           — EnochRoot rwz+eBzmZa0SO3NbfRGzPCpDckykFXSdeX+MNtCOXm2/5n2tiOHp+vAF1aGrQ5ovTG01oOTGwnWLox33WWd1RvMc+QQ=\n";

assert_eq!(got, want.as_bytes());

Structs§

Note
A Note is a text and signatures.
Signature
A Signature is a single signature found in a note.
StandardSigner
StandardSigner is a trivial Signer implementation.
StandardVerifier
StandardVerifier is a trivial Verifier implementation.
VerifierList
VerifierList is a Verifiers implementation that uses the given list of verifiers.

Enums§

NoteError
An error returned for issues parsing, verifying, or adding signatures to notes.
SignerError
An error returned from the StandardSigner::new function when constructing a StandardSigner from an encoded signer key.
VerificationError
An error returned from a Verifier when verifying a signature.
VerifierError
An error returned from the StandardVerifier::new function when constructing a StandardVerifier from an encoded verifier key.

Traits§

Signer
A Signer signs messages using a specific key.
Verifier
A Verifier verifies messages signed with a specific key.
Verifiers
Verifiers is a collection of known verifier keys.

Functions§

generate_key
Generates a signer and verifier key pair for a named server. The signer key skey is private and must be kept secret.
is_key_name_valid
key_id
Computes the key ID for the given server name and encoded public key as RECOMMENDED at https://c2sp.org/signed-note#signatures.
new_ed25519_verifier_key
Returns an encoded verifier key using the given name and Ed25519 public key.