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.
- Standard
Signer StandardSigner
is a trivial Signer implementation.- Standard
Verifier StandardVerifier
is a trivial Verifier implementation.- Verifier
List VerifierList
is a Verifiers implementation that uses the given list of verifiers.
Enums§
- Note
Error - An error returned for issues parsing, verifying, or adding signatures to notes.
- Signer
Error - An error returned from the
StandardSigner::new
function when constructing aStandardSigner
from an encoded signer key. - Verification
Error - An error returned from a Verifier when verifying a signature.
- Verifier
Error - An error returned from the
StandardVerifier::new
function when constructing aStandardVerifier
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.