From 8a996f417abdd910538dfa9a76a1b5225ab330ce Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Tue, 20 Oct 2015 23:56:35 -0700 Subject: [PATCH] updating godeps and notary for some syntax changes in gotuf brought on by golint Signed-off-by: David Lawrence (github: endophage) --- Godeps/Godeps.json | 2 +- .../endophage/gotuf/Godeps/Godeps.json | 17 +- .../endophage/gotuf/client/client.go | 41 +-- .../endophage/gotuf/client/errors.go | 56 ++-- .../github.com/endophage/gotuf/data/keys.go | 17 +- .../github.com/endophage/gotuf/data/roles.go | 25 +- .../github.com/endophage/gotuf/data/root.go | 5 + .../endophage/gotuf/data/snapshot.go | 7 + .../endophage/gotuf/data/targets.go | 12 +- .../endophage/gotuf/data/timestamp.go | 7 + .../github.com/endophage/gotuf/data/types.go | 32 ++- .../endophage/gotuf/errors/errors.go | 13 + .../src/github.com/endophage/gotuf/keys/db.go | 10 + .../endophage/gotuf/signed/ed25519.go | 20 +- .../endophage/gotuf/signed/errors.go | 8 + .../github.com/endophage/gotuf/signed/sign.go | 69 +++-- .../endophage/gotuf/signed/verifiers.go | 7 +- .../endophage/gotuf/signed/verify.go | 10 +- .../endophage/gotuf/store/dbstore.go | 252 ------------------ .../endophage/gotuf/store/errors.go | 2 + .../endophage/gotuf/store/filestore.go | 17 +- .../endophage/gotuf/store/httpstore.go | 23 +- .../endophage/gotuf/store/interfaces.go | 10 +- .../endophage/gotuf/store/memorystore.go | 15 +- .../endophage/gotuf/testutils/repo.go | 12 +- .../endophage/gotuf/testutils/utils.go | 6 +- .../src/github.com/endophage/gotuf/tuf.go | 101 ++++--- .../github.com/endophage/gotuf/utils/util.go | 14 + .../github.com/endophage/gotuf/utils/utils.go | 16 +- client/client.go | 8 +- client/helpers.go | 8 +- client/helpers_test.go | 6 +- server/handlers/validation.go | 2 +- 33 files changed, 426 insertions(+), 424 deletions(-) delete mode 100644 Godeps/_workspace/src/github.com/endophage/gotuf/store/dbstore.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 33090266f6..035c615364 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -96,7 +96,7 @@ }, { "ImportPath": "github.com/endophage/gotuf", - "Rev": "876c31a61bc4aa0dae09bb8ef3946dc26dd04924" + "Rev": "0e290bf7c881a94ac51c11e8753f521012f890bf" }, { "ImportPath": "github.com/go-sql-driver/mysql", diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/Godeps/Godeps.json b/Godeps/_workspace/src/github.com/endophage/gotuf/Godeps/Godeps.json index e887dea581..71f5992dc3 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/Godeps/Godeps.json +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "github.com/endophage/gotuf", - "GoVersion": "go1.4.2", + "GoVersion": "go1.5.1", "Packages": [ "./..." ], @@ -14,6 +14,21 @@ "ImportPath": "github.com/agl/ed25519", "Rev": "d2b94fd789ea21d12fac1a4443dd3a3f79cda72c" }, + { + "ImportPath": "github.com/docker/docker/pkg/term", + "Comment": "v1.7.1", + "Rev": "786b29d4db80a6175e72b47a794ee044918ba734" + }, + { + "ImportPath": "github.com/docker/notary/pkg/passphrase", + "Comment": "docker-v1.8.0-rc1-229-ge646033", + "Rev": "e6460330bdbef7d9a7a9a3dc51df2be83861c1b0" + }, + { + "ImportPath": "github.com/docker/notary/trustmanager", + "Comment": "docker-v1.8.0-rc1-229-ge646033", + "Rev": "e6460330bdbef7d9a7a9a3dc51df2be83861c1b0" + }, { "ImportPath": "github.com/google/gofuzz", "Rev": "bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5" diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/client/client.go b/Godeps/_workspace/src/github.com/endophage/gotuf/client/client.go index 532e474789..5ed1b6530d 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/client/client.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/client/client.go @@ -22,14 +22,16 @@ import ( const maxSize int64 = 5 << 20 +// Client is a usability wrapper around a raw TUF repo type Client struct { - local *tuf.TufRepo + local *tuf.Repo remote store.RemoteStore keysDB *keys.KeyDB cache store.MetadataStore } -func NewClient(local *tuf.TufRepo, remote store.RemoteStore, keysDB *keys.KeyDB, cache store.MetadataStore) *Client { +// NewClient initialized a Client with the given repo, remote source of content, key database, and cache +func NewClient(local *tuf.Repo, remote store.RemoteStore, keysDB *keys.KeyDB, cache store.MetadataStore) *Client { return &Client{ local: local, remote: remote, @@ -38,6 +40,7 @@ func NewClient(local *tuf.TufRepo, remote store.RemoteStore, keysDB *keys.KeyDB, } } +// Update performs an update to the TUF repo as defined by the TUF spec func (c *Client) Update() error { // 1. Get timestamp // a. If timestamp error (verification, expired, etc...) download new root and return to 1. @@ -52,7 +55,7 @@ func (c *Client) Update() error { if err != nil { logrus.Debug("Error occurred. Root will be downloaded and another update attempted") if err := c.downloadRoot(); err != nil { - logrus.Errorf("client Update (Root):", err) + logrus.Error("client Update (Root):", err) return err } // If we error again, we now have the latest root and just want to fail @@ -129,7 +132,7 @@ func (c Client) checkRoot() error { func (c *Client) downloadRoot() error { role := data.RoleName("root") size := maxSize - var expectedSha256 []byte = nil + var expectedSha256 []byte if c.local.Snapshot != nil { size = c.local.Snapshot.Signed.Meta[role].Length expectedSha256 = c.local.Snapshot.Signed.Meta[role].Hashes["sha256"] @@ -140,7 +143,7 @@ func (c *Client) downloadRoot() error { // interpreted as 0. var download bool var err error - var cachedRoot []byte = nil + var cachedRoot []byte old := &data.Signed{} version := 0 @@ -261,8 +264,7 @@ func (c *Client) downloadTimestamp() error { } // unlike root, targets and snapshot, always try and download timestamps // from remote, only using the cache one if we couldn't reach remote. - raw, err := c.remote.GetMeta(role, maxSize) - var s *data.Signed + raw, s, err := c.downloadSigned(role, maxSize, nil) if err != nil || len(raw) == 0 { if err, ok := err.(store.ErrMetaNotFound); ok { return err @@ -279,11 +281,6 @@ func (c *Client) downloadTimestamp() error { s = old } else { download = true - s = &data.Signed{} - err = json.Unmarshal(raw, s) - if err != nil { - return err - } } err = signed.Verify(s, role, version, c.keysDB) if err != nil { @@ -305,10 +302,13 @@ func (c *Client) downloadTimestamp() error { func (c *Client) downloadSnapshot() error { logrus.Debug("downloadSnapshot") role := data.RoleName("snapshot") + if c.local.Timestamp == nil { + return ErrMissingMeta{role: "snapshot"} + } size := c.local.Timestamp.Signed.Meta[role].Length expectedSha256, ok := c.local.Timestamp.Signed.Meta[role].Hashes["sha256"] if !ok { - return fmt.Errorf("Sha256 is currently the only hash supported by this client. No Sha256 found for snapshot") + return ErrMissingMeta{role: "snapshot"} } var download bool @@ -373,6 +373,9 @@ func (c *Client) downloadSnapshot() error { // including delegates roles. func (c *Client) downloadTargets(role string) error { role = data.RoleName(role) // this will really only do something for base targets role + if c.local.Snapshot == nil { + return ErrMissingMeta{role: role} + } snap := c.local.Snapshot.Signed root := c.local.Root.Signed r := c.keysDB.GetRole(role) @@ -380,7 +383,7 @@ func (c *Client) downloadTargets(role string) error { return fmt.Errorf("Invalid role: %s", role) } keyIDs := r.KeyIDs - s, err := c.GetTargetsFile(role, keyIDs, snap.Meta, root.ConsistentSnapshot, r.Threshold) + s, err := c.getTargetsFile(role, keyIDs, snap.Meta, root.ConsistentSnapshot, r.Threshold) if err != nil { logrus.Error("Error getting targets file:", err) return err @@ -398,13 +401,12 @@ func (c *Client) downloadTargets(role string) error { } func (c *Client) downloadSigned(role string, size int64, expectedSha256 []byte) ([]byte, *data.Signed, error) { - logrus.Debugf("downloading new %s", role) raw, err := c.remote.GetMeta(role, size) if err != nil { return nil, nil, err } genHash := sha256.Sum256(raw) - if !bytes.Equal(genHash[:], expectedSha256) { + if expectedSha256 != nil && !bytes.Equal(genHash[:], expectedSha256) { return nil, nil, ErrChecksumMismatch{role: role} } s := &data.Signed{} @@ -415,15 +417,15 @@ func (c *Client) downloadSigned(role string, size int64, expectedSha256 []byte) return raw, s, nil } -func (c Client) GetTargetsFile(role string, keyIDs []string, snapshotMeta data.Files, consistent bool, threshold int) (*data.Signed, error) { +func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.Files, consistent bool, threshold int) (*data.Signed, error) { // require role exists in snapshots roleMeta, ok := snapshotMeta[role] if !ok { - return nil, fmt.Errorf("Snapshot does not contain target role") + return nil, ErrMissingMeta{role: role} } expectedSha256, ok := snapshotMeta[role].Hashes["sha256"] if !ok { - return nil, fmt.Errorf("Sha256 is currently the only hash supported by this client. No Sha256 found for targets role %s", role) + return nil, ErrMissingMeta{role: role} } // try to get meta file from content addressed cache @@ -539,6 +541,7 @@ func (c Client) TargetMeta(path string) (*data.FileMeta, error) { return meta, nil } +// DownloadTarget downloads the target to dst from the remote func (c Client) DownloadTarget(dst io.Writer, path string, meta *data.FileMeta) error { reader, err := c.remote.GetTarget(path) if err != nil { diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/client/errors.go b/Godeps/_workspace/src/github.com/endophage/gotuf/client/errors.go index 311e74a8de..0fb1a8d00c 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/client/errors.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/client/errors.go @@ -5,11 +5,13 @@ import ( "fmt" ) +// Simple client errors var ( ErrNoRootKeys = errors.New("tuf: no root keys found in local meta store") ErrInsufficientKeys = errors.New("tuf: insufficient keys to meet threshold") ) +// ErrChecksumMismatch - a checksum failed verification type ErrChecksumMismatch struct { role string } @@ -18,6 +20,16 @@ func (e ErrChecksumMismatch) Error() string { return fmt.Sprintf("tuf: checksum for %s did not match", e.role) } +// ErrMissingMeta - couldn't find the FileMeta object for a role or target +type ErrMissingMeta struct { + role string +} + +func (e ErrMissingMeta) Error() string { + return fmt.Sprintf("tuf: sha256 checksum required for %s", e.role) +} + +// ErrMissingRemoteMetadata - remote didn't have requested metadata type ErrMissingRemoteMetadata struct { Name string } @@ -26,6 +38,7 @@ func (e ErrMissingRemoteMetadata) Error() string { return fmt.Sprintf("tuf: missing remote metadata %s", e.Name) } +// ErrDownloadFailed - a download failed type ErrDownloadFailed struct { File string Err error @@ -35,6 +48,7 @@ func (e ErrDownloadFailed) Error() string { return fmt.Sprintf("tuf: failed to download %s: %s", e.File, e.Err) } +// ErrDecodeFailed - couldn't parse a download type ErrDecodeFailed struct { File string Err error @@ -52,6 +66,7 @@ func isDecodeFailedWithErr(err, expected error) bool { return e.Err == expected } +// ErrNotFound - didn't find a file type ErrNotFound struct { File string } @@ -60,11 +75,13 @@ func (e ErrNotFound) Error() string { return fmt.Sprintf("tuf: file not found: %s", e.File) } +// IsNotFound - check if an error is an ErrNotFound type func IsNotFound(err error) bool { _, ok := err.(ErrNotFound) return ok } +// ErrWrongSize - the size is wrong type ErrWrongSize struct { File string Actual int64 @@ -75,44 +92,7 @@ func (e ErrWrongSize) Error() string { return fmt.Sprintf("tuf: unexpected file size: %s (expected %d bytes, got %d bytes)", e.File, e.Expected, e.Actual) } -type ErrLatestSnapshot struct { - Version int -} - -func (e ErrLatestSnapshot) Error() string { - return fmt.Sprintf("tuf: the local snapshot version (%d) is the latest", e.Version) -} - -func IsLatestSnapshot(err error) bool { - _, ok := err.(ErrLatestSnapshot) - return ok -} - -type ErrUnknownTarget struct { - Name string -} - -func (e ErrUnknownTarget) Error() string { - return fmt.Sprintf("tuf: unknown target file: %s", e.Name) -} - -type ErrMetaTooLarge struct { - Name string - Size int64 -} - -func (e ErrMetaTooLarge) Error() string { - return fmt.Sprintf("tuf: %s size %d bytes greater than maximum", e.Name, e.Size) -} - -type ErrInvalidURL struct { - URL string -} - -func (e ErrInvalidURL) Error() string { - return fmt.Sprintf("tuf: invalid repository URL %s", e.URL) -} - +// ErrCorruptedCache - local data is incorrect type ErrCorruptedCache struct { file string } diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/data/keys.go b/Godeps/_workspace/src/github.com/endophage/gotuf/data/keys.go index eccccc420d..d3989b6790 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/data/keys.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/data/keys.go @@ -8,22 +8,27 @@ import ( "github.com/jfrazelle/go/canonical/json" ) +// Key is the minimal interface for a public key. It is declared +// independently of PublicKey for composability. type Key interface { ID() string Algorithm() KeyAlgorithm Public() []byte } +// PublicKey is the necessary interface for public keys type PublicKey interface { Key } +// PrivateKey adds the ability to access the private key type PrivateKey interface { Key Private() []byte } +// KeyPair holds the public and private key bytes type KeyPair struct { Public []byte `json:"public"` Private []byte `json:"private"` @@ -35,11 +40,13 @@ type KeyPair struct { // JSON would be different). This structure should normally be accessed through // the PublicKey or PrivateKey interfaces. type TUFKey struct { - id string `json:"-"` + id string Type KeyAlgorithm `json:"keytype"` Value KeyPair `json:"keyval"` } +// NewPrivateKey instantiates a new TUFKey with the private key component +// populated func NewPrivateKey(algorithm KeyAlgorithm, public, private []byte) *TUFKey { return &TUFKey{ Type: algorithm, @@ -50,10 +57,12 @@ func NewPrivateKey(algorithm KeyAlgorithm, public, private []byte) *TUFKey { } } +// Algorithm returns the algorithm of the key func (k TUFKey) Algorithm() KeyAlgorithm { return k.Type } +// ID efficiently generates if necessary, and caches the ID of the key func (k *TUFKey) ID() string { if k.id == "" { pubK := NewPublicKey(k.Algorithm(), k.Public()) @@ -67,14 +76,18 @@ func (k *TUFKey) ID() string { return k.id } +// Public returns the public bytes func (k TUFKey) Public() []byte { return k.Value.Public } +// Private returns the private bytes func (k TUFKey) Private() []byte { return k.Value.Private } +// NewPublicKey instantiates a new TUFKey where the private bytes are +// guaranteed to be nil func NewPublicKey(algorithm KeyAlgorithm, public []byte) PublicKey { return &TUFKey{ Type: algorithm, @@ -85,6 +98,8 @@ func NewPublicKey(algorithm KeyAlgorithm, public []byte) PublicKey { } } +// PublicKeyFromPrivate returns a new TUFKey based on a private key, with +// the private key bytes guaranteed to be nil. func PublicKeyFromPrivate(pk PrivateKey) PublicKey { return &TUFKey{ Type: pk.Algorithm(), diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/data/roles.go b/Godeps/_workspace/src/github.com/endophage/gotuf/data/roles.go index 1034393e1d..12b76b23c4 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/data/roles.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/data/roles.go @@ -15,6 +15,10 @@ const ( CanonicalTimestampRole = "timestamp" ) +// ValidRoles holds an overrideable mapping of canonical role names +// to any custom roles names a user wants to make use of. This allows +// us to be internally consistent while using different roles in the +// public TUF files. var ValidRoles = map[string]string{ CanonicalRootRole: CanonicalRootRole, CanonicalTargetsRole: CanonicalTargetsRole, @@ -22,6 +26,7 @@ var ValidRoles = map[string]string{ CanonicalTimestampRole: CanonicalTimestampRole, } +// SetValidRoles is a utility function to override some or all of the roles func SetValidRoles(rs map[string]string) { // iterate ValidRoles for k := range ValidRoles { @@ -31,13 +36,17 @@ func SetValidRoles(rs map[string]string) { } } -func RoleName(role string) string { - if r, ok := ValidRoles[role]; ok { +// RoleName returns the (possibly overridden) role name for the provided +// canonical role name +func RoleName(canonicalRole string) string { + if r, ok := ValidRoles[canonicalRole]; ok { return r } - return role + return canonicalRole } +// CanonicalRole does a reverse lookup to get the canonical role name +// from the (possibly overridden) role name func CanonicalRole(role string) string { name := strings.ToLower(role) if _, ok := ValidRoles[name]; ok { @@ -79,10 +88,13 @@ func ValidRole(name string) bool { return false } +// RootRole is a cut down role as it appears in the root.json type RootRole struct { KeyIDs []string `json:"keyids"` Threshold int `json:"threshold"` } + +// Role is a more verbose role as they appear in targets delegations type Role struct { RootRole Name string `json:"name"` @@ -91,6 +103,7 @@ type Role struct { Email string `json:"email,omitempty"` } +// NewRole creates a new Role object from the given parameters func NewRole(name string, threshold int, keyIDs, paths, pathHashPrefixes []string) (*Role, error) { if len(paths) > 0 && len(pathHashPrefixes) > 0 { return nil, errors.ErrInvalidRole{} @@ -113,10 +126,13 @@ func NewRole(name string, threshold int, keyIDs, paths, pathHashPrefixes []strin } +// IsValid checks if the role has defined both paths and path hash prefixes, +// having both is invalid func (r Role) IsValid() bool { return !(len(r.Paths) > 0 && len(r.PathHashPrefixes) > 0) } +// ValidKey checks if the given id is a recognized signing key for the role func (r Role) ValidKey(id string) bool { for _, key := range r.KeyIDs { if key == id { @@ -126,6 +142,7 @@ func (r Role) ValidKey(id string) bool { return false } +// CheckPaths checks if a given path is valid for the role func (r Role) CheckPaths(path string) bool { for _, p := range r.Paths { if strings.HasPrefix(path, p) { @@ -135,6 +152,7 @@ func (r Role) CheckPaths(path string) bool { return false } +// CheckPrefixes checks if a given hash matches the prefixes for the role func (r Role) CheckPrefixes(hash string) bool { for _, p := range r.PathHashPrefixes { if strings.HasPrefix(hash, p) { @@ -144,6 +162,7 @@ func (r Role) CheckPrefixes(hash string) bool { return false } +// IsDelegation checks if the role is a delegation or a root role func (r Role) IsDelegation() bool { targetsBase := fmt.Sprintf("%s/", ValidRoles[CanonicalTargetsRole]) return strings.HasPrefix(r.Name, targetsBase) diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/data/root.go b/Godeps/_workspace/src/github.com/endophage/gotuf/data/root.go index 5730e9bf0c..69b92e8e51 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/data/root.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/data/root.go @@ -6,12 +6,14 @@ import ( "github.com/jfrazelle/go/canonical/json" ) +// SignedRoot is a fully unpacked root.json type SignedRoot struct { Signatures []Signature Signed Root Dirty bool } +// Root is the Signed component of a root.json type Root struct { Type string `json:"_type"` Version int `json:"version"` @@ -23,6 +25,7 @@ type Root struct { ConsistentSnapshot bool `json:"consistent_snapshot"` } +// NewRoot initializes a new SignedRoot with a set of keys, roles, and the consistent flag func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent bool) (*SignedRoot, error) { signedRoot := &SignedRoot{ Signatures: make([]Signature, 0), @@ -56,6 +59,7 @@ func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent b return signedRoot, nil } +// ToSigned partially serializes a SignedRoot for further signing func (r SignedRoot) ToSigned() (*Signed, error) { s, err := json.MarshalCanonical(r.Signed) if err != nil { @@ -74,6 +78,7 @@ func (r SignedRoot) ToSigned() (*Signed, error) { }, nil } +// RootFromSigned fully unpacks a Signed object into a SignedRoot func RootFromSigned(s *Signed) (*SignedRoot, error) { r := Root{} err := json.Unmarshal(s.Signed, &r) diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/data/snapshot.go b/Godeps/_workspace/src/github.com/endophage/gotuf/data/snapshot.go index 5b61e46f49..d242a761a3 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/data/snapshot.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/data/snapshot.go @@ -8,12 +8,14 @@ import ( "github.com/jfrazelle/go/canonical/json" ) +// SignedSnapshot is a fully unpacked snapshot.json type SignedSnapshot struct { Signatures []Signature Signed Snapshot Dirty bool } +// Snapshot is the Signed component of a snapshot.json type Snapshot struct { Type string `json:"_type"` Version int `json:"version"` @@ -21,6 +23,8 @@ type Snapshot struct { Meta Files `json:"meta"` } +// NewSnapshot initilizes a SignedSnapshot with a given top level root +// and targets objects func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) { logrus.Debug("generating new snapshot...") targetsJSON, err := json.Marshal(targets) @@ -59,6 +63,7 @@ func (sp *SignedSnapshot) hashForRole(role string) []byte { return sp.Signed.Meta[role].Hashes["sha256"] } +// ToSigned partially serializes a SignedSnapshot for further signing func (sp SignedSnapshot) ToSigned() (*Signed, error) { s, err := json.MarshalCanonical(sp.Signed) if err != nil { @@ -77,11 +82,13 @@ func (sp SignedSnapshot) ToSigned() (*Signed, error) { }, nil } +// AddMeta updates a role in the snapshot with new meta func (sp *SignedSnapshot) AddMeta(role string, meta FileMeta) { sp.Signed.Meta[role] = meta sp.Dirty = true } +// SnapshotFromSigned fully unpacks a Signed object into a SignedSnapshot func SnapshotFromSigned(s *Signed) (*SignedSnapshot, error) { sp := Snapshot{} err := json.Unmarshal(s.Signed, &sp) diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/data/targets.go b/Godeps/_workspace/src/github.com/endophage/gotuf/data/targets.go index a51f60d208..776e1b69c9 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/data/targets.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/data/targets.go @@ -7,18 +7,22 @@ import ( "github.com/jfrazelle/go/canonical/json" ) +// SignedTargets is a fully unpacked targets.json, or target delegation +// json file type SignedTargets struct { Signatures []Signature Signed Targets Dirty bool } +// Targets is the Signed components of a targets.json or delegation json file type Targets struct { SignedCommon Targets Files `json:"targets"` Delegations Delegations `json:"delegations,omitempty"` } +// NewTargets intiializes a new empty SignedTargets object func NewTargets() *SignedTargets { return &SignedTargets{ Signatures: make([]Signature, 0), @@ -53,7 +57,7 @@ func (t SignedTargets) GetMeta(path string) *FileMeta { // to the role slice on Delegations per TUF spec proposal on using // order to determine priority. func (t SignedTargets) GetDelegations(path string) []*Role { - roles := make([]*Role, 0) + var roles []*Role pathHashBytes := sha256.Sum256([]byte(path)) pathHash := hex.EncodeToString(pathHashBytes[:]) for _, r := range t.Signed.Delegations.Roles { @@ -74,15 +78,20 @@ func (t SignedTargets) GetDelegations(path string) []*Role { return roles } +// AddTarget adds or updates the meta for the given path func (t *SignedTargets) AddTarget(path string, meta FileMeta) { t.Signed.Targets[path] = meta t.Dirty = true } +// AddDelegation will add a new delegated role with the given keys, +// ensuring the keys either already exist, or are added to the map +// of delegation keys func (t *SignedTargets) AddDelegation(role *Role, keys []*PublicKey) error { return nil } +// ToSigned partially serializes a SignedTargets for further signing func (t SignedTargets) ToSigned() (*Signed, error) { s, err := json.MarshalCanonical(t.Signed) if err != nil { @@ -101,6 +110,7 @@ func (t SignedTargets) ToSigned() (*Signed, error) { }, nil } +// TargetsFromSigned fully unpacks a Signed object into a SignedTargets func TargetsFromSigned(s *Signed) (*SignedTargets, error) { t := Targets{} err := json.Unmarshal(s.Signed, &t) diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/data/timestamp.go b/Godeps/_workspace/src/github.com/endophage/gotuf/data/timestamp.go index e8a0de7cd2..912682a2e3 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/data/timestamp.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/data/timestamp.go @@ -7,12 +7,14 @@ import ( "github.com/jfrazelle/go/canonical/json" ) +// SignedTimestamp is a fully unpacked timestamp.json type SignedTimestamp struct { Signatures []Signature Signed Timestamp Dirty bool } +// Timestamp is the Signed component of a timestamp.json type Timestamp struct { Type string `json:"_type"` Version int `json:"version"` @@ -20,6 +22,7 @@ type Timestamp struct { Meta Files `json:"meta"` } +// NewTimestamp initializes a timestamp with an existing snapshot func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) { snapshotJSON, err := json.Marshal(snapshot) if err != nil { @@ -42,6 +45,8 @@ func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) { }, nil } +// ToSigned partially serializes a SignedTimestamp such that it can +// be signed func (ts SignedTimestamp) ToSigned() (*Signed, error) { s, err := json.MarshalCanonical(ts.Signed) if err != nil { @@ -60,6 +65,8 @@ func (ts SignedTimestamp) ToSigned() (*Signed, error) { }, nil } +// TimestampFromSigned parsed a Signed object into a fully unpacked +// SignedTimestamp func TimestampFromSigned(s *Signed) (*SignedTimestamp, error) { ts := Timestamp{} err := json.Unmarshal(s.Signed, &ts) diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/data/types.go b/Godeps/_workspace/src/github.com/endophage/gotuf/data/types.go index 7cc84874d5..7aaf9fbf7c 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/data/types.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/data/types.go @@ -14,27 +14,33 @@ import ( "github.com/jfrazelle/go/canonical/json" ) +// KeyAlgorithm for types of keys type KeyAlgorithm string func (k KeyAlgorithm) String() string { return string(k) } +// SigAlgorithm for types of signatures type SigAlgorithm string func (k SigAlgorithm) String() string { return string(k) } -const ( - defaultHashAlgorithm = "sha256" +const defaultHashAlgorithm = "sha256" +// Signature types +const ( EDDSASignature SigAlgorithm = "eddsa" RSAPSSSignature SigAlgorithm = "rsapss" RSAPKCS1v15Signature SigAlgorithm = "rsapkcs1v15" ECDSASignature SigAlgorithm = "ecdsa" PyCryptoSignature SigAlgorithm = "pycrypto-pkcs#1 pss" +) +// Key types +const ( ED25519Key KeyAlgorithm = "ed25519" RSAKey KeyAlgorithm = "rsa" RSAx509Key KeyAlgorithm = "rsa-x509" @@ -42,6 +48,7 @@ const ( ECDSAx509Key KeyAlgorithm = "ecdsa-x509" ) +// TUFTypes is the set of metadata types var TUFTypes = map[string]string{ CanonicalRootRole: "Root", CanonicalTargetsRole: "Targets", @@ -57,6 +64,7 @@ func SetTUFTypes(ts map[string]string) { } } +// ValidTUFType checks if the given type is valid for the role func ValidTUFType(typ, role string) bool { if ValidRole(role) { // All targets delegation roles must have @@ -80,38 +88,54 @@ func ValidTUFType(typ, role string) bool { return false } +// Signed is the high level, partially deserialized metadata object +// used to verify signatures before fully unpacking, or to add signatures +// before fully packing type Signed struct { Signed json.RawMessage `json:"signed"` Signatures []Signature `json:"signatures"` } +// SignedCommon contains the fields common to the Signed component of all +// TUF metadata files type SignedCommon struct { Type string `json:"_type"` Expires time.Time `json:"expires"` Version int `json:"version"` } +// SignedMeta is used in server validation where we only need signatures +// and common fields type SignedMeta struct { Signed SignedCommon `json:"signed"` Signatures []Signature `json:"signatures"` } +// Signature is a signature on a piece of metadata type Signature struct { KeyID string `json:"keyid"` Method SigAlgorithm `json:"method"` Signature []byte `json:"sig"` } +// Files is the map of paths to file meta container in targets and delegations +// metadata files type Files map[string]FileMeta +// Hashes is the map of hash type to digest created for each metadata +// and target file type Hashes map[string][]byte +// FileMeta contains the size and hashes for a metadata or target file. Custom +// data can be optionally added. type FileMeta struct { Length int64 `json:"length"` Hashes Hashes `json:"hashes"` Custom json.RawMessage `json:"custom,omitempty"` } +// NewFileMeta generates a FileMeta object from the reader, using the +// hash algorithms provided func NewFileMeta(r io.Reader, hashAlgorithms ...string) (FileMeta, error) { if len(hashAlgorithms) == 0 { hashAlgorithms = []string{defaultHashAlgorithm} @@ -141,11 +165,13 @@ func NewFileMeta(r io.Reader, hashAlgorithms ...string) (FileMeta, error) { return m, nil } +// Delegations holds a tier of targets delegations type Delegations struct { Keys map[string]PublicKey `json:"keys"` Roles []*Role `json:"roles"` } +// NewDelegations initializes an empty Delegations object func NewDelegations() *Delegations { return &Delegations{ Keys: make(map[string]PublicKey), @@ -172,6 +198,7 @@ func SetDefaultExpiryTimes(times map[string]int) { } } +// DefaultExpires gets the default expiry time for the given role func DefaultExpires(role string) time.Time { var t time.Time if t, ok := defaultExpiryTimes[role]; ok { @@ -182,6 +209,7 @@ func DefaultExpires(role string) time.Time { type unmarshalledSignature Signature +// UnmarshalJSON does a custom unmarshalling of the signature JSON func (s *Signature) UnmarshalJSON(data []byte) error { uSignature := unmarshalledSignature{} err := json.Unmarshal(data, &uSignature) diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/errors/errors.go b/Godeps/_workspace/src/github.com/endophage/gotuf/errors/errors.go index b5bb40a7a8..763be52fb4 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/errors/errors.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/errors/errors.go @@ -6,8 +6,12 @@ import ( "time" ) +// ErrInitNotAllowed - repo has already been initialized var ErrInitNotAllowed = errors.New("tuf: repository already initialized") +// ErrMissingMetadata - cannot find the file meta being requested. +// Specifically, could not find the FileMeta object in the expected +// location. type ErrMissingMetadata struct { Name string } @@ -16,6 +20,7 @@ func (e ErrMissingMetadata) Error() string { return fmt.Sprintf("tuf: missing metadata %s", e.Name) } +// ErrFileNotFound - could not find a file type ErrFileNotFound struct { Path string } @@ -24,6 +29,7 @@ func (e ErrFileNotFound) Error() string { return fmt.Sprintf("tuf: file not found %s", e.Path) } +// ErrInsufficientKeys - did not have enough keys to sign when requested type ErrInsufficientKeys struct { Name string } @@ -32,6 +38,8 @@ func (e ErrInsufficientKeys) Error() string { return fmt.Sprintf("tuf: insufficient keys to sign %s", e.Name) } +// ErrInsufficientSignatures - do not have enough signatures on a piece of +// metadata type ErrInsufficientSignatures struct { Name string Err error @@ -41,6 +49,7 @@ func (e ErrInsufficientSignatures) Error() string { return fmt.Sprintf("tuf: insufficient signatures for %s: %s", e.Name, e.Err) } +// ErrInvalidRole - role is wrong. Typically we're missing the public keys for it type ErrInvalidRole struct { Role string } @@ -49,6 +58,7 @@ func (e ErrInvalidRole) Error() string { return fmt.Sprintf("tuf: invalid role %s", e.Role) } +// ErrInvalidExpires - the expiry time for a metadata file is invalid type ErrInvalidExpires struct { Expires time.Time } @@ -57,6 +67,7 @@ func (e ErrInvalidExpires) Error() string { return fmt.Sprintf("tuf: invalid expires: %s", e.Expires) } +// ErrKeyNotFound - could not find a given key on a role type ErrKeyNotFound struct { Role string KeyID string @@ -66,6 +77,7 @@ func (e ErrKeyNotFound) Error() string { return fmt.Sprintf(`tuf: no key with id "%s" exists for the %s role`, e.KeyID, e.Role) } +// ErrNotEnoughKeys - there are not enough keys to ever meet the signature threshold type ErrNotEnoughKeys struct { Role string Keys int @@ -76,6 +88,7 @@ func (e ErrNotEnoughKeys) Error() string { return fmt.Sprintf("tuf: %s role has insufficient keys for threshold (has %d keys, threshold is %d)", e.Role, e.Keys, e.Threshold) } +// ErrPassphraseRequired - a passphrase is needed and wasn't provided type ErrPassphraseRequired struct { Role string } diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/keys/db.go b/Godeps/_workspace/src/github.com/endophage/gotuf/keys/db.go index 9ac5c96e84..f4837afdc0 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/keys/db.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/keys/db.go @@ -6,6 +6,7 @@ import ( "github.com/endophage/gotuf/data" ) +// Various basic key database errors var ( ErrWrongType = errors.New("tuf: invalid key type") ErrExists = errors.New("tuf: key already in db") @@ -16,11 +17,15 @@ var ( ErrInvalidThreshold = errors.New("tuf: invalid role threshold") ) +// KeyDB is an in memory database of public keys and role associations. +// It is populated when parsing TUF files and used during signature +// verification to look up the keys for a given role type KeyDB struct { roles map[string]*data.Role keys map[string]data.PublicKey } +// NewDB initializes an empty KeyDB func NewDB() *KeyDB { return &KeyDB{ roles: make(map[string]*data.Role), @@ -28,10 +33,13 @@ func NewDB() *KeyDB { } } +// AddKey adds a public key to the database func (db *KeyDB) AddKey(k data.PublicKey) { db.keys[k.ID()] = k } +// AddRole adds a role to the database. Any keys associated with the +// role must have already been added. func (db *KeyDB) AddRole(r *data.Role) error { if !data.ValidRole(r.Name) { return ErrInvalidRole @@ -51,10 +59,12 @@ func (db *KeyDB) AddRole(r *data.Role) error { return nil } +// GetKey pulls a key out of the database by its ID func (db *KeyDB) GetKey(id string) data.PublicKey { return db.keys[id] } +// GetRole retrieves a role based on its name func (db *KeyDB) GetRole(name string) *data.Role { return db.roles[name] } diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/ed25519.go b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/ed25519.go index ca85748686..4cfbdc44a9 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/ed25519.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/ed25519.go @@ -13,6 +13,8 @@ type Ed25519 struct { keys map[string]data.PrivateKey } +// NewEd25519 initializes a new empty Ed25519 CryptoService that operates +// entirely in memory func NewEd25519() *Ed25519 { return &Ed25519{ make(map[string]data.PrivateKey), @@ -24,19 +26,21 @@ func (e *Ed25519) addKey(k data.PrivateKey) { e.keys[k.ID()] = k } +// RemoveKey deletes a key from the signer func (e *Ed25519) RemoveKey(keyID string) error { delete(e.keys, keyID) return nil } +// Sign generates an Ed25519 signature over the data func (e *Ed25519) Sign(keyIDs []string, toSign []byte) ([]data.Signature, error) { signatures := make([]data.Signature, 0, len(keyIDs)) - for _, kID := range keyIDs { + for _, keyID := range keyIDs { priv := [ed25519.PrivateKeySize]byte{} - copy(priv[:], e.keys[kID].Private()) + copy(priv[:], e.keys[keyID].Private()) sig := ed25519.Sign(&priv, toSign) signatures = append(signatures, data.Signature{ - KeyID: kID, + KeyID: keyID, Method: data.EDDSASignature, Signature: sig[:], }) @@ -45,6 +49,7 @@ func (e *Ed25519) Sign(keyIDs []string, toSign []byte) ([]data.Signature, error) } +// Create generates a new key and returns the public part func (e *Ed25519) Create(role string, algorithm data.KeyAlgorithm) (data.PublicKey, error) { if algorithm != data.ED25519Key { return nil, errors.New("only ED25519 supported by this cryptoservice") @@ -60,16 +65,19 @@ func (e *Ed25519) Create(role string, algorithm data.KeyAlgorithm) (data.PublicK return public, nil } +// PublicKeys returns a map of public keys for the ids provided, when those IDs are found +// in the store. func (e *Ed25519) PublicKeys(keyIDs ...string) (map[string]data.PublicKey, error) { k := make(map[string]data.PublicKey) - for _, kID := range keyIDs { - if key, ok := e.keys[kID]; ok { - k[kID] = data.PublicKeyFromPrivate(key) + for _, keyID := range keyIDs { + if key, ok := e.keys[keyID]; ok { + k[keyID] = data.PublicKeyFromPrivate(key) } } return k, nil } +// GetKey returns a single public key based on the ID func (e *Ed25519) GetKey(keyID string) data.PublicKey { return data.PublicKeyFromPrivate(e.keys[keyID]) } diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/errors.go b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/errors.go index 09ecc9a719..0c92f3465e 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/errors.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/errors.go @@ -4,6 +4,7 @@ import ( "fmt" ) +// ErrExpired indicates a piece of metadata has expired type ErrExpired struct { Role string Expired string @@ -13,6 +14,8 @@ func (e ErrExpired) Error() string { return fmt.Sprintf("%s expired at %v", e.Role, e.Expired) } +// ErrLowVersion indicates the piece of metadata has a version number lower than +// a version number we're already seen for this role type ErrLowVersion struct { Actual int Current int @@ -22,18 +25,23 @@ func (e ErrLowVersion) Error() string { return fmt.Sprintf("version %d is lower than current version %d", e.Actual, e.Current) } +// ErrRoleThreshold indicates we did not validate enough signatures to meet the threshold type ErrRoleThreshold struct{} func (e ErrRoleThreshold) Error() string { return "valid signatures did not meet threshold" } +// ErrInvalidKeyType indicates the types for the key and signature it's associated with are +// mismatched. Probably a sign of malicious behaviour type ErrInvalidKeyType struct{} func (e ErrInvalidKeyType) Error() string { return "key type is not valid for signature" } +// ErrInvalidKeyLength indicates that while we may support the cipher, the provided +// key length is not specifically supported, i.e. we support RSA, but not 1024 bit keys type ErrInvalidKeyLength struct { msg string } diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign.go b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign.go index 8bab441009..80eaf5b663 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign.go @@ -2,42 +2,79 @@ package signed import ( "fmt" + "github.com/Sirupsen/logrus" + "github.com/docker/notary/trustmanager" "github.com/endophage/gotuf/data" "github.com/endophage/gotuf/errors" - "strings" ) +type idPair struct { + scopedKeyID string + canonicalKeyID string +} + // Sign takes a data.Signed and a key, calculated and adds the signature // to the data.Signed func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error { logrus.Debugf("sign called with %d keys", len(keys)) signatures := make([]data.Signature, 0, len(s.Signatures)+1) keyIDMemb := make(map[string]struct{}) - keyIDs := make([]string, 0, len(keys)) + keyIDs := make( + []idPair, + 0, + len(keys), + ) for _, key := range keys { + keyID, err := canonicalKeyID(key) + if err != nil { + continue + } keyIDMemb[key.ID()] = struct{}{} - keyIDs = append(keyIDs, key.ID()) + keyIDs = append(keyIDs, idPair{ + scopedKeyID: key.ID(), + canonicalKeyID: keyID, + }) + } + + // we need to ask the signer to sign with the canonical key ID, but + // we need to translate back to the scoped key ID before giving the + // signature back to TUF. + for _, pair := range keyIDs { + newSigs, err := service.Sign([]string{pair.canonicalKeyID}, s.Signed) + if err != nil { + return err + } + // we only asked to sign with 1 key ID, so there will either be 1 + // or zero signatures + if len(newSigs) == 1 { + newSig := newSigs[0] + newSig.KeyID = pair.scopedKeyID + signatures = append(signatures, newSig) + } + } + if len(signatures) < 1 { + return errors.ErrInsufficientSignatures{ + Name: fmt.Sprintf("Cryptoservice failed to produce any signatures for keys with IDs: %v", keyIDs), + Err: nil, + } } - logrus.Debugf("Generated list of signing IDs: %s", strings.Join(keyIDs, ", ")) for _, sig := range s.Signatures { if _, ok := keyIDMemb[sig.KeyID]; ok { continue } signatures = append(signatures, sig) } - newSigs, err := service.Sign(keyIDs, s.Signed) - if err != nil { - return err - } - if len(newSigs) < 1 { - return errors.ErrInsufficientSignatures{ - Name: fmt.Sprint("Cryptoservice failed to produce any signatures for keys with IDs: %s", strings.Join(keyIDs, ", ")), - Err: nil, - } - } - logrus.Debugf("appending %d new signatures", len(newSigs)) - s.Signatures = append(signatures, newSigs...) + s.Signatures = signatures return nil } + +func canonicalKeyID(k data.PublicKey) (string, error) { + switch k.Algorithm() { + case data.ECDSAx509Key, data.RSAx509Key: + return trustmanager.X509PublicKeyID(k) + default: + return k.ID(), nil + } +} diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers.go b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers.go index e11eb4ad64..ef518f0b27 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers.go @@ -50,8 +50,10 @@ func RegisterVerifier(algorithm data.SigAlgorithm, v Verifier) { Verifiers[algorithm] = v } +// Ed25519Verifier used to verify Ed25519 signatures type Ed25519Verifier struct{} +// Verify checks that an ed25519 signature is valid func (v Ed25519Verifier) Verify(key data.PublicKey, sig []byte, msg []byte) error { if key.Algorithm() != data.ED25519Key { return ErrInvalidKeyType{} @@ -156,9 +158,10 @@ func (v RSAPSSVerifier) Verify(key data.PublicKey, sig []byte, msg []byte) error return verifyPSS(pubKey, digest[:], sig) } -// RSAPKCS1v15SVerifier checks RSA PKCS1v15 signatures +// RSAPKCS1v15Verifier checks RSA PKCS1v15 signatures type RSAPKCS1v15Verifier struct{} +// Verify does the actual verification func (v RSAPKCS1v15Verifier) Verify(key data.PublicKey, sig []byte, msg []byte) error { // will return err if keytype is not a recognized RSA type pubKey, err := getRSAPubKey(key) @@ -190,7 +193,7 @@ func (v RSAPKCS1v15Verifier) Verify(key data.PublicKey, sig []byte, msg []byte) return nil } -// RSAPSSVerifier checks RSASSA-PSS signatures +// RSAPyCryptoVerifier checks RSASSA-PSS signatures type RSAPyCryptoVerifier struct{} // Verify does the actual check. diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify.go b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify.go index b1ef6a3aea..b0acf816ad 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify.go @@ -11,6 +11,7 @@ import ( "github.com/jfrazelle/go/canonical/json" ) +// Various basic signing errors var ( ErrMissingKey = errors.New("tuf: missing key") ErrNoSignatures = errors.New("tuf: data has no signatures") @@ -61,6 +62,8 @@ func VerifyRoot(s *data.Signed, minVersion int, keys map[string]data.PublicKey) return ErrRoleThreshold{} } +// Verify checks the signatures and metadata (expiry, version) for the signed role +// data func Verify(s *data.Signed, role string, minVersion int, db *keys.KeyDB) error { if err := VerifySignatures(s, role, db); err != nil { return err @@ -87,10 +90,12 @@ func verifyMeta(s *data.Signed, role string, minVersion int) error { return nil } -var IsExpired = func(t time.Time) bool { +// IsExpired checks if the given time passed before the present time +func IsExpired(t time.Time) bool { return t.Before(time.Now()) } +// VerifySignatures checks the we have sufficient valid signatures for the given role func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error { if len(s.Signatures) == 0 { return ErrNoSignatures @@ -149,6 +154,7 @@ func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error { return nil } +// Unmarshal unmarshals and verifys the raw bytes for a given role's metadata func Unmarshal(b []byte, v interface{}, role string, minVersion int, db *keys.KeyDB) error { s := &data.Signed{} if err := json.Unmarshal(b, s); err != nil { @@ -160,6 +166,8 @@ func Unmarshal(b []byte, v interface{}, role string, minVersion int, db *keys.Ke return json.Unmarshal(s.Signed, v) } +// UnmarshalTrusted unmarshals and verifies signatures only, not metadata, for a +// given role's metadata func UnmarshalTrusted(b []byte, v interface{}, role string, db *keys.KeyDB) error { s := &data.Signed{} if err := json.Unmarshal(b, s); err != nil { diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/store/dbstore.go b/Godeps/_workspace/src/github.com/endophage/gotuf/store/dbstore.go deleted file mode 100644 index 8d65d0db21..0000000000 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/store/dbstore.go +++ /dev/null @@ -1,252 +0,0 @@ -package store - -import ( - "database/sql" - "encoding/hex" - "fmt" - "io/ioutil" - "os" - "path" - - logrus "github.com/Sirupsen/logrus" - "github.com/endophage/gotuf/data" - "github.com/endophage/gotuf/utils" - "github.com/jfrazelle/go/canonical/json" -) - -const ( - tufLoc string = "/tmp/tuf" - metadataSubDir string = "metadata" -) - -// implements LocalStore -type dbStore struct { - db sql.DB - imageName string -} - -// DBStore takes a database connection and the QDN of the image -func DBStore(db *sql.DB, imageName string) *dbStore { - store := dbStore{ - db: *db, - imageName: imageName, - } - - return &store -} - -// GetMeta loads existing TUF metadata files -func (dbs *dbStore) GetMeta(name string) ([]byte, error) { - data, err := dbs.readFile(name) - if err != nil { - return nil, err - } - return data, err -} - -// SetMeta writes individual TUF metadata files -func (dbs *dbStore) SetMeta(name string, meta []byte) error { - return dbs.writeFile(name, meta) -} - -// WalkStagedTargets walks all targets in scope -func (dbs *dbStore) WalkStagedTargets(paths []string, targetsFn targetsWalkFunc) error { - if len(paths) == 0 { - files := dbs.loadTargets("") - for path, meta := range files { - if err := targetsFn(path, meta); err != nil { - return err - } - } - return nil - } - - for _, path := range paths { - files := dbs.loadTargets(path) - meta, ok := files[path] - if !ok { - return fmt.Errorf("File Not Found") - } - if err := targetsFn(path, meta); err != nil { - return err - } - } - return nil -} - -// Commit writes a set of consistent (possibly) TUF metadata files -func (dbs *dbStore) Commit(metafiles map[string][]byte, consistent bool, hashes map[string]data.Hashes) error { - // TODO (endophage): write meta files to cache - return nil - -} - -// GetKeys returns private keys -func (dbs *dbStore) GetKeys(role string) ([]data.PrivateKey, error) { - keys := []data.PrivateKey{} - var r *sql.Rows - var err error - sql := "SELECT `key` FROM `keys` WHERE `role` = ? AND `namespace` = ?;" - tx, err := dbs.db.Begin() - defer tx.Rollback() - r, err = tx.Query(sql, role, dbs.imageName) - if err != nil { - return nil, err - } - defer r.Close() - for r.Next() { - var jsonStr string - key := new(data.TUFKey) - r.Scan(&jsonStr) - err := json.Unmarshal([]byte(jsonStr), key) - if err != nil { - return nil, err - } - keys = append(keys, key) - } - return keys, nil -} - -// SaveKey saves a new private key -func (dbs *dbStore) SaveKey(role string, key data.PrivateKey) error { - jsonBytes, err := json.Marshal(key) - if err != nil { - return fmt.Errorf("Could not JSON Marshal Key") - } - tx, err := dbs.db.Begin() - if err != nil { - logrus.Error(err) - return err - } - _, err = tx.Exec("INSERT INTO `keys` (`namespace`, `role`, `key`) VALUES (?,?,?);", dbs.imageName, role, string(jsonBytes)) - tx.Commit() - return err -} - -// Clean removes staged targets -func (dbs *dbStore) Clean() error { - // TODO (endophage): purge stale items from db? May just/also need a remove method - return nil -} - -// AddBlob adds an object to the store -func (dbs *dbStore) AddBlob(path string, meta data.FileMeta) { - path = utils.NormalizeTarget(path) - jsonbytes := []byte{} - if meta.Custom != nil { - jsonbytes, _ = meta.Custom.MarshalJSON() - } - - tx, err := dbs.db.Begin() - if err != nil { - logrus.Error(err) - return - } - _, err = tx.Exec("INSERT OR REPLACE INTO `filemeta` VALUES (?,?,?,?);", dbs.imageName, path, meta.Length, jsonbytes) - if err != nil { - logrus.Error(err) - } - tx.Commit() - dbs.addBlobHashes(path, meta.Hashes) -} - -func (dbs *dbStore) addBlobHashes(path string, hashes data.Hashes) { - tx, err := dbs.db.Begin() - if err != nil { - logrus.Error(err) - } - for alg, hash := range hashes { - _, err := tx.Exec("INSERT OR REPLACE INTO `filehashes` VALUES (?,?,?,?);", dbs.imageName, path, alg, hex.EncodeToString(hash)) - if err != nil { - logrus.Error(err) - } - } - tx.Commit() -} - -// RemoveBlob removes an object from the store -func (dbs *dbStore) RemoveBlob(path string) error { - tx, err := dbs.db.Begin() - if err != nil { - logrus.Error(err) - return err - } - _, err = tx.Exec("DELETE FROM `filemeta` WHERE `path`=? AND `namespace`=?", path, dbs.imageName) - if err == nil { - tx.Commit() - } else { - tx.Rollback() - } - return err -} - -func (dbs *dbStore) loadTargets(path string) map[string]data.FileMeta { - var err error - var r *sql.Rows - tx, err := dbs.db.Begin() - defer tx.Rollback() - files := make(map[string]data.FileMeta) - sql := "SELECT `filemeta`.`path`, `size`, `alg`, `hash`, `custom` FROM `filemeta` JOIN `filehashes` ON `filemeta`.`path` = `filehashes`.`path` AND `filemeta`.`namespace` = `filehashes`.`namespace` WHERE `filemeta`.`namespace`=?" - if path != "" { - sql = fmt.Sprintf("%s %s", sql, "AND `filemeta`.`path`=?") - r, err = tx.Query(sql, dbs.imageName, path) - } else { - r, err = tx.Query(sql, dbs.imageName) - } - if err != nil { - return files - } - defer r.Close() - for r.Next() { - var absPath, alg, hash string - var size int64 - var custom []byte - r.Scan(&absPath, &size, &alg, &hash, &custom) - hashBytes, err := hex.DecodeString(hash) - if err != nil { - // We're going to skip items with unparseable hashes as they - // won't be valid in the targets - logrus.Debug("Hash was not stored in hex as expected") - continue - } - if file, ok := files[absPath]; ok { - file.Hashes[alg] = hashBytes - } else { - file = data.FileMeta{ - Length: size, - Hashes: data.Hashes{ - alg: hashBytes, - }, - } - if custom != nil { - file.Custom = json.RawMessage(custom) - } - files[absPath] = file - } - } - return files -} - -func (dbs *dbStore) writeFile(name string, content []byte) error { - jsonName := fmt.Sprintf("%s.json", name) - fullPath := path.Join(tufLoc, metadataSubDir, dbs.imageName, jsonName) - dirPath := path.Dir(fullPath) - err := os.MkdirAll(dirPath, 0744) - if err != nil { - logrus.Error("error creating directory path to TUF cache") - return err - } - - err = ioutil.WriteFile(fullPath, content, 0744) - if err != nil { - logrus.Error("Error writing file") - } - return err -} - -func (dbs *dbStore) readFile(name string) ([]byte, error) { - jsonName := fmt.Sprintf("%s.json", name) - fullPath := path.Join(tufLoc, metadataSubDir, dbs.imageName, jsonName) - content, err := ioutil.ReadFile(fullPath) - return content, err -} diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/store/errors.go b/Godeps/_workspace/src/github.com/endophage/gotuf/store/errors.go index 0bc8272f96..b944c2d7a8 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/store/errors.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/store/errors.go @@ -1,5 +1,7 @@ package store +// ErrMetaNotFound indicates we did not find a particular piece +// of metadata in the store type ErrMetaNotFound struct{} func (err ErrMetaNotFound) Error() string { diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/store/filestore.go b/Godeps/_workspace/src/github.com/endophage/gotuf/store/filestore.go index 2ebd9b5bf6..ff3f785fa6 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/store/filestore.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/store/filestore.go @@ -8,7 +8,8 @@ import ( "path/filepath" ) -func NewFilesystemStore(baseDir, metaSubDir, metaExtension, targetsSubDir string) (*filesystemStore, error) { +// NewFilesystemStore creates a new store in a directory tree +func NewFilesystemStore(baseDir, metaSubDir, metaExtension, targetsSubDir string) (*FilesystemStore, error) { metaDir := path.Join(baseDir, metaSubDir) targetsDir := path.Join(baseDir, targetsSubDir) @@ -22,7 +23,7 @@ func NewFilesystemStore(baseDir, metaSubDir, metaExtension, targetsSubDir string return nil, err } - return &filesystemStore{ + return &FilesystemStore{ baseDir: baseDir, metaDir: metaDir, metaExtension: metaExtension, @@ -30,14 +31,16 @@ func NewFilesystemStore(baseDir, metaSubDir, metaExtension, targetsSubDir string }, nil } -type filesystemStore struct { +// FilesystemStore is a store in a locally accessible directory +type FilesystemStore struct { baseDir string metaDir string metaExtension string targetsDir string } -func (f *filesystemStore) GetMeta(name string, size int64) ([]byte, error) { +// GetMeta returns the meta for the given name (a role) +func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) { fileName := fmt.Sprintf("%s.%s", name, f.metaExtension) path := filepath.Join(f.metaDir, fileName) meta, err := ioutil.ReadFile(path) @@ -47,7 +50,8 @@ func (f *filesystemStore) GetMeta(name string, size int64) ([]byte, error) { return meta, nil } -func (f *filesystemStore) SetMultiMeta(metas map[string][]byte) error { +// SetMultiMeta sets the metadata for multiple roles in one operation +func (f *FilesystemStore) SetMultiMeta(metas map[string][]byte) error { for role, blob := range metas { err := f.SetMeta(role, blob) if err != nil { @@ -57,7 +61,8 @@ func (f *filesystemStore) SetMultiMeta(metas map[string][]byte) error { return nil } -func (f *filesystemStore) SetMeta(name string, meta []byte) error { +// SetMeta sets the meta for a single role +func (f *FilesystemStore) SetMeta(name string, meta []byte) error { fileName := fmt.Sprintf("%s.%s", name, f.metaExtension) path := filepath.Join(f.metaDir, fileName) if err := ioutil.WriteFile(path, meta, 0600); err != nil { diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore.go b/Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore.go index 7304382675..5e667432b7 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore.go @@ -14,6 +14,8 @@ import ( "github.com/Sirupsen/logrus" ) +// ErrServerUnavailable indicates an error from the server. code allows us to +// populate the http error we received type ErrServerUnavailable struct { code int } @@ -22,12 +24,9 @@ func (err ErrServerUnavailable) Error() string { return fmt.Sprintf("Unable to reach trust server at this time: %d.", err.code) } -type ErrShortRead struct{} - -func (err ErrShortRead) Error() string { - return "Trust server returned incompelete response." -} - +// ErrMaliciousServer indicates the server returned a response that is highly suspected +// of being malicious. i.e. it attempted to send us more data than the known size of a +// particular role metadata. type ErrMaliciousServer struct{} func (err ErrMaliciousServer) Error() string { @@ -52,7 +51,8 @@ type HTTPStore struct { roundTrip http.RoundTripper } -func NewHTTPStore(baseURL, metaPrefix, metaExtension, targetsPrefix, keyExtension string, roundTrip http.RoundTripper) (*HTTPStore, error) { +// NewHTTPStore initializes a new store against a URL and a number of configuration options +func NewHTTPStore(baseURL, metaPrefix, metaExtension, targetsPrefix, keyExtension string, roundTrip http.RoundTripper) (RemoteStore, error) { base, err := url.Parse(baseURL) if err != nil { return nil, err @@ -99,16 +99,13 @@ func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) { logrus.Debugf("%d when retrieving metadata for %s", resp.StatusCode, name) b := io.LimitReader(resp.Body, size) body, err := ioutil.ReadAll(b) - if resp.ContentLength > 0 && int64(len(body)) < resp.ContentLength { - return nil, ErrShortRead{} - } - if err != nil { return nil, err } return body, nil } +// SetMeta uploads a piece of TUF metadata to the server func (s HTTPStore) SetMeta(name string, blob []byte) error { url, err := s.buildMetaURL("") if err != nil { @@ -131,6 +128,9 @@ func (s HTTPStore) SetMeta(name string, blob []byte) error { return nil } +// SetMultiMeta does a single batch upload of multiple pieces of TUF metadata. +// This should be preferred for updating a remote server as it enable the server +// to remain consistent, either accepting or rejecting the complete update. func (s HTTPStore) SetMultiMeta(metas map[string][]byte) error { url, err := s.buildMetaURL("") if err != nil { @@ -220,6 +220,7 @@ func (s HTTPStore) GetTarget(path string) (io.ReadCloser, error) { return resp.Body, nil } +// GetKey retrieves a public key from the remote server func (s HTTPStore) GetKey(role string) ([]byte, error) { url, err := s.buildKeyURL(role) if err != nil { diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/store/interfaces.go b/Godeps/_workspace/src/github.com/endophage/gotuf/store/interfaces.go index 093f3851b3..0be5f60d19 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/store/interfaces.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/store/interfaces.go @@ -8,26 +8,34 @@ import ( type targetsWalkFunc func(path string, meta data.FileMeta) error +// MetadataStore must be implemented by anything that intends to interact +// with a store of TUF files type MetadataStore interface { GetMeta(name string, size int64) ([]byte, error) SetMeta(name string, blob []byte) error SetMultiMeta(map[string][]byte) error } +// PublicKeyStore must be implemented by a key service type PublicKeyStore interface { GetKey(role string) ([]byte, error) } -// [endophage] I'm of the opinion this should go away. +// TargetStore represents a collection of targets that can be walked similarly +// to walking a directory, passing a callback that receives the path and meta +// for each target type TargetStore interface { WalkStagedTargets(paths []string, targetsFn targetsWalkFunc) error } +// LocalStore represents a local TUF sture type LocalStore interface { MetadataStore TargetStore } +// RemoteStore is similar to LocalStore with the added expectation that it should +// provide a way to download targets once located type RemoteStore interface { MetadataStore PublicKeyStore diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/store/memorystore.go b/Godeps/_workspace/src/github.com/endophage/gotuf/store/memorystore.go index d32c9a4f3d..8adddebe38 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/store/memorystore.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/store/memorystore.go @@ -10,7 +10,9 @@ import ( "github.com/endophage/gotuf/utils" ) -func NewMemoryStore(meta map[string][]byte, files map[string][]byte) *memoryStore { +// NewMemoryStore returns a MetadataStore that operates entirely in memory. +// Very useful for testing +func NewMemoryStore(meta map[string][]byte, files map[string][]byte) RemoteStore { if meta == nil { meta = make(map[string][]byte) } @@ -31,7 +33,14 @@ type memoryStore struct { } func (m *memoryStore) GetMeta(name string, size int64) ([]byte, error) { - return m.meta[name], nil + d, ok := m.meta[name] + if ok { + if int64(len(d)) < size { + return d, nil + } + return d[:size], nil + } + return nil, ErrMetaNotFound{} } func (m *memoryStore) SetMeta(name string, meta []byte) error { @@ -67,7 +76,7 @@ func (m *memoryStore) WalkStagedTargets(paths []string, targetsFn targetsWalkFun for _, path := range paths { dat, ok := m.files[path] if !ok { - return errors.ErrFileNotFound{path} + return errors.ErrFileNotFound{Path: path} } meta, err := data.NewFileMeta(bytes.NewReader(dat), "sha256") if err != 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 index 3fa9c1f676..451714193a 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/testutils/repo.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/testutils/repo.go @@ -16,10 +16,10 @@ import ( // 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) { +func EmptyRepo() (*keys.KeyDB, *tuf.Repo, signed.CryptoService) { c := signed.NewEd25519() kdb := keys.NewDB() - r := tuf.NewTufRepo(kdb, c) + r := tuf.NewRepo(kdb, c) for _, role := range []string{"root", "targets", "snapshot", "timestamp"} { key, _ := c.Create(role, data.ED25519Key) @@ -32,7 +32,8 @@ func EmptyRepo() (*keys.KeyDB, *tuf.TufRepo, signed.CryptoService) { return kdb, r, c } -func AddTarget(role string, r *tuf.TufRepo) (name string, meta data.FileMeta, content []byte, err error) { +// AddTarget generates a fake target and adds it to a repo. +func AddTarget(role string, r *tuf.Repo) (name string, meta data.FileMeta, content []byte, err error) { randness := fuzz.Continue{} content = RandomByteSlice(1024) name = randness.RandString() @@ -48,6 +49,7 @@ func AddTarget(role string, r *tuf.TufRepo) (name string, meta data.FileMeta, co return } +// RandomByteSlice generates some random data to be used for testing only func RandomByteSlice(maxSize int) []byte { r := rand.New(rand.NewSource(time.Now().UnixNano())) contentSize := r.Intn(maxSize) @@ -58,7 +60,8 @@ func RandomByteSlice(maxSize int) []byte { return content } -func Sign(repo *tuf.TufRepo) (root, targets, snapshot, timestamp *data.Signed, err error) { +// Sign signs all top level roles in a repo in the appropriate order +func Sign(repo *tuf.Repo) (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 @@ -78,6 +81,7 @@ func Sign(repo *tuf.TufRepo) (root, targets, snapshot, timestamp *data.Signed, e return } +// Serialize takes the Signed objects for the 4 top level roles and serializes them all to JSON func Serialize(sRoot, sTargets, sSnapshot, sTimestamp *data.Signed) (root, targets, snapshot, timestamp []byte, err error) { root, err = json.Marshal(sRoot) if err != nil { diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/testutils/utils.go b/Godeps/_workspace/src/github.com/endophage/gotuf/testutils/utils.go index 59e0bdbdd3..2dec7ce4a0 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/testutils/utils.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/testutils/utils.go @@ -6,11 +6,13 @@ import ( "os" "github.com/endophage/gotuf/data" + // need to initialize sqlite for tests _ "github.com/mattn/go-sqlite3" ) -var counter int = 1 +var counter = 1 +// SampleMeta returns a static, fake (and invalid) FileMeta object func SampleMeta() data.FileMeta { meta := data.FileMeta{ Length: 1, @@ -22,6 +24,7 @@ func SampleMeta() data.FileMeta { return meta } +// GetSqliteDB creates and initializes a sqlite db func GetSqliteDB() *sql.DB { os.Mkdir("/tmp/sqlite", 0755) conn, err := sql.Open("sqlite3", fmt.Sprintf("/tmp/sqlite/file%d.db", counter)) @@ -40,6 +43,7 @@ func GetSqliteDB() *sql.DB { return conn } +// FlushDB deletes a sqliteDB func FlushDB(db *sql.DB) { tx, _ := db.Begin() tx.Exec("DELETE FROM `filemeta`") diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go b/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go index 39af54018b..a797e8b53c 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go @@ -1,4 +1,4 @@ -// tuf defines the core TUF logic around manipulating a repo. +// Package tuf defines the core TUF logic around manipulating a repo. package tuf import ( @@ -19,24 +19,29 @@ import ( "github.com/endophage/gotuf/utils" ) +// ErrSigVerifyFail - signature verification failed type ErrSigVerifyFail struct{} func (e ErrSigVerifyFail) Error() string { return "Error: Signature verification failed" } +// ErrMetaExpired - metadata file has expired type ErrMetaExpired struct{} func (e ErrMetaExpired) Error() string { return "Error: Metadata has expired" } +// ErrLocalRootExpired - the local root file is out of date type ErrLocalRootExpired struct{} func (e ErrLocalRootExpired) Error() string { return "Error: Local Root Has Expired" } +// ErrNotLoaded - attempted to access data that has not been loaded into +// the repo type ErrNotLoaded struct { role string } @@ -45,12 +50,12 @@ func (err ErrNotLoaded) Error() string { return fmt.Sprintf("%s role has not been loaded", err.role) } -// TufRepo is an in memory representation of the TUF Repo. +// Repo is an in memory representation of the TUF Repo. // It operates at the data.Signed level, accepting and producing -// data.Signed objects. Users of a TufRepo are responsible for +// data.Signed objects. Users of a Repo are responsible for // fetching raw JSON and using the Set* functions to populate -// the TufRepo instance. -type TufRepo struct { +// the Repo instance. +type Repo struct { Root *data.SignedRoot Targets map[string]*data.SignedTargets Snapshot *data.SignedSnapshot @@ -59,10 +64,10 @@ type TufRepo struct { cryptoService signed.CryptoService } -// NewTufRepo initializes a TufRepo instance with a keysDB and a signer. -// If the TufRepo will only be used for reading, the signer should be nil. -func NewTufRepo(keysDB *keys.KeyDB, cryptoService signed.CryptoService) *TufRepo { - repo := &TufRepo{ +// NewRepo initializes a Repo instance with a keysDB and a signer. +// If the Repo will only be used for reading, the signer should be nil. +func NewRepo(keysDB *keys.KeyDB, cryptoService signed.CryptoService) *Repo { + repo := &Repo{ Targets: make(map[string]*data.SignedTargets), keysDB: keysDB, cryptoService: cryptoService, @@ -71,7 +76,7 @@ func NewTufRepo(keysDB *keys.KeyDB, cryptoService signed.CryptoService) *TufRepo } // AddBaseKeys is used to add keys to the role in root.json -func (tr *TufRepo) AddBaseKeys(role string, keys ...data.PublicKey) error { +func (tr *Repo) AddBaseKeys(role string, keys ...data.PublicKey) error { if tr.Root == nil { return ErrNotLoaded{role: "root"} } @@ -101,7 +106,7 @@ func (tr *TufRepo) AddBaseKeys(role string, keys ...data.PublicKey) error { } // ReplaceBaseKeys is used to replace all keys for the given role with the new keys -func (tr *TufRepo) ReplaceBaseKeys(role string, keys ...data.PublicKey) error { +func (tr *Repo) ReplaceBaseKeys(role string, keys ...data.PublicKey) error { r := tr.keysDB.GetRole(role) err := tr.RemoveBaseKeys(role, r.KeyIDs...) if err != nil { @@ -111,11 +116,11 @@ func (tr *TufRepo) ReplaceBaseKeys(role string, keys ...data.PublicKey) error { } // RemoveBaseKeys is used to remove keys from the roles in root.json -func (tr *TufRepo) RemoveBaseKeys(role string, keyIDs ...string) error { +func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error { if tr.Root == nil { return ErrNotLoaded{role: "root"} } - keep := make([]string, 0) + var keep []string toDelete := make(map[string]struct{}) // remove keys from specified role for _, k := range keyIDs { @@ -157,7 +162,7 @@ func (tr *TufRepo) RemoveBaseKeys(role string, keyIDs ...string) error { // An empty before string indicates to add the role to the end of the // delegation list. // A new, empty, targets file will be created for the new role. -func (tr *TufRepo) UpdateDelegations(role *data.Role, keys []data.Key, before string) error { +func (tr *Repo) UpdateDelegations(role *data.Role, keys []data.Key, before string) error { if !role.IsDelegation() || !role.IsValid() { return errors.ErrInvalidRole{} } @@ -201,7 +206,7 @@ func (tr *TufRepo) UpdateDelegations(role *data.Role, keys []data.Key, before st // data.ValidTypes to determine what the role names and filename should be. It // also relies on the keysDB having already been populated with the keys and // roles. -func (tr *TufRepo) InitRepo(consistent bool) error { +func (tr *Repo) InitRepo(consistent bool) error { if err := tr.InitRoot(consistent); err != nil { return err } @@ -214,7 +219,9 @@ func (tr *TufRepo) InitRepo(consistent bool) error { return tr.InitTimestamp() } -func (tr *TufRepo) InitRoot(consistent bool) error { +// InitRoot initializes an empty root file with the 4 core roles based +// on the current content of th ekey db +func (tr *Repo) InitRoot(consistent bool) error { rootRoles := make(map[string]*data.RootRole) rootKeys := make(map[string]data.PublicKey) for _, r := range data.ValidRoles { @@ -240,13 +247,15 @@ func (tr *TufRepo) InitRoot(consistent bool) error { return nil } -func (tr *TufRepo) InitTargets() error { +// InitTargets initializes an empty targets +func (tr *Repo) InitTargets() error { targets := data.NewTargets() tr.Targets[data.ValidRoles["targets"]] = targets return nil } -func (tr *TufRepo) InitSnapshot() error { +// InitSnapshot initializes a snapshot based on the current root and targets +func (tr *Repo) InitSnapshot() error { root, err := tr.Root.ToSigned() if err != nil { return err @@ -263,7 +272,8 @@ func (tr *TufRepo) InitSnapshot() error { return nil } -func (tr *TufRepo) InitTimestamp() error { +// InitTimestamp initializes a timestamp based on the current snapshot +func (tr *Repo) InitTimestamp() error { snap, err := tr.Snapshot.ToSigned() if err != nil { return err @@ -278,9 +288,9 @@ func (tr *TufRepo) InitTimestamp() error { } // SetRoot parses the Signed object into a SignedRoot object, sets -// the keys and roles in the KeyDB, and sets the TufRepo.Root field +// the keys and roles in the KeyDB, and sets the Repo.Root field // to the SignedRoot object. -func (tr *TufRepo) SetRoot(s *data.SignedRoot) error { +func (tr *Repo) SetRoot(s *data.SignedRoot) error { for _, key := range s.Signed.Keys { logrus.Debug("Adding key ", key.ID()) tr.keysDB.AddKey(key) @@ -307,23 +317,23 @@ func (tr *TufRepo) SetRoot(s *data.SignedRoot) error { } // SetTimestamp parses the Signed object into a SignedTimestamp object -// and sets the TufRepo.Timestamp field. -func (tr *TufRepo) SetTimestamp(s *data.SignedTimestamp) error { +// and sets the Repo.Timestamp field. +func (tr *Repo) SetTimestamp(s *data.SignedTimestamp) error { tr.Timestamp = s return nil } // SetSnapshot parses the Signed object into a SignedSnapshots object -// and sets the TufRepo.Snapshot field. -func (tr *TufRepo) SetSnapshot(s *data.SignedSnapshot) error { +// and sets the Repo.Snapshot field. +func (tr *Repo) SetSnapshot(s *data.SignedSnapshot) error { tr.Snapshot = s return nil } // SetTargets parses the Signed object into a SignedTargets object, // reads the delegated roles and keys into the KeyDB, and sets the -// SignedTargets object agaist the role in the TufRepo.Targets map. -func (tr *TufRepo) SetTargets(role string, s *data.SignedTargets) error { +// SignedTargets object agaist the role in the Repo.Targets map. +func (tr *Repo) SetTargets(role string, s *data.SignedTargets) error { for _, k := range s.Signed.Delegations.Keys { tr.keysDB.AddKey(k) } @@ -337,7 +347,7 @@ func (tr *TufRepo) SetTargets(role string, s *data.SignedTargets) error { // TargetMeta returns the FileMeta entry for the given path in the // targets file associated with the given role. This may be nil if // the target isn't found in the targets file. -func (tr TufRepo) TargetMeta(role, path string) *data.FileMeta { +func (tr Repo) TargetMeta(role, path string) *data.FileMeta { if t, ok := tr.Targets[role]; ok { if m, ok := t.Signed.Targets[path]; ok { return &m @@ -348,12 +358,12 @@ func (tr TufRepo) TargetMeta(role, path string) *data.FileMeta { // TargetDelegations returns a slice of Roles that are valid publishers // for the target path provided. -func (tr TufRepo) TargetDelegations(role, path, pathHex string) []*data.Role { +func (tr Repo) TargetDelegations(role, path, pathHex string) []*data.Role { if pathHex == "" { pathDigest := sha256.Sum256([]byte(path)) pathHex = hex.EncodeToString(pathDigest[:]) } - roles := make([]*data.Role, 0) + var roles []*data.Role if t, ok := tr.Targets[role]; ok { for _, r := range t.Signed.Delegations.Roles { if r.CheckPrefixes(pathHex) || r.CheckPaths(path) { @@ -370,7 +380,7 @@ func (tr TufRepo) TargetDelegations(role, path, pathHex string) []*data.Role { // runs out of locations to search. // N.B. Multiple entries may exist in different delegated roles // for the same target. Only the first one encountered is returned. -func (tr TufRepo) FindTarget(path string) *data.FileMeta { +func (tr Repo) FindTarget(path string) *data.FileMeta { pathDigest := sha256.Sum256([]byte(path)) pathHex := hex.EncodeToString(pathDigest[:]) @@ -395,10 +405,10 @@ func (tr TufRepo) FindTarget(path string) *data.FileMeta { // AddTargets will attempt to add the given targets specifically to // the directed role. If the user does not have the signing keys for the role // the function will return an error and the full slice of targets. -func (tr *TufRepo) AddTargets(role string, targets data.Files) (data.Files, error) { +func (tr *Repo) AddTargets(role string, targets data.Files) (data.Files, error) { t, ok := tr.Targets[role] if !ok { - return targets, errors.ErrInvalidRole{role} + return targets, errors.ErrInvalidRole{Role: role} } invalid := make(data.Files) for path, target := range targets { @@ -418,10 +428,11 @@ func (tr *TufRepo) AddTargets(role string, targets data.Files) (data.Files, erro return nil, nil } -func (tr *TufRepo) RemoveTargets(role string, targets ...string) error { +// RemoveTargets removes the given target (paths) from the given target role (delegation) +func (tr *Repo) RemoveTargets(role string, targets ...string) error { t, ok := tr.Targets[role] if !ok { - return errors.ErrInvalidRole{role} + return errors.ErrInvalidRole{Role: role} } for _, path := range targets { @@ -431,7 +442,8 @@ func (tr *TufRepo) RemoveTargets(role string, targets ...string) error { return nil } -func (tr *TufRepo) UpdateSnapshot(role string, s *data.Signed) error { +// UpdateSnapshot updates the FileMeta for the given role based on the Signed object +func (tr *Repo) UpdateSnapshot(role string, s *data.Signed) error { jsonData, err := json.Marshal(s) if err != nil { return err @@ -445,7 +457,8 @@ func (tr *TufRepo) UpdateSnapshot(role string, s *data.Signed) error { return nil } -func (tr *TufRepo) UpdateTimestamp(s *data.Signed) error { +// UpdateTimestamp updates the snapshot meta in the timestamp based on the Signed object +func (tr *Repo) UpdateTimestamp(s *data.Signed) error { jsonData, err := json.Marshal(s) if err != nil { return err @@ -459,7 +472,8 @@ func (tr *TufRepo) UpdateTimestamp(s *data.Signed) error { return nil } -func (tr *TufRepo) SignRoot(expires time.Time, cryptoService signed.CryptoService) (*data.Signed, error) { +// SignRoot signs the root +func (tr *Repo) SignRoot(expires time.Time, cryptoService signed.CryptoService) (*data.Signed, error) { logrus.Debug("signing root...") tr.Root.Signed.Expires = expires tr.Root.Signed.Version++ @@ -476,7 +490,8 @@ func (tr *TufRepo) SignRoot(expires time.Time, cryptoService signed.CryptoServic return signed, nil } -func (tr *TufRepo) SignTargets(role string, expires time.Time, cryptoService signed.CryptoService) (*data.Signed, error) { +// SignTargets signs the targets file for the given top level or delegated targets role +func (tr *Repo) SignTargets(role string, expires time.Time, cryptoService signed.CryptoService) (*data.Signed, error) { logrus.Debugf("sign targets called for role %s", role) tr.Targets[role].Signed.Expires = expires tr.Targets[role].Signed.Version++ @@ -495,7 +510,8 @@ func (tr *TufRepo) SignTargets(role string, expires time.Time, cryptoService sig return signed, nil } -func (tr *TufRepo) SignSnapshot(expires time.Time, cryptoService signed.CryptoService) (*data.Signed, error) { +// SignSnapshot updates the snapshot based on the current targets and root then signs it +func (tr *Repo) SignSnapshot(expires time.Time, cryptoService signed.CryptoService) (*data.Signed, error) { logrus.Debug("signing snapshot...") signedRoot, err := tr.Root.ToSigned() if err != nil { @@ -531,7 +547,8 @@ func (tr *TufRepo) SignSnapshot(expires time.Time, cryptoService signed.CryptoSe return signed, nil } -func (tr *TufRepo) SignTimestamp(expires time.Time, cryptoService signed.CryptoService) (*data.Signed, error) { +// SignTimestamp updates the timestamp based on the current snapshot then signs it +func (tr *Repo) SignTimestamp(expires time.Time, cryptoService signed.CryptoService) (*data.Signed, error) { logrus.Debug("SignTimestamp") signedSnapshot, err := tr.Snapshot.ToSigned() if err != nil { @@ -557,7 +574,7 @@ func (tr *TufRepo) SignTimestamp(expires time.Time, cryptoService signed.CryptoS return signed, nil } -func (tr TufRepo) sign(signedData *data.Signed, role data.Role, cryptoService signed.CryptoService) (*data.Signed, error) { +func (tr Repo) sign(signedData *data.Signed, role data.Role, cryptoService signed.CryptoService) (*data.Signed, error) { ks := make([]data.PublicKey, 0, len(role.KeyIDs)) for _, kid := range role.KeyIDs { k := tr.keysDB.GetKey(kid) diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/utils/util.go b/Godeps/_workspace/src/github.com/endophage/gotuf/utils/util.go index 5184f799a4..65fb6e8a38 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/utils/util.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/utils/util.go @@ -11,23 +11,29 @@ import ( "github.com/endophage/gotuf/data" ) +// ErrWrongLength indicates the length was different to that expected var ErrWrongLength = errors.New("wrong length") +// ErrWrongHash indicates the hash was different to that expected type ErrWrongHash struct { Type string Expected []byte Actual []byte } +// Error implements error interface func (e ErrWrongHash) Error() string { return fmt.Sprintf("wrong %s hash, expected %#x got %#x", e.Type, e.Expected, e.Actual) } +// ErrNoCommonHash indicates the metadata did not provide any hashes this +// client recognizes type ErrNoCommonHash struct { Expected data.Hashes Actual data.Hashes } +// Error implements error interface func (e ErrNoCommonHash) Error() string { types := func(a data.Hashes) []string { t := make([]string, 0, len(a)) @@ -39,16 +45,21 @@ func (e ErrNoCommonHash) Error() string { return fmt.Sprintf("no common hash function, expected one of %s, got %s", types(e.Expected), types(e.Actual)) } +// ErrUnknownHashAlgorithm - client was ashed to use a hash algorithm +// it is not familiar with type ErrUnknownHashAlgorithm struct { Name string } +// Error implements error interface func (e ErrUnknownHashAlgorithm) Error() string { return fmt.Sprintf("unknown hash algorithm: %s", e.Name) } +// PassphraseFunc type for func that request a passphrase type PassphraseFunc func(role string, confirm bool) ([]byte, error) +// FileMetaEqual checks whether 2 FileMeta objects are consistent with eachother func FileMetaEqual(actual data.FileMeta, expected data.FileMeta) error { if actual.Length != expected.Length { return ErrWrongLength @@ -68,10 +79,13 @@ func FileMetaEqual(actual data.FileMeta, expected data.FileMeta) error { return nil } +// NormalizeTarget adds a slash, if required, to the front of a target path func NormalizeTarget(path string) string { return gopath.Join("/", path) } +// HashedPaths prefixes the filename with the known hashes for the file, +// returning a list of possible consistent paths. func HashedPaths(path string, hashes data.Hashes) []string { paths := make([]string, 0, len(hashes)) for _, hash := range hashes { diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/utils/utils.go b/Godeps/_workspace/src/github.com/endophage/gotuf/utils/utils.go index ca164a1aa4..4e9b28f8f7 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/utils/utils.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/utils/utils.go @@ -15,6 +15,7 @@ import ( "github.com/endophage/gotuf/data" ) +// Download does a simple download from a URL func Download(url url.URL) (*http.Response, error) { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, @@ -23,6 +24,7 @@ func Download(url url.URL) (*http.Response, error) { return client.Get(url.String()) } +// Upload does a simple JSON upload to a URL func Upload(url string, body io.Reader) (*http.Response, error) { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, @@ -31,6 +33,8 @@ func Upload(url string, body io.Reader) (*http.Response, error) { return client.Post(url, "application/json", body) } +// ValidateTarget ensures that the data read from reader matches +// the known metadata func ValidateTarget(r io.Reader, m *data.FileMeta) error { h := sha256.New() length, err := io.Copy(h, r) @@ -38,7 +42,7 @@ func ValidateTarget(r io.Reader, m *data.FileMeta) error { return err } if length != m.Length { - return fmt.Errorf("Size of downloaded target did not match targets entry.\nExpected: %s\nReceived: %s\n", m.Length, length) + return fmt.Errorf("Size of downloaded target did not match targets entry.\nExpected: %d\nReceived: %d\n", m.Length, length) } hashDigest := h.Sum(nil) if bytes.Compare(m.Hashes["sha256"], hashDigest[:]) != 0 { @@ -47,6 +51,7 @@ func ValidateTarget(r io.Reader, m *data.FileMeta) error { return nil } +// StrSliceContains checks if the given string appears in the slice func StrSliceContains(ss []string, s string) bool { for _, v := range ss { if v == s { @@ -56,6 +61,8 @@ func StrSliceContains(ss []string, s string) bool { return false } +// StrSliceContainsI checks if the given string appears in the slice +// in a case insensitive manner func StrSliceContainsI(ss []string, s string) bool { s = strings.ToLower(s) for _, v := range ss { @@ -67,19 +74,26 @@ func StrSliceContainsI(ss []string, s string) bool { return false } +// FileExists returns true if a file (or dir) exists at the given path, +// false otherwise func FileExists(path string) bool { _, err := os.Stat(path) return os.IsNotExist(err) } +// NoopCloser is a simple Reader wrapper that does nothing when Close is +// called type NoopCloser struct { io.Reader } +// Close does nothing for a NoopCloser func (nc *NoopCloser) Close() error { return nil } +// DoHash returns the digest of d using the hashing algorithm named +// in alg func DoHash(alg string, d []byte) []byte { switch alg { case "sha256": diff --git a/client/client.go b/client/client.go index ca57a89f1a..a154180979 100644 --- a/client/client.go +++ b/client/client.go @@ -69,7 +69,7 @@ type NotaryRepository struct { tufRepoPath string fileStore store.MetadataStore cryptoService signed.CryptoService - tufRepo *tuf.TufRepo + tufRepo *tuf.Repo roundTrip http.RoundTripper KeyStoreManager *keystoremanager.KeyStoreManager } @@ -214,7 +214,7 @@ func (r *NotaryRepository) Initialize(uCryptoService *cryptoservice.UnlockedCryp return err } - r.tufRepo = tuf.NewTufRepo(kdb, r.cryptoService) + r.tufRepo = tuf.NewRepo(kdb, r.cryptoService) err = r.tufRepo.InitRoot(false) if err != nil { @@ -461,7 +461,7 @@ func (r *NotaryRepository) Publish() error { func (r *NotaryRepository) bootstrapRepo() error { kdb := keys.NewDB() - tufRepo := tuf.NewTufRepo(kdb, r.cryptoService) + tufRepo := tuf.NewRepo(kdb, r.cryptoService) logrus.Debugf("Loading trusted collection.") rootJSON, err := r.fileStore.GetMeta("root", 0) @@ -587,7 +587,7 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { } kdb := keys.NewDB() - r.tufRepo = tuf.NewTufRepo(kdb, r.cryptoService) + r.tufRepo = tuf.NewRepo(kdb, r.cryptoService) signedRoot, err := data.RootFromSigned(root) if err != nil { diff --git a/client/helpers.go b/client/helpers.go index 50be86c6ce..8bfd8c6334 100644 --- a/client/helpers.go +++ b/client/helpers.go @@ -25,7 +25,7 @@ func getRemoteStore(baseURL, gun string, rt http.RoundTripper) (store.RemoteStor ) } -func applyChangelist(repo *tuf.TufRepo, cl changelist.Changelist) error { +func applyChangelist(repo *tuf.Repo, cl changelist.Changelist) error { it, err := cl.NewIterator() if err != nil { return err @@ -53,7 +53,7 @@ func applyChangelist(repo *tuf.TufRepo, cl changelist.Changelist) error { return nil } -func applyTargetsChange(repo *tuf.TufRepo, c changelist.Change) error { +func applyTargetsChange(repo *tuf.Repo, c changelist.Change) error { var err error switch c.Action() { case changelist.ActionCreate: @@ -77,7 +77,7 @@ func applyTargetsChange(repo *tuf.TufRepo, c changelist.Change) error { return nil } -func applyRootChange(repo *tuf.TufRepo, c changelist.Change) error { +func applyRootChange(repo *tuf.Repo, c changelist.Change) error { var err error switch c.Type() { case changelist.TypeRootRole: @@ -88,7 +88,7 @@ func applyRootChange(repo *tuf.TufRepo, c changelist.Change) error { return err // might be nil } -func applyRootRoleChange(repo *tuf.TufRepo, c changelist.Change) error { +func applyRootRoleChange(repo *tuf.Repo, c changelist.Change) error { switch c.Action() { case changelist.ActionCreate: // replaces all keys for a role diff --git a/client/helpers_test.go b/client/helpers_test.go index eb911e4fe6..6f50d28310 100644 --- a/client/helpers_test.go +++ b/client/helpers_test.go @@ -18,7 +18,7 @@ func TestApplyTargetsChange(t *testing.T) { assert.NoError(t, err) kdb.AddRole(role) - repo := tuf.NewTufRepo(kdb, nil) + repo := tuf.NewRepo(kdb, nil) err = repo.InitTargets() assert.NoError(t, err) hash := sha256.Sum256([]byte{}) @@ -61,7 +61,7 @@ func TestApplyChangelist(t *testing.T) { assert.NoError(t, err) kdb.AddRole(role) - repo := tuf.NewTufRepo(kdb, nil) + repo := tuf.NewRepo(kdb, nil) err = repo.InitTargets() assert.NoError(t, err) hash := sha256.Sum256([]byte{}) @@ -109,7 +109,7 @@ func TestApplyChangelistMulti(t *testing.T) { assert.NoError(t, err) kdb.AddRole(role) - repo := tuf.NewTufRepo(kdb, nil) + repo := tuf.NewRepo(kdb, nil) err = repo.InitTargets() assert.NoError(t, err) hash := sha256.Sum256([]byte{}) diff --git a/server/handlers/validation.go b/server/handlers/validation.go index c3c7344e02..13d355c51f 100644 --- a/server/handlers/validation.go +++ b/server/handlers/validation.go @@ -67,7 +67,7 @@ func (err ErrBadSnapshot) Error() string { // are semantically correct and the signatures are correct func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.MetaStore) error { kdb := keys.NewDB() - repo := tuf.NewTufRepo(kdb, nil) + repo := tuf.NewRepo(kdb, nil) rootRole := data.RoleName(data.CanonicalRootRole) targetsRole := data.RoleName(data.CanonicalTargetsRole) snapshotRole := data.RoleName(data.CanonicalSnapshotRole)