| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package note defines the notes signed by the Go module database server. |
| // |
| // This package is part of a DRAFT of what the Go module database server will look like. |
| // Do not assume the details here are final! |
| // |
| // 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 syntactic restrictions on a name are that it be non-empty, |
| // well-formed UTF-8 containing neither Unicode spaces nor plus (U+002B). |
| // |
| // A Go module database server signs texts using public key cryptography. |
| // A given server may have multiple public keys, each |
| // identified by the first 32 bits of the SHA-256 hash of |
| // the concatenation of the server name, a newline, and |
| // the encoded public key. |
| // |
| // # Verifying Notes |
| // |
| // A Verifier allows verification of signatures by one server public key. |
| // It can report the name of the server and the uint32 hash of the key, |
| // and it can verify a purported signature by that key. |
| // |
| // The standard implementation of a Verifier is constructed |
| // by NewVerifier starting from a verifier key, which is a |
| // plain text string of the form "<name>+<hash>+<keydata>". |
| // |
| // A Verifiers allows looking up a Verifier by the combination |
| // of server name and key hash. |
| // |
| // The standard implementation of a Verifiers is constructed |
| // by VerifierList from a list of known verifiers. |
| // |
| // 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). |
| // |
| // A Signature represents a signature on a note, verified or not. |
| // |
| // The Open function takes as input a signed message |
| // and a set of known verifiers. It decodes and verifies |
| // the message signatures and returns a Note structure |
| // containing the message text and (verified or unverified) signatures. |
| // |
| // # Signing Notes |
| // |
| // A Signer allows signing a text with a given key. |
| // It can report the name of the server and the hash of the key |
| // and can sign a raw text using that key. |
| // |
| // The standard implementation of a Signer is constructed |
| // by NewSigner starting from an encoded signer key, which is a |
| // plain text string of the form "PRIVATE+KEY+<name>+<hash>+<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. |
| // |
| // The Sign function takes as input a Note and a list of Signers |
| // and returns an encoded, signed message. |
| // |
| // # 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. |
| // |
| // 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 hash |
| // stored in big-endian order, which is to say they are the first |
| // four bytes of the truncated SHA-256 used to derive the key hash |
| // in the first place. |
| // |
| // 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). |
| // |
| // # 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 GenerateKey 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: |
| // |
| // skey := "PRIVATE+KEY+PeterNeumann+c74f20a3+AYEKFALVFGyNhPJEMzD1QIDr+Y7hfZx09iUvxdXHKDFz" |
| // text := "If you think cryptography is the answer to your problem,\n" + |
| // "then you don't know what your problem is.\n" |
| // |
| // signer, err := note.NewSigner(skey) |
| // if err != nil { |
| // log.Fatal(err) |
| // } |
| // |
| // msg, err := note.Sign(¬e.Note{Text: text}, signer) |
| // if err != nil { |
| // log.Fatal(err) |
| // } |
| // os.Stdout.Write(msg) |
| // |
| // 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 Open is given access to a Verifiers including the |
| // Verifier for this key, then it will succeed at verifiying |
| // the encoded message and returning the parsed Note: |
| // |
| // vkey := "PeterNeumann+c74f20a3+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW" |
| // msg := []byte("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") |
| // |
| // verifier, err := note.NewVerifier(vkey) |
| // if err != nil { |
| // log.Fatal(err) |
| // } |
| // verifiers := note.VerifierList(verifier) |
| // |
| // n, err := note.Open([]byte(msg), verifiers) |
| // if err != nil { |
| // log.Fatal(err) |
| // } |
| // fmt.Printf("%s (%08x):\n%s", n.Sigs[0].Name, n.Sigs[0].Hash, n.Text) |
| // |
| // You can add your own signature to this message by re-signing the note: |
| // |
| // skey, vkey, err := note.GenerateKey(rand.Reader, "EnochRoot") |
| // if err != nil { |
| // log.Fatal(err) |
| // } |
| // _ = vkey // give to verifiers |
| // |
| // me, err := note.NewSigner(skey) |
| // if err != nil { |
| // log.Fatal(err) |
| // } |
| // |
| // msg, err := note.Sign(n, me) |
| // if err != nil { |
| // log.Fatal(err) |
| // } |
| // os.Stdout.Write(msg) |
| // |
| // This will print a doubly-signed message, like: |
| // |
| // If you think cryptography is the answer to your problem, |
| // then you don't know what your problem is. |
| // |
| // — PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM= |
| // — EnochRoot rwz+eBzmZa0SO3NbfRGzPCpDckykFXSdeX+MNtCOXm2/5n2tiOHp+vAF1aGrQ5ovTG01oOTGwnWLox33WWd1RvMc+QQ= |
| package note |
| |
| import ( |
| "bytes" |
| "crypto/sha256" |
| "encoding/base64" |
| "encoding/binary" |
| "errors" |
| "fmt" |
| "io" |
| "strconv" |
| "strings" |
| "unicode" |
| "unicode/utf8" |
| |
| "golang.org/x/crypto/ed25519" |
| ) |
| |
| // A Verifier verifies messages signed with 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 Signer signs messages using a specific key. |
| 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) |
| } |
| |
| // keyHash computes the key hash for the given server name and encoded public key. |
| func keyHash(name string, key []byte) uint32 { |
| h := sha256.New() |
| h.Write([]byte(name)) |
| h.Write([]byte("\n")) |
| h.Write(key) |
| sum := h.Sum(nil) |
| return binary.BigEndian.Uint32(sum) |
| } |
| |
| var ( |
| errVerifierID = errors.New("malformed verifier id") |
| errVerifierAlg = errors.New("unknown verifier algorithm") |
| errVerifierHash = errors.New("invalid verifier hash") |
| ) |
| |
| const ( |
| algEd25519 = 1 |
| ) |
| |
| // isValidName reports whether name is valid. |
| // It must be non-empty and not have any Unicode spaces or pluses. |
| func isValidName(name string) bool { |
| return name != "" && utf8.ValidString(name) && strings.IndexFunc(name, unicode.IsSpace) < 0 && !strings.Contains(name, "+") |
| } |
| |
| // NewVerifier construct a new Verifier from an encoded verifier key. |
| func NewVerifier(vkey string) (Verifier, error) { |
| name, vkey := chop(vkey, "+") |
| hash16, key64 := chop(vkey, "+") |
| hash, err1 := strconv.ParseUint(hash16, 16, 32) |
| key, err2 := base64.StdEncoding.DecodeString(key64) |
| if len(hash16) != 8 || err1 != nil || err2 != nil || !isValidName(name) || len(key) == 0 { |
| return nil, errVerifierID |
| } |
| if uint32(hash) != keyHash(name, key) { |
| return nil, errVerifierHash |
| } |
| |
| v := &verifier{ |
| name: name, |
| hash: uint32(hash), |
| } |
| |
| alg, key := key[0], key[1:] |
| switch alg { |
| default: |
| return nil, errVerifierAlg |
| |
| case algEd25519: |
| if len(key) != 32 { |
| return nil, errVerifierID |
| } |
| v.verify = func(msg, sig []byte) bool { |
| return ed25519.Verify(key, msg, sig) |
| } |
| } |
| |
| return v, nil |
| } |
| |
| // chop chops s at the first instance of sep, if any, |
| // and returns the text before and after sep. |
| // If sep is not present, chop returns before is s and after is empty. |
| func chop(s, sep string) (before, after string) { |
| i := strings.Index(s, sep) |
| if i < 0 { |
| return s, "" |
| } |
| return s[:i], s[i+len(sep):] |
| } |
| |
| // verifier is a trivial Verifier implementation. |
| type verifier struct { |
| name string |
| hash uint32 |
| verify func([]byte, []byte) bool |
| } |
| |
| func (v *verifier) Name() string { return v.name } |
| func (v *verifier) KeyHash() uint32 { return v.hash } |
| func (v *verifier) Verify(msg, sig []byte) bool { return v.verify(msg, sig) } |
| |
| // NewSigner constructs a new Signer from an encoded signer key. |
| func NewSigner(skey string) (Signer, error) { |
| priv1, skey := chop(skey, "+") |
| priv2, skey := chop(skey, "+") |
| name, skey := chop(skey, "+") |
| hash16, key64 := chop(skey, "+") |
| hash, err1 := strconv.ParseUint(hash16, 16, 32) |
| key, err2 := base64.StdEncoding.DecodeString(key64) |
| if priv1 != "PRIVATE" || priv2 != "KEY" || len(hash16) != 8 || err1 != nil || err2 != nil || !isValidName(name) || len(key) == 0 { |
| return nil, errSignerID |
| } |
| |
| // Note: hash is the hash of the public key and we have the private key. |
| // Must verify hash after deriving public key. |
| |
| s := &signer{ |
| name: name, |
| hash: uint32(hash), |
| } |
| |
| var pubkey []byte |
| |
| alg, key := key[0], key[1:] |
| switch alg { |
| default: |
| return nil, errSignerAlg |
| |
| case algEd25519: |
| if len(key) != 32 { |
| return nil, errSignerID |
| } |
| key = ed25519.NewKeyFromSeed(key) |
| pubkey = append([]byte{algEd25519}, key[32:]...) |
| s.sign = func(msg []byte) ([]byte, error) { |
| return ed25519.Sign(key, msg), nil |
| } |
| } |
| |
| if uint32(hash) != keyHash(name, pubkey) { |
| return nil, errSignerHash |
| } |
| |
| return s, nil |
| } |
| |
| var ( |
| errSignerID = errors.New("malformed verifier id") |
| errSignerAlg = errors.New("unknown verifier algorithm") |
| errSignerHash = errors.New("invalid verifier hash") |
| ) |
| |
| // signer is a trivial Signer implementation. |
| type signer struct { |
| name string |
| hash uint32 |
| sign func([]byte) ([]byte, error) |
| } |
| |
| func (s *signer) Name() string { return s.name } |
| func (s *signer) KeyHash() uint32 { return s.hash } |
| func (s *signer) Sign(msg []byte) ([]byte, error) { return s.sign(msg) } |
| |
| // GenerateKey generates a signer and verifier key pair for a named server. |
| // The signer key skey is private and must be kept secret. |
| func GenerateKey(rand io.Reader, name string) (skey, vkey string, err error) { |
| pub, priv, err := ed25519.GenerateKey(rand) |
| if err != nil { |
| return "", "", err |
| } |
| pubkey := append([]byte{algEd25519}, pub...) |
| privkey := append([]byte{algEd25519}, priv.Seed()...) |
| h := keyHash(name, pubkey) |
| |
| skey = fmt.Sprintf("PRIVATE+KEY+%s+%08x+%s", name, h, base64.StdEncoding.EncodeToString(privkey)) |
| vkey = fmt.Sprintf("%s+%08x+%s", name, h, base64.StdEncoding.EncodeToString(pubkey)) |
| return skey, vkey, nil |
| } |
| |
| // NewEd25519VerifierKey returns an encoded verifier key using the given name |
| // and Ed25519 public key. |
| func NewEd25519VerifierKey(name string, key ed25519.PublicKey) (string, error) { |
| if len(key) != ed25519.PublicKeySize { |
| return "", fmt.Errorf("invalid public key size %d, expected %d", len(key), ed25519.PublicKeySize) |
| } |
| |
| pubkey := append([]byte{algEd25519}, key...) |
| hash := keyHash(name, pubkey) |
| |
| b64Key := base64.StdEncoding.EncodeToString(pubkey) |
| return fmt.Sprintf("%s+%08x+%s", name, hash, b64Key), nil |
| } |
| |
| // A Verifiers is a collection of known verifier keys. |
| 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) |
| } |
| |
| // An UnknownVerifierError indicates that the given key is not known. |
| // The Open function records signatures without associated verifiers as |
| // unverified signatures. |
| type UnknownVerifierError struct { |
| Name string |
| KeyHash uint32 |
| } |
| |
| func (e *UnknownVerifierError) Error() string { |
| return fmt.Sprintf("unknown key %s+%08x", e.Name, e.KeyHash) |
| } |
| |
| // An ambiguousVerifierError indicates that the given name and hash |
| // match multiple keys passed to VerifierList. |
| // (If this happens, some malicious actor has taken control of the |
| // verifier list, at which point we may as well give up entirely, |
| // but we diagnose the problem instead.) |
| type ambiguousVerifierError struct { |
| name string |
| hash uint32 |
| } |
| |
| func (e *ambiguousVerifierError) Error() string { |
| return fmt.Sprintf("ambiguous key %s+%08x", e.name, e.hash) |
| } |
| |
| // VerifierList returns a Verifiers implementation that uses the given list of verifiers. |
| func VerifierList(list ...Verifier) Verifiers { |
| m := make(verifierMap) |
| for _, v := range list { |
| k := nameHash{v.Name(), v.KeyHash()} |
| m[k] = append(m[k], v) |
| } |
| return m |
| } |
| |
| type nameHash struct { |
| name string |
| hash uint32 |
| } |
| |
| type verifierMap map[nameHash][]Verifier |
| |
| func (m verifierMap) Verifier(name string, hash uint32) (Verifier, error) { |
| v, ok := m[nameHash{name, hash}] |
| if !ok { |
| return nil, &UnknownVerifierError{name, hash} |
| } |
| if len(v) > 1 { |
| return nil, &ambiguousVerifierError{name, hash} |
| } |
| return v[0], nil |
| } |
| |
| // A Note is a text and signatures. |
| type Note struct { |
| Text string // text of note |
| Sigs []Signature // verified signatures |
| UnverifiedSigs []Signature // unverified signatures |
| } |
| |
| // A Signature is a single signature found in a note. |
| 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 |
| } |
| |
| // An UnverifiedNoteError indicates that the note |
| // successfully parsed but had no verifiable signatures. |
| type UnverifiedNoteError struct { |
| Note *Note |
| } |
| |
| func (e *UnverifiedNoteError) Error() string { |
| return "note has no verifiable signatures" |
| } |
| |
| // An InvalidSignatureError indicates that the given key was known |
| // and the associated Verifier rejected the signature. |
| type InvalidSignatureError struct { |
| Name string |
| Hash uint32 |
| } |
| |
| func (e *InvalidSignatureError) Error() string { |
| return fmt.Sprintf("invalid signature for key %s+%08x", e.Name, e.Hash) |
| } |
| |
| var ( |
| errMalformedNote = errors.New("malformed note") |
| errInvalidSigner = errors.New("invalid signer") |
| |
| sigSplit = []byte("\n\n") |
| sigPrefix = []byte("— ") |
| ) |
| |
| // Open 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. |
| // If known.Verifier returns a verifier and the verifier accepts the signature, |
| // Open records the signature in the returned note's Sigs field. |
| // If known.Verifier returns a verifier but the verifier rejects the signature, |
| // Open returns an InvalidSignatureError. |
| // If known.Verifier returns an UnknownVerifierError, |
| // Open records the signature in the returned note's UnverifiedSigs field. |
| // If known.Verifier returns any other error, Open returns that error. |
| // |
| // If no known verifier has signed an otherwise valid note, |
| // Open returns an UnverifiedNoteError. |
| // In this case, the unverified note can be fetched from inside the error. |
| func Open(msg []byte, known Verifiers) (*Note, error) { |
| if known == nil { |
| // Treat nil Verifiers as empty list, to produce useful error instead of crash. |
| known = VerifierList() |
| } |
| |
| // Must have valid UTF-8 with no non-newline ASCII control characters. |
| for i := 0; i < len(msg); { |
| r, size := utf8.DecodeRune(msg[i:]) |
| if r < 0x20 && r != '\n' || r == utf8.RuneError && size == 1 { |
| return nil, errMalformedNote |
| } |
| i += size |
| } |
| |
| // Must end with signature block preceded by blank line. |
| split := bytes.LastIndex(msg, sigSplit) |
| if split < 0 { |
| return nil, errMalformedNote |
| } |
| text, sigs := msg[:split+1], msg[split+2:] |
| if len(sigs) == 0 || sigs[len(sigs)-1] != '\n' { |
| return nil, errMalformedNote |
| } |
| |
| n := &Note{ |
| Text: string(text), |
| } |
| |
| var buf bytes.Buffer |
| buf.Write(text) |
| |
| // Parse and verify signatures. |
| // Ignore duplicate signatures. |
| seen := make(map[nameHash]bool) |
| seenUnverified := make(map[string]bool) |
| numSig := 0 |
| for len(sigs) > 0 { |
| // Pull out next signature line. |
| // We know sigs[len(sigs)-1] == '\n', so IndexByte always finds one. |
| i := bytes.IndexByte(sigs, '\n') |
| line := sigs[:i] |
| sigs = sigs[i+1:] |
| |
| if !bytes.HasPrefix(line, sigPrefix) { |
| return nil, errMalformedNote |
| } |
| line = line[len(sigPrefix):] |
| name, b64 := chop(string(line), " ") |
| sig, err := base64.StdEncoding.DecodeString(b64) |
| if err != nil || !isValidName(name) || b64 == "" || len(sig) < 5 { |
| return nil, errMalformedNote |
| } |
| hash := binary.BigEndian.Uint32(sig[0:4]) |
| sig = sig[4:] |
| |
| if numSig++; numSig > 100 { |
| // Avoid spending forever parsing a note with many signatures. |
| return nil, errMalformedNote |
| } |
| |
| v, err := known.Verifier(name, hash) |
| if _, ok := err.(*UnknownVerifierError); ok { |
| // Drop repeated identical unverified signatures. |
| if seenUnverified[string(line)] { |
| continue |
| } |
| seenUnverified[string(line)] = true |
| n.UnverifiedSigs = append(n.UnverifiedSigs, Signature{Name: name, Hash: hash, Base64: b64}) |
| continue |
| } |
| if err != nil { |
| return nil, err |
| } |
| |
| // Drop repeated signatures by a single verifier. |
| if seen[nameHash{name, hash}] { |
| continue |
| } |
| seen[nameHash{name, hash}] = true |
| |
| ok := v.Verify(text, sig) |
| if !ok { |
| return nil, &InvalidSignatureError{name, hash} |
| } |
| |
| n.Sigs = append(n.Sigs, Signature{Name: name, Hash: hash, Base64: b64}) |
| } |
| |
| // Parsed and verified all the signatures. |
| if len(n.Sigs) == 0 { |
| return nil, &UnverifiedNoteError{n} |
| } |
| return n, nil |
| } |
| |
| // Sign signs the note with the given signers and returns the encoded message. |
| // The new signatures from signers are listed in the encoded message after |
| // the existing signatures already present in n.Sigs. |
| // If any signer uses the same key as an existing signature, |
| // the existing signature is elided from the output. |
| func Sign(n *Note, signers ...Signer) ([]byte, error) { |
| var buf bytes.Buffer |
| if !strings.HasSuffix(n.Text, "\n") { |
| return nil, errMalformedNote |
| } |
| buf.WriteString(n.Text) |
| |
| // Prepare signatures. |
| var sigs bytes.Buffer |
| have := make(map[nameHash]bool) |
| for _, s := range signers { |
| name := s.Name() |
| hash := s.KeyHash() |
| have[nameHash{name, hash}] = true |
| if !isValidName(name) { |
| return nil, errInvalidSigner |
| } |
| |
| sig, err := s.Sign(buf.Bytes()) // buf holds n.Text |
| if err != nil { |
| return nil, err |
| } |
| |
| var hbuf [4]byte |
| binary.BigEndian.PutUint32(hbuf[:], hash) |
| b64 := base64.StdEncoding.EncodeToString(append(hbuf[:], sig...)) |
| sigs.WriteString("— ") |
| sigs.WriteString(name) |
| sigs.WriteString(" ") |
| sigs.WriteString(b64) |
| sigs.WriteString("\n") |
| } |
| |
| buf.WriteString("\n") |
| |
| // Emit existing signatures not replaced by new ones. |
| for _, list := range [][]Signature{n.Sigs, n.UnverifiedSigs} { |
| for _, sig := range list { |
| name, hash := sig.Name, sig.Hash |
| if !isValidName(name) { |
| return nil, errMalformedNote |
| } |
| if have[nameHash{name, hash}] { |
| continue |
| } |
| // Double-check hash against base64. |
| raw, err := base64.StdEncoding.DecodeString(sig.Base64) |
| if err != nil || len(raw) < 4 || binary.BigEndian.Uint32(raw) != hash { |
| return nil, errMalformedNote |
| } |
| buf.WriteString("— ") |
| buf.WriteString(sig.Name) |
| buf.WriteString(" ") |
| buf.WriteString(sig.Base64) |
| buf.WriteString("\n") |
| } |
| } |
| buf.Write(sigs.Bytes()) |
| |
| return buf.Bytes(), nil |
| } |