note

package
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 29, 2019 License: BSD-3-Clause Imports: 12 Imported by: 0

Documentation

Overview

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(&note.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=

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func GenerateKey

func GenerateKey(rand io.Reader, name string) (skey, vkey string, err error)

GenerateKey generates a signer and verifier key pair for a named server. The signer key skey is private and must be kept secret.

func NewEd25519VerifierKey

func NewEd25519VerifierKey(name string, key ed25519.PublicKey) (string, error)

NewEd25519VerifierKey returns an encoded verifier key using the given name and Ed25519 public key.

func Sign

func Sign(n *Note, signers ...Signer) ([]byte, error)

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.

Example
package main

import (
	"fmt"
	"os"

	"golang.org/x/exp/sumdb/internal/note"
)

func main() {
	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 {
		fmt.Println(err)
		return
	}

	msg, err := note.Sign(&note.Note{Text: text}, signer)
	if err != nil {
		fmt.Println(err)
		return
	}
	os.Stdout.Write(msg)

}
Output:

If you think cryptography is the answer to your problem,
then you don't know what your problem is.

— PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=
Example (Add_signatures)
package main

import (
	"fmt"
	"io"
	"os"

	"golang.org/x/exp/sumdb/internal/note"
)

var rand = struct {
	Reader io.Reader
}{
	zeroReader{},
}

type zeroReader struct{}

func (zeroReader) Read(buf []byte) (int, error) {
	for i := range buf {
		buf[i] = 0
	}
	return len(buf), nil
}

func main() {
	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 {
		fmt.Println(err)
		return
	}
	verifiers := note.VerifierList(verifier)

	n, err := note.Open([]byte(msg), verifiers)
	if err != nil {
		fmt.Println(err)
		return
	}

	skey, vkey, err := note.GenerateKey(rand.Reader, "EnochRoot")
	if err != nil {
		fmt.Println(err)
		return
	}
	_ = vkey // give to verifiers

	me, err := note.NewSigner(skey)
	if err != nil {
		fmt.Println(err)
		return
	}

	msg, err = note.Sign(n, me)
	if err != nil {
		fmt.Println(err)
		return
	}
	os.Stdout.Write(msg)

}
Output:

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=

Types

type InvalidSignatureError

type InvalidSignatureError struct {
	Name string
	Hash uint32
}

An InvalidSignatureError indicates that the given key was known and the associated Verifier rejected the signature.

func (*InvalidSignatureError) Error

func (e *InvalidSignatureError) Error() string

type Note

type Note struct {
	Text           string      // text of note
	Sigs           []Signature // verified signatures
	UnverifiedSigs []Signature // unverified signatures
}

A Note is a text and signatures.

func Open

func Open(msg []byte, known Verifiers) (*Note, error)

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.

Example
package main

import (
	"fmt"

	"golang.org/x/exp/sumdb/internal/note"
)

func main() {
	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 {
		fmt.Println(err)
		return
	}
	verifiers := note.VerifierList(verifier)

	n, err := note.Open(msg, verifiers)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%s (%08x):\n%s", n.Sigs[0].Name, n.Sigs[0].Hash, n.Text)

}
Output:

PeterNeumann (c74f20a3):
If you think cryptography is the answer to your problem,
then you don't know what your problem is.

type Signature

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

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.

func NewSigner

func NewSigner(skey string) (Signer, error)

NewSigner constructs a new Signer from an encoded signer key.

type UnknownVerifierError

type UnknownVerifierError struct {
	Name    string
	KeyHash uint32
}

An UnknownVerifierError indicates that the given key is not known. The Open function records signatures without associated verifiers as unverified signatures.

func (*UnknownVerifierError) Error

func (e *UnknownVerifierError) Error() string

type UnverifiedNoteError

type UnverifiedNoteError struct {
	Note *Note
}

An UnverifiedNoteError indicates that the note successfully parsed but had no verifiable signatures.

func (*UnverifiedNoteError) Error

func (e *UnverifiedNoteError) Error() string

type Verifier

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.

func NewVerifier

func NewVerifier(vkey string) (Verifier, error)

NewVerifier construct a new Verifier from an encoded verifier key.

type Verifiers

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.

func VerifierList

func VerifierList(list ...Verifier) Verifiers

VerifierList returns a Verifiers implementation that uses the given list of verifiers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL