From 6616bed61652ae27afd21cfefc1750f2616059d1 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 5 Aug 2015 17:34:30 -0700 Subject: [PATCH] validation tests Signed-off-by: David Lawrence (github: endophage) --- .gitignore | 1 + Godeps/Godeps.json | 11 +- .../p/gosqlite/sqlite3/driver.go | 498 ++++++++++ .../endophage/gotuf/testutils/repo.go | 99 ++ .../src/github.com/google/gofuzz/.travis.yml | 13 + .../github.com/google/gofuzz/CONTRIBUTING.md | 67 ++ .../src/github.com/google/gofuzz/LICENSE | 202 ++++ .../src/github.com/google/gofuzz/README.md | 71 ++ .../src/github.com/google/gofuzz/doc.go | 18 + .../github.com/google/gofuzz/example_test.go | 225 +++++ .../src/github.com/google/gofuzz/fuzz.go | 446 +++++++++ .../src/github.com/google/gofuzz/fuzz_test.go | 384 ++++++++ server/handlers/validation.go | 103 +- server/handlers/validation_test.go | 893 ++++++++++++++++++ 14 files changed, 3005 insertions(+), 26 deletions(-) create mode 100644 Godeps/_workspace/src/code.google.com/p/gosqlite/sqlite3/driver.go create mode 100644 Godeps/_workspace/src/github.com/endophage/gotuf/testutils/repo.go create mode 100644 Godeps/_workspace/src/github.com/google/gofuzz/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/google/gofuzz/CONTRIBUTING.md create mode 100644 Godeps/_workspace/src/github.com/google/gofuzz/LICENSE create mode 100644 Godeps/_workspace/src/github.com/google/gofuzz/README.md create mode 100644 Godeps/_workspace/src/github.com/google/gofuzz/doc.go create mode 100644 Godeps/_workspace/src/github.com/google/gofuzz/example_test.go create mode 100644 Godeps/_workspace/src/github.com/google/gofuzz/fuzz.go create mode 100644 Godeps/_workspace/src/github.com/google/gofuzz/fuzz_test.go create mode 100644 server/handlers/validation_test.go diff --git a/.gitignore b/.gitignore index c88be6800b..8439935e6e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ cross *.swp .idea *.iml +coverage.out diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index e257c08574..e0da8dc6be 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -5,6 +5,11 @@ "./..." ], "Deps": [ + { + "ImportPath": "code.google.com/p/gosqlite/sqlite3", + "Comment": "null-16", + "Rev": "74691fb6f83716190870cde1b658538dd4b18eb0" + }, { "ImportPath": "github.com/BurntSushi/toml", "Rev": "bd2bdf7f18f849530ef7a1c29a4290217cab32a1" @@ -95,7 +100,7 @@ }, { "ImportPath": "github.com/endophage/gotuf", - "Rev": "78f263e5a0b2f74b8336ea9faef0e40eef1f439d" + "Rev": "4c04df9067a595ead06309f38021ea445acc1d1c" }, { "ImportPath": "github.com/go-sql-driver/mysql", @@ -106,6 +111,10 @@ "ImportPath": "github.com/golang/protobuf/proto", "Rev": "655cdfa588ea190e901bc5590e65d5621688847c" }, + { + "ImportPath": "github.com/google/gofuzz", + "Rev": "bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5" + }, { "ImportPath": "github.com/gorilla/context", "Rev": "14f550f51af52180c2eefed15e5fd18d63c0a64a" diff --git a/Godeps/_workspace/src/code.google.com/p/gosqlite/sqlite3/driver.go b/Godeps/_workspace/src/code.google.com/p/gosqlite/sqlite3/driver.go new file mode 100644 index 0000000000..982e08ec04 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/gosqlite/sqlite3/driver.go @@ -0,0 +1,498 @@ +// Copyright 2010 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 sqlite3 provides access to the SQLite library, version 3. +// +// The package has no exported API. +// It registers a driver for the standard Go database/sql package. +// +// import _ "code.google.com/p/gosqlite/sqlite3" +// +// (For an alternate, earlier API, see the code.google.com/p/gosqlite/sqlite package.) +package sqlite + +/* +#cgo LDFLAGS: -lsqlite3 + +#include +#include + +// These wrappers are necessary because SQLITE_TRANSIENT +// is a pointer constant, and cgo doesn't translate them correctly. +// The definition in sqlite3.h is: +// +// typedef void (*sqlite3_destructor_type)(void*); +// #define SQLITE_STATIC ((sqlite3_destructor_type)0) +// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) + +static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) { + return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT); +} +static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) { + return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT); +} + +*/ +import "C" + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "io" + "strings" + "time" + "unsafe" +) + +func init() { + sql.Register("sqlite3", impl{}) +} + +type errno int + +func (e errno) Error() string { + s := errText[e] + if s == "" { + return fmt.Sprintf("errno %d", int(e)) + } + return s +} + +var ( + errError error = errno(1) // /* SQL error or missing database */ + errInternal error = errno(2) // /* Internal logic error in SQLite */ + errPerm error = errno(3) // /* Access permission denied */ + errAbort error = errno(4) // /* Callback routine requested an abort */ + errBusy error = errno(5) // /* The database file is locked */ + errLocked error = errno(6) // /* A table in the database is locked */ + errNoMem error = errno(7) // /* A malloc() failed */ + errReadOnly error = errno(8) // /* Attempt to write a readonly database */ + errInterrupt error = errno(9) // /* Operation terminated by sqlite3_interrupt()*/ + errIOErr error = errno(10) // /* Some kind of disk I/O error occurred */ + errCorrupt error = errno(11) // /* The database disk image is malformed */ + errFull error = errno(13) // /* Insertion failed because database is full */ + errCantOpen error = errno(14) // /* Unable to open the database file */ + errEmpty error = errno(16) // /* Database is empty */ + errSchema error = errno(17) // /* The database schema changed */ + errTooBig error = errno(18) // /* String or BLOB exceeds size limit */ + errConstraint error = errno(19) // /* Abort due to constraint violation */ + errMismatch error = errno(20) // /* Data type mismatch */ + errMisuse error = errno(21) // /* Library used incorrectly */ + errNolfs error = errno(22) // /* Uses OS features not supported on host */ + errAuth error = errno(23) // /* Authorization denied */ + errFormat error = errno(24) // /* Auxiliary database format error */ + errRange error = errno(25) // /* 2nd parameter to sqlite3_bind out of range */ + errNotDB error = errno(26) // /* File opened that is not a database file */ + stepRow = errno(100) // /* sqlite3_step() has another row ready */ + stepDone = errno(101) // /* sqlite3_step() has finished executing */ +) + +var errText = map[errno]string{ + 1: "SQL error or missing database", + 2: "Internal logic error in SQLite", + 3: "Access permission denied", + 4: "Callback routine requested an abort", + 5: "The database file is locked", + 6: "A table in the database is locked", + 7: "A malloc() failed", + 8: "Attempt to write a readonly database", + 9: "Operation terminated by sqlite3_interrupt()*/", + 10: "Some kind of disk I/O error occurred", + 11: "The database disk image is malformed", + 12: "NOT USED. Table or record not found", + 13: "Insertion failed because database is full", + 14: "Unable to open the database file", + 15: "NOT USED. Database lock protocol error", + 16: "Database is empty", + 17: "The database schema changed", + 18: "String or BLOB exceeds size limit", + 19: "Abort due to constraint violation", + 20: "Data type mismatch", + 21: "Library used incorrectly", + 22: "Uses OS features not supported on host", + 23: "Authorization denied", + 24: "Auxiliary database format error", + 25: "2nd parameter to sqlite3_bind out of range", + 26: "File opened that is not a database file", + 100: "sqlite3_step() has another row ready", + 101: "sqlite3_step() has finished executing", +} + +type impl struct{} + +func (impl) Open(name string) (driver.Conn, error) { + if C.sqlite3_threadsafe() == 0 { + return nil, errors.New("sqlite library was not compiled for thread-safe operation") + } + + var db *C.sqlite3 + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + rv := C.sqlite3_open_v2(cname, &db, + C.SQLITE_OPEN_FULLMUTEX| + C.SQLITE_OPEN_READWRITE| + C.SQLITE_OPEN_CREATE, + nil) + if rv != 0 { + return nil, errno(rv) + } + if db == nil { + return nil, errors.New("sqlite succeeded without returning a database") + } + return &conn{db: db}, nil +} + +type conn struct { + db *C.sqlite3 + closed bool + tx bool +} + +func (c *conn) error(rv C.int) error { + if rv == 0 { + return nil + } + if rv == 21 || c.closed { + return errno(rv) + } + return errors.New(errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db))) +} + +func (c *conn) Prepare(cmd string) (driver.Stmt, error) { + if c.closed { + panic("database/sql/driver: misuse of sqlite driver: Prepare after Close") + } + cmdstr := C.CString(cmd) + defer C.free(unsafe.Pointer(cmdstr)) + var s *C.sqlite3_stmt + var tail *C.char + rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &s, &tail) + if rv != 0 { + return nil, c.error(rv) + } + return &stmt{c: c, stmt: s, sql: cmd, t0: time.Now()}, nil +} + +func (c *conn) Close() error { + if c.closed { + panic("database/sql/driver: misuse of sqlite driver: multiple Close") + } + c.closed = true + rv := C.sqlite3_close(c.db) + c.db = nil + return c.error(rv) +} + +func (c *conn) exec(cmd string) error { + cstring := C.CString(cmd) + defer C.free(unsafe.Pointer(cstring)) + rv := C.sqlite3_exec(c.db, cstring, nil, nil, nil) + return c.error(rv) +} + +func (c *conn) Begin() (driver.Tx, error) { + if c.tx { + panic("database/sql/driver: misuse of sqlite driver: multiple Tx") + } + if err := c.exec("BEGIN TRANSACTION"); err != nil { + return nil, err + } + c.tx = true + return &tx{c}, nil +} + +type tx struct { + c *conn +} + +func (t *tx) Commit() error { + if t.c == nil || !t.c.tx { + panic("database/sql/driver: misuse of sqlite driver: extra Commit") + } + t.c.tx = false + err := t.c.exec("COMMIT TRANSACTION") + t.c = nil + return err +} + +func (t *tx) Rollback() error { + if t.c == nil || !t.c.tx { + panic("database/sql/driver: misuse of sqlite driver: extra Rollback") + } + t.c.tx = false + err := t.c.exec("ROLLBACK") + t.c = nil + return err +} + +type stmt struct { + c *conn + stmt *C.sqlite3_stmt + err error + t0 time.Time + sql string + args string + closed bool + rows bool + colnames []string + coltypes []string +} + +func (s *stmt) Close() error { + if s.rows { + panic("database/sql/driver: misuse of sqlite driver: Close with active Rows") + } + if s.closed { + panic("database/sql/driver: misuse of sqlite driver: double Close of Stmt") + } + s.closed = true + rv := C.sqlite3_finalize(s.stmt) + if rv != 0 { + return s.c.error(rv) + } + return nil +} + +func (s *stmt) NumInput() int { + if s.closed { + panic("database/sql/driver: misuse of sqlite driver: NumInput after Close") + } + return int(C.sqlite3_bind_parameter_count(s.stmt)) +} + +func (s *stmt) reset() error { + return s.c.error(C.sqlite3_reset(s.stmt)) +} + +func (s *stmt) start(args []driver.Value) error { + if err := s.reset(); err != nil { + return err + } + + n := int(C.sqlite3_bind_parameter_count(s.stmt)) + if n != len(args) { + return fmt.Errorf("incorrect argument count for command: have %d want %d", len(args), n) + } + + for i, v := range args { + var str string + switch v := v.(type) { + case nil: + if rv := C.sqlite3_bind_null(s.stmt, C.int(i+1)); rv != 0 { + return s.c.error(rv) + } + continue + + case float64: + if rv := C.sqlite3_bind_double(s.stmt, C.int(i+1), C.double(v)); rv != 0 { + return s.c.error(rv) + } + continue + + case int64: + if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(v)); rv != 0 { + return s.c.error(rv) + } + continue + + case []byte: + var p *byte + if len(v) > 0 { + p = &v[0] + } + if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 { + return s.c.error(rv) + } + continue + + case bool: + var vi int64 + if v { + vi = 1 + } + if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(vi)); rv != 0 { + return s.c.error(rv) + } + continue + + case time.Time: + str = v.UTC().Format(timefmt[0]) + + case string: + str = v + + default: + str = fmt.Sprint(v) + } + + cstr := C.CString(str) + rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str))) + C.free(unsafe.Pointer(cstr)) + if rv != 0 { + return s.c.error(rv) + } + } + + return nil +} + +func (s *stmt) Exec(args []driver.Value) (driver.Result, error) { + if s.closed { + panic("database/sql/driver: misuse of sqlite driver: Exec after Close") + } + if s.rows { + panic("database/sql/driver: misuse of sqlite driver: Exec with active Rows") + } + + err := s.start(args) + if err != nil { + return nil, err + } + + rv := C.sqlite3_step(s.stmt) + if errno(rv) != stepDone { + if rv == 0 { + rv = 21 // errMisuse + } + return nil, s.c.error(rv) + } + + id := int64(C.sqlite3_last_insert_rowid(s.c.db)) + rows := int64(C.sqlite3_changes(s.c.db)) + return &result{id, rows}, nil +} + +func (s *stmt) Query(args []driver.Value) (driver.Rows, error) { + if s.closed { + panic("database/sql/driver: misuse of sqlite driver: Query after Close") + } + if s.rows { + panic("database/sql/driver: misuse of sqlite driver: Query with active Rows") + } + + err := s.start(args) + if err != nil { + return nil, err + } + + s.rows = true + if s.colnames == nil { + n := int64(C.sqlite3_column_count(s.stmt)) + s.colnames = make([]string, n) + s.coltypes = make([]string, n) + for i := range s.colnames { + s.colnames[i] = C.GoString(C.sqlite3_column_name(s.stmt, C.int(i))) + s.coltypes[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(s.stmt, C.int(i)))) + } + } + return &rows{s}, nil +} + +type rows struct { + s *stmt +} + +func (r *rows) Columns() []string { + if r.s == nil { + panic("database/sql/driver: misuse of sqlite driver: Columns of closed Rows") + } + return r.s.colnames +} + +const maxslice = 1<<31 - 1 + +var timefmt = []string{ + "2006-01-02 15:04:05.999999999", + "2006-01-02T15:04:05.999999999", + "2006-01-02 15:04:05", + "2006-01-02T15:04:05", + "2006-01-02 15:04", + "2006-01-02T15:04", + "2006-01-02", +} + +func (r *rows) Next(dst []driver.Value) error { + if r.s == nil { + panic("database/sql/driver: misuse of sqlite driver: Next of closed Rows") + } + + rv := C.sqlite3_step(r.s.stmt) + if errno(rv) != stepRow { + if errno(rv) == stepDone { + return io.EOF + } + if rv == 0 { + rv = 21 + } + return r.s.c.error(rv) + } + + for i := range dst { + switch typ := C.sqlite3_column_type(r.s.stmt, C.int(i)); typ { + default: + return fmt.Errorf("unexpected sqlite3 column type %d", typ) + case C.SQLITE_INTEGER: + val := int64(C.sqlite3_column_int64(r.s.stmt, C.int(i))) + switch r.s.coltypes[i] { + case "timestamp", "datetime": + dst[i] = time.Unix(val, 0).UTC() + case "boolean": + dst[i] = val > 0 + default: + dst[i] = val + } + + case C.SQLITE_FLOAT: + dst[i] = float64(C.sqlite3_column_double(r.s.stmt, C.int(i))) + + case C.SQLITE_BLOB, C.SQLITE_TEXT: + n := int(C.sqlite3_column_bytes(r.s.stmt, C.int(i))) + var b []byte + if n > 0 { + p := C.sqlite3_column_blob(r.s.stmt, C.int(i)) + b = (*[maxslice]byte)(unsafe.Pointer(p))[:n] + } + dst[i] = b + switch r.s.coltypes[i] { + case "timestamp", "datetime": + dst[i] = time.Time{} + s := string(b) + for _, f := range timefmt { + if t, err := time.Parse(f, s); err == nil { + dst[i] = t + break + } + } + } + + case C.SQLITE_NULL: + dst[i] = nil + } + } + return nil +} + +func (r *rows) Close() error { + if r.s == nil { + panic("database/sql/driver: misuse of sqlite driver: Close of closed Rows") + } + r.s.rows = false + r.s = nil + return nil +} + +type result struct { + id int64 + rows int64 +} + +func (r *result) LastInsertId() (int64, error) { + return r.id, nil +} + +func (r *result) RowsAffected() (int64, error) { + return r.rows, nil +} diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/testutils/repo.go b/Godeps/_workspace/src/github.com/endophage/gotuf/testutils/repo.go new file mode 100644 index 0000000000..3fa9c1f676 --- /dev/null +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/testutils/repo.go @@ -0,0 +1,99 @@ +package testutils + +import ( + "encoding/json" + "math/rand" + "time" + + "github.com/endophage/gotuf/data" + "github.com/endophage/gotuf/utils" + fuzz "github.com/google/gofuzz" + + tuf "github.com/endophage/gotuf" + "github.com/endophage/gotuf/keys" + "github.com/endophage/gotuf/signed" +) + +// EmptyRepo creates an in memory key database, crypto service +// and initializes a repo with no targets or delegations. +func EmptyRepo() (*keys.KeyDB, *tuf.TufRepo, signed.CryptoService) { + c := signed.NewEd25519() + kdb := keys.NewDB() + r := tuf.NewTufRepo(kdb, c) + + for _, role := range []string{"root", "targets", "snapshot", "timestamp"} { + key, _ := c.Create(role, data.ED25519Key) + role, _ := data.NewRole(role, 1, []string{key.ID()}, nil, nil) + kdb.AddKey(key) + kdb.AddRole(role) + } + + r.InitRepo(false) + return kdb, r, c +} + +func AddTarget(role string, r *tuf.TufRepo) (name string, meta data.FileMeta, content []byte, err error) { + randness := fuzz.Continue{} + content = RandomByteSlice(1024) + name = randness.RandString() + t := data.FileMeta{ + Length: int64(len(content)), + Hashes: data.Hashes{ + "sha256": utils.DoHash("sha256", content), + "sha512": utils.DoHash("sha512", content), + }, + } + files := data.Files{name: t} + _, err = r.AddTargets(role, files) + return +} + +func RandomByteSlice(maxSize int) []byte { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + contentSize := r.Intn(maxSize) + content := make([]byte, contentSize) + for i := range content { + content[i] = byte(r.Int63() & 0xff) + } + return content +} + +func Sign(repo *tuf.TufRepo) (root, targets, snapshot, timestamp *data.Signed, err error) { + root, err = repo.SignRoot(data.DefaultExpires("root"), nil) + if err != nil { + return nil, nil, nil, nil, err + } + targets, err = repo.SignTargets("targets", data.DefaultExpires("targets"), nil) + if err != nil { + return nil, nil, nil, nil, err + } + snapshot, err = repo.SignSnapshot(data.DefaultExpires("snapshot"), nil) + if err != nil { + return nil, nil, nil, nil, err + } + timestamp, err = repo.SignTimestamp(data.DefaultExpires("timestamp"), nil) + if err != nil { + return nil, nil, nil, nil, err + } + return +} + +func Serialize(sRoot, sTargets, sSnapshot, sTimestamp *data.Signed) (root, targets, snapshot, timestamp []byte, err error) { + root, err = json.Marshal(sRoot) + if err != nil { + return nil, nil, nil, nil, err + } + targets, err = json.Marshal(sTargets) + if err != nil { + return nil, nil, nil, nil, err + } + snapshot, err = json.Marshal(sSnapshot) + if err != nil { + return nil, nil, nil, nil, err + } + timestamp, err = json.Marshal(sTimestamp) + if err != nil { + return nil, nil, nil, nil, err + } + return +} diff --git a/Godeps/_workspace/src/github.com/google/gofuzz/.travis.yml b/Godeps/_workspace/src/github.com/google/gofuzz/.travis.yml new file mode 100644 index 0000000000..f8684d99fc --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/gofuzz/.travis.yml @@ -0,0 +1,13 @@ +language: go + +go: + - 1.4 + - 1.3 + - 1.2 + - tip + +install: + - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi + +script: + - go test -cover diff --git a/Godeps/_workspace/src/github.com/google/gofuzz/CONTRIBUTING.md b/Godeps/_workspace/src/github.com/google/gofuzz/CONTRIBUTING.md new file mode 100644 index 0000000000..51cf5cd1ad --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/gofuzz/CONTRIBUTING.md @@ -0,0 +1,67 @@ +# How to contribute # + +We'd love to accept your patches and contributions to this project. There are +a just a few small guidelines you need to follow. + + +## Contributor License Agreement ## + +Contributions to any Google project must be accompanied by a Contributor +License Agreement. This is not a copyright **assignment**, it simply gives +Google permission to use and redistribute your contributions as part of the +project. + + * If you are an individual writing original source code and you're sure you + own the intellectual property, then you'll need to sign an [individual + CLA][]. + + * If you work for a company that wants to allow you to contribute your work, + then you'll need to sign a [corporate CLA][]. + +You generally only need to submit a CLA once, so if you've already submitted +one (even if it was for a different project), you probably don't need to do it +again. + +[individual CLA]: https://developers.google.com/open-source/cla/individual +[corporate CLA]: https://developers.google.com/open-source/cla/corporate + + +## Submitting a patch ## + + 1. It's generally best to start by opening a new issue describing the bug or + feature you're intending to fix. Even if you think it's relatively minor, + it's helpful to know what people are working on. Mention in the initial + issue that you are planning to work on that bug or feature so that it can + be assigned to you. + + 1. Follow the normal process of [forking][] the project, and setup a new + branch to work in. It's important that each group of changes be done in + separate branches in order to ensure that a pull request only includes the + commits related to that bug or feature. + + 1. Go makes it very simple to ensure properly formatted code, so always run + `go fmt` on your code before committing it. You should also run + [golint][] over your code. As noted in the [golint readme][], it's not + strictly necessary that your code be completely "lint-free", but this will + help you find common style issues. + + 1. Any significant changes should almost always be accompanied by tests. The + project already has good test coverage, so look at some of the existing + tests if you're unsure how to go about it. [gocov][] and [gocov-html][] + are invaluable tools for seeing which parts of your code aren't being + exercised by your tests. + + 1. Do your best to have [well-formed commit messages][] for each change. + This provides consistency throughout the project, and ensures that commit + messages are able to be formatted properly by various git tools. + + 1. Finally, push the commits to your fork and submit a [pull request][]. + +[forking]: https://help.github.com/articles/fork-a-repo +[golint]: https://github.com/golang/lint +[golint readme]: https://github.com/golang/lint/blob/master/README +[gocov]: https://github.com/axw/gocov +[gocov-html]: https://github.com/matm/gocov-html +[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html +[squash]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits +[pull request]: https://help.github.com/articles/creating-a-pull-request diff --git a/Godeps/_workspace/src/github.com/google/gofuzz/LICENSE b/Godeps/_workspace/src/github.com/google/gofuzz/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/gofuzz/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Godeps/_workspace/src/github.com/google/gofuzz/README.md b/Godeps/_workspace/src/github.com/google/gofuzz/README.md new file mode 100644 index 0000000000..68fcf2cabb --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/gofuzz/README.md @@ -0,0 +1,71 @@ +gofuzz +====== + +gofuzz is a library for populating go objects with random values. + +[![GoDoc](https://godoc.org/github.com/google/gofuzz?status.png)](https://godoc.org/github.com/google/gofuzz) +[![Travis](https://travis-ci.org/google/gofuzz.svg?branch=master)](https://travis-ci.org/google/gofuzz) + +This is useful for testing: + +* Do your project's objects really serialize/unserialize correctly in all cases? +* Is there an incorrectly formatted object that will cause your project to panic? + +Import with ```import "github.com/google/gofuzz"``` + +You can use it on single variables: +``` +f := fuzz.New() +var myInt int +f.Fuzz(&myInt) // myInt gets a random value. +``` + +You can use it on maps: +``` +f := fuzz.New().NilChance(0).NumElements(1, 1) +var myMap map[ComplexKeyType]string +f.Fuzz(&myMap) // myMap will have exactly one element. +``` + +Customize the chance of getting a nil pointer: +``` +f := fuzz.New().NilChance(.5) +var fancyStruct struct { + A, B, C, D *string +} +f.Fuzz(&fancyStruct) // About half the pointers should be set. +``` + +You can even customize the randomization completely if needed: +``` +type MyEnum string +const ( + A MyEnum = "A" + B MyEnum = "B" +) +type MyInfo struct { + Type MyEnum + AInfo *string + BInfo *string +} + +f := fuzz.New().NilChance(0).Funcs( + func(e *MyInfo, c fuzz.Continue) { + switch c.Intn(2) { + case 0: + e.Type = A + c.Fuzz(&e.AInfo) + case 1: + e.Type = B + c.Fuzz(&e.BInfo) + } + }, +) + +var myObject MyInfo +f.Fuzz(&myObject) // Type will correspond to whether A or B info is set. +``` + +See more examples in ```example_test.go```. + +Happy testing! diff --git a/Godeps/_workspace/src/github.com/google/gofuzz/doc.go b/Godeps/_workspace/src/github.com/google/gofuzz/doc.go new file mode 100644 index 0000000000..9f9956d4a6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/gofuzz/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package fuzz is a library for populating go objects with random values. +package fuzz diff --git a/Godeps/_workspace/src/github.com/google/gofuzz/example_test.go b/Godeps/_workspace/src/github.com/google/gofuzz/example_test.go new file mode 100644 index 0000000000..792707a3a1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/gofuzz/example_test.go @@ -0,0 +1,225 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fuzz_test + +import ( + "encoding/json" + "fmt" + "math/rand" + + "github.com/google/gofuzz" +) + +func ExampleSimple() { + type MyType struct { + A string + B string + C int + D struct { + E float64 + } + } + + f := fuzz.New() + object := MyType{} + + uniqueObjects := map[MyType]int{} + + for i := 0; i < 1000; i++ { + f.Fuzz(&object) + uniqueObjects[object]++ + } + fmt.Printf("Got %v unique objects.\n", len(uniqueObjects)) + // Output: + // Got 1000 unique objects. +} + +func ExampleCustom() { + type MyType struct { + A int + B string + } + + counter := 0 + f := fuzz.New().Funcs( + func(i *int, c fuzz.Continue) { + *i = counter + counter++ + }, + ) + object := MyType{} + + uniqueObjects := map[MyType]int{} + + for i := 0; i < 100; i++ { + f.Fuzz(&object) + if object.A != i { + fmt.Printf("Unexpected value: %#v\n", object) + } + uniqueObjects[object]++ + } + fmt.Printf("Got %v unique objects.\n", len(uniqueObjects)) + // Output: + // Got 100 unique objects. +} + +func ExampleComplex() { + type OtherType struct { + A string + B string + } + type MyType struct { + Pointer *OtherType + Map map[string]OtherType + PointerMap *map[string]OtherType + Slice []OtherType + SlicePointer []*OtherType + PointerSlicePointer *[]*OtherType + } + + f := fuzz.New().RandSource(rand.NewSource(0)).NilChance(0).NumElements(1, 1).Funcs( + func(o *OtherType, c fuzz.Continue) { + o.A = "Foo" + o.B = "Bar" + }, + func(op **OtherType, c fuzz.Continue) { + *op = &OtherType{"A", "B"} + }, + func(m map[string]OtherType, c fuzz.Continue) { + m["Works Because"] = OtherType{ + "Fuzzer", + "Preallocated", + } + }, + ) + object := MyType{} + f.Fuzz(&object) + bytes, err := json.MarshalIndent(&object, "", " ") + if err != nil { + fmt.Printf("error: %v\n", err) + } + fmt.Printf("%s\n", string(bytes)) + // Output: + // { + // "Pointer": { + // "A": "A", + // "B": "B" + // }, + // "Map": { + // "Works Because": { + // "A": "Fuzzer", + // "B": "Preallocated" + // } + // }, + // "PointerMap": { + // "Works Because": { + // "A": "Fuzzer", + // "B": "Preallocated" + // } + // }, + // "Slice": [ + // { + // "A": "Foo", + // "B": "Bar" + // } + // ], + // "SlicePointer": [ + // { + // "A": "A", + // "B": "B" + // } + // ], + // "PointerSlicePointer": [ + // { + // "A": "A", + // "B": "B" + // } + // ] + // } +} + +func ExampleMap() { + f := fuzz.New().NilChance(0).NumElements(1, 1) + var myMap map[struct{ A, B, C int }]string + f.Fuzz(&myMap) + fmt.Printf("myMap has %v element(s).\n", len(myMap)) + // Output: + // myMap has 1 element(s). +} + +func ExampleSingle() { + f := fuzz.New() + var i int + f.Fuzz(&i) + + // Technically, we'd expect this to fail one out of 2 billion attempts... + fmt.Printf("(i == 0) == %v", i == 0) + // Output: + // (i == 0) == false +} + +func ExampleEnum() { + type MyEnum string + const ( + A MyEnum = "A" + B MyEnum = "B" + ) + type MyInfo struct { + Type MyEnum + AInfo *string + BInfo *string + } + + f := fuzz.New().NilChance(0).Funcs( + func(e *MyInfo, c fuzz.Continue) { + // Note c's embedded Rand allows for direct use. + // We could also use c.RandBool() here. + switch c.Intn(2) { + case 0: + e.Type = A + c.Fuzz(&e.AInfo) + case 1: + e.Type = B + c.Fuzz(&e.BInfo) + } + }, + ) + + for i := 0; i < 100; i++ { + var myObject MyInfo + f.Fuzz(&myObject) + switch myObject.Type { + case A: + if myObject.AInfo == nil { + fmt.Println("AInfo should have been set!") + } + if myObject.BInfo != nil { + fmt.Println("BInfo should NOT have been set!") + } + case B: + if myObject.BInfo == nil { + fmt.Println("BInfo should have been set!") + } + if myObject.AInfo != nil { + fmt.Println("AInfo should NOT have been set!") + } + default: + fmt.Println("Invalid enum value!") + } + } + // Output: +} diff --git a/Godeps/_workspace/src/github.com/google/gofuzz/fuzz.go b/Godeps/_workspace/src/github.com/google/gofuzz/fuzz.go new file mode 100644 index 0000000000..42d9a48b3e --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/gofuzz/fuzz.go @@ -0,0 +1,446 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fuzz + +import ( + "fmt" + "math/rand" + "reflect" + "time" +) + +// fuzzFuncMap is a map from a type to a fuzzFunc that handles that type. +type fuzzFuncMap map[reflect.Type]reflect.Value + +// Fuzzer knows how to fill any object with random fields. +type Fuzzer struct { + fuzzFuncs fuzzFuncMap + defaultFuzzFuncs fuzzFuncMap + r *rand.Rand + nilChance float64 + minElements int + maxElements int +} + +// New returns a new Fuzzer. Customize your Fuzzer further by calling Funcs, +// RandSource, NilChance, or NumElements in any order. +func New() *Fuzzer { + f := &Fuzzer{ + defaultFuzzFuncs: fuzzFuncMap{ + reflect.TypeOf(&time.Time{}): reflect.ValueOf(fuzzTime), + }, + + fuzzFuncs: fuzzFuncMap{}, + r: rand.New(rand.NewSource(time.Now().UnixNano())), + nilChance: .2, + minElements: 1, + maxElements: 10, + } + return f +} + +// Funcs adds each entry in fuzzFuncs as a custom fuzzing function. +// +// Each entry in fuzzFuncs must be a function taking two parameters. +// The first parameter must be a pointer or map. It is the variable that +// function will fill with random data. The second parameter must be a +// fuzz.Continue, which will provide a source of randomness and a way +// to automatically continue fuzzing smaller pieces of the first parameter. +// +// These functions are called sensibly, e.g., if you wanted custom string +// fuzzing, the function `func(s *string, c fuzz.Continue)` would get +// called and passed the address of strings. Maps and pointers will always +// be made/new'd for you, ignoring the NilChange option. For slices, it +// doesn't make much sense to pre-create them--Fuzzer doesn't know how +// long you want your slice--so take a pointer to a slice, and make it +// yourself. (If you don't want your map/pointer type pre-made, take a +// pointer to it, and make it yourself.) See the examples for a range of +// custom functions. +func (f *Fuzzer) Funcs(fuzzFuncs ...interface{}) *Fuzzer { + for i := range fuzzFuncs { + v := reflect.ValueOf(fuzzFuncs[i]) + if v.Kind() != reflect.Func { + panic("Need only funcs!") + } + t := v.Type() + if t.NumIn() != 2 || t.NumOut() != 0 { + panic("Need 2 in and 0 out params!") + } + argT := t.In(0) + switch argT.Kind() { + case reflect.Ptr, reflect.Map: + default: + panic("fuzzFunc must take pointer or map type") + } + if t.In(1) != reflect.TypeOf(Continue{}) { + panic("fuzzFunc's second parameter must be type fuzz.Continue") + } + f.fuzzFuncs[argT] = v + } + return f +} + +// RandSource causes f to get values from the given source of randomness. +// Use if you want deterministic fuzzing. +func (f *Fuzzer) RandSource(s rand.Source) *Fuzzer { + f.r = rand.New(s) + return f +} + +// NilChance sets the probability of creating a nil pointer, map, or slice to +// 'p'. 'p' should be between 0 (no nils) and 1 (all nils), inclusive. +func (f *Fuzzer) NilChance(p float64) *Fuzzer { + if p < 0 || p > 1 { + panic("p should be between 0 and 1, inclusive.") + } + f.nilChance = p + return f +} + +// NumElements sets the minimum and maximum number of elements that will be +// added to a non-nil map or slice. +func (f *Fuzzer) NumElements(atLeast, atMost int) *Fuzzer { + if atLeast > atMost { + panic("atLeast must be <= atMost") + } + if atLeast < 0 { + panic("atLeast must be >= 0") + } + f.minElements = atLeast + f.maxElements = atMost + return f +} + +func (f *Fuzzer) genElementCount() int { + if f.minElements == f.maxElements { + return f.minElements + } + return f.minElements + f.r.Intn(f.maxElements-f.minElements) +} + +func (f *Fuzzer) genShouldFill() bool { + return f.r.Float64() > f.nilChance +} + +// Fuzz recursively fills all of obj's fields with something random. First +// this tries to find a custom fuzz function (see Funcs). If there is no +// custom function this tests whether the object implements fuzz.Interface and, +// if so, calls Fuzz on it to fuzz itself. If that fails, this will see if +// there is a default fuzz function provided by this package. If all of that +// fails, this will generate random values for all primitive fields and then +// recurse for all non-primitives. +// +// Not safe for cyclic or tree-like structs! +// +// obj must be a pointer. Only exported (public) fields can be set (thanks, golang :/ ) +// Intended for tests, so will panic on bad input or unimplemented fields. +func (f *Fuzzer) Fuzz(obj interface{}) { + v := reflect.ValueOf(obj) + if v.Kind() != reflect.Ptr { + panic("needed ptr!") + } + v = v.Elem() + f.doFuzz(v, 0) +} + +// FuzzNoCustom is just like Fuzz, except that any custom fuzz function for +// obj's type will not be called and obj will not be tested for fuzz.Interface +// conformance. This applies only to obj and not other instances of obj's +// type. +// Not safe for cyclic or tree-like structs! +// obj must be a pointer. Only exported (public) fields can be set (thanks, golang :/ ) +// Intended for tests, so will panic on bad input or unimplemented fields. +func (f *Fuzzer) FuzzNoCustom(obj interface{}) { + v := reflect.ValueOf(obj) + if v.Kind() != reflect.Ptr { + panic("needed ptr!") + } + v = v.Elem() + f.doFuzz(v, flagNoCustomFuzz) +} + +const ( + // Do not try to find a custom fuzz function. Does not apply recursively. + flagNoCustomFuzz uint64 = 1 << iota +) + +func (f *Fuzzer) doFuzz(v reflect.Value, flags uint64) { + if !v.CanSet() { + return + } + + if flags&flagNoCustomFuzz == 0 { + // Check for both pointer and non-pointer custom functions. + if v.CanAddr() && f.tryCustom(v.Addr()) { + return + } + if f.tryCustom(v) { + return + } + } + + if fn, ok := fillFuncMap[v.Kind()]; ok { + fn(v, f.r) + return + } + switch v.Kind() { + case reflect.Map: + if f.genShouldFill() { + v.Set(reflect.MakeMap(v.Type())) + n := f.genElementCount() + for i := 0; i < n; i++ { + key := reflect.New(v.Type().Key()).Elem() + f.doFuzz(key, 0) + val := reflect.New(v.Type().Elem()).Elem() + f.doFuzz(val, 0) + v.SetMapIndex(key, val) + } + return + } + v.Set(reflect.Zero(v.Type())) + case reflect.Ptr: + if f.genShouldFill() { + v.Set(reflect.New(v.Type().Elem())) + f.doFuzz(v.Elem(), 0) + return + } + v.Set(reflect.Zero(v.Type())) + case reflect.Slice: + if f.genShouldFill() { + n := f.genElementCount() + v.Set(reflect.MakeSlice(v.Type(), n, n)) + for i := 0; i < n; i++ { + f.doFuzz(v.Index(i), 0) + } + return + } + v.Set(reflect.Zero(v.Type())) + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + f.doFuzz(v.Field(i), 0) + } + case reflect.Array: + fallthrough + case reflect.Chan: + fallthrough + case reflect.Func: + fallthrough + case reflect.Interface: + fallthrough + default: + panic(fmt.Sprintf("Can't handle %#v", v.Interface())) + } +} + +// tryCustom searches for custom handlers, and returns true iff it finds a match +// and successfully randomizes v. +func (f *Fuzzer) tryCustom(v reflect.Value) bool { + // First: see if we have a fuzz function for it. + doCustom, ok := f.fuzzFuncs[v.Type()] + if !ok { + // Second: see if it can fuzz itself. + if v.CanInterface() { + intf := v.Interface() + if fuzzable, ok := intf.(Interface); ok { + fuzzable.Fuzz(Continue{f: f, Rand: f.r}) + return true + } + } + // Finally: see if there is a default fuzz function. + doCustom, ok = f.defaultFuzzFuncs[v.Type()] + if !ok { + return false + } + } + + switch v.Kind() { + case reflect.Ptr: + if v.IsNil() { + if !v.CanSet() { + return false + } + v.Set(reflect.New(v.Type().Elem())) + } + case reflect.Map: + if v.IsNil() { + if !v.CanSet() { + return false + } + v.Set(reflect.MakeMap(v.Type())) + } + default: + return false + } + + doCustom.Call([]reflect.Value{v, reflect.ValueOf(Continue{ + f: f, + Rand: f.r, + })}) + return true +} + +// Interface represents an object that knows how to fuzz itself. Any time we +// find a type that implements this interface we will delegate the act of +// fuzzing itself. +type Interface interface { + Fuzz(c Continue) +} + +// Continue can be passed to custom fuzzing functions to allow them to use +// the correct source of randomness and to continue fuzzing their members. +type Continue struct { + f *Fuzzer + + // For convenience, Continue implements rand.Rand via embedding. + // Use this for generating any randomness if you want your fuzzing + // to be repeatable for a given seed. + *rand.Rand +} + +// Fuzz continues fuzzing obj. obj must be a pointer. +func (c Continue) Fuzz(obj interface{}) { + v := reflect.ValueOf(obj) + if v.Kind() != reflect.Ptr { + panic("needed ptr!") + } + v = v.Elem() + c.f.doFuzz(v, 0) +} + +// FuzzNoCustom continues fuzzing obj, except that any custom fuzz function for +// obj's type will not be called and obj will not be tested for fuzz.Interface +// conformance. This applies only to obj and not other instances of obj's +// type. +func (c Continue) FuzzNoCustom(obj interface{}) { + v := reflect.ValueOf(obj) + if v.Kind() != reflect.Ptr { + panic("needed ptr!") + } + v = v.Elem() + c.f.doFuzz(v, flagNoCustomFuzz) +} + +// RandString makes a random string up to 20 characters long. The returned string +// may include a variety of (valid) UTF-8 encodings. +func (c Continue) RandString() string { + return randString(c.Rand) +} + +// RandUint64 makes random 64 bit numbers. +// Weirdly, rand doesn't have a function that gives you 64 random bits. +func (c Continue) RandUint64() uint64 { + return randUint64(c.Rand) +} + +// RandBool returns true or false randomly. +func (c Continue) RandBool() bool { + return randBool(c.Rand) +} + +func fuzzInt(v reflect.Value, r *rand.Rand) { + v.SetInt(int64(randUint64(r))) +} + +func fuzzUint(v reflect.Value, r *rand.Rand) { + v.SetUint(randUint64(r)) +} + +func fuzzTime(t *time.Time, c Continue) { + var sec, nsec int64 + // Allow for about 1000 years of random time values, which keeps things + // like JSON parsing reasonably happy. + sec = c.Rand.Int63n(1000 * 365 * 24 * 60 * 60) + c.Fuzz(&nsec) + *t = time.Unix(sec, nsec) +} + +var fillFuncMap = map[reflect.Kind]func(reflect.Value, *rand.Rand){ + reflect.Bool: func(v reflect.Value, r *rand.Rand) { + v.SetBool(randBool(r)) + }, + reflect.Int: fuzzInt, + reflect.Int8: fuzzInt, + reflect.Int16: fuzzInt, + reflect.Int32: fuzzInt, + reflect.Int64: fuzzInt, + reflect.Uint: fuzzUint, + reflect.Uint8: fuzzUint, + reflect.Uint16: fuzzUint, + reflect.Uint32: fuzzUint, + reflect.Uint64: fuzzUint, + reflect.Uintptr: fuzzUint, + reflect.Float32: func(v reflect.Value, r *rand.Rand) { + v.SetFloat(float64(r.Float32())) + }, + reflect.Float64: func(v reflect.Value, r *rand.Rand) { + v.SetFloat(r.Float64()) + }, + reflect.Complex64: func(v reflect.Value, r *rand.Rand) { + panic("unimplemented") + }, + reflect.Complex128: func(v reflect.Value, r *rand.Rand) { + panic("unimplemented") + }, + reflect.String: func(v reflect.Value, r *rand.Rand) { + v.SetString(randString(r)) + }, + reflect.UnsafePointer: func(v reflect.Value, r *rand.Rand) { + panic("unimplemented") + }, +} + +// randBool returns true or false randomly. +func randBool(r *rand.Rand) bool { + if r.Int()&1 == 1 { + return true + } + return false +} + +type charRange struct { + first, last rune +} + +// choose returns a random unicode character from the given range, using the +// given randomness source. +func (r *charRange) choose(rand *rand.Rand) rune { + count := int64(r.last - r.first) + return r.first + rune(rand.Int63n(count)) +} + +var unicodeRanges = []charRange{ + {' ', '~'}, // ASCII characters + {'\u00a0', '\u02af'}, // Multi-byte encoded characters + {'\u4e00', '\u9fff'}, // Common CJK (even longer encodings) +} + +// randString makes a random string up to 20 characters long. The returned string +// may include a variety of (valid) UTF-8 encodings. +func randString(r *rand.Rand) string { + n := r.Intn(20) + runes := make([]rune, n) + for i := range runes { + runes[i] = unicodeRanges[r.Intn(len(unicodeRanges))].choose(r) + } + return string(runes) +} + +// randUint64 makes random 64 bit numbers. +// Weirdly, rand doesn't have a function that gives you 64 random bits. +func randUint64(r *rand.Rand) uint64 { + return uint64(r.Uint32())<<32 | uint64(r.Uint32()) +} diff --git a/Godeps/_workspace/src/github.com/google/gofuzz/fuzz_test.go b/Godeps/_workspace/src/github.com/google/gofuzz/fuzz_test.go new file mode 100644 index 0000000000..12abc8f659 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/gofuzz/fuzz_test.go @@ -0,0 +1,384 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fuzz + +import ( + "reflect" + "testing" + "time" +) + +func TestFuzz_basic(t *testing.T) { + obj := &struct { + I int + I8 int8 + I16 int16 + I32 int32 + I64 int64 + U uint + U8 uint8 + U16 uint16 + U32 uint32 + U64 uint64 + Uptr uintptr + S string + B bool + T time.Time + }{} + + failed := map[string]int{} + for i := 0; i < 10; i++ { + New().Fuzz(obj) + + if n, v := "i", obj.I; v == 0 { + failed[n] = failed[n] + 1 + } + if n, v := "i8", obj.I8; v == 0 { + failed[n] = failed[n] + 1 + } + if n, v := "i16", obj.I16; v == 0 { + failed[n] = failed[n] + 1 + } + if n, v := "i32", obj.I32; v == 0 { + failed[n] = failed[n] + 1 + } + if n, v := "i64", obj.I64; v == 0 { + failed[n] = failed[n] + 1 + } + if n, v := "u", obj.U; v == 0 { + failed[n] = failed[n] + 1 + } + if n, v := "u8", obj.U8; v == 0 { + failed[n] = failed[n] + 1 + } + if n, v := "u16", obj.U16; v == 0 { + failed[n] = failed[n] + 1 + } + if n, v := "u32", obj.U32; v == 0 { + failed[n] = failed[n] + 1 + } + if n, v := "u64", obj.U64; v == 0 { + failed[n] = failed[n] + 1 + } + if n, v := "uptr", obj.Uptr; v == 0 { + failed[n] = failed[n] + 1 + } + if n, v := "s", obj.S; v == "" { + failed[n] = failed[n] + 1 + } + if n, v := "b", obj.B; v == false { + failed[n] = failed[n] + 1 + } + if n, v := "t", obj.T; v.IsZero() { + failed[n] = failed[n] + 1 + } + } + checkFailed(t, failed) +} + +func checkFailed(t *testing.T, failed map[string]int) { + for k, v := range failed { + if v > 8 { + t.Errorf("%v seems to not be getting set, was zero value %v times", k, v) + } + } +} + +func TestFuzz_structptr(t *testing.T) { + obj := &struct { + A *struct { + S string + } + }{} + + f := New().NilChance(.5) + failed := map[string]int{} + for i := 0; i < 10; i++ { + f.Fuzz(obj) + + if n, v := "a not nil", obj.A; v == nil { + failed[n] = failed[n] + 1 + } + if n, v := "a nil", obj.A; v != nil { + failed[n] = failed[n] + 1 + } + if n, v := "as", obj.A; v == nil || v.S == "" { + failed[n] = failed[n] + 1 + } + } + checkFailed(t, failed) +} + +// tryFuzz tries fuzzing up to 20 times. Fail if check() never passes, report the highest +// stage it ever got to. +func tryFuzz(t *testing.T, f *Fuzzer, obj interface{}, check func() (stage int, passed bool)) { + maxStage := 0 + for i := 0; i < 20; i++ { + f.Fuzz(obj) + stage, passed := check() + if stage > maxStage { + maxStage = stage + } + if passed { + return + } + } + t.Errorf("Only ever got to stage %v", maxStage) +} + +func TestFuzz_structmap(t *testing.T) { + obj := &struct { + A map[struct { + S string + }]struct { + S2 string + } + B map[string]string + }{} + + tryFuzz(t, New(), obj, func() (int, bool) { + if obj.A == nil { + return 1, false + } + if len(obj.A) == 0 { + return 2, false + } + for k, v := range obj.A { + if k.S == "" { + return 3, false + } + if v.S2 == "" { + return 4, false + } + } + + if obj.B == nil { + return 5, false + } + if len(obj.B) == 0 { + return 6, false + } + for k, v := range obj.B { + if k == "" { + return 7, false + } + if v == "" { + return 8, false + } + } + return 9, true + }) +} + +func TestFuzz_structslice(t *testing.T) { + obj := &struct { + A []struct { + S string + } + B []string + }{} + + tryFuzz(t, New(), obj, func() (int, bool) { + if obj.A == nil { + return 1, false + } + if len(obj.A) == 0 { + return 2, false + } + for _, v := range obj.A { + if v.S == "" { + return 3, false + } + } + + if obj.B == nil { + return 4, false + } + if len(obj.B) == 0 { + return 5, false + } + for _, v := range obj.B { + if v == "" { + return 6, false + } + } + return 7, true + }) +} + +func TestFuzz_custom(t *testing.T) { + obj := &struct { + A string + B *string + C map[string]string + D *map[string]string + }{} + + testPhrase := "gotcalled" + testMap := map[string]string{"C": "D"} + f := New().Funcs( + func(s *string, c Continue) { + *s = testPhrase + }, + func(m map[string]string, c Continue) { + m["C"] = "D" + }, + ) + + tryFuzz(t, f, obj, func() (int, bool) { + if obj.A != testPhrase { + return 1, false + } + if obj.B == nil { + return 2, false + } + if *obj.B != testPhrase { + return 3, false + } + if e, a := testMap, obj.C; !reflect.DeepEqual(e, a) { + return 4, false + } + if obj.D == nil { + return 5, false + } + if e, a := testMap, *obj.D; !reflect.DeepEqual(e, a) { + return 6, false + } + return 7, true + }) +} + +type SelfFuzzer string + +// Implement fuzz.Interface. +func (sf *SelfFuzzer) Fuzz(c Continue) { + *sf = selfFuzzerTestPhrase +} + +const selfFuzzerTestPhrase = "was fuzzed" + +func TestFuzz_interface(t *testing.T) { + f := New() + + var obj1 SelfFuzzer + tryFuzz(t, f, &obj1, func() (int, bool) { + if obj1 != selfFuzzerTestPhrase { + return 1, false + } + return 1, true + }) + + var obj2 map[int]SelfFuzzer + tryFuzz(t, f, &obj2, func() (int, bool) { + for _, v := range obj2 { + if v != selfFuzzerTestPhrase { + return 1, false + } + } + return 1, true + }) +} + +func TestFuzz_interfaceAndFunc(t *testing.T) { + const privateTestPhrase = "private phrase" + f := New().Funcs( + // This should take precedence over SelfFuzzer.Fuzz(). + func(s *SelfFuzzer, c Continue) { + *s = privateTestPhrase + }, + ) + + var obj1 SelfFuzzer + tryFuzz(t, f, &obj1, func() (int, bool) { + if obj1 != privateTestPhrase { + return 1, false + } + return 1, true + }) + + var obj2 map[int]SelfFuzzer + tryFuzz(t, f, &obj2, func() (int, bool) { + for _, v := range obj2 { + if v != privateTestPhrase { + return 1, false + } + } + return 1, true + }) +} + +func TestFuzz_noCustom(t *testing.T) { + type Inner struct { + Str string + } + type Outer struct { + Str string + In Inner + } + + testPhrase := "gotcalled" + f := New().Funcs( + func(outer *Outer, c Continue) { + outer.Str = testPhrase + c.Fuzz(&outer.In) + }, + func(inner *Inner, c Continue) { + inner.Str = testPhrase + }, + ) + c := Continue{f: f, Rand: f.r} + + // Fuzzer.Fuzz() + obj1 := Outer{} + f.Fuzz(&obj1) + if obj1.Str != testPhrase { + t.Errorf("expected Outer custom function to have been called") + } + if obj1.In.Str != testPhrase { + t.Errorf("expected Inner custom function to have been called") + } + + // Continue.Fuzz() + obj2 := Outer{} + c.Fuzz(&obj2) + if obj2.Str != testPhrase { + t.Errorf("expected Outer custom function to have been called") + } + if obj2.In.Str != testPhrase { + t.Errorf("expected Inner custom function to have been called") + } + + // Fuzzer.FuzzNoCustom() + obj3 := Outer{} + f.FuzzNoCustom(&obj3) + if obj3.Str == testPhrase { + t.Errorf("expected Outer custom function to not have been called") + } + if obj3.In.Str != testPhrase { + t.Errorf("expected Inner custom function to have been called") + } + + // Continue.FuzzNoCustom() + obj4 := Outer{} + c.FuzzNoCustom(&obj4) + if obj4.Str == testPhrase { + t.Errorf("expected Outer custom function to not have been called") + } + if obj4.In.Str != testPhrase { + t.Errorf("expected Inner custom function to have been called") + } +} diff --git a/server/handlers/validation.go b/server/handlers/validation.go index 45ad001a9e..c3c7344e02 100644 --- a/server/handlers/validation.go +++ b/server/handlers/validation.go @@ -25,15 +25,15 @@ func (err ErrValidation) Error() string { return fmt.Sprintf("An error occurred during validation: %s", err.msg) } -// ErrBadHeirarchy represents a missing snapshot at this current time. +// ErrBadHierarchy represents a missing snapshot at this current time. // When delegations are implemented it will also represent a missing // delegation parent -type ErrBadHeirarchy struct { +type ErrBadHierarchy struct { msg string } -func (err ErrBadHeirarchy) Error() string { - return fmt.Sprintf("Heirarchy of updates in incorrect: %s", err.msg) +func (err ErrBadHierarchy) Error() string { + return fmt.Sprintf("Hierarchy of updates in incorrect: %s", err.msg) } // ErrBadRoot represents a failure validating the root @@ -77,14 +77,15 @@ func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.Meta for _, v := range updates { roles[v.Role] = v } - if err := heirarchyOK(roles); err != nil { - logrus.Error("ErrBadHeirarchy: ", err.Error()) - return ErrBadHeirarchy{msg: err.Error()} + if err := hierarchyOK(roles); err != nil { + logrus.Error("ErrBadHierarchy: ", err.Error()) + return ErrBadHierarchy{msg: err.Error()} } + logrus.Debug("Successfully validated hierarchy") var root *data.SignedRoot oldRootJSON, err := store.GetCurrent(gun, rootRole) - if _, ok := err.(*storage.ErrNotFound); !ok { + if _, ok := err.(*storage.ErrNotFound); err != nil && !ok { // problem with storage. No expectation we can // write if we can't read so bail. logrus.Error("error reading previous root: ", err.Error()) @@ -102,10 +103,19 @@ func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.Meta logrus.Error("ErrValidation: ", err.Error()) return ErrValidation{msg: err.Error()} } + logrus.Debug("Successfully validated root") } else { if oldRootJSON == nil { return ErrValidation{msg: "no pre-existing root and no root provided in update."} } + parsedOldRoot := &data.SignedRoot{} + if err := json.Unmarshal(oldRootJSON, parsedOldRoot); err != nil { + return ErrValidation{msg: "pre-existing root is corrupted and no root provided in update."} + } + if err = repo.SetRoot(parsedOldRoot); err != nil { + logrus.Error("ErrValidation: ", err.Error()) + return ErrValidation{msg: err.Error()} + } } // TODO: validate delegated targets roles. @@ -117,10 +127,11 @@ func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.Meta } repo.SetTargets(targetsRole, t) } + logrus.Debug("Successfully validated targets") var oldSnap *data.SignedSnapshot oldSnapJSON, err := store.GetCurrent(gun, snapshotRole) - if _, ok := err.(*storage.ErrNotFound); !ok { + if _, ok := err.(*storage.ErrNotFound); err != nil && !ok { // problem with storage. No expectation we can // write if we can't read so bail. logrus.Error("error reading previous snapshot: ", err.Error()) @@ -136,6 +147,7 @@ func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.Meta logrus.Error("ErrBadSnapshot: ", err.Error()) return ErrBadSnapshot{msg: err.Error()} } + logrus.Debug("Successfully validated snapshot") return nil } @@ -151,21 +163,27 @@ func validateSnapshot(role string, oldSnap *data.SignedSnapshot, snapUpdate stor return err } - if oldSnap != nil { - snap, err := data.SnapshotFromSigned(s) - if err != nil { - return errors.New("could not parse snapshot") - } - err = checkSnapshotEntries(role, oldSnap, snap, roles) - if err != nil { - return err - } + snap, err := data.SnapshotFromSigned(s) + if err != nil { + return errors.New("could not parse snapshot") + } + if !data.ValidTUFType(snap.Signed.Type, data.CanonicalSnapshotRole) { + return errors.New("snapshot has wrong type") + } + err = checkSnapshotEntries(role, oldSnap, snap, roles) + if err != nil { + return err } return nil } func checkSnapshotEntries(role string, oldSnap, snap *data.SignedSnapshot, roles map[string]storage.MetaUpdate) error { + snapshotRole := data.RoleName(data.CanonicalSnapshotRole) + timestampRole := data.RoleName(data.CanonicalTimestampRole) // just in case for r, update := range roles { + if r == snapshotRole || r == timestampRole { + continue + } m, ok := snap.Signed.Meta[r] if !ok { return fmt.Errorf("snapshot missing metadata for %s", r) @@ -204,10 +222,17 @@ func validateTargets(role string, roles map[string]storage.MetaUpdate, kdb *keys if err := signed.Verify(s, role, 0, kdb); err != nil { return nil, err } - return data.TargetsFromSigned(s) + t, err := data.TargetsFromSigned(s) + if err != nil { + return nil, err + } + if !data.ValidTUFType(t.Signed.Type, data.CanonicalTargetsRole) { + return nil, fmt.Errorf("%s has wrong type", role) + } + return t, nil } -// check the snapshot is present. If it is, the heirarchy +// check the snapshot is present. If it is, the hierarchy // of the update is OK. This seems like a simplistic check // but is completely sufficient for all possible use cases: // 1. the user is updating only the snapshot. @@ -219,7 +244,7 @@ func validateTargets(role string, roles map[string]storage.MetaUpdate, kdb *keys // timestamp, the server will replace it on next // GET timestamp.jsonshould it detect the current // snapshot has a different hash to the one in the timestamp. -func heirarchyOK(roles map[string]storage.MetaUpdate) error { +func hierarchyOK(roles map[string]storage.MetaUpdate) error { snapshotRole := data.RoleName(data.CanonicalSnapshotRole) if _, ok := roles[snapshotRole]; !ok { return errors.New("snapshot missing from update") @@ -253,6 +278,9 @@ func validateRoot(gun string, oldRoot, newRoot []byte) (*data.SignedRoot, error) // correct old key signatures. return nil, err } + if !data.ValidTUFType(parsedNewRoot.Signed.Type, data.CanonicalRootRole) { + return nil, fmt.Errorf("root has wrong type") + } return parsedNewRoot, nil } @@ -261,6 +289,10 @@ func validateRoot(gun string, oldRoot, newRoot []byte) (*data.SignedRoot, error) // are valid. func checkRoot(oldRoot, newRoot *data.SignedRoot) error { rootRole := data.RoleName(data.CanonicalRootRole) + targetsRole := data.RoleName(data.CanonicalTargetsRole) + snapshotRole := data.RoleName(data.CanonicalSnapshotRole) + timestampRole := data.RoleName(data.CanonicalTimestampRole) + var oldRootRole *data.RootRole newRootRole, ok := newRoot.Signed.Roles[rootRole] if !ok { @@ -311,14 +343,35 @@ func checkRoot(oldRoot, newRoot *data.SignedRoot) error { } } newSigned, err := newRoot.ToSigned() + if err != nil { + return err + } if rotation { - if err != nil { - return err - } err = signed.VerifyRoot(newSigned, oldThreshold, oldKeys) if err != nil { return fmt.Errorf("rotation detected and new root was not signed with at least %d old keys", oldThreshold) } } - return signed.VerifyRoot(newSigned, newRootRole.Threshold, newKeys) + err = signed.VerifyRoot(newSigned, newRootRole.Threshold, newKeys) + if err != nil { + return err + } + root, err := data.RootFromSigned(newSigned) + if err != nil { + return err + } + // at a minimum, check the 4 required roles are present + for _, r := range []string{rootRole, targetsRole, snapshotRole, timestampRole} { + role, ok := root.Signed.Roles[r] + if !ok { + return fmt.Errorf("missing required %s role from root", r) + } + if role.Threshold < 1 { + return fmt.Errorf("%s role has invalid threshold", r) + } + if len(role.KeyIDs) < role.Threshold { + return fmt.Errorf("%s role has insufficient number of keys", r) + } + } + return nil } diff --git a/server/handlers/validation_test.go b/server/handlers/validation_test.go new file mode 100644 index 0000000000..677cd40dc7 --- /dev/null +++ b/server/handlers/validation_test.go @@ -0,0 +1,893 @@ +package handlers + +import ( + "testing" + + "github.com/endophage/gotuf/data" + "github.com/endophage/gotuf/signed" + "github.com/endophage/gotuf/testutils" + "github.com/stretchr/testify/assert" + + "github.com/docker/notary/server/storage" +) + +func TestValidateEmptyNew(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.NoError(t, err) +} + +func TestValidateNoNewRoot(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + store.UpdateCurrent( + "testGUN", + storage.MetaUpdate{ + Role: "root", + Version: 1, + Data: root, + }, + ) + + updates := []storage.MetaUpdate{ + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.NoError(t, err) +} + +func TestValidateNoNewTargets(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + store.UpdateCurrent( + "testGUN", + storage.MetaUpdate{ + Role: "targets", + Version: 1, + Data: targets, + }, + ) + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.NoError(t, err) +} + +func TestValidateOnlySnapshot(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + root, targets, snapshot, _, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + store.UpdateCurrent( + "testGUN", + storage.MetaUpdate{ + Role: "root", + Version: 1, + Data: root, + }, + ) + store.UpdateCurrent( + "testGUN", + storage.MetaUpdate{ + Role: "targets", + Version: 1, + Data: targets, + }, + ) + + updates := []storage.MetaUpdate{ + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.NoError(t, err) +} + +func TestValidateOldRoot(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + store.UpdateCurrent( + "testGUN", + storage.MetaUpdate{ + Role: "root", + Version: 1, + Data: root, + }, + ) + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.NoError(t, err) +} + +func TestValidateRootRotation(t *testing.T) { + _, repo, crypto := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + store.UpdateCurrent( + "testGUN", + storage.MetaUpdate{ + Role: "root", + Version: 1, + Data: root, + }, + ) + + oldRootRole := repo.Root.Signed.Roles["root"] + oldRootKey := repo.Root.Signed.Keys[oldRootRole.KeyIDs[0]] + + rootKey, err := crypto.Create("root", data.ED25519Key) + assert.NoError(t, err) + rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil) + assert.NoError(t, err) + + delete(repo.Root.Signed.Keys, oldRootRole.KeyIDs[0]) + + repo.Root.Signed.Roles["root"] = &rootRole.RootRole + repo.Root.Signed.Keys[rootKey.ID()] = data.NewPrivateKey(rootKey.Algorithm(), rootKey.Public(), nil) + + r, err = repo.SignRoot(data.DefaultExpires(data.CanonicalRootRole), nil) + assert.NoError(t, err) + err = signed.Sign(crypto, r, rootKey, oldRootKey) + assert.NoError(t, err) + + rt, err := data.RootFromSigned(r) + assert.NoError(t, err) + repo.SetRoot(rt) + + sn, err = repo.SignSnapshot(data.DefaultExpires(data.CanonicalSnapshotRole), nil) + assert.NoError(t, err) + root, targets, snapshot, timestamp, err = testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.NoError(t, err) +} + +func TestValidateNoRoot(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + _, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + updates := []storage.MetaUpdate{ + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.Error(t, err) + assert.IsType(t, ErrValidation{}, err) +} + +func TestValidateSnapshotMissing(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + root, targets, _, _, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.Error(t, err) + assert.IsType(t, ErrBadHierarchy{}, err) +} + +// ### Role missing negative tests ### +// These tests remove a role from the Root file and +// check for a ErrBadRoot +func TestValidateRootRoleMissing(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + delete(repo.Root.Signed.Roles, "root") + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.Error(t, err) + assert.IsType(t, ErrBadRoot{}, err) +} + +func TestValidateTargetsRoleMissing(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + delete(repo.Root.Signed.Roles, "targets") + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.Error(t, err) + assert.IsType(t, ErrBadRoot{}, err) +} + +func TestValidateSnapshotRoleMissing(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + delete(repo.Root.Signed.Roles, "snapshot") + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.Error(t, err) + assert.IsType(t, ErrBadRoot{}, err) +} + +// ### End role missing negative tests ### + +// ### Signature missing negative tests ### +func TestValidateRootSigMissing(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + delete(repo.Root.Signed.Roles, "snapshot") + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + + r.Signatures = nil + + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.Error(t, err) + assert.IsType(t, ErrBadRoot{}, err) +} + +func TestValidateTargetsSigMissing(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + + tg.Signatures = nil + + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.Error(t, err) + assert.IsType(t, ErrBadTargets{}, err) +} + +func TestValidateSnapshotSigMissing(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + + sn.Signatures = nil + + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.Error(t, err) + assert.IsType(t, ErrBadSnapshot{}, err) +} + +// ### End signature missing negative tests ### + +// ### Corrupted metadata negative tests ### +func TestValidateRootCorrupt(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + // flip all the bits in the first byte + root[0] = root[0] ^ 0xff + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.Error(t, err) + assert.IsType(t, ErrBadRoot{}, err) +} + +func TestValidateTargetsCorrupt(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + // flip all the bits in the first byte + targets[0] = targets[0] ^ 0xff + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.Error(t, err) + assert.IsType(t, ErrBadTargets{}, err) +} + +func TestValidateSnapshotCorrupt(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + // flip all the bits in the first byte + snapshot[0] = snapshot[0] ^ 0xff + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.Error(t, err) + assert.IsType(t, ErrBadSnapshot{}, err) +} + +// ### End corrupted metadata negative tests ### + +// ### Snapshot size mismatch negative tests ### +func TestValidateRootModifiedSize(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + + // add another copy of the signature so the hash is different + r.Signatures = append(r.Signatures, r.Signatures[0]) + + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + // flip all the bits in the first byte + root[0] = root[0] ^ 0xff + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.Error(t, err) + assert.IsType(t, ErrBadRoot{}, err) +} + +func TestValidateTargetsModifiedSize(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + + // add another copy of the signature so the hash is different + tg.Signatures = append(tg.Signatures, tg.Signatures[0]) + + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.Error(t, err) + assert.IsType(t, ErrBadSnapshot{}, err) +} + +// ### End snapshot size mismatch negative tests ### + +// ### Snapshot hash mismatch negative tests ### +func TestValidateRootModifiedHash(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + + snap, err := data.SnapshotFromSigned(sn) + assert.NoError(t, err) + snap.Signed.Meta["root"].Hashes["sha256"][0] = snap.Signed.Meta["root"].Hashes["sha256"][0] ^ 0xff + + sn, err = snap.ToSigned() + assert.NoError(t, err) + + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.Error(t, err) + assert.IsType(t, ErrBadSnapshot{}, err) +} + +func TestValidateTargetsModifiedHash(t *testing.T) { + _, repo, _ := testutils.EmptyRepo() + store := storage.NewMemStorage() + + r, tg, sn, ts, err := testutils.Sign(repo) + assert.NoError(t, err) + + snap, err := data.SnapshotFromSigned(sn) + assert.NoError(t, err) + snap.Signed.Meta["targets"].Hashes["sha256"][0] = snap.Signed.Meta["targets"].Hashes["sha256"][0] ^ 0xff + + sn, err = snap.ToSigned() + assert.NoError(t, err) + + root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) + assert.NoError(t, err) + + updates := []storage.MetaUpdate{ + { + Role: "root", + Version: 1, + Data: root, + }, + { + Role: "targets", + Version: 1, + Data: targets, + }, + { + Role: "snapshot", + Version: 1, + Data: snapshot, + }, + { + Role: "timestamp", + Version: 1, + Data: timestamp, + }, + } + + err = validateUpdate("testGUN", updates, store) + assert.Error(t, err) + assert.IsType(t, ErrBadSnapshot{}, err) +} + +// ### End snapshot hash mismatch negative tests ###