Package note defines signed notes used by the Go module database server.
import "golang.org/x/mod/sumdb/note"The note package provides functionality for creating and verifying cryptographically signed notes. A note is text signed by one or more server keys. 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 name must be non-empty, well-formed UTF-8 containing neither Unicode spaces nor plus (U+002B).
A signed note consists of:
— server-name base64-signature\nSigned notes must be valid UTF-8 and must not contain any ASCII control characters (those below U+0020) other than newline.
Example:
go.sum database tree
1234567
abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVW=
— sum.golang.org Az3grubJCmM...A signature is a base64 encoding of 4+n bytes:
type Note struct {
Text string // text of note
Sigs []Signature // verified signatures
UnverifiedSigs []Signature // unverified signatures
}A Note is text with signatures.
type Signature struct {
// Name and Hash give the name and key hash
// for the key that generated the signature.
Name string
Hash uint32
// Base64 records the base64-encoded signature bytes.
Base64 string
}A Signature is a single signature found in a note.
type Signer interface {
// Name returns the server name associated with the key.
Name() string
// KeyHash returns the key hash.
KeyHash() uint32
// Sign returns a signature for the given message.
Sign(msg []byte) ([]byte, error)
}A Signer signs messages using a specific key.
type Verifier interface {
// Name returns the server name associated with the key.
Name() string
// KeyHash returns the key hash.
KeyHash() uint32
// Verify reports whether sig is a valid signature of msg.
Verify(msg, sig []byte) bool
}A Verifier verifies messages signed with a specific key.
type Verifiers interface {
// Verifier returns the Verifier associated with the key
// identified by the name and hash.
// If the name, hash pair is unknown, Verifier should return
// an UnknownVerifierError.
Verifier(name string, hash uint32) (Verifier, error)
}A Verifiers is a collection of known verifier keys.
type InvalidSignatureError struct {
Name string
Hash uint32
}An InvalidSignatureError indicates that the given key was known and the associated Verifier rejected the signature.
func (e *InvalidSignatureError) Error() stringtype UnknownVerifierError struct {
Name string
KeyHash uint32
}An UnknownVerifierError indicates that the given key is not known.
func (e *UnknownVerifierError) Error() stringtype UnverifiedNoteError struct {
Note *Note
}An UnverifiedNoteError indicates that the note successfully parsed but had no verifiable signatures.
func (e *UnverifiedNoteError) Error() stringfunc GenerateKey(rand io.Reader, name string) (skey, vkey string, err error)Generates a signer and verifier key pair for a named server. The signer key skey is private and must be kept secret.
Example:
import "crypto/rand"
signerKey, verifierKey, err := note.GenerateKey(rand.Reader, "example.com")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Signer key (keep secret):\n%s\n\n", signerKey)
fmt.Printf("Verifier key (publish):\n%s\n", verifierKey)func NewEd25519VerifierKey(name string, key ed25519.PublicKey) (string, error)Returns an encoded verifier key using the given name and Ed25519 public key.
Example:
import "crypto/ed25519"
pub, _, _ := ed25519.GenerateKey(rand.Reader)
vkey, err := note.NewEd25519VerifierKey("example.com", pub)
if err != nil {
log.Fatal(err)
}
fmt.Println(vkey)func NewSigner(skey string) (Signer, error)Constructs a new Signer from an encoded signer key.
Example:
signer, err := note.NewSigner(signerKey)
if err != nil {
log.Fatal(err)
}func NewVerifier(vkey string) (Verifier, error)Constructs a new Verifier from an encoded verifier key.
Example:
verifier, err := note.NewVerifier(verifierKey)
if err != nil {
log.Fatal(err)
}func VerifierList(list ...Verifier) VerifiersReturns a Verifiers implementation that uses the given list of verifiers.
Example:
v1, _ := note.NewVerifier(vkey1)
v2, _ := note.NewVerifier(vkey2)
verifiers := note.VerifierList(v1, v2)func Sign(n *Note, signers ...Signer) ([]byte, error)Signs the note with the given signers and returns the encoded message. The new signatures from signers are listed in the encoded message after existing signatures. If any signer uses the same key as an existing signature, the existing signature is elided from the output.
Example:
n := ¬e.Note{Text: "This is a message\n"}
signed, err := note.Sign(n, signer)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(signed))func Open(msg []byte, known Verifiers) (*Note, error)Opens and parses the message msg, checking signatures from the known verifiers. For each signature in the message, Open calls known.Verifier to find a verifier. Returns an error if no known verifier has signed an otherwise valid note.
Example:
n, err := note.Open(signed, verifiers)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Text: %s", n.Text)
fmt.Printf("Verified signatures: %d\n", len(n.Sigs))package main
import (
"crypto/rand"
"fmt"
"log"
"golang.org/x/mod/sumdb/note"
)
func main() {
// Generate keys
signerKey, verifierKey, err := note.GenerateKey(rand.Reader, "example.com")
if err != nil {
log.Fatal(err)
}
// Create signer and verifier
signer, err := note.NewSigner(signerKey)
if err != nil {
log.Fatal(err)
}
verifier, err := note.NewVerifier(verifierKey)
if err != nil {
log.Fatal(err)
}
// Create and sign a note
n := ¬e.Note{
Text: "go.sum database tree\n12345\nhash...\n",
}
signed, err := note.Sign(n, signer)
if err != nil {
log.Fatal(err)
}
fmt.Println("Signed note:")
fmt.Println(string(signed))
// Verify the note
verifiers := note.VerifierList(verifier)
verified, err := note.Open(signed, verifiers)
if err != nil {
log.Fatal(err)
}
fmt.Printf("\nVerified! Text: %s", verified.Text)
fmt.Printf("Signatures: %d\n", len(verified.Sigs))
}package main
import (
"crypto/rand"
"log"
"golang.org/x/mod/sumdb/note"
)
func main() {
// Generate two key pairs
skey1, vkey1, _ := note.GenerateKey(rand.Reader, "server1.com")
skey2, vkey2, _ := note.GenerateKey(rand.Reader, "server2.com")
signer1, _ := note.NewSigner(skey1)
signer2, _ := note.NewSigner(skey2)
verifier1, _ := note.NewVerifier(vkey1)
verifier2, _ := note.NewVerifier(vkey2)
// Sign with multiple signers
n := ¬e.Note{Text: "Multi-signed message\n"}
signed, err := note.Sign(n, signer1, signer2)
if err != nil {
log.Fatal(err)
}
// Verify with known verifiers
verifiers := note.VerifierList(verifier1, verifier2)
verified, err := note.Open(signed, verifiers)
if err != nil {
log.Fatal(err)
}
log.Printf("Verified signatures: %d", len(verified.Sigs))
}package main
import (
"fmt"
"log"
"golang.org/x/mod/sumdb/note"
)
func main() {
// Verifier only knows about server1
verifier1, _ := note.NewVerifier(vkey1)
verifiers := note.VerifierList(verifier1)
// Note signed by server1 and server2
n, err := note.Open(signedByBoth, verifiers)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Verified: %d\n", len(n.Sigs))
fmt.Printf("Unverified: %d\n", len(n.UnverifiedSigs))
for _, sig := range n.UnverifiedSigs {
fmt.Printf("Unknown signer: %s (hash: %x)\n", sig.Name, sig.Hash)
}
}package main
import (
"fmt"
"golang.org/x/mod/sumdb/note"
)
type KeyStore struct {
keys map[string]note.Verifier
}
func (ks *KeyStore) Verifier(name string, hash uint32) (note.Verifier, error) {
v, ok := ks.keys[name]
if !ok {
return nil, ¬e.UnknownVerifierError{Name: name, KeyHash: hash}
}
if v.KeyHash() != hash {
return nil, ¬e.UnknownVerifierError{Name: name, KeyHash: hash}
}
return v, nil
}
func main() {
ks := &KeyStore{keys: make(map[string]note.Verifier)}
v, _ := note.NewVerifier(vkey)
ks.keys[v.Name()] = v
n, err := note.Open(signed, ks)
if err != nil {
panic(err)
}
fmt.Printf("Verified note: %s", n.Text)
}Always verify signatures before trusting note content:
n, err := note.Open(msg, verifiers)
if err != nil {
// Do not trust content
return err
}
if len(n.Sigs) == 0 {
// No verified signatures
return errors.New("no valid signatures")
}
// Content is verifiedRequire multiple signatures for critical operations:
if len(n.Sigs) < requiredSignatures {
return errors.New("insufficient signatures")
}The text section contains the actual message:
After a blank line, signatures appear as:
— server-name base64-signatureThe em dash (U+2014) marks each signature line.