From 1346296869a0b9d2ab1ff906d74422664293a116 Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Fri, 3 Jul 2015 16:42:21 -0700 Subject: [PATCH 01/39] Initial libnotary refactor Signed-off-by: Diogo Monica Ported more functionality to libnotary --- {cmd/notary => client}/cli_crypto_service.go | 50 +- client/client.go | 596 +++++++++++++++++++ cmd/notary/keys.go | 42 +- cmd/notary/main.go | 27 +- cmd/notary/tuf.go | 419 ++----------- trustmanager/x509utils.go | 34 ++ 6 files changed, 723 insertions(+), 445 deletions(-) rename {cmd/notary => client}/cli_crypto_service.go (69%) create mode 100644 client/client.go diff --git a/cmd/notary/cli_crypto_service.go b/client/cli_crypto_service.go similarity index 69% rename from cmd/notary/cli_crypto_service.go rename to client/cli_crypto_service.go index 3387e619b2..f54111fa1d 100644 --- a/cmd/notary/cli_crypto_service.go +++ b/client/cli_crypto_service.go @@ -1,4 +1,4 @@ -package main +package client import ( "crypto" @@ -8,41 +8,42 @@ import ( "crypto/x509" "encoding/pem" "fmt" - "io/ioutil" "path/filepath" "github.com/docker/notary/trustmanager" "github.com/endophage/gotuf/data" - "github.com/spf13/viper" ) type CliCryptoService struct { - privateKeys map[string]*data.PrivateKey - gun string + gun string + keyStore trustmanager.FileStore } // NewCryptoService returns an instance ofS cliCryptoService -func NewCryptoService(gun string) *CliCryptoService { - return &CliCryptoService{privateKeys: make(map[string]*data.PrivateKey), gun: gun} +func NewCryptoService(gun string, keyStore trustmanager.FileStore) *CliCryptoService { + return &CliCryptoService{gun: gun, keyStore: keyStore} } // Create is used to generate keys for targets, snapshots and timestamps func (ccs *CliCryptoService) Create(role string) (*data.PublicKey, error) { - _, cert, err := generateKeyAndCert(ccs.gun) + keyData, pemCert, err := GenerateKeyAndCert(ccs.gun) if err != nil { return nil, err } - // PEM ENcode the certificate, which will be put directly inside of TUF's root.json - block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw} - pemdata := pem.EncodeToMemory(&block) - - // If this key has the role root, save it as a trusted certificate on our certificateStore - if role == "root" { - certificateStore.AddCertFromPEM(pemdata) + fingerprint, err := trustmanager.FingerprintPEMCert(pemCert) + if err != nil { + return nil, err } - return data.NewPublicKey("RSA", pemdata), nil + // The key is going to be stored in the private directory, using the GUN and + // the filename will be the TUF-compliant ID. The Store takes care of extensions. + privKeyFilename := filepath.Join(ccs.gun, fingerprint) + + // Store this private key + ccs.keyStore.Add(privKeyFilename, keyData) + + return data.NewPublicKey("RSA", pemCert), nil } // Sign returns the signatures for data with the given keyIDs @@ -54,9 +55,9 @@ func (ccs *CliCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa signatures := make([]data.Signature, 0, len(keyIDs)) for _, fingerprint := range keyIDs { // Get the PrivateKey filename - privKeyFilename := filepath.Join(viper.GetString("privDir"), ccs.gun, fingerprint+".key") + privKeyFilename := filepath.Join(ccs.gun, fingerprint) // Read PrivateKey from file - privPEMBytes, err := ioutil.ReadFile(privKeyFilename) + privPEMBytes, err := ccs.keyStore.Get(privKeyFilename) if err != nil { continue } @@ -84,8 +85,9 @@ func (ccs *CliCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa return signatures, nil } -// generateKeyAndCert deals with the creation and storage of a key and returns a cert -func generateKeyAndCert(gun string) (crypto.PrivateKey, *x509.Certificate, error) { +// generateKeyAndCert deals with the creation and storage of a key and returns a +// PEM encoded cert +func GenerateKeyAndCert(gun string) ([]byte, []byte, error) { // Generates a new RSA key key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { @@ -96,7 +98,7 @@ func generateKeyAndCert(gun string) (crypto.PrivateKey, *x509.Certificate, error // TUF-compliant keyID //TODO (diogo): We're hardcoding the Organization to be the GUN. Probably want to // change it - template := newCertificate(gun, gun) + template := trustmanager.NewCertificate(gun, gun) derBytes, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key) if err != nil { return nil, nil, fmt.Errorf("failed to generate the certificate for key: %v", err) @@ -108,14 +110,10 @@ func generateKeyAndCert(gun string) (crypto.PrivateKey, *x509.Certificate, error return nil, nil, fmt.Errorf("failed to generate the certificate for key: %v", err) } - fingerprint := trustmanager.FingerprintCert(cert) - // The key is going to be stored in the private directory, using the GUN and - // the filename will be the TUF-compliant ID. The Store takes care of extensions. - privKeyFilename := filepath.Join(gun, fingerprint) pemKey, err := trustmanager.KeyToPEM(key) if err != nil { return nil, nil, fmt.Errorf("failed to generate the certificate for key: %v", err) } - return key, cert, privKeyStore.Add(privKeyFilename, pemKey) + return pemKey, trustmanager.CertToPEM(cert), nil } diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000000..88f908aa4e --- /dev/null +++ b/client/client.go @@ -0,0 +1,596 @@ +package client + +import ( + "bytes" + "crypto/x509" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "path" + "path/filepath" + "strings" + "time" + + "github.com/docker/notary/trustmanager" + "github.com/endophage/gotuf" + tufclient "github.com/endophage/gotuf/client" + "github.com/endophage/gotuf/data" + "github.com/endophage/gotuf/keys" + "github.com/endophage/gotuf/signed" + "github.com/endophage/gotuf/store" + + "github.com/spf13/viper" +) + +// Default paths should end with a '/' so directory creation works correctly +const trustDir string = "/trusted_certificates/" +const privDir string = "/private/" +const tufDir string = "/tuf/" +const rootKeysDir string = "/root_keys/" + +// ErrRepositoryNotExist gets returned when trying to make an action over a repository +/// that doesn't exist +var ErrRepositoryNotExist = errors.New("repository does not exist") + +// Client is the interface that defines the Notary Client type +type Client interface { + ListPrivateKeys() []data.PrivateKey + GenRootKey(passphrase string) (*data.PublicKey, error) + GetRepository(gun string, baseURL string, transport http.RoundTripper) (Repository, error) +} + +// Repository is the interface that represents a Notary Repository +type Repository interface { + Update() error + Initialize(key *data.PublicKey) error + + AddTarget(target *Target) error + ListTargets() ([]*Target, error) + GetTargetByName(name string) (*Target, error) + + Publish() error +} + +type NotaryClient struct { + configFile string + caStore trustmanager.X509Store + certificateStore trustmanager.X509Store + rootKeyStore trustmanager.EncryptedFileStore +} + +type NotaryRepository struct { + Gun string + baseURL string + transport http.RoundTripper + signer *signed.Signer + tufRepo *tuf.TufRepo + fileStore store.MetadataStore + privKeyStore trustmanager.FileStore + caStore trustmanager.X509Store + certificateStore trustmanager.X509Store +} + +// Target represents a simplified version of the data TUF operates on. +type Target struct { + Name string + Hashes data.Hashes + Length int64 +} + +// NewTarget is a helper method that returns a Target +func NewTarget(targetName string, targetPath string) (*Target, error) { + b, err := ioutil.ReadFile(targetPath) + if err != nil { + return nil, err + } + + meta, err := data.NewFileMeta(bytes.NewBuffer(b)) + if err != nil { + return nil, err + } + + return &Target{Name: targetName, Hashes: meta.Hashes, Length: meta.Length}, nil +} + +// NewClient is a helper method that returns a new notary Client, given a config +// file. It makes the assumption that the base directory for the config file will +// be the place where trust information is being cached locally. +func NewClient(configFile string) (*NotaryClient, error) { + nClient := &NotaryClient{configFile: configFile} + err := nClient.loadKeys() + if err != nil { + return nil, err + } + + return nClient, nil +} + +// Update forces TUF to download the remote timestamps and verify if there are +// any remote changes. +func (r *NotaryRepository) Update() error { + return nil +} + +// Initialize creates a new repository by using rootKey as the root Key for the +// TUF repository. +func (r *NotaryRepository) Initialize(rootKey *data.PublicKey) error { + remote, err := getRemoteStore(r.Gun) + rawTSKey, err := remote.GetKey("timestamp") + if err != nil { + return err + } + fmt.Println("RawKey: ", string(rawTSKey)) + parsedKey := &data.TUFKey{} + err = json.Unmarshal(rawTSKey, parsedKey) + if err != nil { + return err + } + timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public()) + + targetsKey, err := r.signer.Create("targets") + if err != nil { + return err + } + snapshotKey, err := r.signer.Create("snapshot") + if err != nil { + return err + } + + kdb := keys.NewDB() + + kdb.AddKey(rootKey) + kdb.AddKey(targetsKey) + kdb.AddKey(snapshotKey) + kdb.AddKey(timestampKey) + + rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil) + if err != nil { + return err + } + targetsRole, err := data.NewRole("targets", 1, []string{targetsKey.ID()}, nil, nil) + if err != nil { + return err + } + snapshotRole, err := data.NewRole("snapshot", 1, []string{snapshotKey.ID()}, nil, nil) + if err != nil { + return err + } + timestampRole, err := data.NewRole("timestamp", 1, []string{timestampKey.ID()}, nil, nil) + if err != nil { + return err + } + + err = kdb.AddRole(rootRole) + if err != nil { + return err + } + err = kdb.AddRole(targetsRole) + if err != nil { + return err + } + err = kdb.AddRole(snapshotRole) + if err != nil { + return err + } + err = kdb.AddRole(timestampRole) + if err != nil { + return err + } + + r.tufRepo = tuf.NewTufRepo(kdb, r.signer) + + r.fileStore, err = store.NewFilesystemStore( + path.Join(viper.GetString("tufDir")), + "metadata", + "json", + "targets", + ) + if err != nil { + return err + } + + err = r.tufRepo.InitRepo(false) + if err != nil { + return err + } + + r.saveRepo() + return nil +} + +// AddTarget adds a new target to the repository, forcing a timestamps check from TUF +func (r *NotaryRepository) AddTarget(target *Target) error { + r.bootstrapRepo() + + fmt.Printf("Adding target \"%s\" with sha256 \"%s\" and size %d bytes.\n", target.Name, target.Hashes["sha256"], target.Length) + + meta := data.FileMeta{Length: target.Length, Hashes: target.Hashes} + _, err := r.tufRepo.AddTargets("targets", data.Files{target.Name: meta}) + if err != nil { + return err + } + + r.saveRepo() + + return nil +} + +// ListTargets lists all targets for the current repository +func (r *NotaryRepository) ListTargets() ([]*Target, error) { + r.bootstrapRepo() + + c, err := r.bootstrapClient() + if err != nil { + return nil, err + } + + err = c.Update() + if err != nil { + return nil, err + } + + // TODO(diogo): return hashes + for name, meta := range r.tufRepo.Targets["targets"].Signed.Targets { + fmt.Println(name, " ", meta.Hashes["sha256"], " ", meta.Length) + } + + return nil, nil +} + +// GetTargetByName returns a target given a name +func (r *NotaryRepository) GetTargetByName(name string) (*Target, error) { + r.bootstrapRepo() + + c, err := r.bootstrapClient() + if err != nil { + return nil, err + } + + err = c.Update() + if err != nil { + return nil, err + } + + meta := c.TargetMeta(name) + if meta == nil { + return nil, errors.New("Meta is nil for target") + } + + return &Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, nil +} + +// Publish pushes the local changes in signed material to the remote notary-server +func (r *NotaryRepository) Publish() error { + r.bootstrapRepo() + + remote, err := getRemoteStore(r.Gun) + + root, err := r.fileStore.GetMeta("root", 0) + if err != nil { + return err + } + targets, err := r.fileStore.GetMeta("targets", 0) + if err != nil { + return err + } + snapshot, err := r.fileStore.GetMeta("snapshot", 0) + if err != nil { + return err + } + + err = remote.SetMeta("root", root) + if err != nil { + return err + } + err = remote.SetMeta("targets", targets) + if err != nil { + return err + } + err = remote.SetMeta("snapshot", snapshot) + if err != nil { + return err + } + + return nil +} + +func (r *NotaryRepository) bootstrapRepo() error { + fileStore, err := store.NewFilesystemStore( + path.Join(viper.GetString("tufDir")), + "metadata", + "json", + "targets", + ) + if err != nil { + return err + } + + kdb := keys.NewDB() + tufRepo := tuf.NewTufRepo(kdb, r.signer) + + fmt.Println("Loading trusted collection.") + rootJSON, err := fileStore.GetMeta("root", 0) + if err != nil { + return err + } + root := &data.Signed{} + err = json.Unmarshal(rootJSON, root) + if err != nil { + return err + } + tufRepo.SetRoot(root) + targetsJSON, err := fileStore.GetMeta("targets", 0) + if err != nil { + return err + } + targets := &data.Signed{} + err = json.Unmarshal(targetsJSON, targets) + if err != nil { + return err + } + tufRepo.SetTargets("targets", targets) + snapshotJSON, err := fileStore.GetMeta("snapshot", 0) + if err != nil { + return err + } + snapshot := &data.Signed{} + err = json.Unmarshal(snapshotJSON, snapshot) + if err != nil { + return err + } + tufRepo.SetSnapshot(snapshot) + + r.tufRepo = tufRepo + r.fileStore = fileStore + + return nil +} + +func (r *NotaryRepository) saveRepo() error { + signedRoot, err := r.tufRepo.SignRoot(data.DefaultExpires("root")) + if err != nil { + return err + } + rootJSON, _ := json.Marshal(signedRoot) + r.fileStore.SetMeta("root", rootJSON) + + fmt.Println("Saving changes to Trusted Collection.") + + for t, _ := range r.tufRepo.Targets { + signedTargets, err := r.tufRepo.SignTargets(t, data.DefaultExpires("targets")) + if err != nil { + return err + } + targetsJSON, _ := json.Marshal(signedTargets) + parentDir := filepath.Dir(t) + os.MkdirAll(parentDir, 0755) + r.fileStore.SetMeta(t, targetsJSON) + } + + signedSnapshot, err := r.tufRepo.SignSnapshot(data.DefaultExpires("snapshot")) + if err != nil { + return err + } + snapshotJSON, _ := json.Marshal(signedSnapshot) + r.fileStore.SetMeta("snapshot", snapshotJSON) + + return nil +} + +/* +validateRoot iterates over every root key included in the TUF data and attempts +to validate the certificate by first checking for an exact match on the certificate +store, and subsequently trying to find a valid chain on the caStore. + +Example TUF Content for root role: +"roles" : { + "root" : { + "threshold" : 1, + "keyids" : [ + "e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38" + ] + }, + ... +} + +Example TUF Content for root key: +"e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38" : { + "keytype" : "RSA", + "keyval" : { + "private" : "", + "public" : "Base64-encoded, PEM encoded x509 Certificate" + } +} +*/ +func (r *NotaryRepository) ValidateRoot(root *data.Signed) error { + rootSigned := &data.Root{} + err := json.Unmarshal(root.Signed, rootSigned) + if err != nil { + return err + } + certs := make(map[string]*data.PublicKey) + for _, fingerprint := range rootSigned.Roles["root"].KeyIDs { + // TODO(dlaw): currently assuming only one cert contained in + // public key entry. Need to fix when we want to pass in chains. + k, _ := pem.Decode([]byte(rootSigned.Keys["kid"].Public())) + + decodedCerts, err := x509.ParseCertificates(k.Bytes) + if err != nil { + continue + } + + // TODO(diogo): Assuming that first certificate is the leaf-cert. Need to + // iterate over all decodedCerts and find a non-CA one (should be the last). + leafCert := decodedCerts[0] + leafID := trustmanager.FingerprintCert(leafCert) + + // Check to see if there is an exact match of this certificate. + // Checking the CommonName is not required since ID is calculated over + // Cert.Raw. It's included to prevent breaking logic with changes of how the + // ID gets computed. + _, err = r.certificateStore.GetCertificateByFingerprint(leafID) + if err == nil && leafCert.Subject.CommonName == r.Gun { + certs[fingerprint] = rootSigned.Keys[fingerprint] + } + + // Check to see if this leafCertificate has a chain to one of the Root CAs + // of our CA Store. + certList := []*x509.Certificate{leafCert} + err = trustmanager.Verify(r.caStore, r.Gun, certList) + if err == nil { + certs[fingerprint] = rootSigned.Keys[fingerprint] + } + } + _, err = signed.VerifyRoot(root, 0, certs, 1) + + return err +} + +func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { + remote, err := getRemoteStore(r.Gun) + if err != nil { + return nil, err + } + + rootJSON, err := remote.GetMeta("root", 5<<20) + root := &data.Signed{} + err = json.Unmarshal(rootJSON, root) + if err != nil { + return nil, err + } + + err = r.ValidateRoot(root) + if err != nil { + return nil, err + } + err = r.tufRepo.SetRoot(root) + if err != nil { + return nil, err + } + + // TODO(dlaw): Where does this keyDB come in + kdb := keys.NewDB() + + return tufclient.NewClient( + r.tufRepo, + remote, + kdb, + ), nil +} + +// ListPrivateKeys lists all availables private keys. Does not include private key +// material +func (c *NotaryClient) ListPrivateKeys() []data.PrivateKey { + // TODO(diogo): Make this work + for _, k := range c.rootKeyStore.ListAll() { + fmt.Println(k) + } + return nil +} + +// GenRootKey generates a new root key protected by a given passphrase +func (c *NotaryClient) GenRootKey(passphrase string) (*data.PublicKey, error) { + // When generating a root key, passing in a GUN to put into the cert + // doesn't make sense since this key can be used for multiple distinct + // repositories. + pemKey, _, err := GenerateKeyAndCert("TUF root key") + if err != nil { + return nil, fmt.Errorf("could not generate private key: %v", err) + } + + c.rootKeyStore.AddEncrypted("root", pemKey, passphrase) + + return data.NewPublicKey("RSA", pemKey), nil +} + +// GetRepository returns a new repository +func (c *NotaryClient) GetRepository(gun string, baseURL string, transport http.RoundTripper) (*NotaryRepository, error) { + privKeyStore, err := trustmanager.NewKeyFileStore(viper.GetString("privDir")) + if err != nil { + return nil, err + } + + signer := signed.NewSigner(NewCryptoService(gun, privKeyStore)) + + return &NotaryRepository{Gun: gun, + baseURL: baseURL, + transport: transport, + signer: signer, + caStore: c.caStore, + certificateStore: c.certificateStore}, nil +} + +func (c *NotaryClient) loadKeys() error { + filename := filepath.Base(c.configFile) + ext := filepath.Ext(c.configFile) + basePath := filepath.Dir(c.configFile) + + viper.SetConfigType(strings.TrimPrefix(ext, ".")) + viper.SetConfigName(strings.TrimSuffix(filename, ext)) + viper.AddConfigPath(basePath) + + // Find and read the config file + err := viper.ReadInConfig() + if err != nil { + // Ignore if the configuration file doesn't exist, we can use the defaults + if !os.IsNotExist(err) { + return err + } + } + + // Set up the defaults for our config + viper.SetDefault("trustDir", path.Join(basePath, path.Dir(trustDir))) + viper.SetDefault("privDir", path.Join(basePath, path.Dir(privDir))) + viper.SetDefault("tufDir", path.Join(basePath, path.Dir(tufDir))) + viper.SetDefault("rootKeysDir", path.Join(basePath, path.Dir(rootKeysDir))) + + // Get the final value for the CA directory + finalTrustDir := viper.GetString("trustDir") + finalRootKeysDir := viper.GetString("rootKeysDir") + + // Load all CAs that aren't expired and don't use SHA1 + c.caStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool { + return cert.IsCA && cert.BasicConstraintsValid && cert.SubjectKeyId != nil && + time.Now().Before(cert.NotAfter) && + cert.SignatureAlgorithm != x509.SHA1WithRSA && + cert.SignatureAlgorithm != x509.DSAWithSHA1 && + cert.SignatureAlgorithm != x509.ECDSAWithSHA1 + }) + if err != nil { + return err + } + + // Load all individual (non-CA) certificates that aren't expired and don't use SHA1 + c.certificateStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool { + return !cert.IsCA && + time.Now().Before(cert.NotAfter) && + cert.SignatureAlgorithm != x509.SHA1WithRSA && + cert.SignatureAlgorithm != x509.DSAWithSHA1 && + cert.SignatureAlgorithm != x509.ECDSAWithSHA1 + }) + if err != nil { + return err + } + + c.rootKeyStore, err = trustmanager.NewKeyFileStore(finalRootKeysDir) + if err != nil { + return err + } + + return nil +} + +// Use this to initialize remote HTTPStores from the config settings +func getRemoteStore(gun string) (store.RemoteStore, error) { + return store.NewHTTPStore( + "https://notary:4443/v2/"+gun+"/_trust/tuf/", + "", + "json", + "", + "key", + ) +} diff --git a/cmd/notary/keys.go b/cmd/notary/keys.go index f8ec61bcf7..897ea2472f 100644 --- a/cmd/notary/keys.go +++ b/cmd/notary/keys.go @@ -1,12 +1,9 @@ package main import ( - "crypto/rand" "crypto/x509" - "crypto/x509/pkix" "fmt" "math" - "math/big" "net/url" "os" "path/filepath" @@ -207,39 +204,14 @@ func keysGenerate(cmd *cobra.Command, args []string) { fatalf("invalid Global Unique Name: %s", gun) } - _, cert, err := generateKeyAndCert(gun) - if err != nil { - fatalf("could not generate key: %v", err) - } + // _, cert, err := generateKeyAndCert(gun) + // if err != nil { + // fatalf("could not generate key: %v", err) + // } - certificateStore.AddCert(cert) - fingerprint := trustmanager.FingerprintCert(cert) - fmt.Println("Generated new keypair with ID: ", fingerprint) -} - -func newCertificate(gun, organization string) *x509.Certificate { - notBefore := time.Now() - notAfter := notBefore.Add(time.Hour * 24 * 365 * 2) - - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - fatalf("failed to generate serial number: %s", err) - } - - return &x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - Organization: []string{organization}, - CommonName: gun, - }, - NotBefore: notBefore, - NotAfter: notAfter, - - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, - BasicConstraintsValid: true, - } + // certificateStore.AddCert(cert) + // fingerprint := trustmanager.FingerprintCert(cert) + // fmt.Println("Generated new keypair with ID: ", fingerprint) } func printCert(cert *x509.Certificate) { diff --git a/cmd/notary/main.go b/cmd/notary/main.go index a8bf9b61a1..f7f6895fc4 100644 --- a/cmd/notary/main.go +++ b/cmd/notary/main.go @@ -12,22 +12,20 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + notaryclient "github.com/docker/notary/client" "github.com/docker/notary/trustmanager" ) const configFileName string = "config" - -// Default paths should end with a '/' so directory creation works correctly const configPath string = ".docker/trust/" const trustDir string = configPath + "trusted_certificates/" const privDir string = configPath + "private/" -const tufDir string = configPath + "tuf/" - -var caStore trustmanager.X509Store -var certificateStore trustmanager.X509Store -var privKeyStore trustmanager.EncryptedFileStore var rawOutput bool +var nClient *notaryclient.NotaryClient +var caStore trustmanager.X509Store +var certificateStore trustmanager.X509Store +var privKeyStore trustmanager.FileStore func init() { logrus.SetLevel(logrus.DebugLevel) @@ -48,6 +46,7 @@ func init() { viper.SetConfigName(configFileName) viper.AddConfigPath(path.Join(homeDir, path.Dir(configPath))) viper.SetConfigType("json") + configFilePath := path.Join(homeDir, path.Dir(configPath)) + "/" + configFileName + ".json" // Find and read the config file err = viper.ReadInConfig() @@ -60,8 +59,6 @@ func init() { // Set up the defaults for our config viper.SetDefault("trustDir", path.Join(homeDir, path.Dir(trustDir))) - viper.SetDefault("privDir", path.Join(homeDir, path.Dir(privDir))) - viper.SetDefault("tufDir", path.Join(homeDir, path.Dir(tufDir))) // Get the final value for the CA directory finalTrustDir := viper.GetString("trustDir") @@ -79,7 +76,7 @@ func init() { fatalf("could not create X509FileStore: %v", err) } - // Load all individual (non-CA) certificates that aren't expired and don't use SHA1 + // Load all individual (nonCA) certificates that aren't expired and don't use SHA1 certificateStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool { return !cert.IsCA && time.Now().Before(cert.NotAfter) && @@ -96,6 +93,12 @@ func init() { fatalf("could not create FileStore: %v", err) } + // TODO(diogo): Client should receive the config + nClient, err = notaryclient.NewClient(configFilePath) + if err != nil { + fatalf("could not create FileStore: %v", err) + } + } func main() { @@ -108,13 +111,13 @@ func main() { NotaryCmd.AddCommand(cmdKeys) NotaryCmd.AddCommand(cmdTufInit) NotaryCmd.AddCommand(cmdTufList) - cmdTufList.Flags().BoolVarP(&rawOutput, "raw", "", false, "Instructs notary list to output a non-pretty printed version of the targets list. Useful if you need to parse the list.") + cmdTufList.Flags().BoolVarP(&rawOutput, "raw", "", false, "Instructs notary list to output a nonpretty printed version of the targets list. Useful if you need to parse the list.") NotaryCmd.AddCommand(cmdTufAdd) NotaryCmd.AddCommand(cmdTufRemove) NotaryCmd.AddCommand(cmdTufPublish) cmdTufPublish.Flags().StringVarP(&remoteTrustServer, "remote", "r", "", "Remote trust server location") NotaryCmd.AddCommand(cmdTufLookup) - cmdTufLookup.Flags().BoolVarP(&rawOutput, "raw", "", false, "Instructs notary lookup to output a non-pretty printed version of the targets list. Useful if you need to parse the list.") + cmdTufLookup.Flags().BoolVarP(&rawOutput, "raw", "", false, "Instructs notary lookup to output a nonpretty printed version of the targets list. Useful if you need to parse the list.") cmdTufLookup.Flags().StringVarP(&remoteTrustServer, "remote", "r", "", "Remote trust server location") NotaryCmd.AddCommand(cmdVerify) diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 958a0d68ae..8309a5ee12 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -1,27 +1,16 @@ package main import ( - "bytes" "crypto/sha256" - "crypto/x509" - "encoding/json" - "encoding/pem" "fmt" "io/ioutil" + "net/http" "os" - "path" - "path/filepath" "github.com/Sirupsen/logrus" - "github.com/docker/notary/trustmanager" - "github.com/endophage/gotuf" - "github.com/endophage/gotuf/client" - "github.com/endophage/gotuf/data" - "github.com/endophage/gotuf/keys" - "github.com/endophage/gotuf/signed" + notaryclient "github.com/docker/notary/client" "github.com/endophage/gotuf/store" "github.com/spf13/cobra" - "github.com/spf13/viper" ) var remoteTrustServer string @@ -84,30 +73,19 @@ func tufAdd(cmd *cobra.Command, args []string) { gun := args[0] targetName := args[1] targetPath := args[2] - kdb := keys.NewDB() - signer := signed.NewSigner(NewCryptoService(gun)) - repo := tuf.NewTufRepo(kdb, signer) - b, err := ioutil.ReadFile(targetPath) + t := &http.Transport{} + repo, err := nClient.GetRepository(gun, "", t) if err != nil { fatalf(err.Error()) } - filestore := bootstrapRepo(gun, repo) - - fmt.Println("Generating metadata for target") - meta, err := data.NewFileMeta(bytes.NewBuffer(b)) + target, err := notaryclient.NewTarget(targetName, targetPath) if err != nil { fatalf(err.Error()) } - - fmt.Printf("Adding target \"%s\" with sha256 \"%s\" and size %d bytes.\n", targetName, meta.Hashes["sha256"], meta.Length) - _, err = repo.AddTargets("targets", data.Files{targetName: meta}) - if err != nil { - fatalf(err.Error()) - } - - saveRepo(repo, filestore) + repo.AddTarget(target) + fmt.Println("Successfully added targets") } func tufInit(cmd *cobra.Command, args []string) { @@ -116,92 +94,19 @@ func tufInit(cmd *cobra.Command, args []string) { fatalf("Must specify a GUN") } - gun := args[0] - kdb := keys.NewDB() - signer := signed.NewSigner(NewCryptoService(gun)) - - remote, err := getRemoteStore(gun) - rawTSKey, err := remote.GetKey("timestamp") - if err != nil { - fatalf(err.Error()) - } - fmt.Println("RawKey: ", string(rawTSKey)) - parsedKey := &data.TUFKey{} - err = json.Unmarshal(rawTSKey, parsedKey) - if err != nil { - fatalf(err.Error()) - } - timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public()) - - rootKey, err := signer.Create("root") - if err != nil { - fatalf(err.Error()) - } - targetsKey, err := signer.Create("targets") - if err != nil { - fatalf(err.Error()) - } - snapshotKey, err := signer.Create("snapshot") + t := &http.Transport{} + repo, err := nClient.GetRepository(args[0], "", t) if err != nil { fatalf(err.Error()) } - kdb.AddKey(rootKey) - kdb.AddKey(targetsKey) - kdb.AddKey(snapshotKey) - kdb.AddKey(timestampKey) - - rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil) + // TODO(diogo): We don't want to generate a new root every time. Ask the user + // which key she wants to use if there > 0 root keys available. + newRootKey, err := nClient.GenRootKey("passphrase") if err != nil { fatalf(err.Error()) } - targetsRole, err := data.NewRole("targets", 1, []string{targetsKey.ID()}, nil, nil) - if err != nil { - fatalf(err.Error()) - } - snapshotRole, err := data.NewRole("snapshot", 1, []string{snapshotKey.ID()}, nil, nil) - if err != nil { - fatalf(err.Error()) - } - timestampRole, err := data.NewRole("timestamp", 1, []string{timestampKey.ID()}, nil, nil) - if err != nil { - fatalf(err.Error()) - } - - err = kdb.AddRole(rootRole) - if err != nil { - fatalf(err.Error()) - } - err = kdb.AddRole(targetsRole) - if err != nil { - fatalf(err.Error()) - } - err = kdb.AddRole(snapshotRole) - if err != nil { - fatalf(err.Error()) - } - err = kdb.AddRole(timestampRole) - if err != nil { - fatalf(err.Error()) - } - - repo := tuf.NewTufRepo(kdb, signer) - - filestore, err := store.NewFilesystemStore( - path.Join(viper.GetString("tufDir"), gun), // TODO: base trust dir from config - "metadata", - "json", - "targets", - ) - if err != nil { - fatalf(err.Error()) - } - - err = repo.InitRepo(false) - if err != nil { - fatalf(err.Error()) - } - saveRepo(repo, filestore) + repo.Initialize(newRootKey) } func tufList(cmd *cobra.Command, args []string) { @@ -210,32 +115,15 @@ func tufList(cmd *cobra.Command, args []string) { fatalf("must specify a GUN") } gun := args[0] - kdb := keys.NewDB() - repo := tuf.NewTufRepo(kdb, nil) - remote, err := getRemoteStore(gun) + t := &http.Transport{} + repo, err := nClient.GetRepository(gun, "", t) if err != nil { - return - } - c, err := bootstrapClient(gun, remote, repo, kdb) - if err != nil { - return - } - err = c.Update() - if err != nil { - logrus.Error("Error updating client: ", err.Error()) - return + fatalf(err.Error()) } - if rawOutput { - for name, meta := range repo.Targets["targets"].Signed.Targets { - fmt.Println(name, " ", meta.Hashes["sha256"], " ", meta.Length) - } - } else { - for name, meta := range repo.Targets["targets"].Signed.Targets { - fmt.Println(name, " ", meta.Hashes["sha256"], " ", meta.Length) - } - } + // TODO(diogo): Parse Targets and print them + _, _ = repo.ListTargets() } func tufLookup(cmd *cobra.Command, args []string) { @@ -245,29 +133,20 @@ func tufLookup(cmd *cobra.Command, args []string) { } gun := args[0] targetName := args[1] - kdb := keys.NewDB() - repo := tuf.NewTufRepo(kdb, nil) - remote, err := getRemoteStore(gun) - c, err := bootstrapClient(gun, remote, repo, kdb) + t := &http.Transport{} + repo, err := nClient.GetRepository(gun, "", t) if err != nil { - return + fatalf(err.Error()) } - err = c.Update() + + // TODO(diogo): Parse Targets and print them + target, err := repo.GetTargetByName(targetName) if err != nil { - logrus.Error("Error updating client: ", err.Error()) - return - } - meta := c.TargetMeta(targetName) - if meta == nil { - logrus.Infof("Target %s not found in %s.", targetName, gun) - return - } - if rawOutput { - fmt.Println(targetName, fmt.Sprintf("sha256:%s", meta.Hashes["sha256"]), meta.Length) - } else { - fmt.Println(targetName, fmt.Sprintf("sha256:%s", meta.Hashes["sha256"]), meta.Length) + fatalf(err.Error()) } + + fmt.Println(target.Name, fmt.Sprintf("sha256:%s", target.Hashes["sha256"]), target.Length) } func tufPublish(cmd *cobra.Command, args []string) { @@ -277,44 +156,16 @@ func tufPublish(cmd *cobra.Command, args []string) { } gun := args[0] + fmt.Println("Pushing changes to ", gun, ".") - remote, err := getRemoteStore(gun) - filestore, err := store.NewFilesystemStore( - path.Join(viper.GetString("tufDir"), gun), - "metadata", - "json", - "targets", - ) + t := &http.Transport{} + repo, err := nClient.GetRepository(gun, "", t) if err != nil { fatalf(err.Error()) } - root, err := filestore.GetMeta("root", 0) - if err != nil { - fatalf(err.Error()) - } - targets, err := filestore.GetMeta("targets", 0) - if err != nil { - fatalf(err.Error()) - } - snapshot, err := filestore.GetMeta("snapshot", 0) - if err != nil { - fatalf(err.Error()) - } - - err = remote.SetMeta("root", root) - if err != nil { - fatalf(err.Error()) - } - err = remote.SetMeta("targets", targets) - if err != nil { - fatalf(err.Error()) - } - err = remote.SetMeta("snapshot", snapshot) - if err != nil { - fatalf(err.Error()) - } + repo.Publish() } func tufRemove(cmd *cobra.Command, args []string) { @@ -324,20 +175,19 @@ func tufRemove(cmd *cobra.Command, args []string) { } gun := args[0] targetName := args[1] - kdb := keys.NewDB() - signer := signed.NewSigner(NewCryptoService(gun)) - repo := tuf.NewTufRepo(kdb, signer) - fmt.Println("Removing target ", targetName, " from ", gun) - - filestore := bootstrapRepo(gun, repo) - - err := repo.RemoveTargets("targets", targetName) + t := &http.Transport{} + _, err := nClient.GetRepository(gun, "", t) if err != nil { fatalf(err.Error()) } - saveRepo(repo, filestore) + // TODO(diogo): Implement RemoveTargets in libnotary + fmt.Println("Removing target ", targetName, " from ", gun) + // repo.RemoveTargets("targets", targetName) + // if err != nil { + // fatalf(err.Error()) + // } } func verify(cmd *cobra.Command, args []string) { @@ -356,31 +206,22 @@ func verify(cmd *cobra.Command, args []string) { //TODO (diogo): This code is copy/pasted from lookup. gun := args[0] targetName := args[1] - kdb := keys.NewDB() - repo := tuf.NewTufRepo(kdb, nil) - - remote, err := getRemoteStore(gun) - - c, err := bootstrapClient(gun, remote, repo, kdb) + t := &http.Transport{} + repo, err := nClient.GetRepository(gun, "", t) if err != nil { - logrus.Error("Unable to setup client.") - return - } - - err = c.Update() - if err != nil { - fmt.Println("Update failed") fatalf(err.Error()) } - meta := c.TargetMeta(targetName) - if meta == nil { + + // TODO(diogo): Parse Targets and print them + target, err := repo.GetTargetByName(targetName) + if err != nil { logrus.Error("notary: data not present in the trusted collection.") - os.Exit(1) + os.Exit(-11) } // Create hasher and hash data stdinHash := fmt.Sprintf("sha256:%x", sha256.Sum256(payload)) - serverHash := fmt.Sprintf("sha256:%s", meta.Hashes["sha256"]) + serverHash := fmt.Sprintf("sha256:%s", target.Hashes["sha256"]) if stdinHash != serverHash { logrus.Error("notary: data not present in the trusted collection.") os.Exit(1) @@ -390,172 +231,6 @@ func verify(cmd *cobra.Command, args []string) { return } -func saveRepo(repo *tuf.TufRepo, filestore store.MetadataStore) error { - fmt.Println("Saving changes to Trusted Collection.") - signedRoot, err := repo.SignRoot(data.DefaultExpires("root")) - if err != nil { - return err - } - rootJSON, _ := json.Marshal(signedRoot) - filestore.SetMeta("root", rootJSON) - - for r, _ := range repo.Targets { - signedTargets, err := repo.SignTargets(r, data.DefaultExpires("targets")) - if err != nil { - return err - } - targetsJSON, _ := json.Marshal(signedTargets) - parentDir := filepath.Dir(r) - os.MkdirAll(parentDir, 0755) - filestore.SetMeta(r, targetsJSON) - } - - signedSnapshot, err := repo.SignSnapshot(data.DefaultExpires("snapshot")) - if err != nil { - return err - } - snapshotJSON, _ := json.Marshal(signedSnapshot) - filestore.SetMeta("snapshot", snapshotJSON) - - return nil -} - -func bootstrapClient(gun string, remote store.RemoteStore, repo *tuf.TufRepo, kdb *keys.KeyDB) (*client.Client, error) { - rootJSON, err := remote.GetMeta("root", 5<<20) - root := &data.Signed{} - err = json.Unmarshal(rootJSON, root) - if err != nil { - return nil, err - } - err = validateRoot(gun, root) - if err != nil { - return nil, err - } - err = repo.SetRoot(root) - if err != nil { - return nil, err - } - return client.NewClient( - repo, - remote, - kdb, - ), nil -} - -/* -validateRoot iterates over every root key included in the TUF data and attempts -to validate the certificate by first checking for an exact match on the certificate -store, and subsequently trying to find a valid chain on the caStore. - -Example TUF Content for root role: -"roles" : { - "root" : { - "threshold" : 1, - "keyids" : [ - "e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38" - ] - }, - ... -} - -Example TUF Content for root key: -"e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38" : { - "keytype" : "RSA", - "keyval" : { - "private" : "", - "public" : "Base64-encoded, PEM encoded x509 Certificate" - } -} -*/ -func validateRoot(gun string, root *data.Signed) error { - rootSigned := &data.Root{} - err := json.Unmarshal(root.Signed, rootSigned) - if err != nil { - return err - } - certs := make(map[string]*data.PublicKey) - for _, fingerprint := range rootSigned.Roles["root"].KeyIDs { - // TODO(dlaw): currently assuming only one cert contained in - // public key entry. Need to fix when we want to pass in chains. - k, _ := pem.Decode([]byte(rootSigned.Keys["kid"].Public())) - - decodedCerts, err := x509.ParseCertificates(k.Bytes) - if err != nil { - continue - } - - // TODO(diogo): Assuming that first certificate is the leaf-cert. Need to - // iterate over all decodedCerts and find a non-CA one (should be the last). - leafCert := decodedCerts[0] - leafID := trustmanager.FingerprintCert(leafCert) - - // Check to see if there is an exact match of this certificate. - // Checking the CommonName is not required since ID is calculated over - // Cert.Raw. It's included to prevent breaking logic with changes of how the - // ID gets computed. - _, err = certificateStore.GetCertificateByFingerprint(leafID) - if err == nil && leafCert.Subject.CommonName == gun { - certs[fingerprint] = rootSigned.Keys[fingerprint] - } - - // Check to see if this leafCertificate has a chain to one of the Root CAs - // of our CA Store. - certList := []*x509.Certificate{leafCert} - err = trustmanager.Verify(caStore, gun, certList) - if err == nil { - certs[fingerprint] = rootSigned.Keys[fingerprint] - } - } - _, err = signed.VerifyRoot(root, 0, certs, 1) - - return err -} - -func bootstrapRepo(gun string, repo *tuf.TufRepo) store.MetadataStore { - filestore, err := store.NewFilesystemStore( - path.Join(viper.GetString("tufDir"), gun), - "metadata", - "json", - "targets", - ) - if err != nil { - fatalf(err.Error()) - } - - fmt.Println("Loading trusted collection.") - rootJSON, err := filestore.GetMeta("root", 0) - if err != nil { - fatalf(err.Error()) - } - root := &data.Signed{} - err = json.Unmarshal(rootJSON, root) - if err != nil { - fatalf(err.Error()) - } - repo.SetRoot(root) - targetsJSON, err := filestore.GetMeta("targets", 0) - if err != nil { - fatalf(err.Error()) - } - targets := &data.Signed{} - err = json.Unmarshal(targetsJSON, targets) - if err != nil { - fatalf(err.Error()) - } - repo.SetTargets("targets", targets) - snapshotJSON, err := filestore.GetMeta("snapshot", 0) - if err != nil { - fatalf(err.Error()) - } - snapshot := &data.Signed{} - err = json.Unmarshal(snapshotJSON, snapshot) - if err != nil { - fatalf(err.Error()) - } - repo.SetSnapshot(snapshot) - return filestore -} - // Use this to initialize remote HTTPStores from the config settings func getRemoteStore(gun string) (store.RemoteStore, error) { return store.NewHTTPStore( diff --git a/trustmanager/x509utils.go b/trustmanager/x509utils.go index 9e2eed0c8d..fe5099cfe6 100644 --- a/trustmanager/x509utils.go +++ b/trustmanager/x509utils.go @@ -5,12 +5,15 @@ import ( "crypto/rand" "crypto/rsa" "crypto/x509" + "crypto/x509/pkix" "encoding/pem" "errors" "fmt" "io/ioutil" + "math/big" "net/http" "net/url" + "time" "github.com/endophage/gotuf/data" ) @@ -117,6 +120,14 @@ func loadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) { return nil, errors.New("no certificates found in PEM data") } +func FingerprintPEMCert(pemCert []byte) (string, error) { + cert, err := loadCertFromPEM(pemCert) + if err != nil { + return "", err + } + return FingerprintCert(cert), nil +} + // FingerprintCert returns a TUF compliant fingerprint for a X509 Certificate func FingerprintCert(cert *x509.Certificate) string { return string(fingerprintCert(cert)) @@ -215,3 +226,26 @@ func ParsePEMEncryptedPrivateKey(pemBytes []byte, passphrase string) (crypto.Pri return nil, fmt.Errorf("unsupported key type %q", block.Type) } } + +func NewCertificate(gun, organization string) *x509.Certificate { + notBefore := time.Now() + notAfter := notBefore.Add(time.Hour * 24 * 365 * 2) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + // TODO(diogo): Don't silently ignore this error + serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit) + + return &x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{organization}, + CommonName: gun, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + BasicConstraintsValid: true, + } +} From 30c085626646b30b09daf0c1086c0639acf0a4fa Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Mon, 6 Jul 2015 21:39:12 -0700 Subject: [PATCH 02/39] Remove config from libnotary --- client/client.go | 48 ++++++++++++---------------------------------- cmd/notary/main.go | 5 +++-- 2 files changed, 15 insertions(+), 38 deletions(-) diff --git a/client/client.go b/client/client.go index 88f908aa4e..924ea1e26e 100644 --- a/client/client.go +++ b/client/client.go @@ -12,7 +12,6 @@ import ( "os" "path" "path/filepath" - "strings" "time" "github.com/docker/notary/trustmanager" @@ -56,7 +55,6 @@ type Repository interface { } type NotaryClient struct { - configFile string caStore trustmanager.X509Store certificateStore trustmanager.X509Store rootKeyStore trustmanager.EncryptedFileStore @@ -99,9 +97,10 @@ func NewTarget(targetName string, targetPath string) (*Target, error) { // NewClient is a helper method that returns a new notary Client, given a config // file. It makes the assumption that the base directory for the config file will // be the place where trust information is being cached locally. -func NewClient(configFile string) (*NotaryClient, error) { - nClient := &NotaryClient{configFile: configFile} - err := nClient.loadKeys() +func NewClient(trustDir, rootKeysDir string) (*NotaryClient, error) { + nClient := &NotaryClient{} + + err := nClient.loadKeys(trustDir, rootKeysDir) if err != nil { return nil, err } @@ -524,36 +523,9 @@ func (c *NotaryClient) GetRepository(gun string, baseURL string, transport http. certificateStore: c.certificateStore}, nil } -func (c *NotaryClient) loadKeys() error { - filename := filepath.Base(c.configFile) - ext := filepath.Ext(c.configFile) - basePath := filepath.Dir(c.configFile) - - viper.SetConfigType(strings.TrimPrefix(ext, ".")) - viper.SetConfigName(strings.TrimSuffix(filename, ext)) - viper.AddConfigPath(basePath) - - // Find and read the config file - err := viper.ReadInConfig() - if err != nil { - // Ignore if the configuration file doesn't exist, we can use the defaults - if !os.IsNotExist(err) { - return err - } - } - - // Set up the defaults for our config - viper.SetDefault("trustDir", path.Join(basePath, path.Dir(trustDir))) - viper.SetDefault("privDir", path.Join(basePath, path.Dir(privDir))) - viper.SetDefault("tufDir", path.Join(basePath, path.Dir(tufDir))) - viper.SetDefault("rootKeysDir", path.Join(basePath, path.Dir(rootKeysDir))) - - // Get the final value for the CA directory - finalTrustDir := viper.GetString("trustDir") - finalRootKeysDir := viper.GetString("rootKeysDir") - +func (c *NotaryClient) loadKeys(trustDir, rootKeysDir string) error { // Load all CAs that aren't expired and don't use SHA1 - c.caStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool { + caStore, err := trustmanager.NewX509FilteredFileStore(trustDir, func(cert *x509.Certificate) bool { return cert.IsCA && cert.BasicConstraintsValid && cert.SubjectKeyId != nil && time.Now().Before(cert.NotAfter) && cert.SignatureAlgorithm != x509.SHA1WithRSA && @@ -565,7 +537,7 @@ func (c *NotaryClient) loadKeys() error { } // Load all individual (non-CA) certificates that aren't expired and don't use SHA1 - c.certificateStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool { + certificateStore, err := trustmanager.NewX509FilteredFileStore(trustDir, func(cert *x509.Certificate) bool { return !cert.IsCA && time.Now().Before(cert.NotAfter) && cert.SignatureAlgorithm != x509.SHA1WithRSA && @@ -576,11 +548,15 @@ func (c *NotaryClient) loadKeys() error { return err } - c.rootKeyStore, err = trustmanager.NewKeyFileStore(finalRootKeysDir) + rootKeyStore, err := trustmanager.NewKeyFileStore(rootKeysDir) if err != nil { return err } + c.caStore = caStore + c.certificateStore = certificateStore + c.rootKeyStore = rootKeyStore + return nil } diff --git a/cmd/notary/main.go b/cmd/notary/main.go index f7f6895fc4..150065d203 100644 --- a/cmd/notary/main.go +++ b/cmd/notary/main.go @@ -20,6 +20,7 @@ const configFileName string = "config" const configPath string = ".docker/trust/" const trustDir string = configPath + "trusted_certificates/" const privDir string = configPath + "private/" +const rootKeysDir string = configPath + "root_keys/" var rawOutput bool var nClient *notaryclient.NotaryClient @@ -46,7 +47,6 @@ func init() { viper.SetConfigName(configFileName) viper.AddConfigPath(path.Join(homeDir, path.Dir(configPath))) viper.SetConfigType("json") - configFilePath := path.Join(homeDir, path.Dir(configPath)) + "/" + configFileName + ".json" // Find and read the config file err = viper.ReadInConfig() @@ -63,6 +63,7 @@ func init() { // Get the final value for the CA directory finalTrustDir := viper.GetString("trustDir") finalPrivDir := viper.GetString("privDir") + finalRootKeysDir := viper.GetString("rootKeysDir") // Load all CAs that aren't expired and don't use SHA1 caStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool { @@ -94,7 +95,7 @@ func init() { } // TODO(diogo): Client should receive the config - nClient, err = notaryclient.NewClient(configFilePath) + nClient, err = notaryclient.NewClient(finalTrustDir, finalRootKeysDir) if err != nil { fatalf("could not create FileStore: %v", err) } From 93f7d9911f129bcc2e2d1a1e76edae649d0bb8bb Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Mon, 6 Jul 2015 22:23:04 -0700 Subject: [PATCH 03/39] Implementing ListTargets --- client/client.go | 28 ++++++++++++++++------------ cmd/notary/main.go | 25 +++++++++++-------------- cmd/notary/tuf.go | 12 ++++++++++-- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/client/client.go b/client/client.go index 924ea1e26e..c8df05e50c 100644 --- a/client/client.go +++ b/client/client.go @@ -10,7 +10,6 @@ import ( "io/ioutil" "net/http" "os" - "path" "path/filepath" "time" @@ -21,8 +20,6 @@ import ( "github.com/endophage/gotuf/keys" "github.com/endophage/gotuf/signed" "github.com/endophage/gotuf/store" - - "github.com/spf13/viper" ) // Default paths should end with a '/' so directory creation works correctly @@ -55,6 +52,7 @@ type Repository interface { } type NotaryClient struct { + baseDir string caStore trustmanager.X509Store certificateStore trustmanager.X509Store rootKeyStore trustmanager.EncryptedFileStore @@ -63,6 +61,7 @@ type NotaryClient struct { type NotaryRepository struct { Gun string baseURL string + tufRepoPath string transport http.RoundTripper signer *signed.Signer tufRepo *tuf.TufRepo @@ -97,8 +96,11 @@ func NewTarget(targetName string, targetPath string) (*Target, error) { // NewClient is a helper method that returns a new notary Client, given a config // file. It makes the assumption that the base directory for the config file will // be the place where trust information is being cached locally. -func NewClient(trustDir, rootKeysDir string) (*NotaryClient, error) { - nClient := &NotaryClient{} +func NewClient(baseDir string) (*NotaryClient, error) { + trustDir := filepath.Join(baseDir, trustDir) + rootKeysDir := filepath.Join(baseDir, rootKeysDir) + + nClient := &NotaryClient{baseDir: baseDir} err := nClient.loadKeys(trustDir, rootKeysDir) if err != nil { @@ -183,7 +185,7 @@ func (r *NotaryRepository) Initialize(rootKey *data.PublicKey) error { r.tufRepo = tuf.NewTufRepo(kdb, r.signer) r.fileStore, err = store.NewFilesystemStore( - path.Join(viper.GetString("tufDir")), + r.tufRepoPath, "metadata", "json", "targets", @@ -232,12 +234,13 @@ func (r *NotaryRepository) ListTargets() ([]*Target, error) { return nil, err } - // TODO(diogo): return hashes + targetList := make([]*Target, 0) for name, meta := range r.tufRepo.Targets["targets"].Signed.Targets { - fmt.Println(name, " ", meta.Hashes["sha256"], " ", meta.Length) + target := &Target{Name: name, Hashes: meta.Hashes, Length: meta.Length} + targetList = append(targetList, target) } - return nil, nil + return targetList, nil } // GetTargetByName returns a target given a name @@ -299,7 +302,7 @@ func (r *NotaryRepository) Publish() error { func (r *NotaryRepository) bootstrapRepo() error { fileStore, err := store.NewFilesystemStore( - path.Join(viper.GetString("tufDir")), + r.tufRepoPath, "metadata", "json", "targets", @@ -415,7 +418,7 @@ func (r *NotaryRepository) ValidateRoot(root *data.Signed) error { for _, fingerprint := range rootSigned.Roles["root"].KeyIDs { // TODO(dlaw): currently assuming only one cert contained in // public key entry. Need to fix when we want to pass in chains. - k, _ := pem.Decode([]byte(rootSigned.Keys["kid"].Public())) + k, _ := pem.Decode([]byte(rootSigned.Keys[fingerprint].Public())) decodedCerts, err := x509.ParseCertificates(k.Bytes) if err != nil { @@ -508,7 +511,7 @@ func (c *NotaryClient) GenRootKey(passphrase string) (*data.PublicKey, error) { // GetRepository returns a new repository func (c *NotaryClient) GetRepository(gun string, baseURL string, transport http.RoundTripper) (*NotaryRepository, error) { - privKeyStore, err := trustmanager.NewKeyFileStore(viper.GetString("privDir")) + privKeyStore, err := trustmanager.NewKeyFileStore(filepath.Join(c.baseDir, privDir)) if err != nil { return nil, err } @@ -517,6 +520,7 @@ func (c *NotaryClient) GetRepository(gun string, baseURL string, transport http. return &NotaryRepository{Gun: gun, baseURL: baseURL, + tufRepoPath: filepath.Join(c.baseDir, tufDir), transport: transport, signer: signer, caStore: c.caStore, diff --git a/cmd/notary/main.go b/cmd/notary/main.go index 150065d203..4ab8c0d0d2 100644 --- a/cmd/notary/main.go +++ b/cmd/notary/main.go @@ -18,9 +18,9 @@ import ( const configFileName string = "config" const configPath string = ".docker/trust/" -const trustDir string = configPath + "trusted_certificates/" -const privDir string = configPath + "private/" -const rootKeysDir string = configPath + "root_keys/" +const trustDir string = "trusted_certificates/" +const privDir string = "private/" +const rootKeysDir string = "root_keys/" var rawOutput bool var nClient *notaryclient.NotaryClient @@ -58,12 +58,11 @@ func init() { } // Set up the defaults for our config - viper.SetDefault("trustDir", path.Join(homeDir, path.Dir(trustDir))) + viper.SetDefault("baseTrustDir", path.Join(homeDir, path.Dir(configPath))) // Get the final value for the CA directory - finalTrustDir := viper.GetString("trustDir") - finalPrivDir := viper.GetString("privDir") - finalRootKeysDir := viper.GetString("rootKeysDir") + finalTrustDir := path.Join(viper.GetString("baseTrustDir"), trustDir) + finalPrivDir := path.Join(viper.GetString("baseTrustDir"), privDir) // Load all CAs that aren't expired and don't use SHA1 caStore, err = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool { @@ -74,7 +73,7 @@ func init() { cert.SignatureAlgorithm != x509.ECDSAWithSHA1 }) if err != nil { - fatalf("could not create X509FileStore: %v", err) + fatalf("could not create CA X509FileStore: %v", err) } // Load all individual (nonCA) certificates that aren't expired and don't use SHA1 @@ -86,20 +85,18 @@ func init() { cert.SignatureAlgorithm != x509.ECDSAWithSHA1 }) if err != nil { - fatalf("could not create X509FileStore: %v", err) + fatalf("could not create Certificate X509FileStore: %v", err) } privKeyStore, err = trustmanager.NewKeyFileStore(finalPrivDir) if err != nil { - fatalf("could not create FileStore: %v", err) + fatalf("could not create KeyFileStore: %v", err) } - // TODO(diogo): Client should receive the config - nClient, err = notaryclient.NewClient(finalTrustDir, finalRootKeysDir) + nClient, err = notaryclient.NewClient(viper.GetString("baseTrustDir")) if err != nil { - fatalf("could not create FileStore: %v", err) + fatalf("could not create Notary Client: %v", err) } - } func main() { diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 8309a5ee12..28083140be 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -122,8 +122,16 @@ func tufList(cmd *cobra.Command, args []string) { fatalf(err.Error()) } - // TODO(diogo): Parse Targets and print them - _, _ = repo.ListTargets() + // Retreive the remote list of signed targets + targetList, err := repo.ListTargets() + if err != nil { + fatalf(err.Error()) + } + + // Print all the available targets + for _, t := range targetList { + fmt.Println(t.Name, " ", t.Hashes["sha256"], " ", t.Length) + } } func tufLookup(cmd *cobra.Command, args []string) { From e66dc12ecad7179adb7fd38f4b184fd468735d4c Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Tue, 7 Jul 2015 15:39:12 -0700 Subject: [PATCH 04/39] More refactor --- Godeps/Godeps.json | 2 +- .../endophage/gotuf/store/errors.go | 13 ++ .../endophage/gotuf/store/httpstore.go | 4 + .../src/github.com/endophage/gotuf/tuf.go | 68 +++++--- .../github.com/endophage/gotuf/tuf_test.go | 8 +- client/cli_crypto_service.go | 121 ++++++++------ client/client.go | 153 +++++++++++++----- cmd/notary/tuf.go | 9 +- trustmanager/keyfilestore.go | 4 +- 9 files changed, 264 insertions(+), 118 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/endophage/gotuf/store/errors.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 14ce8748df..99953fa538 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -47,7 +47,7 @@ }, { "ImportPath": "github.com/endophage/gotuf", - "Rev": "682ec56d6a7b60e432bc2560e17d8e1aec84d171" + "Rev": "060b7012cc28711473151872690ce9a0be9ab501" }, { "ImportPath": "github.com/go-sql-driver/mysql", diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/store/errors.go b/Godeps/_workspace/src/github.com/endophage/gotuf/store/errors.go new file mode 100644 index 0000000000..69d2090f04 --- /dev/null +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/store/errors.go @@ -0,0 +1,13 @@ +package store + +import ( + "fmt" +) + +type ErrMetaNotFound struct { + role string +} + +func (err ErrMetaNotFound) Error() string { + return fmt.Sprintf("no metadata for %s", err.role) +} 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 fcc2d838fb..86299ba911 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http" "net/url" "path" @@ -61,6 +62,9 @@ func (s HTTPStore) GetMeta(name string, size int64) (json.RawMessage, error) { return nil, err } defer resp.Body.Close() + if resp.StatusCode == http.StatusNotFound { + return nil, &ErrMetaNotFound{role: name} + } b := io.LimitReader(resp.Body, int64(size)) body, err := ioutil.ReadAll(b) diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go b/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go index efca615620..cd95fc9e1e 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go @@ -177,6 +177,22 @@ func (tr *TufRepo) UpdateDelegations(role *data.Role, keys []data.Key, before st // also relies on the keysDB having already been populated with the keys and // roles. func (tr *TufRepo) InitRepo(consistent bool) error { + err := tr.InitRoot(consistent) + if err != nil { + return err + } + tr.InitTargets() + if err != nil { + return err + } + tr.InitSnapshot() + if err != nil { + return err + } + return tr.InitTimestamp() +} + +func (tr *TufRepo) InitRoot(consistent bool) error { rootRoles := make(map[string]*data.RootRole) rootKeys := make(map[string]*data.PublicKey) for _, r := range data.ValidRoles { @@ -199,15 +215,21 @@ func (tr *TufRepo) InitRepo(consistent bool) error { return err } tr.Root = root + return nil +} +func (tr *TufRepo) InitTargets() error { targets := data.NewTargets() tr.Targets[data.ValidRoles["targets"]] = targets + return nil +} - signedRoot, err := tr.SignRoot(data.DefaultExpires("root")) +func (tr *TufRepo) InitSnapshot() error { + signedRoot, err := tr.SignRoot(data.DefaultExpires("root"), nil) if err != nil { return err } - signedTargets, err := tr.SignTargets("targets", data.DefaultExpires("targets")) + signedTargets, err := tr.SignTargets("targets", data.DefaultExpires("targets"), nil) if err != nil { return err } @@ -216,8 +238,11 @@ func (tr *TufRepo) InitRepo(consistent bool) error { return err } tr.Snapshot = snapshot + return nil +} - signedSnapshot, err := tr.SignSnapshot(data.DefaultExpires("snapshot")) +func (tr *TufRepo) InitTimestamp() error { + signedSnapshot, err := tr.SignSnapshot(data.DefaultExpires("snapshot"), nil) if err != nil { return err } @@ -428,7 +453,7 @@ func (tr *TufRepo) UpdateTimestamp(s *data.Signed) error { return nil } -func (tr *TufRepo) SignRoot(expires time.Time) (*data.Signed, error) { +func (tr *TufRepo) SignRoot(expires time.Time, signer *signed.Signer) (*data.Signed, error) { logrus.Debug("SignRoot") if tr.Root.Dirty { tr.Root.Signed.Version++ @@ -438,7 +463,7 @@ func (tr *TufRepo) SignRoot(expires time.Time) (*data.Signed, error) { if err != nil { return nil, err } - signed, err = tr.sign(signed, *root) + signed, err = tr.sign(signed, *root, signer) if err != nil { return nil, err } @@ -446,7 +471,7 @@ func (tr *TufRepo) SignRoot(expires time.Time) (*data.Signed, error) { return signed, nil } -func (tr *TufRepo) SignTargets(role string, expires time.Time) (*data.Signed, error) { +func (tr *TufRepo) SignTargets(role string, expires time.Time, signer *signed.Signer) (*data.Signed, error) { logrus.Debug("SignTargets") logrus.Debug("Got targets data.Signed object") if tr.Targets[role].Dirty { @@ -458,7 +483,7 @@ func (tr *TufRepo) SignTargets(role string, expires time.Time) (*data.Signed, er } targets := tr.keysDB.GetRole(role) logrus.Debug("About to sign ", role) - signed, err = tr.sign(signed, *targets) + signed, err = tr.sign(signed, *targets, signer) if err != nil { logrus.Debug("errored signing ", role) return nil, err @@ -476,10 +501,10 @@ func (tr *TufRepo) SignTargets(role string, expires time.Time) (*data.Signed, er } } -func (tr *TufRepo) SignSnapshot(expires time.Time) (*data.Signed, error) { +func (tr *TufRepo) SignSnapshot(expires time.Time, signer *signed.Signer) (*data.Signed, error) { logrus.Debug("SignSnapshot") if tr.Root.Dirty { - signedRoot, err := tr.SignRoot(data.DefaultExpires("root")) + signedRoot, err := tr.SignRoot(data.DefaultExpires("root"), signer) if err != nil { return nil, err } @@ -493,7 +518,7 @@ func (tr *TufRepo) SignSnapshot(expires time.Time) (*data.Signed, error) { if !targets.Dirty { continue } - signedTargets, err := tr.SignTargets(role, data.DefaultExpires("targets")) + signedTargets, err := tr.SignTargets(role, data.DefaultExpires("targets"), signer) if err != nil { return nil, err } @@ -510,7 +535,7 @@ func (tr *TufRepo) SignSnapshot(expires time.Time) (*data.Signed, error) { return nil, err } snapshot := tr.keysDB.GetRole(data.ValidRoles["snapshot"]) - signed, err = tr.sign(signed, *snapshot) + signed, err = tr.sign(signed, *snapshot, signer) if err != nil { return nil, err } @@ -525,10 +550,10 @@ func (tr *TufRepo) SignSnapshot(expires time.Time) (*data.Signed, error) { } } -func (tr *TufRepo) SignTimestamp(expires time.Time) (*data.Signed, error) { +func (tr *TufRepo) SignTimestamp(expires time.Time, signer *signed.Signer) (*data.Signed, error) { logrus.Debug("SignTimestamp") if tr.Snapshot.Dirty { - signedSnapshot, err := tr.SignSnapshot(data.DefaultExpires("snapshot")) + signedSnapshot, err := tr.SignSnapshot(data.DefaultExpires("snapshot"), signer) if err != nil { return nil, err } @@ -544,7 +569,7 @@ func (tr *TufRepo) SignTimestamp(expires time.Time) (*data.Signed, error) { return nil, err } timestamp := tr.keysDB.GetRole(data.ValidRoles["timestamp"]) - signed, err = tr.sign(signed, *timestamp) + signed, err = tr.sign(signed, *timestamp, signer) if err != nil { return nil, err } @@ -560,7 +585,7 @@ func (tr *TufRepo) SignTimestamp(expires time.Time) (*data.Signed, error) { } } -func (tr TufRepo) sign(signed *data.Signed, role data.Role) (*data.Signed, error) { +func (tr TufRepo) sign(signed *data.Signed, role data.Role, signer *signed.Signer) (*data.Signed, error) { ks := make([]*data.PublicKey, 0, len(role.KeyIDs)) for _, kid := range role.KeyIDs { k := tr.keysDB.GetKey(kid) @@ -572,9 +597,16 @@ func (tr TufRepo) sign(signed *data.Signed, role data.Role) (*data.Signed, error if len(ks) < 1 { return nil, keys.ErrInvalidKey } - err := tr.signer.Sign(signed, ks...) - if err != nil { - return nil, err + if signer != nil { + err := signer.Sign(signed, ks...) + if err != nil { + return nil, err + } + } else { + err := tr.signer.Sign(signed, ks...) + if err != nil { + return nil, err + } } return signed, nil } diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/tuf_test.go b/Godeps/_workspace/src/github.com/endophage/gotuf/tuf_test.go index 01722ae91f..21a895165a 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/tuf_test.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/tuf_test.go @@ -88,7 +88,7 @@ func writeRepo(t *testing.T, dir string, repo *TufRepo) { if err != nil { t.Fatal(err) } - signedRoot, err := repo.SignRoot(data.DefaultExpires("root")) + signedRoot, err := repo.SignRoot(data.DefaultExpires("root"), nil) if err != nil { t.Fatal(err) } @@ -96,7 +96,7 @@ func writeRepo(t *testing.T, dir string, repo *TufRepo) { ioutil.WriteFile(dir+"/root.json", rootJSON, 0755) for r, _ := range repo.Targets { - signedTargets, err := repo.SignTargets(r, data.DefaultExpires("targets")) + signedTargets, err := repo.SignTargets(r, data.DefaultExpires("targets"), nil) if err != nil { t.Fatal(err) } @@ -107,14 +107,14 @@ func writeRepo(t *testing.T, dir string, repo *TufRepo) { ioutil.WriteFile(p, targetsJSON, 0755) } - signedSnapshot, err := repo.SignSnapshot(data.DefaultExpires("snapshot")) + signedSnapshot, err := repo.SignSnapshot(data.DefaultExpires("snapshot"), nil) if err != nil { t.Fatal(err) } snapshotJSON, _ := json.Marshal(signedSnapshot) ioutil.WriteFile(dir+"/snapshot.json", snapshotJSON, 0755) - signedTimestamp, err := repo.SignTimestamp(data.DefaultExpires("timestamp")) + signedTimestamp, err := repo.SignTimestamp(data.DefaultExpires("timestamp"), nil) if err != nil { t.Fatal(err) } diff --git a/client/cli_crypto_service.go b/client/cli_crypto_service.go index f54111fa1d..719d35609a 100644 --- a/client/cli_crypto_service.go +++ b/client/cli_crypto_service.go @@ -7,6 +7,7 @@ import ( "crypto/sha256" "crypto/x509" "encoding/pem" + "errors" "fmt" "path/filepath" @@ -14,40 +15,52 @@ import ( "github.com/endophage/gotuf/data" ) -type CliCryptoService struct { +type CryptoService struct { gun string - keyStore trustmanager.FileStore + keyStore trustmanager.KeyFileStore } -// NewCryptoService returns an instance ofS cliCryptoService -func NewCryptoService(gun string, keyStore trustmanager.FileStore) *CliCryptoService { - return &CliCryptoService{gun: gun, keyStore: keyStore} +type RootCryptoService struct { + // TODO(diogo): support multiple passphrases per key + passphrase string + rootKeyStore trustmanager.KeyFileStore +} + +// NewCryptoService returns an instance of CryptoService +func NewCryptoService(gun string, keyStore trustmanager.KeyFileStore) *CryptoService { + return &CryptoService{gun: gun, keyStore: keyStore} +} + +// NewRootCryptoService returns an instance of CryptoService +func NewRootCryptoService(rootKeyStore trustmanager.KeyFileStore, passphrase string) *RootCryptoService { + return &RootCryptoService{rootKeyStore: rootKeyStore, passphrase: passphrase} } // Create is used to generate keys for targets, snapshots and timestamps -func (ccs *CliCryptoService) Create(role string) (*data.PublicKey, error) { - keyData, pemCert, err := GenerateKeyAndCert(ccs.gun) +func (ccs *CryptoService) Create(role string) (*data.PublicKey, error) { + // Generates a new RSA key + key, err := rsa.GenerateKey(rand.Reader, rsaKeySize) if err != nil { - return nil, err + return nil, fmt.Errorf("could not generate private key: %v", err) } - fingerprint, err := trustmanager.FingerprintPEMCert(pemCert) + pemKey, err := trustmanager.KeyToPEM(key) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to generate the certificate for key: %v", err) } - // The key is going to be stored in the private directory, using the GUN and - // the filename will be the TUF-compliant ID. The Store takes care of extensions. - privKeyFilename := filepath.Join(ccs.gun, fingerprint) + tufKey := data.NewPublicKey("RSA", pemKey) - // Store this private key - ccs.keyStore.Add(privKeyFilename, keyData) + // Passing in the the GUN + keyID as the name for the private key and adding it + // to our KeyFileStore. Final storage will be under $BASE_PATH/GUN/keyID.key + privKeyFilename := filepath.Join(ccs.gun, tufKey.ID()) + ccs.keyStore.Add(privKeyFilename, pemKey) - return data.NewPublicKey("RSA", pemCert), nil + return tufKey, nil } // Sign returns the signatures for data with the given keyIDs -func (ccs *CliCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { +func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { // Create hasher and hash data hash := crypto.SHA256 hashed := sha256.Sum256(payload) @@ -85,35 +98,47 @@ func (ccs *CliCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signa return signatures, nil } -// generateKeyAndCert deals with the creation and storage of a key and returns a -// PEM encoded cert -func GenerateKeyAndCert(gun string) ([]byte, []byte, error) { - // Generates a new RSA key - key, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, nil, fmt.Errorf("could not generate private key: %v", err) - } - - // Creates a new Certificate template. We need the certificate to calculate the - // TUF-compliant keyID - //TODO (diogo): We're hardcoding the Organization to be the GUN. Probably want to - // change it - template := trustmanager.NewCertificate(gun, gun) - derBytes, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key) - if err != nil { - return nil, nil, fmt.Errorf("failed to generate the certificate for key: %v", err) - } - - // Encode the new certificate into PEM - cert, err := x509.ParseCertificate(derBytes) - if err != nil { - return nil, nil, fmt.Errorf("failed to generate the certificate for key: %v", err) - } - - pemKey, err := trustmanager.KeyToPEM(key) - if err != nil { - return nil, nil, fmt.Errorf("failed to generate the certificate for key: %v", err) - } - - return pemKey, trustmanager.CertToPEM(cert), nil +// Create in a root crypto service is not implemented +func (rcs *RootCryptoService) Create(role string) (*data.PublicKey, error) { + return nil, errors.New("create on a root key filestore is not implemented") +} + +// Sign returns the signatures for data with the given root Key ID, falling back +// if not rootKeyID is found +// TODO(diogo): This code has 1 line change from the Sign from Crypto service. DRY it up. +func (ccs *RootCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { + // Create hasher and hash data + hash := crypto.SHA256 + hashed := sha256.Sum256(payload) + + signatures := make([]data.Signature, 0, len(keyIDs)) + for _, fingerprint := range keyIDs { + // Read PrivateKey from file + privPEMBytes, err := ccs.rootKeyStore.GetDecrypted(fingerprint, ccs.passphrase) + if err != nil { + // TODO(diogo): This error should be returned to the user in someway + continue + } + + // Parse PrivateKey + privKeyBytes, _ := pem.Decode(privPEMBytes) + privKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes.Bytes) + if err != nil { + return nil, err + } + + // Sign the data + sig, err := rsa.SignPKCS1v15(rand.Reader, privKey, hash, hashed[:]) + if err != nil { + return nil, err + } + + // Append signatures to result array + signatures = append(signatures, data.Signature{ + KeyID: fingerprint, + Method: "RSASSA-PKCS1-V1_5-SIGN", + Signature: sig[:], + }) + } + return signatures, nil } diff --git a/client/client.go b/client/client.go index c8df05e50c..475a3af512 100644 --- a/client/client.go +++ b/client/client.go @@ -2,6 +2,8 @@ package client import ( "bytes" + "crypto/rand" + "crypto/rsa" "crypto/x509" "encoding/json" "encoding/pem" @@ -27,6 +29,7 @@ const trustDir string = "/trusted_certificates/" const privDir string = "/private/" const tufDir string = "/tuf/" const rootKeysDir string = "/root_keys/" +const rsaKeySize int = 2048 // ErrRepositoryNotExist gets returned when trying to make an action over a repository /// that doesn't exist @@ -34,28 +37,32 @@ var ErrRepositoryNotExist = errors.New("repository does not exist") // Client is the interface that defines the Notary Client type type Client interface { - ListPrivateKeys() []data.PrivateKey - GenRootKey(passphrase string) (*data.PublicKey, error) + ListPrivateKeys() []string + GenRootKey(passphrase string) (string, error) + GetRootKey(rootKeyID string, passphrase string) (UnlockedRootKey, error) GetRepository(gun string, baseURL string, transport http.RoundTripper) (Repository, error) } // Repository is the interface that represents a Notary Repository type Repository interface { - Update() error - Initialize(key *data.PublicKey) error - - AddTarget(target *Target) error - ListTargets() ([]*Target, error) - GetTargetByName(name string) (*Target, error) - + Initialize(UnlockedRootKey) error + AddTarget(target Target) error + ListTargets() ([]Target, error) + GetTargetByName(name string) (Target, error) Publish() error } +type UnlockedRootKey struct { + cipher string + pemBytes []byte + signer *signed.Signer +} + type NotaryClient struct { baseDir string caStore trustmanager.X509Store certificateStore trustmanager.X509Store - rootKeyStore trustmanager.EncryptedFileStore + rootKeyStore trustmanager.KeyFileStore } type NotaryRepository struct { @@ -66,7 +73,7 @@ type NotaryRepository struct { signer *signed.Signer tufRepo *tuf.TufRepo fileStore store.MetadataStore - privKeyStore trustmanager.FileStore + privKeyStore trustmanager.KeyFileStore caStore trustmanager.X509Store certificateStore trustmanager.X509Store } @@ -110,27 +117,25 @@ func NewClient(baseDir string) (*NotaryClient, error) { return nClient, nil } -// Update forces TUF to download the remote timestamps and verify if there are -// any remote changes. -func (r *NotaryRepository) Update() error { - return nil -} - // Initialize creates a new repository by using rootKey as the root Key for the // TUF repository. -func (r *NotaryRepository) Initialize(rootKey *data.PublicKey) error { +func (r *NotaryRepository) Initialize(uRootKey UnlockedRootKey) error { remote, err := getRemoteStore(r.Gun) rawTSKey, err := remote.GetKey("timestamp") if err != nil { return err } + fmt.Println("RawKey: ", string(rawTSKey)) + parsedKey := &data.TUFKey{} err = json.Unmarshal(rawTSKey, parsedKey) if err != nil { return err } + timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public()) + rootKey := data.NewPublicKey(uRootKey.cipher, uRootKey.pemBytes) targetsKey, err := r.signer.Create("targets") if err != nil { @@ -165,6 +170,7 @@ func (r *NotaryRepository) Initialize(rootKey *data.PublicKey) error { return err } + // TODO(diogo): change to inline error catching err = kdb.AddRole(rootRole) if err != nil { return err @@ -194,13 +200,23 @@ func (r *NotaryRepository) Initialize(rootKey *data.PublicKey) error { return err } - err = r.tufRepo.InitRepo(false) - if err != nil { + if err := r.tufRepo.InitRepo(false); err != nil { return err } - r.saveRepo() - return nil + if err := r.saveMetadata(uRootKey.signer); err != nil { + return err + } + + // Creates and saves a trusted certificate for this store, with this root key + rootCert, err := uRootKey.GenerateCertificate(r.Gun) + if err != nil { + return err + } + r.certificateStore.AddCert(rootCert) + + // Creates an empty snapshot + return r.snapshot() } // AddTarget adds a new target to the repository, forcing a timestamps check from TUF @@ -215,7 +231,7 @@ func (r *NotaryRepository) AddTarget(target *Target) error { return err } - r.saveRepo() + r.snapshot() return nil } @@ -352,18 +368,21 @@ func (r *NotaryRepository) bootstrapRepo() error { return nil } -func (r *NotaryRepository) saveRepo() error { - signedRoot, err := r.tufRepo.SignRoot(data.DefaultExpires("root")) +func (r *NotaryRepository) saveMetadata(rootSigner *signed.Signer) error { + signedRoot, err := r.tufRepo.SignRoot(data.DefaultExpires("root"), rootSigner) if err != nil { return err } - rootJSON, _ := json.Marshal(signedRoot) - r.fileStore.SetMeta("root", rootJSON) + rootJSON, _ := json.Marshal(signedRoot) + return r.fileStore.SetMeta("root", rootJSON) +} + +func (r *NotaryRepository) snapshot() error { fmt.Println("Saving changes to Trusted Collection.") for t, _ := range r.tufRepo.Targets { - signedTargets, err := r.tufRepo.SignTargets(t, data.DefaultExpires("targets")) + signedTargets, err := r.tufRepo.SignTargets(t, data.DefaultExpires("targets"), nil) if err != nil { return err } @@ -373,14 +392,13 @@ func (r *NotaryRepository) saveRepo() error { r.fileStore.SetMeta(t, targetsJSON) } - signedSnapshot, err := r.tufRepo.SignSnapshot(data.DefaultExpires("snapshot")) + signedSnapshot, err := r.tufRepo.SignSnapshot(data.DefaultExpires("snapshot"), nil) if err != nil { return err } snapshotJSON, _ := json.Marshal(signedSnapshot) - r.fileStore.SetMeta("snapshot", snapshotJSON) - return nil + return r.fileStore.SetMeta("snapshot", snapshotJSON) } /* @@ -447,6 +465,11 @@ func (r *NotaryRepository) ValidateRoot(root *data.Signed) error { certs[fingerprint] = rootSigned.Keys[fingerprint] } } + + if len(certs) < 1 { + return errors.New("could not validate the path to a trusted root") + } + _, err = signed.VerifyRoot(root, 0, certs, 1) return err @@ -486,7 +509,7 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { // ListPrivateKeys lists all availables private keys. Does not include private key // material -func (c *NotaryClient) ListPrivateKeys() []data.PrivateKey { +func (c *NotaryClient) ListPrivateKeys() []string { // TODO(diogo): Make this work for _, k := range c.rootKeyStore.ListAll() { fmt.Println(k) @@ -495,18 +518,37 @@ func (c *NotaryClient) ListPrivateKeys() []data.PrivateKey { } // GenRootKey generates a new root key protected by a given passphrase -func (c *NotaryClient) GenRootKey(passphrase string) (*data.PublicKey, error) { - // When generating a root key, passing in a GUN to put into the cert - // doesn't make sense since this key can be used for multiple distinct - // repositories. - pemKey, _, err := GenerateKeyAndCert("TUF root key") +func (c *NotaryClient) GenRootKey(passphrase string) (string, error) { + // Generates a new RSA key + key, err := rsa.GenerateKey(rand.Reader, rsaKeySize) if err != nil { - return nil, fmt.Errorf("could not generate private key: %v", err) + return "", fmt.Errorf("could not generate private key: %v", err) } - c.rootKeyStore.AddEncrypted("root", pemKey, passphrase) + pemKey, err := trustmanager.KeyToPEM(key) + if err != nil { + return "", fmt.Errorf("failed to generate the certificate for key: %v", err) + } - return data.NewPublicKey("RSA", pemKey), nil + keyID := data.NewPublicKey("RSA", pemKey).ID() + c.rootKeyStore.AddEncrypted(keyID, pemKey, passphrase) + + return keyID, nil +} + +// GetRootKey retreives a root key that includes the ID and a signer +func (c *NotaryClient) GetRootKey(rootKeyID, passphrase string) (UnlockedRootKey, error) { + rootKeyPem, err := c.rootKeyStore.GetDecrypted(rootKeyID, passphrase) + if err != nil { + return UnlockedRootKey{}, fmt.Errorf("could not get encrypted root key: %v", err) + } + + signer := signed.NewSigner(NewRootCryptoService(c.rootKeyStore, passphrase)) + + return UnlockedRootKey{ + cipher: "RSA", + pemBytes: rootKeyPem, + signer: signer}, nil } // GetRepository returns a new repository @@ -516,7 +558,7 @@ func (c *NotaryClient) GetRepository(gun string, baseURL string, transport http. return nil, err } - signer := signed.NewSigner(NewCryptoService(gun, privKeyStore)) + signer := signed.NewSigner(NewCryptoService(gun, *privKeyStore)) return &NotaryRepository{Gun: gun, baseURL: baseURL, @@ -559,11 +601,38 @@ func (c *NotaryClient) loadKeys(trustDir, rootKeysDir string) error { c.caStore = caStore c.certificateStore = certificateStore - c.rootKeyStore = rootKeyStore + c.rootKeyStore = *rootKeyStore return nil } +func (uk *UnlockedRootKey) ID() string { + return data.NewPublicKey(uk.cipher, uk.pemBytes).ID() +} + +func (uk *UnlockedRootKey) GenerateCertificate(gun string) (*x509.Certificate, error) { + privKeyBytes, _ := pem.Decode(uk.pemBytes) + privKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to generate the certificate for key: %v", err) + } + + //TODO (diogo): We're hardcoding the Organization to be the GUN. Probably want to change it + template := trustmanager.NewCertificate(gun, gun) + derBytes, err := x509.CreateCertificate(rand.Reader, template, template, privKey.Public(), privKey) + if err != nil { + return nil, fmt.Errorf("failed to generate the certificate for key: %v", err) + } + + // Encode the new certificate into PEM + cert, err := x509.ParseCertificate(derBytes) + if err != nil { + return nil, fmt.Errorf("failed to generate the certificate for key: %v", err) + } + + return cert, nil +} + // Use this to initialize remote HTTPStores from the config settings func getRemoteStore(gun string) (store.RemoteStore, error) { return store.NewHTTPStore( diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 28083140be..f3b4b9c3e7 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -102,11 +102,16 @@ func tufInit(cmd *cobra.Command, args []string) { // TODO(diogo): We don't want to generate a new root every time. Ask the user // which key she wants to use if there > 0 root keys available. - newRootKey, err := nClient.GenRootKey("passphrase") + rootKeyID, err := nClient.GenRootKey("passphrase") if err != nil { fatalf(err.Error()) } - repo.Initialize(newRootKey) + rootKey, err := nClient.GetRootKey(rootKeyID, "passphrase") + if err != nil { + fatalf(err.Error()) + } + + repo.Initialize(rootKey) } func tufList(cmd *cobra.Command, args []string) { diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go index 43a1c1ff42..478bfec777 100644 --- a/trustmanager/keyfilestore.go +++ b/trustmanager/keyfilestore.go @@ -1,7 +1,5 @@ package trustmanager -import "errors" - const ( keyExtension = "key" ) @@ -43,7 +41,7 @@ func (s *KeyFileStore) AddEncrypted(fileName string, pemKey []byte, passphrase s func (s *KeyFileStore) GetDecrypted(fileName string, passphrase string) ([]byte, error) { keyBytes, err := s.Get(fileName) if err != nil { - return nil, errors.New("could not retrieve private key material") + return nil, err } // Gets an unencrypted PrivateKey. From 3891f724bbd1f7404dad7862797ac3aa7a2e9fea Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Tue, 7 Jul 2015 17:00:36 -0700 Subject: [PATCH 05/39] Changed root directory --- client/client.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/client/client.go b/client/client.go index 475a3af512..0aa144e443 100644 --- a/client/client.go +++ b/client/client.go @@ -25,10 +25,12 @@ import ( ) // Default paths should end with a '/' so directory creation works correctly -const trustDir string = "/trusted_certificates/" -const privDir string = "/private/" -const tufDir string = "/tuf/" -const rootKeysDir string = "/root_keys/" +const ( + trustDir string = "/trusted_certificates/" + privDir string = "/private/" + tufDir string = "/tuf/" + rootKeysDir string = privDir + "/root_keys/" +) const rsaKeySize int = 2048 // ErrRepositoryNotExist gets returned when trying to make an action over a repository @@ -432,6 +434,7 @@ func (r *NotaryRepository) ValidateRoot(root *data.Signed) error { if err != nil { return err } + certs := make(map[string]*data.PublicKey) for _, fingerprint := range rootSigned.Roles["root"].KeyIDs { // TODO(dlaw): currently assuming only one cert contained in @@ -442,10 +445,10 @@ func (r *NotaryRepository) ValidateRoot(root *data.Signed) error { if err != nil { continue } - // TODO(diogo): Assuming that first certificate is the leaf-cert. Need to // iterate over all decodedCerts and find a non-CA one (should be the last). leafCert := decodedCerts[0] + leafID := trustmanager.FingerprintCert(leafCert) // Check to see if there is an exact match of this certificate. From be6e22c35579824eaf7c919e81592b61ad67e119 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Tue, 7 Jul 2015 17:02:32 -0700 Subject: [PATCH 06/39] fixes for list/lookup Signed-off-by: David Lawrence (github: endophage) --- client/client.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/client/client.go b/client/client.go index 0aa144e443..e0a08bae45 100644 --- a/client/client.go +++ b/client/client.go @@ -15,6 +15,7 @@ import ( "path/filepath" "time" + "github.com/Sirupsen/logrus" "github.com/docker/notary/trustmanager" "github.com/endophage/gotuf" tufclient "github.com/endophage/gotuf/client" @@ -240,7 +241,7 @@ func (r *NotaryRepository) AddTarget(target *Target) error { // ListTargets lists all targets for the current repository func (r *NotaryRepository) ListTargets() ([]*Target, error) { - r.bootstrapRepo() + //r.bootstrapRepo() c, err := r.bootstrapClient() if err != nil { @@ -263,7 +264,7 @@ func (r *NotaryRepository) ListTargets() ([]*Target, error) { // GetTargetByName returns a target given a name func (r *NotaryRepository) GetTargetByName(name string) (*Target, error) { - r.bootstrapRepo() + //r.bootstrapRepo() c, err := r.bootstrapClient() if err != nil { @@ -483,7 +484,6 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { if err != nil { return nil, err } - rootJSON, err := remote.GetMeta("root", 5<<20) root := &data.Signed{} err = json.Unmarshal(rootJSON, root) @@ -495,13 +495,16 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { if err != nil { return nil, err } + + kdb := keys.NewDB() + r.tufRepo = tuf.NewTufRepo(kdb, r.signer) + err = r.tufRepo.SetRoot(root) if err != nil { return nil, err } // TODO(dlaw): Where does this keyDB come in - kdb := keys.NewDB() return tufclient.NewClient( r.tufRepo, From 21d45a0f8da56f0d5f45cfd25ab968a43339af11 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Tue, 7 Jul 2015 17:49:28 -0700 Subject: [PATCH 07/39] IDs for root are now correct Signed-off-by: David Lawrence (github: endophage) --- client/client.go | 19 ++++++++++--------- trustmanager/x509filestore.go | 3 ++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/client/client.go b/client/client.go index e0a08bae45..d7b0cd4397 100644 --- a/client/client.go +++ b/client/client.go @@ -138,7 +138,14 @@ func (r *NotaryRepository) Initialize(uRootKey UnlockedRootKey) error { } timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public()) - rootKey := data.NewPublicKey(uRootKey.cipher, uRootKey.pemBytes) + //rootKey := data.NewPublicKey(uRootKey.cipher, uRootKey.pemBytes) + // Creates and saves a trusted certificate for this store, with this root key + rootCert, err := uRootKey.GenerateCertificate(r.Gun) + if err != nil { + return err + } + r.certificateStore.AddCert(rootCert) + rootKey := data.NewPublicKey("RSA", trustmanager.CertToPEM(rootCert)) targetsKey, err := r.signer.Create("targets") if err != nil { @@ -211,13 +218,6 @@ func (r *NotaryRepository) Initialize(uRootKey UnlockedRootKey) error { return err } - // Creates and saves a trusted certificate for this store, with this root key - rootCert, err := uRootKey.GenerateCertificate(r.Gun) - if err != nil { - return err - } - r.certificateStore.AddCert(rootCert) - // Creates an empty snapshot return r.snapshot() } @@ -441,7 +441,8 @@ func (r *NotaryRepository) ValidateRoot(root *data.Signed) error { // TODO(dlaw): currently assuming only one cert contained in // public key entry. Need to fix when we want to pass in chains. k, _ := pem.Decode([]byte(rootSigned.Keys[fingerprint].Public())) - + logrus.Debug("Root PEM: ", k) + logrus.Debug("Root ID: ", fingerprint) decodedCerts, err := x509.ParseCertificates(k.Bytes) if err != nil { continue diff --git a/trustmanager/x509filestore.go b/trustmanager/x509filestore.go index a9128b0cf1..2f41606164 100644 --- a/trustmanager/x509filestore.go +++ b/trustmanager/x509filestore.go @@ -3,6 +3,7 @@ package trustmanager import ( "crypto/x509" "errors" + "github.com/Sirupsen/logrus" "os" "path" ) @@ -69,7 +70,7 @@ func (s X509FileStore) AddCert(cert *x509.Certificate) error { // stored under. If the file does not exist on disk, saves it. func (s X509FileStore) addNamedCert(cert *x509.Certificate) error { fingerprint := fingerprintCert(cert) - + logrus.Debug("Adding cert with fingerprint: ", fingerprint) // Validate if we already loaded this certificate before if _, ok := s.fingerprintMap[fingerprint]; ok { return errors.New("certificate already in the store") From 9d5e988586010e34ad60555c4406f076f7d10399 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Tue, 7 Jul 2015 19:13:25 -0700 Subject: [PATCH 08/39] working refactor Signed-off-by: David Lawrence (github: endophage) --- client/cli_crypto_service.go | 16 +++++++---- client/client.go | 53 ++++++++++++++++++++++++++++-------- cmd/notary/tuf.go | 9 +++--- trustmanager/keyfilestore.go | 8 ++++++ 4 files changed, 64 insertions(+), 22 deletions(-) diff --git a/client/cli_crypto_service.go b/client/cli_crypto_service.go index 719d35609a..f65e866849 100644 --- a/client/cli_crypto_service.go +++ b/client/cli_crypto_service.go @@ -46,10 +46,15 @@ func (ccs *CryptoService) Create(role string) (*data.PublicKey, error) { pemKey, err := trustmanager.KeyToPEM(key) if err != nil { - return nil, fmt.Errorf("failed to generate the certificate for key: %v", err) + return nil, fmt.Errorf("failed to generate the certificate for key: %v (%s)", role, err) } - - tufKey := data.NewPublicKey("RSA", pemKey) + rsaPublicKey := key.PublicKey + // Using x509 to Marshal the Public key into der encoding + pubBytes, err := x509.MarshalPKIXPublicKey(&rsaPublicKey) + if err != nil { + return nil, errors.New("Failed to Marshal public key.") + } + tufKey := data.NewPublicKey("RSA", pubBytes) // Passing in the the GUN + keyID as the name for the private key and adding it // to our KeyFileStore. Final storage will be under $BASE_PATH/GUN/keyID.key @@ -90,8 +95,9 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur // Append signatures to result array signatures = append(signatures, data.Signature{ - KeyID: fingerprint, - Method: "RSASSA-PKCS1-V1_5-SIGN", + KeyID: fingerprint, + Method: "RSA", + //Method: "RSASSA-PKCS1-V1_5-SIGN", Signature: sig[:], }) } diff --git a/client/client.go b/client/client.go index d7b0cd4397..549476871a 100644 --- a/client/client.go +++ b/client/client.go @@ -122,7 +122,7 @@ func NewClient(baseDir string) (*NotaryClient, error) { // Initialize creates a new repository by using rootKey as the root Key for the // TUF repository. -func (r *NotaryRepository) Initialize(uRootKey UnlockedRootKey) error { +func (r *NotaryRepository) Initialize(uRootKey UnlockedRootKey, rootKey *data.PublicKey) error { remote, err := getRemoteStore(r.Gun) rawTSKey, err := remote.GetKey("timestamp") if err != nil { @@ -138,14 +138,6 @@ func (r *NotaryRepository) Initialize(uRootKey UnlockedRootKey) error { } timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public()) - //rootKey := data.NewPublicKey(uRootKey.cipher, uRootKey.pemBytes) - // Creates and saves a trusted certificate for this store, with this root key - rootCert, err := uRootKey.GenerateCertificate(r.Gun) - if err != nil { - return err - } - r.certificateStore.AddCert(rootCert) - rootKey := data.NewPublicKey("RSA", trustmanager.CertToPEM(rootCert)) targetsKey, err := r.signer.Create("targets") if err != nil { @@ -576,6 +568,43 @@ func (c *NotaryClient) GetRepository(gun string, baseURL string, transport http. certificateStore: c.certificateStore}, nil } +func (c *NotaryClient) InitRepository(gun string, baseURL string, transport http.RoundTripper, uRootKey UnlockedRootKey) (*NotaryRepository, error) { + //rootKey := data.NewPublicKey(uRootKey.cipher, uRootKey.pemBytes) + // Creates and saves a trusted certificate for this store, with this root key + rootCert, err := uRootKey.GenerateCertificate(gun) + if err != nil { + return nil, err + } + c.certificateStore.AddCert(rootCert) + rootKey := data.NewPublicKey("RSA", trustmanager.CertToPEM(rootCert)) + err = c.rootKeyStore.Link(uRootKey.ID(), rootKey.ID()) + if err != nil { + return nil, err + } + + privKeyStore, err := trustmanager.NewKeyFileStore(filepath.Join(c.baseDir, privDir)) + if err != nil { + return nil, err + } + + signer := signed.NewSigner(NewCryptoService(gun, *privKeyStore)) + + nRepo := &NotaryRepository{Gun: gun, + baseURL: baseURL, + tufRepoPath: filepath.Join(c.baseDir, tufDir), + transport: transport, + signer: signer, + caStore: c.caStore, + certificateStore: c.certificateStore} + + err = nRepo.Initialize(uRootKey, rootKey) + if err != nil { + return nil, err + } + + return nRepo, nil +} + func (c *NotaryClient) loadKeys(trustDir, rootKeysDir string) error { // Load all CAs that aren't expired and don't use SHA1 caStore, err := trustmanager.NewX509FilteredFileStore(trustDir, func(cert *x509.Certificate) bool { @@ -621,20 +650,20 @@ func (uk *UnlockedRootKey) GenerateCertificate(gun string) (*x509.Certificate, e privKeyBytes, _ := pem.Decode(uk.pemBytes) privKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes.Bytes) if err != nil { - return nil, fmt.Errorf("failed to generate the certificate for key: %v", err) + return nil, fmt.Errorf("failed to parse root key: %v (%s)", gun, err.Error()) } //TODO (diogo): We're hardcoding the Organization to be the GUN. Probably want to change it template := trustmanager.NewCertificate(gun, gun) derBytes, err := x509.CreateCertificate(rand.Reader, template, template, privKey.Public(), privKey) if err != nil { - return nil, fmt.Errorf("failed to generate the certificate for key: %v", err) + return nil, fmt.Errorf("failed to generate the certificate for: %v (%s)", gun, err.Error()) } // Encode the new certificate into PEM cert, err := x509.ParseCertificate(derBytes) if err != nil { - return nil, fmt.Errorf("failed to generate the certificate for key: %v", err) + return nil, fmt.Errorf("failed to parse the certificate for key: %v (%s)", gun, err.Error()) } return cert, nil diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index f3b4b9c3e7..257de98a6f 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -95,10 +95,6 @@ func tufInit(cmd *cobra.Command, args []string) { } t := &http.Transport{} - repo, err := nClient.GetRepository(args[0], "", t) - if err != nil { - fatalf(err.Error()) - } // TODO(diogo): We don't want to generate a new root every time. Ask the user // which key she wants to use if there > 0 root keys available. @@ -111,7 +107,10 @@ func tufInit(cmd *cobra.Command, args []string) { fatalf(err.Error()) } - repo.Initialize(rootKey) + _, err = nClient.InitRepository(args[0], "", t, rootKey) + if err != nil { + fatalf(err.Error()) + } } func tufList(cmd *cobra.Command, args []string) { diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go index 478bfec777..27223e5082 100644 --- a/trustmanager/keyfilestore.go +++ b/trustmanager/keyfilestore.go @@ -52,3 +52,11 @@ func (s *KeyFileStore) GetDecrypted(fileName string, passphrase string) ([]byte, return KeyToPEM(privKey) } + +func (s *KeyFileStore) Link(base, fileName string) error { + keyBytes, err := s.Get(base) + if err != nil { + return err + } + return s.Add(fileName, keyBytes) +} From 1d163650a339f73c7b3e68ff072f603b6125af2f Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 8 Jul 2015 08:43:48 -0700 Subject: [PATCH 09/39] changelist implementation Signed-off-by: David Lawrence (github: endophage) Signed-off-by: David Lawrence (github: endophage) --- client/changelist/change.go | 46 +++++++++ client/changelist/changelist.go | 102 ++++++++++++++++++++ client/changelist/changelist_test.go | 41 ++++++++ client/changelist/files_changelist.go | 105 +++++++++++++++++++++ client/changelist/files_changelist_test.go | 76 +++++++++++++++ client/changelist/interface.go | 54 +++++++++++ client/client.go | 57 +++++++++-- cmd/notary-server/config.json | 9 +- cmd/notary/tuf.go | 99 +++++++++++++++---- server/handlers/default.go | 14 +++ trustmanager/filestore.go | 8 ++ trustmanager/keyfilestore.go | 8 +- 12 files changed, 581 insertions(+), 38 deletions(-) create mode 100644 client/changelist/change.go create mode 100644 client/changelist/changelist.go create mode 100644 client/changelist/changelist_test.go create mode 100644 client/changelist/files_changelist.go create mode 100644 client/changelist/files_changelist_test.go create mode 100644 client/changelist/interface.go diff --git a/client/changelist/change.go b/client/changelist/change.go new file mode 100644 index 0000000000..85c72a44c9 --- /dev/null +++ b/client/changelist/change.go @@ -0,0 +1,46 @@ +package changelist + +// tufChange represents a change to a TUF repo +type tufChange struct { + // Abbreviated because Go doesn't permit a field and method of the same name + Actn int `json:"action"` + Role string `json:"role"` + ChangeType string `json:"type"` + ChangePath string `json:"path"` + Data []byte `json:"data"` +} + +// NewTufChange initializes a tufChange object +func NewTufChange(action int, role, changeType, changePath string, content []byte) *tufChange { + return &tufChange{ + Actn: action, + Role: role, + ChangeType: changeType, + ChangePath: changePath, + Data: content, + } +} + +// Action return c.Actn +func (c tufChange) Action() int { + return c.Actn +} + +// Scope returns c.Role +func (c tufChange) Scope() string { + return c.Role +} + +// Type returns c.ChangeType +func (c tufChange) Type() string { + return c.ChangeType +} + +// Path return c.ChangePath +func (c tufChange) Path() string { + return c.ChangePath +} + +func (c tufChange) Content() []byte { + return c.Data +} diff --git a/client/changelist/changelist.go b/client/changelist/changelist.go new file mode 100644 index 0000000000..d7a6913fc2 --- /dev/null +++ b/client/changelist/changelist.go @@ -0,0 +1,102 @@ +package changelist + +import ( + "bufio" + "encoding/json" + "fmt" + "os" +) + +type appendChangelist struct { + path string + file *os.File + closed bool +} + +func NewAppendChangelist(path string) (*appendChangelist, error) { + file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600) + if err != nil { + return nil, err + } + return &appendChangelist{ + path: path, + file: file, + }, nil +} + +func (cl *appendChangelist) List() []Change { + cl.file.Seek(0, 0) // seek to start of file + changes := make([]Change, 0) + scnr := bufio.NewScanner(cl.file) + for scnr.Scan() { + line := scnr.Bytes() + c := &tufChange{} + err := json.Unmarshal(line, c) + if err != nil { + // TODO(david): How should we handle this? + fmt.Println(err.Error()) + continue + } + changes = append(changes, c) + } + return changes +} + +func (cl *appendChangelist) Add(c Change) error { + cl.file.Seek(0, 2) // seek to end of file + entry, err := json.Marshal(c) + if err != nil { + return err + } + n, err := cl.file.Write(entry) + if err != nil { + if n > 0 { + // trim partial write if necessary + size, _ := cl.file.Seek(-int64(n), 2) + cl.file.Truncate(size) + } + return err + } + cl.file.Write([]byte("\n")) + cl.file.Sync() + return nil +} + +// Clear empties the changelist file. It does not currently +// support archiving +func (cl *appendChangelist) Clear(archive string) error { + cl.file.Seek(0, 0) // seek to start + cl.file.Truncate(0) // truncate + cl.file.Sync() + return nil +} + +func (cl *appendChangelist) Close() error { + cl.file.Sync() + cl.closed = true + return cl.file.Close() +} + +// memChangeList implements a simple in memory change list. +type memChangelist struct { + changes []Change +} + +func (cl memChangelist) List() []Change { + return cl.changes +} + +func (cl *memChangelist) Add(c Change) error { + cl.changes = append(cl.changes, c) + return nil +} + +func (cl *memChangelist) Clear(archive string) error { + // appending to a nil list initializes it. + cl.changes = nil + return nil +} + +func (cl *memChangelist) Close() error { + return nil +} diff --git a/client/changelist/changelist_test.go b/client/changelist/changelist_test.go new file mode 100644 index 0000000000..664cf17e16 --- /dev/null +++ b/client/changelist/changelist_test.go @@ -0,0 +1,41 @@ +package changelist + +import ( + "io/ioutil" + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFileChangelist(t *testing.T) { + tmpDir, err := ioutil.TempDir("/tmp", "test") + if err != nil { + t.Fatal(err.Error()) + } + defer os.RemoveAll(tmpDir) + file := path.Join(tmpDir, "list") + cl, err := NewAppendChangelist(file) + assert.Nil(t, err, "Error initializing appendChangelist") + + c := NewTufChange(ActionCreate, "targets", "target", "test/targ", []byte{1}) + + err = cl.Add(c) + assert.Nil(t, err, "Non-nil error while adding change") + + cs := cl.List() + + assert.Equal(t, 1, len(cs), "List should have returned exactly one item") + assert.Equal(t, c.Action(), cs[0].Action(), "Action mismatch") + assert.Equal(t, c.Scope(), cs[0].Scope(), "Scope mismatch") + assert.Equal(t, c.Type(), cs[0].Type(), "Type mismatch") + assert.Equal(t, c.Path(), cs[0].Path(), "Path mismatch") + assert.Equal(t, c.Content(), cs[0].Content(), "Content mismatch") + + err = cl.Clear("") + assert.Nil(t, err, "Non-nil error while clearing") + + cs = cl.List() + assert.Equal(t, 0, len(cs), "List should be empty") +} diff --git a/client/changelist/files_changelist.go b/client/changelist/files_changelist.go new file mode 100644 index 0000000000..e2d2ce12fc --- /dev/null +++ b/client/changelist/files_changelist.go @@ -0,0 +1,105 @@ +package changelist + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path" + "sort" + "time" + + "code.google.com/p/go-uuid/uuid" + "github.com/Sirupsen/logrus" +) + +type fileChangelist struct { + dir string +} + +func NewFileChangelist(dir string) (*fileChangelist, error) { + logrus.Debug("Making dir path: ", dir) + err := os.MkdirAll(dir, 0700) + if err != nil { + return nil, err + } + return &fileChangelist{dir: dir}, nil +} + +func (cl fileChangelist) List() []Change { + changes := make([]Change, 0) + dir, err := os.Open(cl.dir) + if err != nil { + return changes + } + fileInfos, err := dir.Readdir(0) + if err != nil { + return changes + } + sort.Sort(fileChanges(fileInfos)) + for _, f := range fileInfos { + if f.IsDir() { + continue + } + raw, err := ioutil.ReadFile(path.Join(cl.dir, f.Name())) + if err != nil { + // TODO(david): How should we handle this? + fmt.Println(err.Error()) + continue + } + c := &tufChange{} + err = json.Unmarshal(raw, c) + if err != nil { + // TODO(david): How should we handle this? + fmt.Println(err.Error()) + continue + } + changes = append(changes, c) + } + return changes +} + +func (cl fileChangelist) Add(c Change) error { + cJson, err := json.Marshal(c) + if err != nil { + return err + } + filename := fmt.Sprintf("%020d_%s.change", time.Now().UnixNano(), uuid.New()) + return ioutil.WriteFile(path.Join(cl.dir, filename), cJson, 0644) +} + +func (cl fileChangelist) Clear(archive string) error { + dir, err := os.Open(cl.dir) + if err != nil { + return err + } + files, err := dir.Readdir(0) + if err != nil { + return err + } + for _, f := range files { + os.Remove(path.Join(cl.dir, f.Name())) + } + return nil +} + +func (cl fileChangelist) Close() error { + // Nothing to do here + return nil +} + +type fileChanges []os.FileInfo + +func (cs fileChanges) Len() int { + return len(cs) +} + +func (cs fileChanges) Less(i, j int) bool { + return cs[i].Name() < cs[j].Name() +} + +func (cs fileChanges) Swap(i, j int) { + tmp := cs[i] + cs[i] = cs[j] + cs[j] = tmp +} diff --git a/client/changelist/files_changelist_test.go b/client/changelist/files_changelist_test.go new file mode 100644 index 0000000000..848e3bb785 --- /dev/null +++ b/client/changelist/files_changelist_test.go @@ -0,0 +1,76 @@ +package changelist + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAdd(t *testing.T) { + tmpDir, err := ioutil.TempDir("/tmp", "test") + if err != nil { + t.Fatal(err.Error()) + } + defer os.RemoveAll(tmpDir) + + cl, err := NewFileChangelist(tmpDir) + assert.Nil(t, err, "Error initializing fileChangelist") + + c := NewTufChange(ActionCreate, "targets", "target", "test/targ", []byte{1}) + err = cl.Add(c) + assert.Nil(t, err, "Non-nil error while adding change") + + cs := cl.List() + + assert.Equal(t, 1, len(cs), "List should have returned exactly one item") + assert.Equal(t, c.Action(), cs[0].Action(), "Action mismatch") + assert.Equal(t, c.Scope(), cs[0].Scope(), "Scope mismatch") + assert.Equal(t, c.Type(), cs[0].Type(), "Type mismatch") + assert.Equal(t, c.Path(), cs[0].Path(), "Path mismatch") + assert.Equal(t, c.Content(), cs[0].Content(), "Content mismatch") + + err = cl.Clear("") + assert.Nil(t, err, "Non-nil error while clearing") + + cs = cl.List() + assert.Equal(t, 0, len(cs), "List should be empty") + + err = os.Remove(tmpDir) // will error if anything left in dir + assert.Nil(t, err, "Clear should have left the tmpDir empty") +} + +func TestListOrder(t *testing.T) { + tmpDir, err := ioutil.TempDir("/tmp", "test") + if err != nil { + t.Fatal(err.Error()) + } + defer os.RemoveAll(tmpDir) + + cl, err := NewFileChangelist(tmpDir) + assert.Nil(t, err, "Error initializing fileChangelist") + + c1 := NewTufChange(ActionCreate, "targets", "target", "test/targ1", []byte{1}) + err = cl.Add(c1) + assert.Nil(t, err, "Non-nil error while adding change") + + c2 := NewTufChange(ActionCreate, "targets", "target", "test/targ2", []byte{1}) + err = cl.Add(c2) + assert.Nil(t, err, "Non-nil error while adding change") + + cs := cl.List() + + assert.Equal(t, 2, len(cs), "List should have returned exactly one item") + assert.Equal(t, c1.Action(), cs[0].Action(), "Action mismatch") + assert.Equal(t, c1.Scope(), cs[0].Scope(), "Scope mismatch") + assert.Equal(t, c1.Type(), cs[0].Type(), "Type mismatch") + assert.Equal(t, c1.Path(), cs[0].Path(), "Path mismatch") + assert.Equal(t, c1.Content(), cs[0].Content(), "Content mismatch") + + assert.Equal(t, c2.Action(), cs[1].Action(), "Action 2 mismatch") + assert.Equal(t, c2.Scope(), cs[1].Scope(), "Scope 2 mismatch") + assert.Equal(t, c2.Type(), cs[1].Type(), "Type 2 mismatch") + assert.Equal(t, c2.Path(), cs[1].Path(), "Path 2 mismatch") + assert.Equal(t, c2.Content(), cs[1].Content(), "Content 2 mismatch") +} diff --git a/client/changelist/interface.go b/client/changelist/interface.go new file mode 100644 index 0000000000..fbe22131d9 --- /dev/null +++ b/client/changelist/interface.go @@ -0,0 +1,54 @@ +package changelist + +type Changelist interface { + // List returns the ordered list of changes + // currently stored + List() []Change + + // Add change appends the provided change to + // the list of changes + Add(Change) error + + // Clear empties the current change list. + // Archive may be provided as a directory path + // to save a copy of the changelist in that location + Clear(archive string) error + + // Close syncronizes any pending writes to the underlying + // storage and closes the file/connection + Close() error +} + +const ( + ActionCreate = iota + ActionUpdate + ActionDelete +) + +type Change interface { + // "create","update", or "delete" + Action() int + + // Where the change should be made. + // For TUF this will be the role + Scope() string + + // The content type being affected. + // For TUF this will be "target", or "delegation". + // If the type is "delegation", the Scope will be + // used to determine if a root role is being updated + // or a target delegation. + Type() string + + // Path indicates the entry within a role to be affected by the + // change. For targets, this is simply the target's path, + // for delegations it's the delegated role name. + Path() string + + // Serialized content that the interpreter of a changelist + // can use to apply the change. + // For TUF this will be the serialized JSON that needs + // to be inserted or merged. In the case of a "delete" + // action, it will be nil. + Content() []byte +} diff --git a/client/client.go b/client/client.go index 549476871a..7420f32ddc 100644 --- a/client/client.go +++ b/client/client.go @@ -16,6 +16,7 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/notary/client/changelist" "github.com/docker/notary/trustmanager" "github.com/endophage/gotuf" tufclient "github.com/endophage/gotuf/client" @@ -216,19 +217,24 @@ func (r *NotaryRepository) Initialize(uRootKey UnlockedRootKey, rootKey *data.Pu // AddTarget adds a new target to the repository, forcing a timestamps check from TUF func (r *NotaryRepository) AddTarget(target *Target) error { - r.bootstrapRepo() - + cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) + if err != nil { + return err + } fmt.Printf("Adding target \"%s\" with sha256 \"%s\" and size %d bytes.\n", target.Name, target.Hashes["sha256"], target.Length) meta := data.FileMeta{Length: target.Length, Hashes: target.Hashes} - _, err := r.tufRepo.AddTargets("targets", data.Files{target.Name: meta}) + metaJSON, err := json.Marshal(meta) if err != nil { return err } - r.snapshot() - - return nil + c := changelist.NewTufChange(changelist.ActionCreate, "targets", "target", target.Name, metaJSON) + err = cl.Add(c) + if err != nil { + return err + } + return cl.Close() } // ListTargets lists all targets for the current repository @@ -561,7 +567,7 @@ func (c *NotaryClient) GetRepository(gun string, baseURL string, transport http. return &NotaryRepository{Gun: gun, baseURL: baseURL, - tufRepoPath: filepath.Join(c.baseDir, tufDir), + tufRepoPath: filepath.Join(c.baseDir, tufDir, gun), transport: transport, signer: signer, caStore: c.caStore, @@ -591,7 +597,7 @@ func (c *NotaryClient) InitRepository(gun string, baseURL string, transport http nRepo := &NotaryRepository{Gun: gun, baseURL: baseURL, - tufRepoPath: filepath.Join(c.baseDir, tufDir), + tufRepoPath: filepath.Join(c.baseDir, tufDir, gun), transport: transport, signer: signer, caStore: c.caStore, @@ -679,3 +685,38 @@ func getRemoteStore(gun string) (store.RemoteStore, error) { "key", ) } + +func applyChangelist(repo *tuf.TufRepo, cl changelist.Changelist) error { + changes := cl.List() + var err error + for _, c := range changes { + if c.Scope() == "targets" { + applyTargetsChange(repo, c) + } + if err != nil { + return err + } + } + return nil +} + +func applyTargetsChange(repo *tuf.TufRepo, c changelist.Change) error { + var err error + meta := &data.FileMeta{} + err = json.Unmarshal(c.Content(), meta) + if err != nil { + return nil + } + if c.Action() == changelist.ActionCreate { + files := data.Files{c.Path(): *meta} + _, err = repo.AddTargets("targets", files) + } else if c.Action() == changelist.ActionDelete { + err = repo.RemoveTargets("targets", c.Path()) + } + if err != nil { + // TODO(endophage): print out rem entries as files that couldn't + // be added. + return err + } + return nil +} diff --git a/cmd/notary-server/config.json b/cmd/notary-server/config.json index 830334db7a..290f6eb126 100644 --- a/cmd/notary-server/config.json +++ b/cmd/notary-server/config.json @@ -4,10 +4,11 @@ "tls_cert_file": "./fixtures/notary.pem", "tls_key_file": "./fixtures/notary.key" }, - "trust_service":{ - "type": "local", - "hostname": "", - "port": "" + "trust_service": { + "type": "remote", + "hostname": "rufus", + "port": "7899", + "tls_ca_file": "./fixtures/ca.cert" }, "logging": { "level": 5 diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 257de98a6f..d51c778502 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -9,7 +9,8 @@ import ( "github.com/Sirupsen/logrus" notaryclient "github.com/docker/notary/client" - "github.com/endophage/gotuf/store" + "github.com/endophage/gotuf/data" + "github.com/endophage/gotuf/keys" "github.com/spf13/cobra" ) @@ -84,7 +85,10 @@ func tufAdd(cmd *cobra.Command, args []string) { if err != nil { fatalf(err.Error()) } - repo.AddTarget(target) + err = repo.AddTarget(target) + if err != nil { + fatalf(err.Error()) + } fmt.Println("Successfully added targets") } @@ -188,18 +192,14 @@ func tufRemove(cmd *cobra.Command, args []string) { gun := args[0] targetName := args[1] - t := &http.Transport{} - _, err := nClient.GetRepository(gun, "", t) - if err != nil { - fatalf(err.Error()) - } + //c := changelist.NewTufChange(changelist.ActionDelete, "targets", "target", targetName, nil) + //err := cl.Add(c) + //if err != nil { + // fatalf(err.Error()) + //} // TODO(diogo): Implement RemoveTargets in libnotary fmt.Println("Removing target ", targetName, " from ", gun) - // repo.RemoveTargets("targets", targetName) - // if err != nil { - // fatalf(err.Error()) - // } } func verify(cmd *cobra.Command, args []string) { @@ -243,13 +243,72 @@ func verify(cmd *cobra.Command, args []string) { return } -// Use this to initialize remote HTTPStores from the config settings -func getRemoteStore(gun string) (store.RemoteStore, error) { - return store.NewHTTPStore( - "https://notary:4443/v2/"+gun+"/_trust/tuf/", - "", - "json", - "", - "key", - ) +//func generateKeys(kdb *keys.KeyDB, signer *signed.Signer, remote store.RemoteStore) (string, string, string, string, error) { +// rawTSKey, err := remote.GetKey("timestamp") +// if err != nil { +// return "", "", "", "", err +// } +// fmt.Println("RawKey: ", string(rawTSKey)) +// parsedKey := &data.TUFKey{} +// err = json.Unmarshal(rawTSKey, parsedKey) +// if err != nil { +// return "", "", "", "", err +// } +// timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public()) +// +// rootKey, err := signer.Create("root") +// if err != nil { +// return "", "", "", "", err +// } +// targetsKey, err := signer.Create("targets") +// if err != nil { +// return "", "", "", "", err +// } +// snapshotKey, err := signer.Create("snapshot") +// if err != nil { +// return "", "", "", "", err +// } +// +// kdb.AddKey(rootKey) +// kdb.AddKey(targetsKey) +// kdb.AddKey(snapshotKey) +// kdb.AddKey(timestampKey) +// return rootKey.ID(), targetsKey.ID(), snapshotKey.ID(), timestampKey.ID(), nil +//} + +func generateRoles(kdb *keys.KeyDB, rootKeyID, targetsKeyID, snapshotKeyID, timestampKeyID string) error { + rootRole, err := data.NewRole("root", 1, []string{rootKeyID}, nil, nil) + if err != nil { + return err + } + targetsRole, err := data.NewRole("targets", 1, []string{targetsKeyID}, nil, nil) + if err != nil { + return err + } + snapshotRole, err := data.NewRole("snapshot", 1, []string{snapshotKeyID}, nil, nil) + if err != nil { + return err + } + timestampRole, err := data.NewRole("timestamp", 1, []string{timestampKeyID}, nil, nil) + if err != nil { + return err + } + + err = kdb.AddRole(rootRole) + if err != nil { + return err + } + err = kdb.AddRole(targetsRole) + if err != nil { + return err + } + err = kdb.AddRole(snapshotRole) + if err != nil { + return err + } + err = kdb.AddRole(timestampRole) + if err != nil { + return err + } + return nil } diff --git a/server/handlers/default.go b/server/handlers/default.go index da594abe7c..fce87200e2 100644 --- a/server/handlers/default.go +++ b/server/handlers/default.go @@ -102,6 +102,13 @@ func GetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *er tufRole := vars["tufRole"] out, err := store.GetCurrent(gun, tufRole) if err != nil { + if _, ok := err.(*storage.ErrNotFound); ok { + return &errors.HTTPError{ + HTTPStatus: http.StatusNotFound, + Code: 9999, + Err: err, + } + } logrus.Errorf("[Notary Server] 500 GET repository: %s, role: %s", gun, tufRole) return &errors.HTTPError{ HTTPStatus: http.StatusInternalServerError, @@ -172,6 +179,13 @@ func GetTimestampHandler(ctx context.Context, w http.ResponseWriter, r *http.Req out, err := timestamp.GetOrCreateTimestamp(gun, store, signer) if err != nil { + if _, ok := err.(*storage.ErrNoKey); ok { + return &errors.HTTPError{ + HTTPStatus: http.StatusNotFound, + Code: 9999, + Err: err, + } + } return &errors.HTTPError{ HTTPStatus: http.StatusInternalServerError, Code: 9999, diff --git a/trustmanager/filestore.go b/trustmanager/filestore.go index af22ae1ea6..a054b49aeb 100644 --- a/trustmanager/filestore.go +++ b/trustmanager/filestore.go @@ -19,6 +19,7 @@ type FileStore interface { GetPath(fileName string) string ListAll() []string ListDir(directoryName string) []string + Link(src, dst string) error } type EncryptedFileStore interface { @@ -148,6 +149,13 @@ func (f *SimpleFileStore) genFilePath(name string) string { return filepath.Join(f.baseDir, fileName) } +func (f *SimpleFileStore) Link(src, dst string) error { + return os.Link( + f.genFilePath(src), + f.genFilePath(dst), + ) +} + // CreateDirectory uses createDirectory to create a chmod 755 Directory func CreateDirectory(dir string) error { return createDirectory(dir, visible) diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go index 27223e5082..9c25581c7e 100644 --- a/trustmanager/keyfilestore.go +++ b/trustmanager/keyfilestore.go @@ -53,10 +53,6 @@ func (s *KeyFileStore) GetDecrypted(fileName string, passphrase string) ([]byte, return KeyToPEM(privKey) } -func (s *KeyFileStore) Link(base, fileName string) error { - keyBytes, err := s.Get(base) - if err != nil { - return err - } - return s.Add(fileName, keyBytes) +func (s *KeyFileStore) Link(src, dst string) error { + return s.FileStore.Link(src, dst) } From 3bcc0e1d4d1e8139fed354cddea8644aee197232 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 8 Jul 2015 09:36:22 -0700 Subject: [PATCH 10/39] updating wo make notary work with rufus again Signed-off-by: David Lawrence (github: endophage) --- Godeps/Godeps.json | 2 +- .../github.com/docker/rufus/proto/rufus.pb.go | 52 +++++++++++++++---- .../github.com/docker/rufus/proto/rufus.proto | 20 ++++--- signer/rufus_trust.go | 6 +-- 4 files changed, 61 insertions(+), 19 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 99953fa538..82541cf4d2 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -43,7 +43,7 @@ }, { "ImportPath": "github.com/docker/rufus/proto", - "Rev": "61b53384b24bfa83e8e0a5f11f28ae83457fd80c" + "Rev": "7f61f678c264ae0a329f25cbaa8af6fd55ada7b6" }, { "ImportPath": "github.com/endophage/gotuf", diff --git a/Godeps/_workspace/src/github.com/docker/rufus/proto/rufus.pb.go b/Godeps/_workspace/src/github.com/docker/rufus/proto/rufus.pb.go index 49217eb51d..9cbfb6a14f 100644 --- a/Godeps/_workspace/src/github.com/docker/rufus/proto/rufus.pb.go +++ b/Godeps/_workspace/src/github.com/docker/rufus/proto/rufus.pb.go @@ -10,6 +10,7 @@ It is generated from these files: It has these top-level messages: KeyID + Algorithm PublicKey Signature SignatureRequest @@ -40,10 +41,20 @@ func (m *KeyID) Reset() { *m = KeyID{} } func (m *KeyID) String() string { return proto1.CompactTextString(m) } func (*KeyID) ProtoMessage() {} -// PublicKey has a KeyID that is used to reference the key and opaque bytes of a publicKey +// Type holds the type of crypto algorithm used +type Algorithm struct { + Algorithm string `protobuf:"bytes,1,opt,name=algorithm" json:"algorithm,omitempty"` +} + +func (m *Algorithm) Reset() { *m = Algorithm{} } +func (m *Algorithm) String() string { return proto1.CompactTextString(m) } +func (*Algorithm) ProtoMessage() {} + +// PublicKey has a KeyID that is used to reference the key, the key type, and opaque bytes of a publicKey type PublicKey struct { - KeyID *KeyID `protobuf:"bytes,1,opt,name=keyID" json:"keyID,omitempty"` - PublicKey []byte `protobuf:"bytes,2,opt,name=publicKey,proto3" json:"publicKey,omitempty"` + KeyID *KeyID `protobuf:"bytes,1,opt,name=keyID" json:"keyID,omitempty"` + Algorithm *Algorithm `protobuf:"bytes,2,opt,name=algorithm" json:"algorithm,omitempty"` + PublicKey []byte `protobuf:"bytes,3,opt,name=publicKey,proto3" json:"publicKey,omitempty"` } func (m *PublicKey) Reset() { *m = PublicKey{} } @@ -57,10 +68,18 @@ func (m *PublicKey) GetKeyID() *KeyID { return nil } -// Signature specifies a KeyID that was used for signing and signed content +func (m *PublicKey) GetAlgorithm() *Algorithm { + if m != nil { + return m.Algorithm + } + return nil +} + +// Signature specifies a KeyID that was used for signing, the key type, and signed content type Signature struct { - KeyID *KeyID `protobuf:"bytes,1,opt,name=keyID" json:"keyID,omitempty"` - Content []byte `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"` + KeyID *KeyID `protobuf:"bytes,1,opt,name=keyID" json:"keyID,omitempty"` + Algorithm *Algorithm `protobuf:"bytes,2,opt,name=algorithm" json:"algorithm,omitempty"` + Content []byte `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"` } func (m *Signature) Reset() { *m = Signature{} } @@ -74,10 +93,18 @@ func (m *Signature) GetKeyID() *KeyID { return nil } -// SignatureRequests specifies a KeyID for signing and content to be signed +func (m *Signature) GetAlgorithm() *Algorithm { + if m != nil { + return m.Algorithm + } + return nil +} + +// SignatureRequests specifies a KeyID for signing, the type of signature requested, and content to be signed type SignatureRequest struct { - KeyID *KeyID `protobuf:"bytes,1,opt,name=keyID" json:"keyID,omitempty"` - Content []byte `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"` + KeyID *KeyID `protobuf:"bytes,1,opt,name=keyID" json:"keyID,omitempty"` + Algorithm *Algorithm `protobuf:"bytes,2,opt,name=algorithm" json:"algorithm,omitempty"` + Content []byte `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"` } func (m *SignatureRequest) Reset() { *m = SignatureRequest{} } @@ -91,6 +118,13 @@ func (m *SignatureRequest) GetKeyID() *KeyID { return nil } +func (m *SignatureRequest) GetAlgorithm() *Algorithm { + if m != nil { + return m.Algorithm + } + return nil +} + // Void represents an empty message type type Void struct { } diff --git a/Godeps/_workspace/src/github.com/docker/rufus/proto/rufus.proto b/Godeps/_workspace/src/github.com/docker/rufus/proto/rufus.proto index 403ba16437..7d11a106ae 100644 --- a/Godeps/_workspace/src/github.com/docker/rufus/proto/rufus.proto +++ b/Godeps/_workspace/src/github.com/docker/rufus/proto/rufus.proto @@ -26,22 +26,30 @@ message KeyID { string ID = 1; } -// PublicKey has a KeyID that is used to reference the key and opaque bytes of a publicKey +// Type holds the type of crypto algorithm used +message Algorithm { + string algorithm = 1; +} + +// PublicKey has a KeyID that is used to reference the key, the key type, and opaque bytes of a publicKey message PublicKey { KeyID keyID = 1; - bytes publicKey = 2; + Algorithm algorithm = 2; + bytes publicKey = 3; } -// Signature specifies a KeyID that was used for signing and signed content +// Signature specifies a KeyID that was used for signing, the key type, and signed content message Signature { KeyID keyID = 1; - bytes content = 2; + Algorithm algorithm = 2; + bytes content = 3; } -// SignatureRequests specifies a KeyID for signing and content to be signed +// SignatureRequests specifies a KeyID for signing, the type of signature requested, and content to be signed message SignatureRequest { KeyID keyID = 1; - bytes content = 2; + Algorithm algorithm = 2; + bytes content = 3; } // Void represents an empty message type diff --git a/signer/rufus_trust.go b/signer/rufus_trust.go index 24438d55b8..ce51457f5d 100644 --- a/signer/rufus_trust.go +++ b/signer/rufus_trust.go @@ -53,7 +53,7 @@ func (trust *RufusSigner) Sign(keyIDs []string, toSign []byte) ([]data.Signature } signatures = append(signatures, data.Signature{ KeyID: sig.KeyID.ID, - Method: sig.Algorithm, + Method: sig.Algorithm.Algorithm, Signature: sig.Content, }) } @@ -67,7 +67,7 @@ func (trust *RufusSigner) Create(role string) (*data.PublicKey, error) { return nil, err } //TODO(mccauley): Update API to return algorithm and/or take it as a param - public := data.NewPublicKey(publicKey.Algorithm, publicKey.PublicKey) + public := data.NewPublicKey(publicKey.Algorithm.Algorithm, publicKey.PublicKey) return public, nil } @@ -81,7 +81,7 @@ func (trust *RufusSigner) PublicKeys(keyIDs ...string) (map[string]*data.PublicK return nil, err } publicKeys[public.KeyID.ID] = - data.NewPublicKey(public.Algorithm, public.PublicKey) + data.NewPublicKey(public.Algorithm.Algorithm, public.PublicKey) } return publicKeys, nil } From 12b4b3d80de47db80c3f02a3dacf0d3df5598fcd Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 8 Jul 2015 10:05:55 -0700 Subject: [PATCH 11/39] working on publish with changelist Signed-off-by: David Lawrence (github: endophage) --- client/client.go | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/client/client.go b/client/client.go index 7420f32ddc..0584875753 100644 --- a/client/client.go +++ b/client/client.go @@ -284,32 +284,58 @@ func (r *NotaryRepository) GetTargetByName(name string) (*Target, error) { // Publish pushes the local changes in signed material to the remote notary-server func (r *NotaryRepository) Publish() error { - r.bootstrapRepo() + _, err := r.bootstrapClient() // just need the repo to be initialized from remote + if err != nil { + if _, ok := err.(*store.ErrMetaNotFound); ok { + // init or return error to make caller init, then publish again + } else { + return err + } + } + + cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) + if err != nil { + return err + } + applyChangelist(r.tufRepo, cl) remote, err := getRemoteStore(r.Gun) - root, err := r.fileStore.GetMeta("root", 0) + root, err := r.tufRepo.SignRoot(data.DefaultExpires("root"), r.signer) if err != nil { return err } - targets, err := r.fileStore.GetMeta("targets", 0) + targets, err := r.tufRepo.SignTargets("targets", data.DefaultExpires("targets"), nil) if err != nil { return err } - snapshot, err := r.fileStore.GetMeta("snapshot", 0) + snapshot, err := r.tufRepo.SignSnapshot(data.DefaultExpires("snapshot"), nil) if err != nil { return err } - err = remote.SetMeta("root", root) + rootJSON, err := json.Marshal(root) if err != nil { return err } - err = remote.SetMeta("targets", targets) + targetsJSON, err := json.Marshal(targets) if err != nil { return err } - err = remote.SetMeta("snapshot", snapshot) + snapshotJSON, err := json.Marshal(snapshot) + if err != nil { + return err + } + + err = remote.SetMeta("root", rootJSON) + if err != nil { + return err + } + err = remote.SetMeta("targets", targetsJSON) + if err != nil { + return err + } + err = remote.SetMeta("snapshot", snapshotJSON) if err != nil { return err } From f4e1d3e9327e2f317ebd70bc77f12841b5b78e30 Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Wed, 8 Jul 2015 11:36:15 -0700 Subject: [PATCH 12/39] Changing hardlink to symlink Signed-off-by: Diogo Monica --- trustmanager/filestore.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/trustmanager/filestore.go b/trustmanager/filestore.go index a054b49aeb..7cf7e4fdc4 100644 --- a/trustmanager/filestore.go +++ b/trustmanager/filestore.go @@ -145,14 +145,25 @@ func (f *SimpleFileStore) list(path string) []string { // genFilePath returns the full path with extension given a file name func (f *SimpleFileStore) genFilePath(name string) string { - fileName := fmt.Sprintf("%s.%s", name, f.fileExt) + fileName := f.genFileName(name) return filepath.Join(f.baseDir, fileName) } -func (f *SimpleFileStore) Link(src, dst string) error { - return os.Link( - f.genFilePath(src), - f.genFilePath(dst), +// genFileName returns the name using the right extension +func (f *SimpleFileStore) genFileName(name string) string { + return fmt.Sprintf("%s.%s", name, f.fileExt) +} + +// Link creates a symlink beetween the ID of the certificate used by a repository +// and the ID of the root key that is being used. +// We use full path for the source and local for the destination to use relative +// path for the symlink +func (f *SimpleFileStore) Link(oldname, newname string) error { + fmt.Println("Src: ", f.genFileName(oldname)) + fmt.Println("dst: ", f.genFilePath(newname)) + return os.Symlink( + f.genFileName(oldname), + f.genFilePath(newname), ) } From e7462dcdad1894e3e2da7c174a7c1d56deffc1e4 Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Wed, 8 Jul 2015 13:06:40 -0700 Subject: [PATCH 13/39] Write test for FileStore's Link method Also remove debugging print statements from the Link method. --- trustmanager/filestore.go | 2 -- trustmanager/filestore_test.go | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/trustmanager/filestore.go b/trustmanager/filestore.go index 7cf7e4fdc4..54d41bb50e 100644 --- a/trustmanager/filestore.go +++ b/trustmanager/filestore.go @@ -159,8 +159,6 @@ func (f *SimpleFileStore) genFileName(name string) string { // We use full path for the source and local for the destination to use relative // path for the symlink func (f *SimpleFileStore) Link(oldname, newname string) error { - fmt.Println("Src: ", f.genFileName(oldname)) - fmt.Println("dst: ", f.genFilePath(newname)) return os.Symlink( f.genFileName(oldname), f.genFilePath(newname), diff --git a/trustmanager/filestore_test.go b/trustmanager/filestore_test.go index 2acba47fbc..8b08686343 100644 --- a/trustmanager/filestore_test.go +++ b/trustmanager/filestore_test.go @@ -209,6 +209,51 @@ func TestListDir(t *testing.T) { t.Fatalf("expected 0 files in listing, got: %d", len(files)) } } + +func TestLink(t *testing.T) { + testName := "docker.com/notary/certificate" + testSymlink := "docker.com/notary/certificate-symlink" + testExt := "crt" + perms := os.FileMode(0755) + + // Temporary directory where test files will be created + tempBaseDir, err := ioutil.TempDir("", "notary-test-") + if err != nil { + t.Fatalf("failed to create a temporary directory: %v", err) + } + + // Since we're generating this manually we need to add the extension '.' + expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt) + expectedSymlinkPath := filepath.Join(tempBaseDir, testSymlink+"."+testExt) + + _, err = generateRandomFile(expectedFilePath, perms) + if err != nil { + t.Fatalf("failed to generate random file: %v", err) + } + + // Create our SimpleFileStore + store := &SimpleFileStore{ + baseDir: tempBaseDir, + fileExt: testExt, + perms: perms, + } + + // Call the Link function + err = store.Link("certificate", testSymlink) + if err != nil { + t.Fatalf("failed to create symlink: %v", err) + } + + // Check to see if the symlink exists + actualSymlinkDest, err := os.Readlink(expectedSymlinkPath) + if err != nil { + t.Fatalf("expected to find symlink at path: %s", expectedSymlinkPath) + } + if actualSymlinkDest != "certificate."+testExt { + t.Fatalf("symlink has wrong destination: %s", actualSymlinkDest) + } +} + func TestGetPath(t *testing.T) { testExt := "crt" perms := os.FileMode(0755) From 8b1e9e0fafb70a01b2864e81ede637d1a7db2d77 Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Wed, 8 Jul 2015 15:32:36 -0700 Subject: [PATCH 14/39] Fix uninitialized privKeyStore member in NotaryRepository Store a pointer to trustmanager.KeyFileStore in CryptoService, RootCryptoService, NotaryClient, and NotaryRepository, instead of copying the KeyFileStore structure. Populate this pointer when creating a NotaryRepository. Previously, it was left uninitialized. --- client/cli_crypto_service.go | 8 ++++---- client/client.go | 12 +++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/client/cli_crypto_service.go b/client/cli_crypto_service.go index f65e866849..4fde462d85 100644 --- a/client/cli_crypto_service.go +++ b/client/cli_crypto_service.go @@ -17,22 +17,22 @@ import ( type CryptoService struct { gun string - keyStore trustmanager.KeyFileStore + keyStore *trustmanager.KeyFileStore } type RootCryptoService struct { // TODO(diogo): support multiple passphrases per key passphrase string - rootKeyStore trustmanager.KeyFileStore + rootKeyStore *trustmanager.KeyFileStore } // NewCryptoService returns an instance of CryptoService -func NewCryptoService(gun string, keyStore trustmanager.KeyFileStore) *CryptoService { +func NewCryptoService(gun string, keyStore *trustmanager.KeyFileStore) *CryptoService { return &CryptoService{gun: gun, keyStore: keyStore} } // NewRootCryptoService returns an instance of CryptoService -func NewRootCryptoService(rootKeyStore trustmanager.KeyFileStore, passphrase string) *RootCryptoService { +func NewRootCryptoService(rootKeyStore *trustmanager.KeyFileStore, passphrase string) *RootCryptoService { return &RootCryptoService{rootKeyStore: rootKeyStore, passphrase: passphrase} } diff --git a/client/client.go b/client/client.go index 0584875753..f4299f4a31 100644 --- a/client/client.go +++ b/client/client.go @@ -66,7 +66,7 @@ type NotaryClient struct { baseDir string caStore trustmanager.X509Store certificateStore trustmanager.X509Store - rootKeyStore trustmanager.KeyFileStore + rootKeyStore *trustmanager.KeyFileStore } type NotaryRepository struct { @@ -77,7 +77,7 @@ type NotaryRepository struct { signer *signed.Signer tufRepo *tuf.TufRepo fileStore store.MetadataStore - privKeyStore trustmanager.KeyFileStore + privKeyStore *trustmanager.KeyFileStore caStore trustmanager.X509Store certificateStore trustmanager.X509Store } @@ -589,13 +589,14 @@ func (c *NotaryClient) GetRepository(gun string, baseURL string, transport http. return nil, err } - signer := signed.NewSigner(NewCryptoService(gun, *privKeyStore)) + signer := signed.NewSigner(NewCryptoService(gun, privKeyStore)) return &NotaryRepository{Gun: gun, baseURL: baseURL, tufRepoPath: filepath.Join(c.baseDir, tufDir, gun), transport: transport, signer: signer, + privKeyStore: privKeyStore, caStore: c.caStore, certificateStore: c.certificateStore}, nil } @@ -619,13 +620,14 @@ func (c *NotaryClient) InitRepository(gun string, baseURL string, transport http return nil, err } - signer := signed.NewSigner(NewCryptoService(gun, *privKeyStore)) + signer := signed.NewSigner(NewCryptoService(gun, privKeyStore)) nRepo := &NotaryRepository{Gun: gun, baseURL: baseURL, tufRepoPath: filepath.Join(c.baseDir, tufDir, gun), transport: transport, signer: signer, + privKeyStore: privKeyStore, caStore: c.caStore, certificateStore: c.certificateStore} @@ -669,7 +671,7 @@ func (c *NotaryClient) loadKeys(trustDir, rootKeysDir string) error { c.caStore = caStore c.certificateStore = certificateStore - c.rootKeyStore = *rootKeyStore + c.rootKeyStore = rootKeyStore return nil } From 458a7a8facedbd7c5ae737e74d35d8a017baa2d1 Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Wed, 8 Jul 2015 15:35:01 -0700 Subject: [PATCH 15/39] Add TestInitRepo This test initializes a repo and then performs a series of sanity checks on the filesystem hierarchy. --- client/client_test.go | 116 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 client/client_test.go diff --git a/client/client_test.go b/client/client_test.go new file mode 100644 index 0000000000..f0ecc709af --- /dev/null +++ b/client/client_test.go @@ -0,0 +1,116 @@ +package client + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/docker/notary/trustmanager" + "github.com/endophage/gotuf/data" +) + +// TestInitRepo runs through the process of initializing a repository and makes +// sure the repository looks correct on disk. +func TestInitRepo(t *testing.T) { + // Temporary directory where test files will be created + tempBaseDir, err := ioutil.TempDir("", "notary-test-") + if err != nil { + t.Fatalf("failed to create a temporary directory: %s", err) + } + + client, err := NewClient(tempBaseDir) + if err != nil { + t.Fatalf("error creating client: %s", err) + } + + rootKeyID, err := client.GenRootKey("passphrase") + if err != nil { + t.Fatalf("error generating root key: %s", err) + } + + rootKey, err := client.GetRootKey(rootKeyID, "passphrase") + if err != nil { + t.Fatalf("error retreiving root key: %s", err) + } + + gun := "docker.com/notary" + repo, err := client.InitRepository(gun, "", nil, rootKey) + if err != nil { + t.Fatalf("error creating repository: %s", err) + } + + // Inspect contents of the temporary directory + expectedDirs := []string{ + "private", + filepath.Join("private", gun), + filepath.Join("private", "root_keys"), + "trusted_certificates", + filepath.Join("trusted_certificates", gun), + "tuf", + filepath.Join("tuf", gun, "metadata"), + filepath.Join("tuf", gun, "targets"), + } + for _, dir := range expectedDirs { + fi, err := os.Stat(filepath.Join(tempBaseDir, dir)) + if err != nil { + t.Fatalf("missing directory in base directory: %s", dir) + } + if !fi.Mode().IsDir() { + t.Fatalf("%s is not a directory", dir) + } + } + + // Look for keys in private. The filenames should match the key IDs + // in the private key store. + privKeyList := repo.privKeyStore.ListAll() + for _, privKeyName := range privKeyList { + if _, err := os.Stat(privKeyName); err != nil { + t.Fatalf("missing private key: %s", privKeyName) + } + } + + // Look for keys in root_keys + // There should be a file named after the key ID of the root key we + // passed in. + rootKeyFilename := rootKey.ID() + ".key" + if _, err := os.Stat(filepath.Join(tempBaseDir, "private", "root_keys", rootKeyFilename)); err != nil { + t.Fatal("missing root key") + } + + // Also expect a symlink from the key ID of the certificate key to this + // root key + certificates := client.certificateStore.GetCertificates() + if len(certificates) != 1 { + t.Fatalf("unexpected number of certificates (%d)", len(certificates)) + } + + certID := data.NewPublicKey("RSA", trustmanager.CertToPEM(certificates[0])).ID() + + actualDest, err := os.Readlink(filepath.Join(tempBaseDir, "private", "root_keys", certID+".key")) + if err != nil { + t.Fatal("missing symlink to root key") + } + + if actualDest != rootKeyFilename { + t.Fatalf("symlink to root key has wrong destination (got: %s, expected: %s)", actualDest, rootKeyFilename) + } + + // There should be a trusted certificate + if _, err := os.Stat(filepath.Join(tempBaseDir, "trusted_certificates", gun, certID+".crt")); err != nil { + t.Fatal("missing trusted certificate") + } + + // Check that tuf metadata files were created + expectedTUFMetadataFiles := []string{ + filepath.Join("tuf", gun, "metadata", "root.json"), + filepath.Join("tuf", gun, "metadata", "snapshot.json"), + filepath.Join("tuf", gun, "metadata", "targets.json"), + } + for _, filename := range expectedTUFMetadataFiles { + _, err := os.Stat(filepath.Join(tempBaseDir, filename)) + if err != nil { + t.Fatalf("missing TUF metadata file: %s", filename) + } + } +} From 42e3f1a1c004c4ad7ee55709aeab77e148d17780 Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Wed, 8 Jul 2015 15:56:32 -0700 Subject: [PATCH 16/39] Use trustmanager.FingerprintCert instead of more complex method --- client/client_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index f0ecc709af..905fcc7ef3 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/docker/notary/trustmanager" - "github.com/endophage/gotuf/data" ) // TestInitRepo runs through the process of initializing a repository and makes @@ -85,7 +84,7 @@ func TestInitRepo(t *testing.T) { t.Fatalf("unexpected number of certificates (%d)", len(certificates)) } - certID := data.NewPublicKey("RSA", trustmanager.CertToPEM(certificates[0])).ID() + certID := trustmanager.FingerprintCert(certificates[0]) actualDest, err := os.Readlink(filepath.Join(tempBaseDir, "private", "root_keys", certID+".key")) if err != nil { From 4602f5fb6c6431302f6afa0d325440f40f762e2a Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Wed, 8 Jul 2015 16:26:35 -0700 Subject: [PATCH 17/39] Extend TestInitRepo to parse and sanity-check TUF metadata --- client/client_test.go | 61 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index 905fcc7ef3..cbb00f0d56 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -1,12 +1,15 @@ package client import ( + "encoding/json" "io/ioutil" "os" "path/filepath" + "strings" "testing" "github.com/docker/notary/trustmanager" + "github.com/endophage/gotuf/data" ) // TestInitRepo runs through the process of initializing a repository and makes @@ -100,16 +103,70 @@ func TestInitRepo(t *testing.T) { t.Fatal("missing trusted certificate") } - // Check that tuf metadata files were created + // Sanity check the TUF metadata files. Verify that they exist, the JSON is + // well-formed, and the signatures exist. For the root.json file, also check + // that the root, snapshot, and targets key IDs are present. expectedTUFMetadataFiles := []string{ filepath.Join("tuf", gun, "metadata", "root.json"), filepath.Join("tuf", gun, "metadata", "snapshot.json"), filepath.Join("tuf", gun, "metadata", "targets.json"), } for _, filename := range expectedTUFMetadataFiles { - _, err := os.Stat(filepath.Join(tempBaseDir, filename)) + fullPath := filepath.Join(tempBaseDir, filename) + _, err := os.Stat(fullPath) if err != nil { t.Fatalf("missing TUF metadata file: %s", filename) } + + jsonBytes, err := ioutil.ReadFile(fullPath) + if err != nil { + t.Fatalf("error reading TUF metadata file %s: %s", filename, err) + } + + var decoded data.Signed + if err := json.Unmarshal(jsonBytes, &decoded); err != nil { + t.Fatalf("error parsing TUF metadata file %s: %s", filename, err) + } + + if len(decoded.Signatures) != 1 { + t.Fatalf("incorrect number of signatures in TUF metadata file %s", filename) + } + + if decoded.Signatures[0].KeyID == "" || decoded.Signatures[0].Method == "" || len(decoded.Signatures[0].Signature) == 0 { + t.Fatalf("bad content in signature on TUF metadata file %s", filename) + } + + // Special case for root.json: also check that the signed + // content for keys and roles + if strings.HasSuffix(filename, "root.json") { + var decodedRoot data.Root + if err := json.Unmarshal(decoded.Signed, &decodedRoot); err != nil { + t.Fatalf("error parsing root.json signed section: %s", err) + } + + if decodedRoot.Type != "Root" { + t.Fatal("_type mismatch in root.json") + } + + if decodedRoot.Type != "Root" { + t.Fatal("_type mismatch in root.json") + } + + // Expect 4 keys in the Keys map: root, targets, snapshot, timestamp + if len(decodedRoot.Keys) != 4 { + t.Fatal("wrong number of keys in root.json") + } + + roleCount := 0 + for role := range decodedRoot.Roles { + roleCount++ + if role != "root" && role != "snapshot" && role != "targets" && role != "timestamp" { + t.Fatalf("unexpected role %s in root.json", role) + } + } + if roleCount != 4 { + t.Fatalf("wrong number of roles (%d) in root.json", roleCount) + } + } } } From aa2caade20048abc8ff83a8db1d60dddf760b38a Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Wed, 8 Jul 2015 17:21:28 -0700 Subject: [PATCH 18/39] Convert client_test.go to use testify This simplifies the code and will be more consistent with upcoming tests. --- client/client_test.go | 100 +++++++++++++----------------------------- 1 file changed, 30 insertions(+), 70 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index cbb00f0d56..37e7785130 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -10,6 +10,7 @@ import ( "github.com/docker/notary/trustmanager" "github.com/endophage/gotuf/data" + "github.com/stretchr/testify/assert" ) // TestInitRepo runs through the process of initializing a repository and makes @@ -17,30 +18,20 @@ import ( func TestInitRepo(t *testing.T) { // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") - if err != nil { - t.Fatalf("failed to create a temporary directory: %s", err) - } + assert.NoError(t, err, "failed to create a temporary directory: %s", err) client, err := NewClient(tempBaseDir) - if err != nil { - t.Fatalf("error creating client: %s", err) - } + assert.NoError(t, err, "error creating client: %s", err) rootKeyID, err := client.GenRootKey("passphrase") - if err != nil { - t.Fatalf("error generating root key: %s", err) - } + assert.NoError(t, err, "error generating root key: %s", err) rootKey, err := client.GetRootKey(rootKeyID, "passphrase") - if err != nil { - t.Fatalf("error retreiving root key: %s", err) - } + assert.NoError(t, err, "error retreiving root key: %s", err) gun := "docker.com/notary" repo, err := client.InitRepository(gun, "", nil, rootKey) - if err != nil { - t.Fatalf("error creating repository: %s", err) - } + assert.NoError(t, err, "error creating repository: %s", err) // Inspect contents of the temporary directory expectedDirs := []string{ @@ -55,53 +46,40 @@ func TestInitRepo(t *testing.T) { } for _, dir := range expectedDirs { fi, err := os.Stat(filepath.Join(tempBaseDir, dir)) - if err != nil { - t.Fatalf("missing directory in base directory: %s", dir) - } - if !fi.Mode().IsDir() { - t.Fatalf("%s is not a directory", dir) - } + assert.NoError(t, err, "missing directory in base directory: %s", dir) + assert.True(t, fi.Mode().IsDir(), "%s is not a directory", dir) } // Look for keys in private. The filenames should match the key IDs // in the private key store. privKeyList := repo.privKeyStore.ListAll() for _, privKeyName := range privKeyList { - if _, err := os.Stat(privKeyName); err != nil { - t.Fatalf("missing private key: %s", privKeyName) - } + _, err := os.Stat(privKeyName) + assert.NoError(t, err, "missing private key: %s", privKeyName) } // Look for keys in root_keys // There should be a file named after the key ID of the root key we // passed in. rootKeyFilename := rootKey.ID() + ".key" - if _, err := os.Stat(filepath.Join(tempBaseDir, "private", "root_keys", rootKeyFilename)); err != nil { - t.Fatal("missing root key") - } + _, err = os.Stat(filepath.Join(tempBaseDir, "private", "root_keys", rootKeyFilename)) + assert.NoError(t, err, "missing root key") // Also expect a symlink from the key ID of the certificate key to this // root key certificates := client.certificateStore.GetCertificates() - if len(certificates) != 1 { - t.Fatalf("unexpected number of certificates (%d)", len(certificates)) - } + assert.Len(t, certificates, 1, "unexpected number of certificates") certID := trustmanager.FingerprintCert(certificates[0]) actualDest, err := os.Readlink(filepath.Join(tempBaseDir, "private", "root_keys", certID+".key")) - if err != nil { - t.Fatal("missing symlink to root key") - } + assert.NoError(t, err, "missing symlink to root key") - if actualDest != rootKeyFilename { - t.Fatalf("symlink to root key has wrong destination (got: %s, expected: %s)", actualDest, rootKeyFilename) - } + assert.Equal(t, rootKeyFilename, actualDest, "symlink to root key has wrong destination") // There should be a trusted certificate - if _, err := os.Stat(filepath.Join(tempBaseDir, "trusted_certificates", gun, certID+".crt")); err != nil { - t.Fatal("missing trusted certificate") - } + _, err = os.Stat(filepath.Join(tempBaseDir, "trusted_certificates", gun, certID+".crt")) + assert.NoError(t, err, "missing trusted certificate") // Sanity check the TUF metadata files. Verify that they exist, the JSON is // well-formed, and the signatures exist. For the root.json file, also check @@ -114,48 +92,32 @@ func TestInitRepo(t *testing.T) { for _, filename := range expectedTUFMetadataFiles { fullPath := filepath.Join(tempBaseDir, filename) _, err := os.Stat(fullPath) - if err != nil { - t.Fatalf("missing TUF metadata file: %s", filename) - } + assert.NoError(t, err, "missing TUF metadata file: %s", filename) jsonBytes, err := ioutil.ReadFile(fullPath) - if err != nil { - t.Fatalf("error reading TUF metadata file %s: %s", filename, err) - } + assert.NoError(t, err, "error reading TUF metadata file %s: %s", filename, err) var decoded data.Signed - if err := json.Unmarshal(jsonBytes, &decoded); err != nil { - t.Fatalf("error parsing TUF metadata file %s: %s", filename, err) - } + err = json.Unmarshal(jsonBytes, &decoded) + assert.NoError(t, err, "error parsing TUF metadata file %s: %s", filename, err) - if len(decoded.Signatures) != 1 { - t.Fatalf("incorrect number of signatures in TUF metadata file %s", filename) - } + assert.Len(t, decoded.Signatures, 1, "incorrect number of signatures in TUF metadata file %s", filename) - if decoded.Signatures[0].KeyID == "" || decoded.Signatures[0].Method == "" || len(decoded.Signatures[0].Signature) == 0 { - t.Fatalf("bad content in signature on TUF metadata file %s", filename) - } + assert.NotEmpty(t, decoded.Signatures[0].KeyID, "empty key ID field in TUF metadata file %s", filename) + assert.NotEmpty(t, decoded.Signatures[0].Method, "empty method field in TUF metadata file %s", filename) + assert.NotEmpty(t, decoded.Signatures[0].Signature, "empty signature in TUF metadata file %s", filename) // Special case for root.json: also check that the signed // content for keys and roles if strings.HasSuffix(filename, "root.json") { var decodedRoot data.Root - if err := json.Unmarshal(decoded.Signed, &decodedRoot); err != nil { - t.Fatalf("error parsing root.json signed section: %s", err) - } + err := json.Unmarshal(decoded.Signed, &decodedRoot) + assert.NoError(t, err, "error parsing root.json signed section: %s", err) - if decodedRoot.Type != "Root" { - t.Fatal("_type mismatch in root.json") - } - - if decodedRoot.Type != "Root" { - t.Fatal("_type mismatch in root.json") - } + assert.Equal(t, "Root", decodedRoot.Type, "_type mismatch in root.json") // Expect 4 keys in the Keys map: root, targets, snapshot, timestamp - if len(decodedRoot.Keys) != 4 { - t.Fatal("wrong number of keys in root.json") - } + assert.Len(t, decodedRoot.Keys, 4, "wrong number of keys in root.json") roleCount := 0 for role := range decodedRoot.Roles { @@ -164,9 +126,7 @@ func TestInitRepo(t *testing.T) { t.Fatalf("unexpected role %s in root.json", role) } } - if roleCount != 4 { - t.Fatalf("wrong number of roles (%d) in root.json", roleCount) - } + assert.Equal(t, 4, roleCount, "wrong number of roles (%d) in root.json", roleCount) } } } From 2f986f1a1be91300f79f1489e39edceae2fa9887 Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Wed, 8 Jul 2015 13:58:25 -0700 Subject: [PATCH 19/39] WIP --- client/client.go | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/client/client.go b/client/client.go index f4299f4a31..e149d02644 100644 --- a/client/client.go +++ b/client/client.go @@ -57,9 +57,10 @@ type Repository interface { } type UnlockedRootKey struct { - cipher string - pemBytes []byte - signer *signed.Signer + cipher string + pemPrivKey []byte + pemPubKey []byte + signer *signed.Signer } type NotaryClient struct { @@ -561,7 +562,9 @@ func (c *NotaryClient) GenRootKey(passphrase string) (string, error) { return "", fmt.Errorf("failed to generate the certificate for key: %v", err) } - keyID := data.NewPublicKey("RSA", pemKey).ID() + // + keyID := data.NewPrivateKey("RSA", pemKey, pemKey).ID() + c.rootKeyStore.AddEncrypted(keyID, pemKey, passphrase) return keyID, nil @@ -569,7 +572,7 @@ func (c *NotaryClient) GenRootKey(passphrase string) (string, error) { // GetRootKey retreives a root key that includes the ID and a signer func (c *NotaryClient) GetRootKey(rootKeyID, passphrase string) (UnlockedRootKey, error) { - rootKeyPem, err := c.rootKeyStore.GetDecrypted(rootKeyID, passphrase) + pemPrivKey, err := c.rootKeyStore.GetDecrypted(rootKeyID, passphrase) if err != nil { return UnlockedRootKey{}, fmt.Errorf("could not get encrypted root key: %v", err) } @@ -577,9 +580,9 @@ func (c *NotaryClient) GetRootKey(rootKeyID, passphrase string) (UnlockedRootKey signer := signed.NewSigner(NewRootCryptoService(c.rootKeyStore, passphrase)) return UnlockedRootKey{ - cipher: "RSA", - pemBytes: rootKeyPem, - signer: signer}, nil + cipher: "RSA", + pemPrivKey: pemPrivKey, + signer: signer}, nil } // GetRepository returns a new repository @@ -602,7 +605,6 @@ func (c *NotaryClient) GetRepository(gun string, baseURL string, transport http. } func (c *NotaryClient) InitRepository(gun string, baseURL string, transport http.RoundTripper, uRootKey UnlockedRootKey) (*NotaryRepository, error) { - //rootKey := data.NewPublicKey(uRootKey.cipher, uRootKey.pemBytes) // Creates and saves a trusted certificate for this store, with this root key rootCert, err := uRootKey.GenerateCertificate(gun) if err != nil { @@ -676,12 +678,14 @@ func (c *NotaryClient) loadKeys(trustDir, rootKeysDir string) error { return nil } +// ID gets a consistent ID based on the PrivateKey bytes and cipher type func (uk *UnlockedRootKey) ID() string { - return data.NewPublicKey(uk.cipher, uk.pemBytes).ID() + return data.NewPrivateKey(uk.cipher, uk.pemPrivKey, uk.pemPrivKey).ID() } +// GenerateCertificate func (uk *UnlockedRootKey) GenerateCertificate(gun string) (*x509.Certificate, error) { - privKeyBytes, _ := pem.Decode(uk.pemBytes) + privKeyBytes, _ := pem.Decode(uk.pemPrivKey) privKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes.Bytes) if err != nil { return nil, fmt.Errorf("failed to parse root key: %v (%s)", gun, err.Error()) From f9f11e57815655e369aab390741181793e5927d4 Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Wed, 8 Jul 2015 18:01:01 -0700 Subject: [PATCH 20/39] Starting the key refactor; rename UnlockedRootKey Signed-off-by: Diogo Monica --- client/cli_crypto_service.go | 22 +++++--- client/client.go | 106 +++++++++++++++-------------------- client/client_test.go | 6 +- cmd/notary/tuf.go | 4 +- trustmanager/x509utils.go | 46 ++++++++++++--- 5 files changed, 103 insertions(+), 81 deletions(-) diff --git a/client/cli_crypto_service.go b/client/cli_crypto_service.go index 4fde462d85..ea47d59395 100644 --- a/client/cli_crypto_service.go +++ b/client/cli_crypto_service.go @@ -39,27 +39,33 @@ func NewRootCryptoService(rootKeyStore *trustmanager.KeyFileStore, passphrase st // Create is used to generate keys for targets, snapshots and timestamps func (ccs *CryptoService) Create(role string) (*data.PublicKey, error) { // Generates a new RSA key - key, err := rsa.GenerateKey(rand.Reader, rsaKeySize) + rsaPrivKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize) if err != nil { return nil, fmt.Errorf("could not generate private key: %v", err) } - pemKey, err := trustmanager.KeyToPEM(key) - if err != nil { - return nil, fmt.Errorf("failed to generate the certificate for key: %v (%s)", role, err) - } - rsaPublicKey := key.PublicKey - // Using x509 to Marshal the Public key into der encoding + rsaPublicKey := rsaPrivKey.PublicKey + + // Using x509 to Marshal the Public key into DER encoding pubBytes, err := x509.MarshalPKIXPublicKey(&rsaPublicKey) if err != nil { return nil, errors.New("Failed to Marshal public key.") } + tufKey := data.NewPublicKey("RSA", pubBytes) // Passing in the the GUN + keyID as the name for the private key and adding it // to our KeyFileStore. Final storage will be under $BASE_PATH/GUN/keyID.key privKeyFilename := filepath.Join(ccs.gun, tufKey.ID()) - ccs.keyStore.Add(privKeyFilename, pemKey) + + // Get a PEM encoded representation of the private key + pemRSAPrivKey, err := trustmanager.KeyToPEM(rsaPrivKey) + if err != nil { + return nil, fmt.Errorf("failed to generate the certificate for key: %v (%s)", role, err) + } + + // Store the PEM-encoded private key into our keystore + ccs.keyStore.Add(privKeyFilename, pemRSAPrivKey) return tufKey, nil } diff --git a/client/client.go b/client/client.go index e149d02644..98523a3ecd 100644 --- a/client/client.go +++ b/client/client.go @@ -39,28 +39,9 @@ const rsaKeySize int = 2048 /// that doesn't exist var ErrRepositoryNotExist = errors.New("repository does not exist") -// Client is the interface that defines the Notary Client type -type Client interface { - ListPrivateKeys() []string - GenRootKey(passphrase string) (string, error) - GetRootKey(rootKeyID string, passphrase string) (UnlockedRootKey, error) - GetRepository(gun string, baseURL string, transport http.RoundTripper) (Repository, error) -} - -// Repository is the interface that represents a Notary Repository -type Repository interface { - Initialize(UnlockedRootKey) error - AddTarget(target Target) error - ListTargets() ([]Target, error) - GetTargetByName(name string) (Target, error) - Publish() error -} - -type UnlockedRootKey struct { - cipher string - pemPrivKey []byte - pemPubKey []byte - signer *signed.Signer +type UnlockedSigner struct { + privKey *data.PrivateKey + signer *signed.Signer } type NotaryClient struct { @@ -114,8 +95,7 @@ func NewClient(baseDir string) (*NotaryClient, error) { nClient := &NotaryClient{baseDir: baseDir} - err := nClient.loadKeys(trustDir, rootKeysDir) - if err != nil { + if err := nClient.loadKeys(trustDir, rootKeysDir); err != nil { return nil, err } @@ -124,15 +104,13 @@ func NewClient(baseDir string) (*NotaryClient, error) { // Initialize creates a new repository by using rootKey as the root Key for the // TUF repository. -func (r *NotaryRepository) Initialize(uRootKey UnlockedRootKey, rootKey *data.PublicKey) error { +func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error { remote, err := getRemoteStore(r.Gun) rawTSKey, err := remote.GetKey("timestamp") if err != nil { return err } - fmt.Println("RawKey: ", string(rawTSKey)) - parsedKey := &data.TUFKey{} err = json.Unmarshal(rawTSKey, parsedKey) if err != nil { @@ -140,6 +118,7 @@ func (r *NotaryRepository) Initialize(uRootKey UnlockedRootKey, rootKey *data.Pu } timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public()) + rootKey := uSigner.PublicKey() targetsKey, err := r.signer.Create("targets") if err != nil { @@ -174,21 +153,16 @@ func (r *NotaryRepository) Initialize(uRootKey UnlockedRootKey, rootKey *data.Pu return err } - // TODO(diogo): change to inline error catching - err = kdb.AddRole(rootRole) - if err != nil { + if err := kdb.AddRole(rootRole); err != nil { return err } - err = kdb.AddRole(targetsRole) - if err != nil { + if err := kdb.AddRole(targetsRole); err != nil { return err } - err = kdb.AddRole(snapshotRole) - if err != nil { + if err := kdb.AddRole(snapshotRole); err != nil { return err } - err = kdb.AddRole(timestampRole) - if err != nil { + if err := kdb.AddRole(timestampRole); err != nil { return err } @@ -208,7 +182,7 @@ func (r *NotaryRepository) Initialize(uRootKey UnlockedRootKey, rootKey *data.Pu return err } - if err := r.saveMetadata(uRootKey.signer); err != nil { + if err := r.saveMetadata(uSigner.signer); err != nil { return err } @@ -551,38 +525,46 @@ func (c *NotaryClient) ListPrivateKeys() []string { // GenRootKey generates a new root key protected by a given passphrase func (c *NotaryClient) GenRootKey(passphrase string) (string, error) { + // TODO(diogo): Refactor TUF Key creation. We should never see crypto.privatekeys // Generates a new RSA key - key, err := rsa.GenerateKey(rand.Reader, rsaKeySize) + rsaPrivKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize) if err != nil { return "", fmt.Errorf("could not generate private key: %v", err) } - pemKey, err := trustmanager.KeyToPEM(key) + // Encode the private key in PEM format since that is the final storage format + pemPrivKey, err := trustmanager.KeyToPEM(rsaPrivKey) if err != nil { - return "", fmt.Errorf("failed to generate the certificate for key: %v", err) + return "", fmt.Errorf("failed to encode the private key: %v", err) } - // - keyID := data.NewPrivateKey("RSA", pemKey, pemKey).ID() + tufPrivKey, err := trustmanager.RSAToPrivateKey(rsaPrivKey) + if err != nil { + return "", fmt.Errorf("failed to convert private key: ", err) + } - c.rootKeyStore.AddEncrypted(keyID, pemKey, passphrase) + c.rootKeyStore.AddEncrypted(tufPrivKey.ID(), pemPrivKey, passphrase) - return keyID, nil + return tufPrivKey.ID(), nil } -// GetRootKey retreives a root key that includes the ID and a signer -func (c *NotaryClient) GetRootKey(rootKeyID, passphrase string) (UnlockedRootKey, error) { +// GetRootSigner retreives a root key that includes the ID and a signer +func (c *NotaryClient) GetRootSigner(rootKeyID, passphrase string) (*UnlockedSigner, error) { pemPrivKey, err := c.rootKeyStore.GetDecrypted(rootKeyID, passphrase) if err != nil { - return UnlockedRootKey{}, fmt.Errorf("could not get encrypted root key: %v", err) + return nil, fmt.Errorf("could not get decrypted root key: %v", err) + } + + tufPrivKey, err := trustmanager.TufParsePEMPrivateKey(pemPrivKey) + if err != nil { + return nil, fmt.Errorf("could not get parse root key: %v", err) } signer := signed.NewSigner(NewRootCryptoService(c.rootKeyStore, passphrase)) - return UnlockedRootKey{ - cipher: "RSA", - pemPrivKey: pemPrivKey, - signer: signer}, nil + return &UnlockedSigner{ + privKey: tufPrivKey, + signer: signer}, nil } // GetRepository returns a new repository @@ -604,15 +586,15 @@ func (c *NotaryClient) GetRepository(gun string, baseURL string, transport http. certificateStore: c.certificateStore}, nil } -func (c *NotaryClient) InitRepository(gun string, baseURL string, transport http.RoundTripper, uRootKey UnlockedRootKey) (*NotaryRepository, error) { +func (c *NotaryClient) InitRepository(gun string, baseURL string, transport http.RoundTripper, uSigner *UnlockedSigner) (*NotaryRepository, error) { // Creates and saves a trusted certificate for this store, with this root key - rootCert, err := uRootKey.GenerateCertificate(gun) + rootCert, err := uSigner.GenerateCertificate(gun) if err != nil { return nil, err } c.certificateStore.AddCert(rootCert) rootKey := data.NewPublicKey("RSA", trustmanager.CertToPEM(rootCert)) - err = c.rootKeyStore.Link(uRootKey.ID(), rootKey.ID()) + err = c.rootKeyStore.Link(uSigner.ID(), rootKey.ID()) if err != nil { return nil, err } @@ -633,7 +615,7 @@ func (c *NotaryClient) InitRepository(gun string, baseURL string, transport http caStore: c.caStore, certificateStore: c.certificateStore} - err = nRepo.Initialize(uRootKey, rootKey) + err = nRepo.Initialize(uSigner) if err != nil { return nil, err } @@ -679,14 +661,18 @@ func (c *NotaryClient) loadKeys(trustDir, rootKeysDir string) error { } // ID gets a consistent ID based on the PrivateKey bytes and cipher type -func (uk *UnlockedRootKey) ID() string { - return data.NewPrivateKey(uk.cipher, uk.pemPrivKey, uk.pemPrivKey).ID() +func (uk *UnlockedSigner) ID() string { + return uk.PublicKey().ID() +} + +// PublicKey Returns the public key associated with the Root Key +func (uk *UnlockedSigner) PublicKey() *data.PublicKey { + return data.PublicKeyFromPrivate(*uk.privKey) } // GenerateCertificate -func (uk *UnlockedRootKey) GenerateCertificate(gun string) (*x509.Certificate, error) { - privKeyBytes, _ := pem.Decode(uk.pemPrivKey) - privKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes.Bytes) +func (uk *UnlockedSigner) GenerateCertificate(gun string) (*x509.Certificate, error) { + privKey, err := x509.ParsePKCS1PrivateKey(uk.privKey.Private()) if err != nil { return nil, fmt.Errorf("failed to parse root key: %v (%s)", gun, err.Error()) } diff --git a/client/client_test.go b/client/client_test.go index 37e7785130..c2526a9810 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -26,11 +26,11 @@ func TestInitRepo(t *testing.T) { rootKeyID, err := client.GenRootKey("passphrase") assert.NoError(t, err, "error generating root key: %s", err) - rootKey, err := client.GetRootKey(rootKeyID, "passphrase") + rootSigner, err := client.GetRootSigner(rootKeyID, "passphrase") assert.NoError(t, err, "error retreiving root key: %s", err) gun := "docker.com/notary" - repo, err := client.InitRepository(gun, "", nil, rootKey) + repo, err := client.InitRepository(gun, "", nil, rootSigner) assert.NoError(t, err, "error creating repository: %s", err) // Inspect contents of the temporary directory @@ -61,7 +61,7 @@ func TestInitRepo(t *testing.T) { // Look for keys in root_keys // There should be a file named after the key ID of the root key we // passed in. - rootKeyFilename := rootKey.ID() + ".key" + rootKeyFilename := rootSigner.ID() + ".key" _, err = os.Stat(filepath.Join(tempBaseDir, "private", "root_keys", rootKeyFilename)) assert.NoError(t, err, "missing root key") diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index d51c778502..1a49b87356 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -106,12 +106,12 @@ func tufInit(cmd *cobra.Command, args []string) { if err != nil { fatalf(err.Error()) } - rootKey, err := nClient.GetRootKey(rootKeyID, "passphrase") + rootSigner, err := nClient.GetRootSigner(rootKeyID, "passphrase") if err != nil { fatalf(err.Error()) } - _, err = nClient.InitRepository(args[0], "", t, rootKey) + _, err = nClient.InitRepository(args[0], "", t, rootSigner) if err != nil { fatalf(err.Error()) } diff --git a/trustmanager/x509utils.go b/trustmanager/x509utils.go index fe5099cfe6..563342f65a 100644 --- a/trustmanager/x509utils.go +++ b/trustmanager/x509utils.go @@ -120,14 +120,6 @@ func loadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) { return nil, errors.New("no certificates found in PEM data") } -func FingerprintPEMCert(pemCert []byte) (string, error) { - cert, err := loadCertFromPEM(pemCert) - if err != nil { - return "", err - } - return FingerprintCert(cert), nil -} - // FingerprintCert returns a TUF compliant fingerprint for a X509 Certificate func FingerprintCert(cert *x509.Certificate) string { return string(fingerprintCert(cert)) @@ -202,6 +194,44 @@ func ParsePEMPrivateKey(pemBytes []byte) (crypto.PrivateKey, error) { } } +// TufParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It +// only supports RSA (PKCS#1). +func TufParsePEMPrivateKey(pemBytes []byte) (*data.PrivateKey, error) { + block, _ := pem.Decode(pemBytes) + if block == nil { + return nil, errors.New("no valid key found") + } + + switch block.Type { + case "RSA PRIVATE KEY": + rsaPrivKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("could not parse PEM: %v", err) + } + + tufRSAPrivateKey, err := RSAToPrivateKey(rsaPrivKey) + if err != nil { + return nil, fmt.Errorf("could not convert crypto.PrivateKey to PrivateKey: %v", err) + } + return tufRSAPrivateKey, nil + default: + return nil, fmt.Errorf("unsupported key type %q", block.Type) + } +} + +func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey) (*data.PrivateKey, error) { + // Get a DER-encoded representation of the PublicKey + rsaPubBytes, err := x509.MarshalPKIXPublicKey(&rsaPrivKey.PublicKey) + if err != nil { + return nil, fmt.Errorf("failed to marshal private key: %v", err) + } + + // Get a DER-encoded representation of the PrivateKey + rsaPrivBytes := x509.MarshalPKCS1PrivateKey(rsaPrivKey) + + return data.NewPrivateKey("RSA", rsaPubBytes, rsaPrivBytes), nil +} + // ParsePEMEncryptedPrivateKey returns a private key from a PEM encrypted private key. It // only supports RSA (PKCS#1). func ParsePEMEncryptedPrivateKey(pemBytes []byte, passphrase string) (crypto.PrivateKey, error) { From e7163aacffa79196a8d89949252bc33300b9675b Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Wed, 8 Jul 2015 18:59:17 -0700 Subject: [PATCH 21/39] Add missing Close calls to address file descriptor leak --- client/changelist/files_changelist.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/changelist/files_changelist.go b/client/changelist/files_changelist.go index e2d2ce12fc..abc15e048e 100644 --- a/client/changelist/files_changelist.go +++ b/client/changelist/files_changelist.go @@ -32,6 +32,7 @@ func (cl fileChangelist) List() []Change { if err != nil { return changes } + defer dir.Close() fileInfos, err := dir.Readdir(0) if err != nil { return changes @@ -73,6 +74,7 @@ func (cl fileChangelist) Clear(archive string) error { if err != nil { return err } + defer dir.Close() files, err := dir.Readdir(0) if err != nil { return err From c9ab3394deb20783e15ce9c7181160389feea113 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 8 Jul 2015 17:59:25 -0700 Subject: [PATCH 22/39] further publish updates, it pushes now, but doesn't sign roots correctly Signed-off-by: David Lawrence (github: endophage) --- client/client.go | 36 +++++++++++++++++++++++++++++++----- cmd/notary/tuf.go | 5 ++++- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/client/client.go b/client/client.go index 98523a3ecd..6489a6591a 100644 --- a/client/client.go +++ b/client/client.go @@ -26,6 +26,12 @@ import ( "github.com/endophage/gotuf/store" ) +type ErrRepoNotInitialized struct{} + +func (err *ErrRepoNotInitialized) Error() string { + return "Repository has not been initialized" +} + // Default paths should end with a '/' so directory creation works correctly const ( trustDir string = "/trusted_certificates/" @@ -259,22 +265,35 @@ func (r *NotaryRepository) GetTargetByName(name string) (*Target, error) { // Publish pushes the local changes in signed material to the remote notary-server func (r *NotaryRepository) Publish() error { - _, err := r.bootstrapClient() // just need the repo to be initialized from remote + c, err := r.bootstrapClient() // just need the repo to be initialized from remote if err != nil { if _, ok := err.(*store.ErrMetaNotFound); ok { - // init or return error to make caller init, then publish again + // attempt to load locally to see if it's already init'ed + err := r.bootstrapRepo() + if err != nil { + logrus.Debug("Repository not initialized during Publish") + return &ErrRepoNotInitialized{} // caller must init + } } else { + logrus.Error("Could not publish Repository: ", err.Error()) return err } } + err = c.Update() + if err != nil { + return err + } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { + logrus.Debug("Error initializing changelist") + return err + } + err = applyChangelist(r.tufRepo, cl) + if err != nil { + logrus.Debug("Error applying changelist") return err } - applyChangelist(r.tufRepo, cl) - - remote, err := getRemoteStore(r.Gun) root, err := r.tufRepo.SignRoot(data.DefaultExpires("root"), r.signer) if err != nil { @@ -302,6 +321,10 @@ func (r *NotaryRepository) Publish() error { return err } + remote, err := getRemoteStore(r.Gun) + if err != nil { + return err + } err = remote.SetMeta("root", rootJSON) if err != nil { return err @@ -485,6 +508,9 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { return nil, err } rootJSON, err := remote.GetMeta("root", 5<<20) + if err != nil { + return nil, err + } root := &data.Signed{} err = json.Unmarshal(rootJSON, root) if err != nil { diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 1a49b87356..42e57d9022 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -181,7 +181,10 @@ func tufPublish(cmd *cobra.Command, args []string) { fatalf(err.Error()) } - repo.Publish() + err = repo.Publish() + if err != nil { + fatalf(err.Error()) + } } func tufRemove(cmd *cobra.Command, args []string) { From 6982d2f1ae6e7491cc3aeda68b965cab97f2de1f Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 8 Jul 2015 18:21:42 -0700 Subject: [PATCH 23/39] put rootSigner on repository Signed-off-by: David Lawrence (github: endophage) --- client/client.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/client/client.go b/client/client.go index 6489a6591a..056f446820 100644 --- a/client/client.go +++ b/client/client.go @@ -68,6 +68,7 @@ type NotaryRepository struct { privKeyStore *trustmanager.KeyFileStore caStore trustmanager.X509Store certificateStore trustmanager.X509Store + rootSigner *UnlockedSigner } // Target represents a simplified version of the data TUF operates on. @@ -110,7 +111,7 @@ func NewClient(baseDir string) (*NotaryClient, error) { // Initialize creates a new repository by using rootKey as the root Key for the // TUF repository. -func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error { +func (r *NotaryRepository) Initialize() error { remote, err := getRemoteStore(r.Gun) rawTSKey, err := remote.GetKey("timestamp") if err != nil { @@ -124,7 +125,7 @@ func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error { } timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public()) - rootKey := uSigner.PublicKey() + rootKey := r.rootSigner.PublicKey() targetsKey, err := r.signer.Create("targets") if err != nil { @@ -295,7 +296,7 @@ func (r *NotaryRepository) Publish() error { return err } - root, err := r.tufRepo.SignRoot(data.DefaultExpires("root"), r.signer) + root, err := r.tufRepo.SignRoot(data.DefaultExpires("root"), r.rootSigner) if err != nil { return err } @@ -594,7 +595,7 @@ func (c *NotaryClient) GetRootSigner(rootKeyID, passphrase string) (*UnlockedSig } // GetRepository returns a new repository -func (c *NotaryClient) GetRepository(gun string, baseURL string, transport http.RoundTripper) (*NotaryRepository, error) { +func (c *NotaryClient) GetRepository(gun string, baseURL string, transport http.RoundTripper, uSigner *UnlockedSigner) (*NotaryRepository, error) { privKeyStore, err := trustmanager.NewKeyFileStore(filepath.Join(c.baseDir, privDir)) if err != nil { return nil, err @@ -609,7 +610,9 @@ func (c *NotaryClient) GetRepository(gun string, baseURL string, transport http. signer: signer, privKeyStore: privKeyStore, caStore: c.caStore, - certificateStore: c.certificateStore}, nil + certificateStore: c.certificateStore, + rootSigner: uSigner, + }, nil } func (c *NotaryClient) InitRepository(gun string, baseURL string, transport http.RoundTripper, uSigner *UnlockedSigner) (*NotaryRepository, error) { @@ -639,9 +642,11 @@ func (c *NotaryClient) InitRepository(gun string, baseURL string, transport http signer: signer, privKeyStore: privKeyStore, caStore: c.caStore, - certificateStore: c.certificateStore} + certificateStore: c.certificateStore, + rootSigner: uSigner, + } - err = nRepo.Initialize(uSigner) + err = nRepo.Initialize() if err != nil { return nil, err } From c3e49afe1a681a47f3f59b7a14e809110911aec0 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 8 Jul 2015 18:33:02 -0700 Subject: [PATCH 24/39] passing cert to initialize Signed-off-by: David Lawrence (github: endophage) --- client/client.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/client.go b/client/client.go index 056f446820..9c010f8cc2 100644 --- a/client/client.go +++ b/client/client.go @@ -111,7 +111,7 @@ func NewClient(baseDir string) (*NotaryClient, error) { // Initialize creates a new repository by using rootKey as the root Key for the // TUF repository. -func (r *NotaryRepository) Initialize() error { +func (r *NotaryRepository) Initialize(rootKey *data.PublicKey) error { remote, err := getRemoteStore(r.Gun) rawTSKey, err := remote.GetKey("timestamp") if err != nil { @@ -125,7 +125,6 @@ func (r *NotaryRepository) Initialize() error { } timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public()) - rootKey := r.rootSigner.PublicKey() targetsKey, err := r.signer.Create("targets") if err != nil { @@ -646,7 +645,7 @@ func (c *NotaryClient) InitRepository(gun string, baseURL string, transport http rootSigner: uSigner, } - err = nRepo.Initialize() + err = nRepo.Initialize(rootKey) if err != nil { return nil, err } From ebbb30b56c17f89c77058eb41fe5ce5ddd992bcd Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 8 Jul 2015 18:44:22 -0700 Subject: [PATCH 25/39] hold unlocked signer on repository Signed-off-by: David Lawrence (github: endophage) --- client/client.go | 4 ++-- cmd/notary/tuf.go | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/client.go b/client/client.go index 9c010f8cc2..dfa2b391f0 100644 --- a/client/client.go +++ b/client/client.go @@ -188,7 +188,7 @@ func (r *NotaryRepository) Initialize(rootKey *data.PublicKey) error { return err } - if err := r.saveMetadata(uSigner.signer); err != nil { + if err := r.saveMetadata(r.rootSigner.signer); err != nil { return err } @@ -295,7 +295,7 @@ func (r *NotaryRepository) Publish() error { return err } - root, err := r.tufRepo.SignRoot(data.DefaultExpires("root"), r.rootSigner) + root, err := r.tufRepo.SignRoot(data.DefaultExpires("root"), r.rootSigner.signer) if err != nil { return err } diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 42e57d9022..2adbb5dda2 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -76,7 +76,7 @@ func tufAdd(cmd *cobra.Command, args []string) { targetPath := args[2] t := &http.Transport{} - repo, err := nClient.GetRepository(gun, "", t) + repo, err := nClient.GetRepository(gun, "", t, nil) if err != nil { fatalf(err.Error()) } @@ -125,7 +125,7 @@ func tufList(cmd *cobra.Command, args []string) { gun := args[0] t := &http.Transport{} - repo, err := nClient.GetRepository(gun, "", t) + repo, err := nClient.GetRepository(gun, "", t, nil) if err != nil { fatalf(err.Error()) } @@ -151,7 +151,7 @@ func tufLookup(cmd *cobra.Command, args []string) { targetName := args[1] t := &http.Transport{} - repo, err := nClient.GetRepository(gun, "", t) + repo, err := nClient.GetRepository(gun, "", t, nil) if err != nil { fatalf(err.Error()) } @@ -176,7 +176,7 @@ func tufPublish(cmd *cobra.Command, args []string) { fmt.Println("Pushing changes to ", gun, ".") t := &http.Transport{} - repo, err := nClient.GetRepository(gun, "", t) + repo, err := nClient.GetRepository(gun, "", t, nil) if err != nil { fatalf(err.Error()) } @@ -222,7 +222,7 @@ func verify(cmd *cobra.Command, args []string) { gun := args[0] targetName := args[1] t := &http.Transport{} - repo, err := nClient.GetRepository(gun, "", t) + repo, err := nClient.GetRepository(gun, "", t, nil) if err != nil { fatalf(err.Error()) } From 6bff14a679b3d8a79f250bd42ae974382abc31b6 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 8 Jul 2015 22:20:39 -0700 Subject: [PATCH 26/39] refactoring NotaryClient out Signed-off-by: David Lawrence (github: endophage) --- client/client.go | 136 +++++++++++++--------------------- client/client_test.go | 14 ++-- cmd/notary-server/config.json | 2 +- cmd/notary/main.go | 6 -- cmd/notary/tuf.go | 25 ++++--- 5 files changed, 76 insertions(+), 107 deletions(-) diff --git a/client/client.go b/client/client.go index dfa2b391f0..1a94e3e8d8 100644 --- a/client/client.go +++ b/client/client.go @@ -50,14 +50,8 @@ type UnlockedSigner struct { signer *signed.Signer } -type NotaryClient struct { - baseDir string - caStore trustmanager.X509Store - certificateStore trustmanager.X509Store - rootKeyStore *trustmanager.KeyFileStore -} - type NotaryRepository struct { + baseDir string Gun string baseURL string tufRepoPath string @@ -68,6 +62,7 @@ type NotaryRepository struct { privKeyStore *trustmanager.KeyFileStore caStore trustmanager.X509Store certificateStore trustmanager.X509Store + rootKeyStore *trustmanager.KeyFileStore rootSigner *UnlockedSigner } @@ -96,22 +91,48 @@ func NewTarget(targetName string, targetPath string) (*Target, error) { // NewClient is a helper method that returns a new notary Client, given a config // file. It makes the assumption that the base directory for the config file will // be the place where trust information is being cached locally. -func NewClient(baseDir string) (*NotaryClient, error) { +func NewNotaryRepository(baseDir, gun, baseURL string, transport http.RoundTripper) (*NotaryRepository, error) { trustDir := filepath.Join(baseDir, trustDir) rootKeysDir := filepath.Join(baseDir, rootKeysDir) - nClient := &NotaryClient{baseDir: baseDir} - - if err := nClient.loadKeys(trustDir, rootKeysDir); err != nil { + privKeyStore, err := trustmanager.NewKeyFileStore(filepath.Join(baseDir, privDir)) + if err != nil { return nil, err } - return nClient, nil + signer := signed.NewSigner(NewCryptoService(gun, privKeyStore)) + + nRepo := &NotaryRepository{ + Gun: gun, + baseDir: baseDir, + baseURL: baseURL, + tufRepoPath: filepath.Join(baseDir, tufDir, gun), + transport: transport, + signer: signer, + privKeyStore: privKeyStore, + } + + if err := nRepo.loadKeys(trustDir, rootKeysDir); err != nil { + return nil, err + } + + return nRepo, nil } // Initialize creates a new repository by using rootKey as the root Key for the // TUF repository. -func (r *NotaryRepository) Initialize(rootKey *data.PublicKey) error { +func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error { + rootCert, err := uSigner.GenerateCertificate(r.Gun) + if err != nil { + return err + } + r.certificateStore.AddCert(rootCert) + rootKey := data.NewPublicKey("RSA", trustmanager.CertToPEM(rootCert)) + err = r.rootKeyStore.Link(uSigner.ID(), rootKey.ID()) + if err != nil { + return err + } + remote, err := getRemoteStore(r.Gun) rawTSKey, err := remote.GetKey("timestamp") if err != nil { @@ -188,7 +209,7 @@ func (r *NotaryRepository) Initialize(rootKey *data.PublicKey) error { return err } - if err := r.saveMetadata(r.rootSigner.signer); err != nil { + if err := r.saveMetadata(uSigner.signer); err != nil { return err } @@ -264,7 +285,7 @@ func (r *NotaryRepository) GetTargetByName(name string) (*Target, error) { } // Publish pushes the local changes in signed material to the remote notary-server -func (r *NotaryRepository) Publish() error { +func (r *NotaryRepository) Publish(passphrase string) error { c, err := r.bootstrapClient() // just need the repo to be initialized from remote if err != nil { if _, ok := err.(*store.ErrMetaNotFound); ok { @@ -278,10 +299,11 @@ func (r *NotaryRepository) Publish() error { logrus.Error("Could not publish Repository: ", err.Error()) return err } - } - err = c.Update() - if err != nil { - return err + } else { + err = c.Update() + if err != nil { + return err + } } cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) @@ -295,7 +317,13 @@ func (r *NotaryRepository) Publish() error { return err } - root, err := r.tufRepo.SignRoot(data.DefaultExpires("root"), r.rootSigner.signer) + rootKeyID := r.tufRepo.Root.Signed.Roles["root"].KeyIDs[0] + rootSigner, err := r.GetRootSigner(rootKeyID, passphrase) + if err != nil { + return err + } + + root, err := r.tufRepo.SignRoot(data.DefaultExpires("root"), rootSigner.signer) if err != nil { return err } @@ -541,7 +569,7 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { // ListPrivateKeys lists all availables private keys. Does not include private key // material -func (c *NotaryClient) ListPrivateKeys() []string { +func (c *NotaryRepository) ListPrivateKeys() []string { // TODO(diogo): Make this work for _, k := range c.rootKeyStore.ListAll() { fmt.Println(k) @@ -550,7 +578,7 @@ func (c *NotaryClient) ListPrivateKeys() []string { } // GenRootKey generates a new root key protected by a given passphrase -func (c *NotaryClient) GenRootKey(passphrase string) (string, error) { +func (c *NotaryRepository) GenRootKey(passphrase string) (string, error) { // TODO(diogo): Refactor TUF Key creation. We should never see crypto.privatekeys // Generates a new RSA key rsaPrivKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize) @@ -575,7 +603,7 @@ func (c *NotaryClient) GenRootKey(passphrase string) (string, error) { } // GetRootSigner retreives a root key that includes the ID and a signer -func (c *NotaryClient) GetRootSigner(rootKeyID, passphrase string) (*UnlockedSigner, error) { +func (c *NotaryRepository) GetRootSigner(rootKeyID, passphrase string) (*UnlockedSigner, error) { pemPrivKey, err := c.rootKeyStore.GetDecrypted(rootKeyID, passphrase) if err != nil { return nil, fmt.Errorf("could not get decrypted root key: %v", err) @@ -593,67 +621,7 @@ func (c *NotaryClient) GetRootSigner(rootKeyID, passphrase string) (*UnlockedSig signer: signer}, nil } -// GetRepository returns a new repository -func (c *NotaryClient) GetRepository(gun string, baseURL string, transport http.RoundTripper, uSigner *UnlockedSigner) (*NotaryRepository, error) { - privKeyStore, err := trustmanager.NewKeyFileStore(filepath.Join(c.baseDir, privDir)) - if err != nil { - return nil, err - } - - signer := signed.NewSigner(NewCryptoService(gun, privKeyStore)) - - return &NotaryRepository{Gun: gun, - baseURL: baseURL, - tufRepoPath: filepath.Join(c.baseDir, tufDir, gun), - transport: transport, - signer: signer, - privKeyStore: privKeyStore, - caStore: c.caStore, - certificateStore: c.certificateStore, - rootSigner: uSigner, - }, nil -} - -func (c *NotaryClient) InitRepository(gun string, baseURL string, transport http.RoundTripper, uSigner *UnlockedSigner) (*NotaryRepository, error) { - // Creates and saves a trusted certificate for this store, with this root key - rootCert, err := uSigner.GenerateCertificate(gun) - if err != nil { - return nil, err - } - c.certificateStore.AddCert(rootCert) - rootKey := data.NewPublicKey("RSA", trustmanager.CertToPEM(rootCert)) - err = c.rootKeyStore.Link(uSigner.ID(), rootKey.ID()) - if err != nil { - return nil, err - } - - privKeyStore, err := trustmanager.NewKeyFileStore(filepath.Join(c.baseDir, privDir)) - if err != nil { - return nil, err - } - - signer := signed.NewSigner(NewCryptoService(gun, privKeyStore)) - - nRepo := &NotaryRepository{Gun: gun, - baseURL: baseURL, - tufRepoPath: filepath.Join(c.baseDir, tufDir, gun), - transport: transport, - signer: signer, - privKeyStore: privKeyStore, - caStore: c.caStore, - certificateStore: c.certificateStore, - rootSigner: uSigner, - } - - err = nRepo.Initialize(rootKey) - if err != nil { - return nil, err - } - - return nRepo, nil -} - -func (c *NotaryClient) loadKeys(trustDir, rootKeysDir string) error { +func (c *NotaryRepository) loadKeys(trustDir, rootKeysDir string) error { // Load all CAs that aren't expired and don't use SHA1 caStore, err := trustmanager.NewX509FilteredFileStore(trustDir, func(cert *x509.Certificate) bool { return cert.IsCA && cert.BasicConstraintsValid && cert.SubjectKeyId != nil && diff --git a/client/client_test.go b/client/client_test.go index c2526a9810..246bbdf8f5 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -16,21 +16,21 @@ import ( // TestInitRepo runs through the process of initializing a repository and makes // sure the repository looks correct on disk. func TestInitRepo(t *testing.T) { + gun := "docker.com/notary" // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") assert.NoError(t, err, "failed to create a temporary directory: %s", err) - client, err := NewClient(tempBaseDir) - assert.NoError(t, err, "error creating client: %s", err) + repo, err := NewNotaryRepository(tempBaseDir, gun, "", nil) + assert.NoError(t, err, "error creating repo: %s", err) - rootKeyID, err := client.GenRootKey("passphrase") + rootKeyID, err := repo.GenRootKey("passphrase") assert.NoError(t, err, "error generating root key: %s", err) - rootSigner, err := client.GetRootSigner(rootKeyID, "passphrase") + rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase") assert.NoError(t, err, "error retreiving root key: %s", err) - gun := "docker.com/notary" - repo, err := client.InitRepository(gun, "", nil, rootSigner) + err = repo.Initialize(rootSigner) assert.NoError(t, err, "error creating repository: %s", err) // Inspect contents of the temporary directory @@ -67,7 +67,7 @@ func TestInitRepo(t *testing.T) { // Also expect a symlink from the key ID of the certificate key to this // root key - certificates := client.certificateStore.GetCertificates() + certificates := repo.certificateStore.GetCertificates() assert.Len(t, certificates, 1, "unexpected number of certificates") certID := trustmanager.FingerprintCert(certificates[0]) diff --git a/cmd/notary-server/config.json b/cmd/notary-server/config.json index 290f6eb126..2af3d7c221 100644 --- a/cmd/notary-server/config.json +++ b/cmd/notary-server/config.json @@ -5,7 +5,7 @@ "tls_key_file": "./fixtures/notary.key" }, "trust_service": { - "type": "remote", + "type": "local", "hostname": "rufus", "port": "7899", "tls_ca_file": "./fixtures/ca.cert" diff --git a/cmd/notary/main.go b/cmd/notary/main.go index 4ab8c0d0d2..174401d24d 100644 --- a/cmd/notary/main.go +++ b/cmd/notary/main.go @@ -12,7 +12,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - notaryclient "github.com/docker/notary/client" "github.com/docker/notary/trustmanager" ) @@ -23,7 +22,6 @@ const privDir string = "private/" const rootKeysDir string = "root_keys/" var rawOutput bool -var nClient *notaryclient.NotaryClient var caStore trustmanager.X509Store var certificateStore trustmanager.X509Store var privKeyStore trustmanager.FileStore @@ -93,10 +91,6 @@ func init() { fatalf("could not create KeyFileStore: %v", err) } - nClient, err = notaryclient.NewClient(viper.GetString("baseTrustDir")) - if err != nil { - fatalf("could not create Notary Client: %v", err) - } } func main() { diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 2adbb5dda2..8df16ba0a6 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -12,6 +12,7 @@ import ( "github.com/endophage/gotuf/data" "github.com/endophage/gotuf/keys" "github.com/spf13/cobra" + "github.com/spf13/viper" ) var remoteTrustServer string @@ -76,7 +77,7 @@ func tufAdd(cmd *cobra.Command, args []string) { targetPath := args[2] t := &http.Transport{} - repo, err := nClient.GetRepository(gun, "", t, nil) + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, "", t) if err != nil { fatalf(err.Error()) } @@ -98,20 +99,26 @@ func tufInit(cmd *cobra.Command, args []string) { fatalf("Must specify a GUN") } + gun := args[0] t := &http.Transport{} + nRepo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, "", t) + if err != nil { + fatalf(err.Error()) + } + // TODO(diogo): We don't want to generate a new root every time. Ask the user // which key she wants to use if there > 0 root keys available. - rootKeyID, err := nClient.GenRootKey("passphrase") + rootKeyID, err := nRepo.GenRootKey("passphrase") if err != nil { fatalf(err.Error()) } - rootSigner, err := nClient.GetRootSigner(rootKeyID, "passphrase") + rootSigner, err := nRepo.GetRootSigner(rootKeyID, "passphrase") if err != nil { fatalf(err.Error()) } - _, err = nClient.InitRepository(args[0], "", t, rootSigner) + nRepo.Initialize(rootSigner) if err != nil { fatalf(err.Error()) } @@ -125,7 +132,7 @@ func tufList(cmd *cobra.Command, args []string) { gun := args[0] t := &http.Transport{} - repo, err := nClient.GetRepository(gun, "", t, nil) + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, "", t) if err != nil { fatalf(err.Error()) } @@ -151,7 +158,7 @@ func tufLookup(cmd *cobra.Command, args []string) { targetName := args[1] t := &http.Transport{} - repo, err := nClient.GetRepository(gun, "", t, nil) + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, "", t) if err != nil { fatalf(err.Error()) } @@ -176,12 +183,12 @@ func tufPublish(cmd *cobra.Command, args []string) { fmt.Println("Pushing changes to ", gun, ".") t := &http.Transport{} - repo, err := nClient.GetRepository(gun, "", t, nil) + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, "", t) if err != nil { fatalf(err.Error()) } - err = repo.Publish() + err = repo.Publish("passphrase") if err != nil { fatalf(err.Error()) } @@ -222,7 +229,7 @@ func verify(cmd *cobra.Command, args []string) { gun := args[0] targetName := args[1] t := &http.Transport{} - repo, err := nClient.GetRepository(gun, "", t, nil) + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, "", t) if err != nil { fatalf(err.Error()) } From abe320307d167ce8c7efb98d97c79d9d28a1edb9 Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Wed, 8 Jul 2015 18:31:14 -0700 Subject: [PATCH 27/39] Add TestAddTarget This test adds two targets, and after each one, inspects the changelist files to make sure they meet expectations. --- client/client_test.go | 107 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/client/client_test.go b/client/client_test.go index 246bbdf8f5..f610e8afed 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -130,3 +130,110 @@ func TestInitRepo(t *testing.T) { } } } + +type tufChange struct { + // Abbreviated because Go doesn't permit a field and method of the same name + Actn int `json:"action"` + Role string `json:"role"` + ChangeType string `json:"type"` + ChangePath string `json:"path"` + Data []byte `json:"data"` +} + +// TestAddTarget adds a target to the repo and confirms that the changelist +// is updated correctly. +func TestAddTarget(t *testing.T) { + // Temporary directory where test files will be created + tempBaseDir, err := ioutil.TempDir("", "notary-test-") + assert.NoError(t, err, "failed to create a temporary directory: %s", err) + + client, err := NewClient(tempBaseDir) + assert.NoError(t, err, "error creating client: %s", err) + + rootKeyID, err := client.GenRootKey("passphrase") + assert.NoError(t, err, "error generating root key: %s", err) + + rootKey, err := client.GetRootKey(rootKeyID, "passphrase") + assert.NoError(t, err, "error retreiving root key: %s", err) + + gun := "docker.com/notary" + repo, err := client.InitRepository(gun, "", nil, rootKey) + assert.NoError(t, err, "error creating repository: %s", err) + + // Add fixtures/ca.cert as a target. There's no particular reason + // for using this file except that it happens to be available as + // a fixture. + target, err := NewTarget("latest", "../fixtures/ca.cert") + assert.NoError(t, err, "error creating target") + err = repo.AddTarget(target) + assert.NoError(t, err, "error adding target") + + // Look for the changelist file + changelistDirPath := filepath.Join(tempBaseDir, "tuf", gun, "changelist") + + changelistDir, err := os.Open(changelistDirPath) + assert.NoError(t, err, "could not open changelist directory") + + fileInfos, err := changelistDir.Readdir(0) + assert.NoError(t, err, "could not read changelist directory") + + // Should only be one file in the directory + assert.Len(t, fileInfos, 1, "wrong number of changelist files found") + + clName := fileInfos[0].Name() + raw, err := ioutil.ReadFile(filepath.Join(changelistDirPath, clName)) + assert.NoError(t, err, "could not read changelist file %s", clName) + + c := &tufChange{} + err = json.Unmarshal(raw, c) + assert.NoError(t, err, "could not unmarshal changelist file %s", clName) + + assert.EqualValues(t, 0, c.Actn) + assert.Equal(t, "targets", c.Role) + assert.Equal(t, "target", c.ChangeType) + assert.Equal(t, "latest", c.ChangePath) + assert.NotEmpty(t, c.Data) + + changelistDir.Close() + + // Create a second target + target, err = NewTarget("current", "../fixtures/ca.cert") + assert.NoError(t, err, "error creating target") + err = repo.AddTarget(target) + assert.NoError(t, err, "error adding target") + + changelistDir, err = os.Open(changelistDirPath) + assert.NoError(t, err, "could not open changelist directory") + + // There should now be a second file in the directory + fileInfos, err = changelistDir.Readdir(0) + assert.NoError(t, err, "could not read changelist directory") + + assert.Len(t, fileInfos, 2, "wrong number of changelist files found") + + newFileFound := false + for _, fileInfo := range fileInfos { + if fileInfo.Name() != clName { + clName2 := fileInfo.Name() + raw, err := ioutil.ReadFile(filepath.Join(changelistDirPath, clName2)) + assert.NoError(t, err, "could not read changelist file %s", clName2) + + c := &tufChange{} + err = json.Unmarshal(raw, c) + assert.NoError(t, err, "could not unmarshal changelist file %s", clName2) + + assert.EqualValues(t, 0, c.Actn) + assert.Equal(t, "targets", c.Role) + assert.Equal(t, "target", c.ChangeType) + assert.Equal(t, "current", c.ChangePath) + assert.NotEmpty(t, c.Data) + + newFileFound = true + break + } + } + + assert.True(t, newFileFound, "second changelist file not found") + + changelistDir.Close() +} From 4f6b2da44dc24244072f78e82e3ac964e4703335 Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Wed, 8 Jul 2015 18:53:22 -0700 Subject: [PATCH 28/39] Add TestValidateRootKey, validates presence of x509 cert in root.json --- client/client_test.go | 53 ++++++++++++++++++++++++++++++++++- trustmanager/x509filestore.go | 2 +- trustmanager/x509utils.go | 4 +-- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index f610e8afed..d07aa9b8a9 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -153,7 +153,7 @@ func TestAddTarget(t *testing.T) { rootKeyID, err := client.GenRootKey("passphrase") assert.NoError(t, err, "error generating root key: %s", err) - rootKey, err := client.GetRootKey(rootKeyID, "passphrase") + rootKey, err := client.GetRootSigner(rootKeyID, "passphrase") assert.NoError(t, err, "error retreiving root key: %s", err) gun := "docker.com/notary" @@ -237,3 +237,54 @@ func TestAddTarget(t *testing.T) { changelistDir.Close() } + +// TestValidateRootKey verifies that the public data in root.json for the root +// key is a valid x509 certificate. +func TestValidateRootKey(t *testing.T) { + // Temporary directory where test files will be created + tempBaseDir, err := ioutil.TempDir("", "notary-test-") + assert.NoError(t, err, "failed to create a temporary directory: %s", err) + + client, err := NewClient(tempBaseDir) + assert.NoError(t, err, "error creating client: %s", err) + + rootKeyID, err := client.GenRootKey("passphrase") + assert.NoError(t, err, "error generating root key: %s", err) + + rootSigner, err := client.GetRootSigner(rootKeyID, "passphrase") + assert.NoError(t, err, "error retreiving root key: %s", err) + + gun := "docker.com/notary" + _, err = client.InitRepository(gun, "", nil, rootSigner) + assert.NoError(t, err, "error creating repository: %s", err) + + rootJSONFile := filepath.Join(tempBaseDir, "tuf", gun, "metadata", "root.json") + + jsonBytes, err := ioutil.ReadFile(rootJSONFile) + assert.NoError(t, err, "error reading TUF metadata file %s: %s", rootJSONFile, err) + + var decoded data.Signed + err = json.Unmarshal(jsonBytes, &decoded) + assert.NoError(t, err, "error parsing TUF metadata file %s: %s", rootJSONFile, err) + + var decodedRoot data.Root + err = json.Unmarshal(decoded.Signed, &decodedRoot) + assert.NoError(t, err, "error parsing root.json signed section: %s", err) + + keyids := []string{} + for role, roleData := range decodedRoot.Roles { + if role == "root" { + keyids = append(keyids, roleData.KeyIDs...) + } + } + assert.NotEmpty(t, keyids) + + for _, keyid := range keyids { + if key, ok := decodedRoot.Keys[keyid]; !ok { + t.Fatal("key id not found in keys") + } else { + _, err := trustmanager.LoadCertFromPEM(key.Value.Public) + assert.NoError(t, err, "key is not a valid cert") + } + } +} diff --git a/trustmanager/x509filestore.go b/trustmanager/x509filestore.go index 2f41606164..8c476d7d29 100644 --- a/trustmanager/x509filestore.go +++ b/trustmanager/x509filestore.go @@ -132,7 +132,7 @@ func (s X509FileStore) RemoveCert(cert *x509.Certificate) error { // AddCertFromPEM adds the first certificate that it finds in the byte[], returning // an error if no Certificates are found func (s X509FileStore) AddCertFromPEM(pemBytes []byte) error { - cert, err := loadCertFromPEM(pemBytes) + cert, err := LoadCertFromPEM(pemBytes) if err != nil { return err } diff --git a/trustmanager/x509utils.go b/trustmanager/x509utils.go index 563342f65a..aeb8f8b009 100644 --- a/trustmanager/x509utils.go +++ b/trustmanager/x509utils.go @@ -44,7 +44,7 @@ func GetCertFromURL(urlStr string) (*x509.Certificate, error) { } // Try to extract the first valid PEM certificate from the bytes - cert, err := loadCertFromPEM(certBytes) + cert, err := LoadCertFromPEM(certBytes) if err != nil { return nil, err } @@ -98,7 +98,7 @@ func EncryptPrivateKey(key crypto.PrivateKey, passphrase string) ([]byte, error) // loadCertFromPEM returns the first certificate found in a bunch of bytes or error // if nothing is found. Taken from https://golang.org/src/crypto/x509/cert_pool.go#L85. -func loadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) { +func LoadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) { for len(pemBytes) > 0 { var block *pem.Block block, pemBytes = pem.Decode(pemBytes) From 6738a40cd8bb96e719b259154f650326a6cb2155 Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Wed, 8 Jul 2015 23:00:45 -0700 Subject: [PATCH 29/39] Rebased and fixed issues --- client/client_test.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index d07aa9b8a9..d89b0552b4 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -147,17 +147,17 @@ func TestAddTarget(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "notary-test-") assert.NoError(t, err, "failed to create a temporary directory: %s", err) - client, err := NewClient(tempBaseDir) - assert.NoError(t, err, "error creating client: %s", err) + gun := "docker.com/notary" + repo, err := NewNotaryRepository(tempBaseDir, gun, "", nil) + assert.NoError(t, err, "error creating repository: %s", err) - rootKeyID, err := client.GenRootKey("passphrase") + rootKeyID, err := repo.GenRootKey("passphrase") assert.NoError(t, err, "error generating root key: %s", err) - rootKey, err := client.GetRootSigner(rootKeyID, "passphrase") + rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase") assert.NoError(t, err, "error retreiving root key: %s", err) - gun := "docker.com/notary" - repo, err := client.InitRepository(gun, "", nil, rootKey) + err = repo.Initialize(rootSigner) assert.NoError(t, err, "error creating repository: %s", err) // Add fixtures/ca.cert as a target. There's no particular reason @@ -245,17 +245,17 @@ func TestValidateRootKey(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "notary-test-") assert.NoError(t, err, "failed to create a temporary directory: %s", err) - client, err := NewClient(tempBaseDir) - assert.NoError(t, err, "error creating client: %s", err) + gun := "docker.com/notary" + repo, err := NewNotaryRepository(tempBaseDir, gun, "", nil) + assert.NoError(t, err, "error creating repository: %s", err) - rootKeyID, err := client.GenRootKey("passphrase") + rootKeyID, err := repo.GenRootKey("passphrase") assert.NoError(t, err, "error generating root key: %s", err) - rootSigner, err := client.GetRootSigner(rootKeyID, "passphrase") + rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase") assert.NoError(t, err, "error retreiving root key: %s", err) - gun := "docker.com/notary" - _, err = client.InitRepository(gun, "", nil, rootSigner) + err = repo.Initialize(rootSigner) assert.NoError(t, err, "error creating repository: %s", err) rootJSONFile := filepath.Join(tempBaseDir, "tuf", gun, "metadata", "root.json") From 73ca4562973c571f0961d1f346f9402645d5f3dc Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 8 Jul 2015 23:19:37 -0700 Subject: [PATCH 30/39] annotating Publish and making it accept a password retriever function Signed-off-by: David Lawrence (github: endophage) --- client/client.go | 118 ++++++++++++++++++++-------------------------- cmd/notary/tuf.go | 6 ++- 2 files changed, 56 insertions(+), 68 deletions(-) diff --git a/client/client.go b/client/client.go index 1a94e3e8d8..67cea167c8 100644 --- a/client/client.go +++ b/client/client.go @@ -28,6 +28,8 @@ import ( type ErrRepoNotInitialized struct{} +type passwordRetriever func() (string, error) + func (err *ErrRepoNotInitialized) Error() string { return "Repository has not been initialized" } @@ -285,48 +287,71 @@ func (r *NotaryRepository) GetTargetByName(name string) (*Target, error) { } // Publish pushes the local changes in signed material to the remote notary-server -func (r *NotaryRepository) Publish(passphrase string) error { - c, err := r.bootstrapClient() // just need the repo to be initialized from remote +// Conceptually it performs an operation similar to a `git rebase` +func (r *NotaryRepository) Publish(getPass passwordRetriever) error { + // attempt to initialize the repo from the remote store + c, err := r.bootstrapClient() if err != nil { if _, ok := err.(*store.ErrMetaNotFound); ok { - // attempt to load locally to see if it's already init'ed + // if the remote store return a 404 (translated into ErrMetaNotFound), + // the repo hasn't been initialized yet. Attempt to load it from disk. err := r.bootstrapRepo() if err != nil { + // Repo hasn't been initialized, It must be initialized before + // it can be published. Return an error and let caller determine + // what it wants to do. logrus.Debug("Repository not initialized during Publish") - return &ErrRepoNotInitialized{} // caller must init + return &ErrRepoNotInitialized{} } } else { + // The remote store returned an error other than 404. We're + // unable to determine if the repo has been initialized or not. logrus.Error("Could not publish Repository: ", err.Error()) return err } } else { + // If we were successfully able to bootstrap the client (which only pulls + // root.json), update it the rest of the tuf metadata in preparation for + // applying the changelist. err = c.Update() if err != nil { return err } } + // load the changelist for this repo cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) if err != nil { logrus.Debug("Error initializing changelist") return err } + // apply the changelist to the repo err = applyChangelist(r.tufRepo, cl) if err != nil { logrus.Debug("Error applying changelist") return err } - rootKeyID := r.tufRepo.Root.Signed.Roles["root"].KeyIDs[0] - rootSigner, err := r.GetRootSigner(rootKeyID, passphrase) - if err != nil { - return err - } - - root, err := r.tufRepo.SignRoot(data.DefaultExpires("root"), rootSigner.signer) - if err != nil { - return err + // check if our root file is nearing expiry. Resign if it is. + var updateRoot bool + var root *data.Signed + if nearExpiry(r.tufRepo.Root) || r.tufRepo.Root.Dirty { + passphrase, err := getPass() + if err != nil { + return err + } + rootKeyID := r.tufRepo.Root.Signed.Roles["root"].KeyIDs[0] + rootSigner, err := r.GetRootSigner(rootKeyID, passphrase) + if err != nil { + return err + } + root, err = r.tufRepo.SignRoot(data.DefaultExpires("root"), rootSigner.signer) + if err != nil { + return err + } + updateRoot = true } + // we will always resign targets and snapshots targets, err := r.tufRepo.SignTargets("targets", data.DefaultExpires("targets"), nil) if err != nil { return err @@ -336,10 +361,12 @@ func (r *NotaryRepository) Publish(passphrase string) error { return err } - rootJSON, err := json.Marshal(root) + remote, err := getRemoteStore(r.Gun) if err != nil { return err } + + // ensure we can marshal all the json before sending anything to remote targetsJSON, err := json.Marshal(targets) if err != nil { return err @@ -349,13 +376,16 @@ func (r *NotaryRepository) Publish(passphrase string) error { return err } - remote, err := getRemoteStore(r.Gun) - if err != nil { - return err - } - err = remote.SetMeta("root", rootJSON) - if err != nil { - return err + // if we need to update the root, marshal it and push the update to remote + if updateRoot { + rootJSON, err := json.Marshal(root) + if err != nil { + return err + } + err = remote.SetMeta("root", rootJSON) + if err != nil { + return err + } } err = remote.SetMeta("targets", targetsJSON) if err != nil { @@ -690,49 +720,3 @@ func (uk *UnlockedSigner) GenerateCertificate(gun string) (*x509.Certificate, er return cert, nil } - -// Use this to initialize remote HTTPStores from the config settings -func getRemoteStore(gun string) (store.RemoteStore, error) { - return store.NewHTTPStore( - "https://notary:4443/v2/"+gun+"/_trust/tuf/", - "", - "json", - "", - "key", - ) -} - -func applyChangelist(repo *tuf.TufRepo, cl changelist.Changelist) error { - changes := cl.List() - var err error - for _, c := range changes { - if c.Scope() == "targets" { - applyTargetsChange(repo, c) - } - if err != nil { - return err - } - } - return nil -} - -func applyTargetsChange(repo *tuf.TufRepo, c changelist.Change) error { - var err error - meta := &data.FileMeta{} - err = json.Unmarshal(c.Content(), meta) - if err != nil { - return nil - } - if c.Action() == changelist.ActionCreate { - files := data.Files{c.Path(): *meta} - _, err = repo.AddTargets("targets", files) - } else if c.Action() == changelist.ActionDelete { - err = repo.RemoveTargets("targets", c.Path()) - } - if err != nil { - // TODO(endophage): print out rem entries as files that couldn't - // be added. - return err - } - return nil -} diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 8df16ba0a6..4a7badaae2 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -188,7 +188,7 @@ func tufPublish(cmd *cobra.Command, args []string) { fatalf(err.Error()) } - err = repo.Publish("passphrase") + err = repo.Publish(passwordRetriever) if err != nil { fatalf(err.Error()) } @@ -322,3 +322,7 @@ func generateRoles(kdb *keys.KeyDB, rootKeyID, targetsKeyID, snapshotKeyID, time } return nil } + +func passwordRetriever() (string, error) { + return "passphrase", nil +} From 4635bed2db8f22740aa8ca4653feab62ca23a56d Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Thu, 9 Jul 2015 01:57:19 -0700 Subject: [PATCH 31/39] Major refactor of keys Signed-off-by: Diogo Monica --- client/cli_crypto_service.go | 83 ++++++++----------- client/client.go | 55 +++++-------- trustmanager/filestore.go | 4 +- trustmanager/keyfilestore.go | 52 ++++++++---- trustmanager/keyfilestore_test.go | 91 +++++++++------------ trustmanager/x509utils.go | 128 ++++++++++++------------------ 6 files changed, 179 insertions(+), 234 deletions(-) diff --git a/client/cli_crypto_service.go b/client/cli_crypto_service.go index ea47d59395..62273998cb 100644 --- a/client/cli_crypto_service.go +++ b/client/cli_crypto_service.go @@ -6,7 +6,6 @@ import ( "crypto/rsa" "crypto/sha256" "crypto/x509" - "encoding/pem" "errors" "fmt" "path/filepath" @@ -38,36 +37,15 @@ func NewRootCryptoService(rootKeyStore *trustmanager.KeyFileStore, passphrase st // Create is used to generate keys for targets, snapshots and timestamps func (ccs *CryptoService) Create(role string) (*data.PublicKey, error) { - // Generates a new RSA key - rsaPrivKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize) + privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize) if err != nil { - return nil, fmt.Errorf("could not generate private key: %v", err) + return nil, fmt.Errorf("failed to generate RSA key: %v", err) } - rsaPublicKey := rsaPrivKey.PublicKey + // Store the private key into our keystore with the name being: /GUN/ID.key + ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey) - // Using x509 to Marshal the Public key into DER encoding - pubBytes, err := x509.MarshalPKIXPublicKey(&rsaPublicKey) - if err != nil { - return nil, errors.New("Failed to Marshal public key.") - } - - tufKey := data.NewPublicKey("RSA", pubBytes) - - // Passing in the the GUN + keyID as the name for the private key and adding it - // to our KeyFileStore. Final storage will be under $BASE_PATH/GUN/keyID.key - privKeyFilename := filepath.Join(ccs.gun, tufKey.ID()) - - // Get a PEM encoded representation of the private key - pemRSAPrivKey, err := trustmanager.KeyToPEM(rsaPrivKey) - if err != nil { - return nil, fmt.Errorf("failed to generate the certificate for key: %v (%s)", role, err) - } - - // Store the PEM-encoded private key into our keystore - ccs.keyStore.Add(privKeyFilename, pemRSAPrivKey) - - return tufKey, nil + return data.PublicKeyFromPrivate(*privKey), nil } // Sign returns the signatures for data with the given keyIDs @@ -81,29 +59,20 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur // Get the PrivateKey filename privKeyFilename := filepath.Join(ccs.gun, fingerprint) // Read PrivateKey from file - privPEMBytes, err := ccs.keyStore.Get(privKeyFilename) + privKey, err := ccs.keyStore.GetKey(privKeyFilename) if err != nil { continue } - // Parse PrivateKey - privKeyBytes, _ := pem.Decode(privPEMBytes) - privKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes.Bytes) - if err != nil { - return nil, err - } - - // Sign the data - sig, err := rsa.SignPKCS1v15(rand.Reader, privKey, hash, hashed[:]) + sig, err := sign(privKey, hash, hashed[:]) if err != nil { return nil, err } // Append signatures to result array signatures = append(signatures, data.Signature{ - KeyID: fingerprint, - Method: "RSA", - //Method: "RSASSA-PKCS1-V1_5-SIGN", + KeyID: fingerprint, + Method: "RSA", Signature: sig[:], }) } @@ -126,21 +95,13 @@ func (ccs *RootCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Sign signatures := make([]data.Signature, 0, len(keyIDs)) for _, fingerprint := range keyIDs { // Read PrivateKey from file - privPEMBytes, err := ccs.rootKeyStore.GetDecrypted(fingerprint, ccs.passphrase) + privKey, err := ccs.rootKeyStore.GetDecryptedKey(fingerprint, ccs.passphrase) if err != nil { // TODO(diogo): This error should be returned to the user in someway continue } - // Parse PrivateKey - privKeyBytes, _ := pem.Decode(privPEMBytes) - privKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes.Bytes) - if err != nil { - return nil, err - } - - // Sign the data - sig, err := rsa.SignPKCS1v15(rand.Reader, privKey, hash, hashed[:]) + sig, err := sign(privKey, hash, hashed[:]) if err != nil { return nil, err } @@ -152,5 +113,27 @@ func (ccs *RootCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Sign Signature: sig[:], }) } + return signatures, nil } + +func sign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) { + // TODO(diogo): Implement support for ECDSA. + if privKey.Cipher() != "RSA" { + return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher()) + } + + // Create an rsa.PrivateKey out of the private key bytes + rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKey.Private()) + if err != nil { + return nil, err + } + + // Use the RSA key to sign the data + sig, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivKey, hash, hashed[:]) + if err != nil { + return nil, err + } + + return sig, nil +} diff --git a/client/client.go b/client/client.go index 67cea167c8..c66047cf78 100644 --- a/client/client.go +++ b/client/client.go @@ -3,7 +3,6 @@ package client import ( "bytes" "crypto/rand" - "crypto/rsa" "crypto/x509" "encoding/json" "encoding/pem" @@ -58,12 +57,12 @@ type NotaryRepository struct { baseURL string tufRepoPath string transport http.RoundTripper - signer *signed.Signer - tufRepo *tuf.TufRepo - fileStore store.MetadataStore - privKeyStore *trustmanager.KeyFileStore caStore trustmanager.X509Store certificateStore trustmanager.X509Store + fileStore store.MetadataStore + signer *signed.Signer + tufRepo *tuf.TufRepo + privKeyStore *trustmanager.KeyFileStore rootKeyStore *trustmanager.KeyFileStore rootSigner *UnlockedSigner } @@ -609,45 +608,29 @@ func (c *NotaryRepository) ListPrivateKeys() []string { // GenRootKey generates a new root key protected by a given passphrase func (c *NotaryRepository) GenRootKey(passphrase string) (string, error) { - // TODO(diogo): Refactor TUF Key creation. We should never see crypto.privatekeys - // Generates a new RSA key - rsaPrivKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize) - if err != nil { - return "", fmt.Errorf("could not generate private key: %v", err) - } - - // Encode the private key in PEM format since that is the final storage format - pemPrivKey, err := trustmanager.KeyToPEM(rsaPrivKey) - if err != nil { - return "", fmt.Errorf("failed to encode the private key: %v", err) - } - - tufPrivKey, err := trustmanager.RSAToPrivateKey(rsaPrivKey) + privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize) if err != nil { return "", fmt.Errorf("failed to convert private key: ", err) } - c.rootKeyStore.AddEncrypted(tufPrivKey.ID(), pemPrivKey, passphrase) + c.rootKeyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase) - return tufPrivKey.ID(), nil + return privKey.ID(), nil } // GetRootSigner retreives a root key that includes the ID and a signer func (c *NotaryRepository) GetRootSigner(rootKeyID, passphrase string) (*UnlockedSigner, error) { - pemPrivKey, err := c.rootKeyStore.GetDecrypted(rootKeyID, passphrase) + privKey, err := c.rootKeyStore.GetDecryptedKey(rootKeyID, passphrase) if err != nil { return nil, fmt.Errorf("could not get decrypted root key: %v", err) } - tufPrivKey, err := trustmanager.TufParsePEMPrivateKey(pemPrivKey) - if err != nil { - return nil, fmt.Errorf("could not get parse root key: %v", err) - } - + // This signer will be used for all of the normal TUF operations, except for + // when a root key is needed. signer := signed.NewSigner(NewRootCryptoService(c.rootKeyStore, passphrase)) return &UnlockedSigner{ - privKey: tufPrivKey, + privKey: privKey, signer: signer}, nil } @@ -676,6 +659,7 @@ func (c *NotaryRepository) loadKeys(trustDir, rootKeysDir string) error { return err } + // Load the keystore that will hold all of our encrypted Root Private Keys rootKeyStore, err := trustmanager.NewKeyFileStore(rootKeysDir) if err != nil { return err @@ -693,29 +677,32 @@ func (uk *UnlockedSigner) ID() string { return uk.PublicKey().ID() } -// PublicKey Returns the public key associated with the Root Key +// PublicKey Returns the public key associated with the Private Key within the Signer func (uk *UnlockedSigner) PublicKey() *data.PublicKey { return data.PublicKeyFromPrivate(*uk.privKey) } -// GenerateCertificate +// GenerateCertificate generates an X509 Certificate from a template, given a GUN func (uk *UnlockedSigner) GenerateCertificate(gun string) (*x509.Certificate, error) { privKey, err := x509.ParsePKCS1PrivateKey(uk.privKey.Private()) if err != nil { return nil, fmt.Errorf("failed to parse root key: %v (%s)", gun, err.Error()) } - //TODO (diogo): We're hardcoding the Organization to be the GUN. Probably want to change it - template := trustmanager.NewCertificate(gun, gun) + template, err := trustmanager.NewCertificate(gun) + if err != nil { + return nil, fmt.Errorf("failed to create the certificate template for: %s (%v)", gun, err) + } + derBytes, err := x509.CreateCertificate(rand.Reader, template, template, privKey.Public(), privKey) if err != nil { - return nil, fmt.Errorf("failed to generate the certificate for: %v (%s)", gun, err.Error()) + return nil, fmt.Errorf("failed to create the certificate for: %s (%v)", gun, err) } // Encode the new certificate into PEM cert, err := x509.ParseCertificate(derBytes) if err != nil { - return nil, fmt.Errorf("failed to parse the certificate for key: %v (%s)", gun, err.Error()) + return nil, fmt.Errorf("failed to parse the certificate for key: %s (%v)", gun, err) } return cert, nil diff --git a/trustmanager/filestore.go b/trustmanager/filestore.go index 54d41bb50e..e55dbdeba0 100644 --- a/trustmanager/filestore.go +++ b/trustmanager/filestore.go @@ -24,8 +24,8 @@ type FileStore interface { type EncryptedFileStore interface { FileStore - AddEncrypted(fileName string, keyBytes []byte, passphrase string) error - GetDecrypted(fileName string, passphrase string) ([]byte, error) + AddEncrypted(fileName string, data []byte, passphrase string) error + GetDecrypted(fileName, passphrase string) ([]byte, error) } // SimpleFileStore implements FileStore diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go index 9c25581c7e..beaa7fbef9 100644 --- a/trustmanager/keyfilestore.go +++ b/trustmanager/keyfilestore.go @@ -1,5 +1,7 @@ package trustmanager +import "github.com/endophage/gotuf/data" + const ( keyExtension = "key" ) @@ -20,39 +22,55 @@ func NewKeyFileStore(baseDir string) (*KeyFileStore, error) { return &KeyFileStore{fileStore}, nil } +// AddKey stores the contents of a PEM-encoded private key as a PEM block +func (s *KeyFileStore) AddKey(name string, privKey *data.PrivateKey) error { + pemPrivKey, err := KeyToPEM(privKey) + if err != nil { + return err + } + + return s.Add(name, pemPrivKey) +} + +// GetKey returns the PrivateKey given a KeyID +func (s *KeyFileStore) GetKey(name string) (*data.PrivateKey, error) { + keyBytes, err := s.Get(name) + if err != nil { + return nil, err + } + + // Convert PEM encoded bytes back to a PrivateKey + privKey, err := ParsePEMPrivateKey(keyBytes, "") + if err != nil { + return nil, err + } + + return privKey, nil +} + // AddEncrypted stores the contents of a PEM-encoded private key as an encrypted PEM block -func (s *KeyFileStore) AddEncrypted(fileName string, pemKey []byte, passphrase string) error { - - privKey, err := ParsePEMPrivateKey(pemKey) +func (s *KeyFileStore) AddEncryptedKey(name string, privKey *data.PrivateKey, passphrase string) error { + encryptedPrivKey, err := EncryptPrivateKey(privKey, passphrase) if err != nil { return err } - encryptedKey, err := EncryptPrivateKey(privKey, passphrase) - if err != nil { - return err - } - - return s.Add(fileName, encryptedKey) + return s.Add(name, encryptedPrivKey) } // GetDecrypted decrypts and returns the PEM Encoded private key given a flename // and a passphrase -func (s *KeyFileStore) GetDecrypted(fileName string, passphrase string) ([]byte, error) { - keyBytes, err := s.Get(fileName) +func (s *KeyFileStore) GetDecryptedKey(name string, passphrase string) (*data.PrivateKey, error) { + keyBytes, err := s.Get(name) if err != nil { return nil, err } // Gets an unencrypted PrivateKey. - privKey, err := ParsePEMEncryptedPrivateKey(keyBytes, passphrase) + privKey, err := ParsePEMPrivateKey(keyBytes, passphrase) if err != nil { return nil, err } - return KeyToPEM(privKey) -} - -func (s *KeyFileStore) Link(src, dst string) error { - return s.FileStore.Link(src, dst) + return privKey, nil } diff --git a/trustmanager/keyfilestore_test.go b/trustmanager/keyfilestore_test.go index f76283d455..3cbff4ef90 100644 --- a/trustmanager/keyfilestore_test.go +++ b/trustmanager/keyfilestore_test.go @@ -3,7 +3,6 @@ package trustmanager import ( "bytes" "crypto/rand" - "crypto/rsa" "io/ioutil" "os" "path/filepath" @@ -30,19 +29,13 @@ func TestAddKey(t *testing.T) { t.Fatalf("failed to create new key filestore: %v", err) } - key, err := rsa.GenerateKey(rand.Reader, 1024) + privKey, err := GenerateRSAKey(rand.Reader, 512) if err != nil { t.Fatalf("could not generate private key: %v", err) } - // Get the PEM for the key - pemKey, err := KeyToPEM(key) - if err != nil { - t.Fatalf("failed to convert private key to PEM: %v", err) - } - - // Call the Add function - err = store.Add(testName, pemKey) + // Call the AddKey function + err = store.AddKey(testName, privKey) if err != nil { t.Fatalf("failed to add file to store: %v", err) } @@ -111,19 +104,23 @@ EMl3eFOJXjIch/wIesRSN+2dGOsl7neercjMh1i9RvpCwHDx/E0= t.Fatalf("failed to create new key filestore: %v", err) } - // Call the Get function - pemKey, err := store.Get(testName) + // Call the GetKey function + privKey, err := store.GetKey(testName) if err != nil { t.Fatalf("failed to get file from store: %v", err) } - if !bytes.Equal(testData, pemKey) { + pemPrivKey, err := KeyToPEM(privKey) + if err != nil { + t.Fatalf("failed to convert key to PEM: %v", err) + } + + if !bytes.Equal(testData, pemPrivKey) { t.Fatalf("unexpected content in the file: %s", filePath) } } func TestAddEncryptedAndGetDecrypted(t *testing.T) { - testName := "docker.com/notary/root" testExt := "key" // Temporary directory where test files will be created @@ -132,9 +129,6 @@ func TestAddEncryptedAndGetDecrypted(t *testing.T) { t.Fatalf("failed to create a temporary directory: %v", err) } - // Since we're generating this manually we need to add the extension '.' - expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt) - // Create our FileStore store, err := NewKeyFileStore(tempBaseDir) if err != nil { @@ -142,35 +136,38 @@ func TestAddEncryptedAndGetDecrypted(t *testing.T) { } // Generate new PrivateKey - key, err := rsa.GenerateKey(rand.Reader, 1024) + privKey, err := GenerateRSAKey(rand.Reader, 512) if err != nil { t.Fatalf("could not generate private key: %v", err) } - // Get PEM encodedd key - pemKey, err := KeyToPEM(key) - if err != nil { - t.Fatalf("Could not encode key to PEM: %v", err) - } - - // Call the Add function - err = store.AddEncrypted(testName, pemKey, "diogomonica") + // Call the AddEncryptedKey function + err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica") if err != nil { t.Fatalf("failed to add file to store: %v", err) } - pemPrivKey, err := store.GetDecrypted(testName, "diogomonica") + // Since we're generating this manually we need to add the extension '.' + expectedFilePath := filepath.Join(tempBaseDir, privKey.ID()+"."+testExt) + + // Check to see if file exists + _, err = ioutil.ReadFile(expectedFilePath) + if err != nil { + t.Fatalf("expected file not found: %v", err) + } + + // Call the GetDecryptedKey function + readPrivKey, err := store.GetDecryptedKey(privKey.ID(), "diogomonica") if err != nil { t.Fatalf("could not decrypt private key: %v", err) } - if !strings.Contains(string(pemKey), string(pemPrivKey)) { - t.Fatalf("expected private key content in the file: %s", expectedFilePath) + if !bytes.Equal(privKey.Private(), readPrivKey.Private()) { + t.Fatalf("written key and loaded key do not match") } } func TestGetDecryptedWithTamperedCipherText(t *testing.T) { - testName := "docker.com/notary/root" testExt := "key" // Temporary directory where test files will be created @@ -179,9 +176,6 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) { t.Fatalf("failed to create a temporary directory: %v", err) } - // Since we're generating this manually we need to add the extension '.' - expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt) - // Create our FileStore store, err := NewKeyFileStore(tempBaseDir) if err != nil { @@ -189,24 +183,22 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) { } // Generate a new Private Key - key, err := rsa.GenerateKey(rand.Reader, 1024) + privKey, err := GenerateRSAKey(rand.Reader, 512) if err != nil { t.Fatalf("could not generate private key: %v", err) } - // Get PEM encodedd key - pemKey, err := KeyToPEM(key) - if err != nil { - t.Fatalf("Could not encode key to PEM: %v", err) - } - // Call the Add function - err = store.AddEncrypted(testName, pemKey, "diogomonica") + // Call the AddEncryptedKey function + err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica") if err != nil { t.Fatalf("failed to add file to store: %v", err) } + // Since we're generating this manually we need to add the extension '.' + expectedFilePath := filepath.Join(tempBaseDir, privKey.ID()+"."+testExt) + // Get file description, open file - fp, _ := os.OpenFile(expectedFilePath, os.O_WRONLY, 0600) + fp, err := os.OpenFile(expectedFilePath, os.O_WRONLY, 0600) if err != nil { t.Fatalf("expected file not found: %v", err) } @@ -215,7 +207,7 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) { fp.WriteAt([]byte("a"), int64(1)) // Try to decrypt the file - _, err = store.GetDecrypted(testName, "diogomonica") + _, err = store.GetDecryptedKey(privKey.ID(), "diogomonica") if err == nil { t.Fatalf("expected error while decrypting the content due to invalid cipher text") } @@ -237,24 +229,19 @@ func TestGetDecryptedWithInvalidPassphrase(t *testing.T) { } // Generate a new random RSA Key - key, err := rsa.GenerateKey(rand.Reader, 1024) + privKey, err := GenerateRSAKey(rand.Reader, 512) if err != nil { t.Fatalf("could not generate private key: %v", err) } - // Get PEM encodedd key - pemKey, err := KeyToPEM(key) - if err != nil { - t.Fatalf("Could not encode key to PEM: %v", err) - } - // Call the Add function - err = store.AddEncrypted(testName, pemKey, "diogomonica") + // Call the AddEncryptedKey function + err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica") if err != nil { t.Fatalf("failed to add file to stoAFre: %v", err) } // Try to decrypt the file with an invalid passphrase - _, err = store.GetDecrypted(testName, "diegomonica") + _, err = store.GetDecryptedKey(testName, "diegomonica") if err == nil { t.Fatalf("expected error while decrypting the content due to invalid passphrase") } diff --git a/trustmanager/x509utils.go b/trustmanager/x509utils.go index aeb8f8b009..1994d2de50 100644 --- a/trustmanager/x509utils.go +++ b/trustmanager/x509utils.go @@ -1,7 +1,6 @@ package trustmanager import ( - "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" @@ -9,6 +8,7 @@ import ( "encoding/pem" "errors" "fmt" + "io" "io/ioutil" "math/big" "net/http" @@ -59,34 +59,30 @@ func CertToPEM(cert *x509.Certificate) []byte { return pemCert } -// KeyToPEM returns a PEM encoded key from a crypto.PrivateKey -func KeyToPEM(key crypto.PrivateKey) ([]byte, error) { - rsaKey, ok := key.(*rsa.PrivateKey) - if !ok { +// KeyToPEM returns a PEM encoded key from a Private Key +func KeyToPEM(privKey *data.PrivateKey) ([]byte, error) { + if privKey.Cipher() != "RSA" { return nil, errors.New("only RSA keys are currently supported") } - keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey) - return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}), nil + return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privKey.Private()}), nil } -// EncryptPrivateKey returns an encrypted PEM encoded key given a Private key +// EncryptPrivateKey returns an encrypted PEM key given a Privatekey // and a passphrase -func EncryptPrivateKey(key crypto.PrivateKey, passphrase string) ([]byte, error) { - rsaKey, ok := key.(*rsa.PrivateKey) - if !ok { +func EncryptPrivateKey(key *data.PrivateKey, passphrase string) ([]byte, error) { + // TODO(diogo): Currently only supports RSA Private keys + if key.Cipher() != "RSA" { return nil, errors.New("only RSA keys are currently supported") } - keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey) - password := []byte(passphrase) cipherType := x509.PEMCipherAES256 blockType := "RSA PRIVATE KEY" encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader, blockType, - keyBytes, + key.Private(), password, cipherType) if err != nil { @@ -164,61 +160,55 @@ func LoadCertFromFile(filename string) (*x509.Certificate, error) { return nil, errors.New("could not load certificate from file") } -// LoadKeyFromFile returns a PrivateKey given a filename -func LoadKeyFromFile(filename string) (crypto.PrivateKey, error) { - pemBytes, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - - key, err := ParsePEMPrivateKey(pemBytes) - if err != nil { - return nil, err - } - return key, nil -} - -// ParsePEMPrivateKey returns a private key from a PEM encoded private key. It -// only supports RSA (PKCS#1). -func ParsePEMPrivateKey(pemBytes []byte) (crypto.PrivateKey, error) { +// ParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It +// only supports RSA (PKCS#1) and attempts to decrypt using the passphrase, if encrypted. +func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (*data.PrivateKey, error) { block, _ := pem.Decode(pemBytes) if block == nil { - return nil, errors.New("no valid key found") + return nil, errors.New("no valid private key found") } switch block.Type { case "RSA PRIVATE KEY": - return x509.ParsePKCS1PrivateKey(block.Bytes) - default: - return nil, fmt.Errorf("unsupported key type %q", block.Type) - } -} + var privKeyBytes []byte + var err error -// TufParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It -// only supports RSA (PKCS#1). -func TufParsePEMPrivateKey(pemBytes []byte) (*data.PrivateKey, error) { - block, _ := pem.Decode(pemBytes) - if block == nil { - return nil, errors.New("no valid key found") - } + if x509.IsEncryptedPEMBlock(block) { + privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase)) + if err != nil { + return nil, errors.New("could not decrypt private key") + } + } else { + privKeyBytes = block.Bytes + } - switch block.Type { - case "RSA PRIVATE KEY": - rsaPrivKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes) if err != nil { - return nil, fmt.Errorf("could not parse PEM: %v", err) + return nil, fmt.Errorf("could not parse DER encoded key: %v", err) } tufRSAPrivateKey, err := RSAToPrivateKey(rsaPrivKey) if err != nil { - return nil, fmt.Errorf("could not convert crypto.PrivateKey to PrivateKey: %v", err) + return nil, fmt.Errorf("could not convert rsa.PrivateKey to data.PrivateKey: %v", err) } + return tufRSAPrivateKey, nil default: return nil, fmt.Errorf("unsupported key type %q", block.Type) } } +// GenerateRSAKey generates an RSA Private key and returns a TUF PrivateKey +func GenerateRSAKey(random io.Reader, bits int) (*data.PrivateKey, error) { + rsaPrivKey, err := rsa.GenerateKey(random, bits) + if err != nil { + return nil, fmt.Errorf("could not generate private key: %v", err) + } + + return RSAToPrivateKey(rsaPrivKey) +} + +// RSAToPrivateKey converts an rsa.Private key to a TUF data.PrivateKey type func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey) (*data.PrivateKey, error) { // Get a DER-encoded representation of the PublicKey rsaPubBytes, err := x509.MarshalPKIXPublicKey(&rsaPrivKey.PublicKey) @@ -232,43 +222,23 @@ func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey) (*data.PrivateKey, error) { return data.NewPrivateKey("RSA", rsaPubBytes, rsaPrivBytes), nil } -// ParsePEMEncryptedPrivateKey returns a private key from a PEM encrypted private key. It -// only supports RSA (PKCS#1). -func ParsePEMEncryptedPrivateKey(pemBytes []byte, passphrase string) (crypto.PrivateKey, error) { - block, _ := pem.Decode(pemBytes) - if block == nil { - return nil, errors.New("no valid private key found") - } - - switch block.Type { - case "RSA PRIVATE KEY": - if !x509.IsEncryptedPEMBlock(block) { - return nil, errors.New("private key is not encrypted") - } - - decryptedPEMBlock, err := x509.DecryptPEMBlock(block, []byte(passphrase)) - if err != nil { - return nil, errors.New("could not decrypt private key") - } - - return x509.ParsePKCS1PrivateKey(decryptedPEMBlock) - default: - return nil, fmt.Errorf("unsupported key type %q", block.Type) - } -} - -func NewCertificate(gun, organization string) *x509.Certificate { +// NewCertificate returns an X509 Certificate following a template, given a GUN. +func NewCertificate(gun string) (*x509.Certificate, error) { notBefore := time.Now() notAfter := notBefore.Add(time.Hour * 24 * 365 * 2) serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - // TODO(diogo): Don't silently ignore this error - serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, fmt.Errorf("failed to generate new certificate: %v", err) + } + + // TODO(diogo): Currently hard coding organization to be the gun. Revisit. return &x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ - Organization: []string{organization}, + Organization: []string{gun}, CommonName: gun, }, NotBefore: notBefore, @@ -277,5 +247,5 @@ func NewCertificate(gun, organization string) *x509.Certificate { KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, BasicConstraintsValid: true, - } + }, nil } From 8d9ce1b2e5ab8d0bde4ff6a5f825a741b5a90ae9 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Thu, 9 Jul 2015 08:09:04 -0700 Subject: [PATCH 32/39] forgot to add client/helpers file Signed-off-by: David Lawrence (github: endophage) --- client/helpers.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 client/helpers.go diff --git a/client/helpers.go b/client/helpers.go new file mode 100644 index 0000000000..b8d62afa81 --- /dev/null +++ b/client/helpers.go @@ -0,0 +1,62 @@ +package client + +import ( + "encoding/json" + "time" + + "github.com/docker/notary/client/changelist" + "github.com/endophage/gotuf" + "github.com/endophage/gotuf/data" + "github.com/endophage/gotuf/store" +) + +// Use this to initialize remote HTTPStores from the config settings +func getRemoteStore(gun string) (store.RemoteStore, error) { + return store.NewHTTPStore( + "https://notary:4443/v2/"+gun+"/_trust/tuf/", + "", + "json", + "", + "key", + ) +} + +func applyChangelist(repo *tuf.TufRepo, cl changelist.Changelist) error { + changes := cl.List() + var err error + for _, c := range changes { + if c.Scope() == "targets" { + applyTargetsChange(repo, c) + } + if err != nil { + return err + } + } + return nil +} + +func applyTargetsChange(repo *tuf.TufRepo, c changelist.Change) error { + var err error + meta := &data.FileMeta{} + err = json.Unmarshal(c.Content(), meta) + if err != nil { + return nil + } + if c.Action() == changelist.ActionCreate { + files := data.Files{c.Path(): *meta} + _, err = repo.AddTargets("targets", files) + } else if c.Action() == changelist.ActionDelete { + err = repo.RemoveTargets("targets", c.Path()) + } + if err != nil { + // TODO(endophage): print out rem entries as files that couldn't + // be added. + return err + } + return nil +} + +func nearExpiry(r *data.SignedRoot) bool { + plus6mo := time.Now().AddDate(0, 6, 0) + return r.Signed.Expires.Before(plus6mo) +} From 53ad4a7539fc0aba9fa31a71c503946fd289442e Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Thu, 9 Jul 2015 11:05:09 -0700 Subject: [PATCH 33/39] fixing publish Signed-off-by: David Lawrence (github: endophage) --- client/client.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/client/client.go b/client/client.go index c66047cf78..a786c15e17 100644 --- a/client/client.go +++ b/client/client.go @@ -288,6 +288,8 @@ func (r *NotaryRepository) GetTargetByName(name string) (*Target, error) { // Publish pushes the local changes in signed material to the remote notary-server // Conceptually it performs an operation similar to a `git rebase` func (r *NotaryRepository) Publish(getPass passwordRetriever) error { + var updateRoot bool + var root *data.Signed // attempt to initialize the repo from the remote store c, err := r.bootstrapClient() if err != nil { @@ -302,6 +304,13 @@ func (r *NotaryRepository) Publish(getPass passwordRetriever) error { logrus.Debug("Repository not initialized during Publish") return &ErrRepoNotInitialized{} } + // We had local data but the server doesn't know about the repo yet, + // ensure we will push the initial root file + root, err = r.tufRepo.Root.ToSigned() + if err != nil { + return err + } + updateRoot = true } else { // The remote store returned an error other than 404. We're // unable to determine if the repo has been initialized or not. @@ -332,8 +341,6 @@ func (r *NotaryRepository) Publish(getPass passwordRetriever) error { } // check if our root file is nearing expiry. Resign if it is. - var updateRoot bool - var root *data.Signed if nearExpiry(r.tufRepo.Root) || r.tufRepo.Root.Dirty { passphrase, err := getPass() if err != nil { From 8c6de46aca69787082af2f6a14375d54d78ffd07 Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Thu, 9 Jul 2015 14:51:31 -0700 Subject: [PATCH 34/39] Added list keys that ignores symlinks --- client/client.go | 16 +++++----------- client/client_test.go | 2 +- cmd/notary/keys.go | 4 ++-- trustmanager/filestore.go | 32 ++++++++++++++++---------------- trustmanager/filestore_test.go | 27 ++++++++++++++++++++------- trustmanager/keyfilestore.go | 9 ++++++++- trustmanager/x509filestore.go | 5 +++-- trustmanager/x509utils.go | 2 +- 8 files changed, 56 insertions(+), 41 deletions(-) diff --git a/client/client.go b/client/client.go index a786c15e17..444d7ed630 100644 --- a/client/client.go +++ b/client/client.go @@ -515,7 +515,7 @@ Example TUF Content for root key: } } */ -func (r *NotaryRepository) ValidateRoot(root *data.Signed) error { +func (r *NotaryRepository) validateRoot(root *data.Signed) error { rootSigned := &data.Root{} err := json.Unmarshal(root.Signed, rootSigned) if err != nil { @@ -581,7 +581,7 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { return nil, err } - err = r.ValidateRoot(root) + err = r.validateRoot(root) if err != nil { return nil, err } @@ -594,8 +594,6 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { return nil, err } - // TODO(dlaw): Where does this keyDB come in - return tufclient.NewClient( r.tufRepo, remote, @@ -603,14 +601,10 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { ), nil } -// ListPrivateKeys lists all availables private keys. Does not include private key +// ListPrivateKeys lists all available root keys. Does not include private key // material -func (c *NotaryRepository) ListPrivateKeys() []string { - // TODO(diogo): Make this work - for _, k := range c.rootKeyStore.ListAll() { - fmt.Println(k) - } - return nil +func (c *NotaryRepository) ListRootKeys() []string { + return c.rootKeyStore.ListKeys() } // GenRootKey generates a new root key protected by a given passphrase diff --git a/client/client_test.go b/client/client_test.go index d89b0552b4..bb89273cd2 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -52,7 +52,7 @@ func TestInitRepo(t *testing.T) { // Look for keys in private. The filenames should match the key IDs // in the private key store. - privKeyList := repo.privKeyStore.ListAll() + privKeyList := repo.privKeyStore.ListFiles(true) for _, privKeyName := range privKeyList { _, err := os.Stat(privKeyName) assert.NoError(t, err, "missing private key: %s", privKeyName) diff --git a/cmd/notary/keys.go b/cmd/notary/keys.go index 897ea2472f..7b3cc58923 100644 --- a/cmd/notary/keys.go +++ b/cmd/notary/keys.go @@ -92,7 +92,7 @@ func keysRemove(cmd *cobra.Command, args []string) { } // We didn't find a certificate with this ID, let's try to see if we can find keys. - keyList := privKeyStore.ListDir(gunOrID) + keyList := privKeyStore.ListDir(gunOrID, true) if len(keyList) < 1 { fatalf("no Private Keys found under Global Unique Name: %s", gunOrID) } @@ -187,7 +187,7 @@ func keysList(cmd *cobra.Command, args []string) { fmt.Println("") fmt.Println("# Signing keys: ") - for _, k := range privKeyStore.ListAll() { + for _, k := range privKeyStore.ListFiles(true) { printKey(k) } } diff --git a/trustmanager/filestore.go b/trustmanager/filestore.go index e55dbdeba0..44ad6b2774 100644 --- a/trustmanager/filestore.go +++ b/trustmanager/filestore.go @@ -17,17 +17,11 @@ type FileStore interface { RemoveDir(directoryName string) error Get(fileName string) ([]byte, error) GetPath(fileName string) string - ListAll() []string - ListDir(directoryName string) []string + ListFiles(symlinks bool) []string + ListDir(directoryName string, symlinks bool) []string Link(src, dst string) error } -type EncryptedFileStore interface { - FileStore - AddEncrypted(fileName string, data []byte, passphrase string) error - GetDecrypted(fileName, passphrase string) ([]byte, error) -} - // SimpleFileStore implements FileStore type SimpleFileStore struct { baseDir string @@ -36,7 +30,7 @@ type SimpleFileStore struct { } // NewFileStore creates a directory with 755 permissions -func NewFileStore(baseDir string, fileExt string) (FileStore, error) { +func NewSimpleFileStore(baseDir string, fileExt string) (FileStore, error) { if err := CreateDirectory(baseDir); err != nil { return nil, err } @@ -49,7 +43,7 @@ func NewFileStore(baseDir string, fileExt string) (FileStore, error) { } // NewPrivateFileStore creates a directory with 700 permissions -func NewPrivateFileStore(baseDir string, fileExt string) (FileStore, error) { +func NewPrivateSimpleFileStore(baseDir string, fileExt string) (FileStore, error) { if err := CreatePrivateDirectory(baseDir); err != nil { return nil, err } @@ -110,18 +104,18 @@ func (f *SimpleFileStore) GetPath(name string) string { } // List lists all the files inside of a store -func (f *SimpleFileStore) ListAll() []string { - return f.list(f.baseDir) +func (f *SimpleFileStore) ListFiles(symlinks bool) []string { + return f.list(f.baseDir, symlinks) } // List lists all the files inside of a directory identified by a name -func (f *SimpleFileStore) ListDir(name string) []string { +func (f *SimpleFileStore) ListDir(name string, symlinks bool) []string { fullPath := filepath.Join(f.baseDir, name) - return f.list(fullPath) + return f.list(fullPath, symlinks) } -// list lists all the files in a directory given a full path -func (f *SimpleFileStore) list(path string) []string { +// list lists all the files in a directory given a full path. Ignores symlinks. +func (f *SimpleFileStore) list(path string, symlinks bool) []string { files := make([]string, 0, 0) filepath.Walk(path, func(fp string, fi os.FileInfo, err error) error { // If there are errors, ignore this particular file @@ -132,6 +126,12 @@ func (f *SimpleFileStore) list(path string) []string { if fi.IsDir() { return nil } + + // If this is a symlink, and symlinks is true, ignore it + if !symlinks && fi.Mode()&os.ModeSymlink == os.ModeSymlink { + return nil + } + // Only allow matches that end with our certificate extension (e.g. *.crt) matched, _ := filepath.Match("*"+f.fileExt, fi.Name()) diff --git a/trustmanager/filestore_test.go b/trustmanager/filestore_test.go index 8b08686343..392e8835b4 100644 --- a/trustmanager/filestore_test.go +++ b/trustmanager/filestore_test.go @@ -129,7 +129,7 @@ func TestRemoveDir(t *testing.T) { } } -func TestListAll(t *testing.T) { +func TestListFiles(t *testing.T) { testName := "docker.com/notary/certificate" testExt := "crt" perms := os.FileMode(0755) @@ -144,11 +144,18 @@ func TestListAll(t *testing.T) { // Create 10 randomfiles for i := 1; i <= 10; i++ { // Since we're generating this manually we need to add the extension '.' - expectedFilePath = filepath.Join(tempBaseDir, testName+string(i)+"."+testExt) + expectedFilename := testName + strconv.Itoa(i) + "." + testExt + expectedFilePath = filepath.Join(tempBaseDir, expectedFilename) _, err = generateRandomFile(expectedFilePath, perms) if err != nil { t.Fatalf("failed to generate random file: %v", err) } + + // Create symlinks for all the files + err = os.Symlink(expectedFilename, filepath.Join(tempBaseDir, expectedFilename+".link."+testExt)) + if err != nil { + t.Fatalf("failed to create symlink: %v", err) + } } // Create our SimpleFileStore @@ -158,11 +165,17 @@ func TestListAll(t *testing.T) { perms: perms, } - // Call the List function - files := store.ListAll() + // Call the List function. Expect 10 real files when not listing symlinks + files := store.ListFiles(false) if len(files) != 10 { t.Fatalf("expected 10 files in listing, got: %d", len(files)) } + + // Call the List function. Expect 20 total files when listing symlinks + files = store.ListFiles(true) + if len(files) != 20 { + t.Fatalf("expected 20 files in listing, got: %d", len(files)) + } } func TestListDir(t *testing.T) { @@ -196,15 +209,15 @@ func TestListDir(t *testing.T) { } // Call the ListDir function - files := store.ListDir("docker.com/") + files := store.ListDir("docker.com/", true) if len(files) != 10 { t.Fatalf("expected 10 files in listing, got: %d", len(files)) } - files = store.ListDir("docker.com/notary") + files = store.ListDir("docker.com/notary", true) if len(files) != 10 { t.Fatalf("expected 10 files in listing, got: %d", len(files)) } - files = store.ListDir("fakedocker.com/") + files = store.ListDir("fakedocker.com/", true) if len(files) != 0 { t.Fatalf("expected 0 files in listing, got: %d", len(files)) } diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go index beaa7fbef9..c734f035bb 100644 --- a/trustmanager/keyfilestore.go +++ b/trustmanager/keyfilestore.go @@ -14,7 +14,7 @@ type KeyFileStore struct { // NewKeyFileStore returns a new KeyFileStore creating a private directory to // hold the keys. func NewKeyFileStore(baseDir string) (*KeyFileStore, error) { - fileStore, err := NewFileStore(baseDir, keyExtension) + fileStore, err := NewPrivateSimpleFileStore(baseDir, keyExtension) if err != nil { return nil, err } @@ -74,3 +74,10 @@ func (s *KeyFileStore) GetDecryptedKey(name string, passphrase string) (*data.Pr return privKey, nil } + +// ListKeys returns a list of unique PublicKeys present on the KeyFileStore. +// There might be symlinks associating Certificate IDs to Public Keys, so this +// method only returns the IDs that aren't symlinks +func (s *KeyFileStore) ListKeys() []string { + return s.ListFiles(false) +} diff --git a/trustmanager/x509filestore.go b/trustmanager/x509filestore.go index 8c476d7d29..ea2b7b0f14 100644 --- a/trustmanager/x509filestore.go +++ b/trustmanager/x509filestore.go @@ -3,9 +3,10 @@ package trustmanager import ( "crypto/x509" "errors" - "github.com/Sirupsen/logrus" "os" "path" + + "github.com/Sirupsen/logrus" ) // X509FileStore implements X509Store that persists on disk @@ -30,7 +31,7 @@ func NewX509FilteredFileStore(directory string, validate func(*x509.Certificate) } func newX509FileStore(directory string, validate func(*x509.Certificate) bool) (*X509FileStore, error) { - fileStore, err := NewFileStore(directory, certExtension) + fileStore, err := NewSimpleFileStore(directory, certExtension) if err != nil { return nil, err } diff --git a/trustmanager/x509utils.go b/trustmanager/x509utils.go index 1994d2de50..fb408019ae 100644 --- a/trustmanager/x509utils.go +++ b/trustmanager/x509utils.go @@ -133,7 +133,7 @@ func fingerprintCert(cert *x509.Certificate) CertID { // loadCertsFromDir receives a store AddCertFromFile for each certificate found func loadCertsFromDir(s *X509FileStore) { - certFiles := s.fileStore.ListAll() + certFiles := s.fileStore.ListFiles(true) for _, c := range certFiles { s.AddCertFromFile(c) } From 082d4f3c7c219f45e217c679b8bac32fd46a3132 Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Thu, 9 Jul 2015 11:22:52 -0700 Subject: [PATCH 35/39] Change NotaryRepository to honor the baseURL passed in Remove "transport", because it's not used. In the actual notary client, pass in a hard-coded URL for now (same one previously hardcoded in getRemoteStore). In tests, create a trivial HTTP server using net/http/httptest, which returns a timestamp.key file. Signed-off-by: Aaron Lehmann Signed-off-by: Diogo Monica --- client/client.go | 11 ++++------- client/client_test.go | 30 +++++++++++++++++++++++++++--- client/helpers.go | 4 ++-- cmd/notary/tuf.go | 22 +++++++++------------- 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/client/client.go b/client/client.go index 444d7ed630..ea55b8c3c9 100644 --- a/client/client.go +++ b/client/client.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "io/ioutil" - "net/http" "os" "path/filepath" "time" @@ -56,7 +55,6 @@ type NotaryRepository struct { Gun string baseURL string tufRepoPath string - transport http.RoundTripper caStore trustmanager.X509Store certificateStore trustmanager.X509Store fileStore store.MetadataStore @@ -92,7 +90,7 @@ func NewTarget(targetName string, targetPath string) (*Target, error) { // NewClient is a helper method that returns a new notary Client, given a config // file. It makes the assumption that the base directory for the config file will // be the place where trust information is being cached locally. -func NewNotaryRepository(baseDir, gun, baseURL string, transport http.RoundTripper) (*NotaryRepository, error) { +func NewNotaryRepository(baseDir, gun, baseURL string) (*NotaryRepository, error) { trustDir := filepath.Join(baseDir, trustDir) rootKeysDir := filepath.Join(baseDir, rootKeysDir) @@ -108,7 +106,6 @@ func NewNotaryRepository(baseDir, gun, baseURL string, transport http.RoundTripp baseDir: baseDir, baseURL: baseURL, tufRepoPath: filepath.Join(baseDir, tufDir, gun), - transport: transport, signer: signer, privKeyStore: privKeyStore, } @@ -134,7 +131,7 @@ func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error { return err } - remote, err := getRemoteStore(r.Gun) + remote, err := getRemoteStore(r.baseURL, r.Gun) rawTSKey, err := remote.GetKey("timestamp") if err != nil { return err @@ -367,7 +364,7 @@ func (r *NotaryRepository) Publish(getPass passwordRetriever) error { return err } - remote, err := getRemoteStore(r.Gun) + remote, err := getRemoteStore(r.baseURL, r.Gun) if err != nil { return err } @@ -567,7 +564,7 @@ func (r *NotaryRepository) validateRoot(root *data.Signed) error { } func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { - remote, err := getRemoteStore(r.Gun) + remote, err := getRemoteStore(r.baseURL, r.Gun) if err != nil { return nil, err } diff --git a/client/client_test.go b/client/client_test.go index bb89273cd2..0b541e6643 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -2,7 +2,10 @@ package client import ( "encoding/json" + "fmt" "io/ioutil" + "net/http" + "net/http/httptest" "os" "path/filepath" "strings" @@ -13,6 +16,16 @@ import ( "github.com/stretchr/testify/assert" ) +func createTestServer(t *testing.T) *httptest.Server { + // TUF will request /v2/docker.com/notary/_trust/tuf/timestamp.key + // Return a canned timestamp.key + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, `{"keytype":"ed25519","keyval":{"public":"y4wnCW7Y8NYCmKZyWqxxUUj8p7SSoV5Cr1Zc+jqBxBw=","private":null}}`) + })) + + return ts +} + // TestInitRepo runs through the process of initializing a repository and makes // sure the repository looks correct on disk. func TestInitRepo(t *testing.T) { @@ -21,7 +34,10 @@ func TestInitRepo(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "notary-test-") assert.NoError(t, err, "failed to create a temporary directory: %s", err) - repo, err := NewNotaryRepository(tempBaseDir, gun, "", nil) + ts := createTestServer(t) + defer ts.Close() + + repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL) assert.NoError(t, err, "error creating repo: %s", err) rootKeyID, err := repo.GenRootKey("passphrase") @@ -148,7 +164,11 @@ func TestAddTarget(t *testing.T) { assert.NoError(t, err, "failed to create a temporary directory: %s", err) gun := "docker.com/notary" - repo, err := NewNotaryRepository(tempBaseDir, gun, "", nil) + + ts := createTestServer(t) + defer ts.Close() + + repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL) assert.NoError(t, err, "error creating repository: %s", err) rootKeyID, err := repo.GenRootKey("passphrase") @@ -246,7 +266,11 @@ func TestValidateRootKey(t *testing.T) { assert.NoError(t, err, "failed to create a temporary directory: %s", err) gun := "docker.com/notary" - repo, err := NewNotaryRepository(tempBaseDir, gun, "", nil) + + ts := createTestServer(t) + defer ts.Close() + + repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL) assert.NoError(t, err, "error creating repository: %s", err) rootKeyID, err := repo.GenRootKey("passphrase") diff --git a/client/helpers.go b/client/helpers.go index b8d62afa81..a92f1e711b 100644 --- a/client/helpers.go +++ b/client/helpers.go @@ -11,9 +11,9 @@ import ( ) // Use this to initialize remote HTTPStores from the config settings -func getRemoteStore(gun string) (store.RemoteStore, error) { +func getRemoteStore(baseURL, gun string) (store.RemoteStore, error) { return store.NewHTTPStore( - "https://notary:4443/v2/"+gun+"/_trust/tuf/", + baseURL+"/v2/"+gun+"/_trust/tuf/", "", "json", "", diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 4a7badaae2..af21933410 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -4,7 +4,6 @@ import ( "crypto/sha256" "fmt" "io/ioutil" - "net/http" "os" "github.com/Sirupsen/logrus" @@ -15,6 +14,9 @@ import ( "github.com/spf13/viper" ) +// FIXME: This should not be hardcoded +const hardcodedBaseURL = "https://notary:4443" + var remoteTrustServer string var cmdTufList = &cobra.Command{ @@ -76,8 +78,7 @@ func tufAdd(cmd *cobra.Command, args []string) { targetName := args[1] targetPath := args[2] - t := &http.Transport{} - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, "", t) + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL) if err != nil { fatalf(err.Error()) } @@ -100,9 +101,8 @@ func tufInit(cmd *cobra.Command, args []string) { } gun := args[0] - t := &http.Transport{} - nRepo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, "", t) + nRepo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL) if err != nil { fatalf(err.Error()) } @@ -131,8 +131,7 @@ func tufList(cmd *cobra.Command, args []string) { } gun := args[0] - t := &http.Transport{} - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, "", t) + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL) if err != nil { fatalf(err.Error()) } @@ -157,8 +156,7 @@ func tufLookup(cmd *cobra.Command, args []string) { gun := args[0] targetName := args[1] - t := &http.Transport{} - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, "", t) + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL) if err != nil { fatalf(err.Error()) } @@ -182,8 +180,7 @@ func tufPublish(cmd *cobra.Command, args []string) { fmt.Println("Pushing changes to ", gun, ".") - t := &http.Transport{} - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, "", t) + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL) if err != nil { fatalf(err.Error()) } @@ -228,8 +225,7 @@ func verify(cmd *cobra.Command, args []string) { //TODO (diogo): This code is copy/pasted from lookup. gun := args[0] targetName := args[1] - t := &http.Transport{} - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, "", t) + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL) if err != nil { fatalf(err.Error()) } From d7999b6cdc240c9d65d4ecf23c7a486ccad7de30 Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Thu, 9 Jul 2015 16:50:58 -0700 Subject: [PATCH 36/39] Fixing stat and error checking for x509filestore Signed-off-by: Diogo Monica --- trustmanager/x509filestore.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/trustmanager/x509filestore.go b/trustmanager/x509filestore.go index ea2b7b0f14..82c6c1b5cc 100644 --- a/trustmanager/x509filestore.go +++ b/trustmanager/x509filestore.go @@ -83,10 +83,12 @@ func (s X509FileStore) addNamedCert(cert *x509.Certificate) error { fileName := fileName(cert) // Save the file to disk if not already there. - if _, err := os.Stat(fileName); os.IsNotExist(err) { + if _, err := os.Stat(s.fileStore.GetPath(fileName)); os.IsNotExist(err) { if err := s.fileStore.Add(fileName, certBytes); err != nil { return err } + } else if err != nil { + return err } // We wrote the certificate succcessfully, add it to our in-memory storage From 682e7ea00b590d2b13924ff0bd7caab2ae838d11 Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Thu, 9 Jul 2015 17:51:04 -0700 Subject: [PATCH 37/39] Fixing lint Signed-off-by: Diogo Monica --- client/changelist/change.go | 19 ++++---- client/changelist/changelist.go | 28 ++++++++---- client/changelist/files_changelist.go | 31 +++++++++----- client/changelist/interface.go | 5 +++ client/cli_crypto_service.go | 8 +++- client/client.go | 62 ++++++++++++++++----------- server/handlers/default.go | 5 ++- server/server.go | 2 +- server/storage/database.go | 10 ++++- server/storage/errors.go | 8 ++++ server/storage/interface.go | 1 + server/storage/memory.go | 32 ++++++++------ signer/rufus_trust.go | 1 + trustmanager/filestore.go | 8 ++-- trustmanager/keyfilestore.go | 4 +- trustmanager/x509utils.go | 2 +- 16 files changed, 145 insertions(+), 81 deletions(-) diff --git a/client/changelist/change.go b/client/changelist/change.go index 85c72a44c9..77544dc661 100644 --- a/client/changelist/change.go +++ b/client/changelist/change.go @@ -1,7 +1,7 @@ package changelist -// tufChange represents a change to a TUF repo -type tufChange struct { +// TufChange represents a change to a TUF repo +type TufChange struct { // Abbreviated because Go doesn't permit a field and method of the same name Actn int `json:"action"` Role string `json:"role"` @@ -11,8 +11,8 @@ type tufChange struct { } // NewTufChange initializes a tufChange object -func NewTufChange(action int, role, changeType, changePath string, content []byte) *tufChange { - return &tufChange{ +func NewTufChange(action int, role, changeType, changePath string, content []byte) *TufChange { + return &TufChange{ Actn: action, Role: role, ChangeType: changeType, @@ -22,25 +22,26 @@ func NewTufChange(action int, role, changeType, changePath string, content []byt } // Action return c.Actn -func (c tufChange) Action() int { +func (c TufChange) Action() int { return c.Actn } // Scope returns c.Role -func (c tufChange) Scope() string { +func (c TufChange) Scope() string { return c.Role } // Type returns c.ChangeType -func (c tufChange) Type() string { +func (c TufChange) Type() string { return c.ChangeType } // Path return c.ChangePath -func (c tufChange) Path() string { +func (c TufChange) Path() string { return c.ChangePath } -func (c tufChange) Content() []byte { +// Content returns c.Data +func (c TufChange) Content() []byte { return c.Data } diff --git a/client/changelist/changelist.go b/client/changelist/changelist.go index d7a6913fc2..e6b16df855 100644 --- a/client/changelist/changelist.go +++ b/client/changelist/changelist.go @@ -7,30 +7,34 @@ import ( "os" ) -type appendChangelist struct { +// AppendChangelist represents a list of TUF changes +type AppendChangelist struct { path string file *os.File closed bool } -func NewAppendChangelist(path string) (*appendChangelist, error) { +// NewAppendChangelist is a convinience method that returns an append only TUF +// change list +func NewAppendChangelist(path string) (*AppendChangelist, error) { file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600) if err != nil { return nil, err } - return &appendChangelist{ + return &AppendChangelist{ path: path, file: file, }, nil } -func (cl *appendChangelist) List() []Change { +// List returns a list of Changes +func (cl *AppendChangelist) List() []Change { cl.file.Seek(0, 0) // seek to start of file - changes := make([]Change, 0) + var changes []Change scnr := bufio.NewScanner(cl.file) for scnr.Scan() { line := scnr.Bytes() - c := &tufChange{} + c := &TufChange{} err := json.Unmarshal(line, c) if err != nil { // TODO(david): How should we handle this? @@ -42,7 +46,8 @@ func (cl *appendChangelist) List() []Change { return changes } -func (cl *appendChangelist) Add(c Change) error { +// Add adds a change to the append only changelist +func (cl *AppendChangelist) Add(c Change) error { cl.file.Seek(0, 2) // seek to end of file entry, err := json.Marshal(c) if err != nil { @@ -64,14 +69,15 @@ func (cl *appendChangelist) Add(c Change) error { // Clear empties the changelist file. It does not currently // support archiving -func (cl *appendChangelist) Clear(archive string) error { +func (cl *AppendChangelist) Clear(archive string) error { cl.file.Seek(0, 0) // seek to start cl.file.Truncate(0) // truncate cl.file.Sync() return nil } -func (cl *appendChangelist) Close() error { +// Close marks the change list as closed +func (cl *AppendChangelist) Close() error { cl.file.Sync() cl.closed = true return cl.file.Close() @@ -82,21 +88,25 @@ type memChangelist struct { changes []Change } +// List returns a list of Changes func (cl memChangelist) List() []Change { return cl.changes } +// Add adds a change to the in-memory change list func (cl *memChangelist) Add(c Change) error { cl.changes = append(cl.changes, c) return nil } +// Clear empties the changelist file. func (cl *memChangelist) Clear(archive string) error { // appending to a nil list initializes it. cl.changes = nil return nil } +// Close is a no-op in this in-memory change-list func (cl *memChangelist) Close() error { return nil } diff --git a/client/changelist/files_changelist.go b/client/changelist/files_changelist.go index abc15e048e..42f201b24e 100644 --- a/client/changelist/files_changelist.go +++ b/client/changelist/files_changelist.go @@ -13,21 +13,24 @@ import ( "github.com/Sirupsen/logrus" ) -type fileChangelist struct { +// FileChangelist stores all the changes as files +type FileChangelist struct { dir string } -func NewFileChangelist(dir string) (*fileChangelist, error) { +// NewFileChangelist is a convenience method for returning FileChangeLists +func NewFileChangelist(dir string) (*FileChangelist, error) { logrus.Debug("Making dir path: ", dir) err := os.MkdirAll(dir, 0700) if err != nil { return nil, err } - return &fileChangelist{dir: dir}, nil + return &FileChangelist{dir: dir}, nil } -func (cl fileChangelist) List() []Change { - changes := make([]Change, 0) +// List returns a list of sorted changes +func (cl FileChangelist) List() []Change { + var changes []Change dir, err := os.Open(cl.dir) if err != nil { return changes @@ -48,7 +51,7 @@ func (cl fileChangelist) List() []Change { fmt.Println(err.Error()) continue } - c := &tufChange{} + c := &TufChange{} err = json.Unmarshal(raw, c) if err != nil { // TODO(david): How should we handle this? @@ -60,16 +63,18 @@ func (cl fileChangelist) List() []Change { return changes } -func (cl fileChangelist) Add(c Change) error { - cJson, err := json.Marshal(c) +// Add adds a change to the file change list +func (cl FileChangelist) Add(c Change) error { + cJSON, err := json.Marshal(c) if err != nil { return err } filename := fmt.Sprintf("%020d_%s.change", time.Now().UnixNano(), uuid.New()) - return ioutil.WriteFile(path.Join(cl.dir, filename), cJson, 0644) + return ioutil.WriteFile(path.Join(cl.dir, filename), cJSON, 0644) } -func (cl fileChangelist) Clear(archive string) error { +// Clear clears the change list +func (cl FileChangelist) Clear(archive string) error { dir, err := os.Open(cl.dir) if err != nil { return err @@ -85,21 +90,25 @@ func (cl fileChangelist) Clear(archive string) error { return nil } -func (cl fileChangelist) Close() error { +// Close is a no-op +func (cl FileChangelist) Close() error { // Nothing to do here return nil } type fileChanges []os.FileInfo +// Len returns the length of a file change list func (cs fileChanges) Len() int { return len(cs) } +// Less compares the names of two different file changes func (cs fileChanges) Less(i, j int) bool { return cs[i].Name() < cs[j].Name() } +// Swap swaps the position of two file changes func (cs fileChanges) Swap(i, j int) { tmp := cs[i] cs[i] = cs[j] diff --git a/client/changelist/interface.go b/client/changelist/interface.go index fbe22131d9..fd24b65c54 100644 --- a/client/changelist/interface.go +++ b/client/changelist/interface.go @@ -1,5 +1,6 @@ package changelist +// Changelist is the interface for all TUF change lists type Changelist interface { // List returns the ordered list of changes // currently stored @@ -20,11 +21,15 @@ type Changelist interface { } const ( + // ActionCreate represents a Create action ActionCreate = iota + // ActionUpdate represents an Update action ActionUpdate + // ActionDelete represents a Delete action ActionDelete ) +// Change is the interface for a TUF Change type Change interface { // "create","update", or "delete" Action() int diff --git a/client/cli_crypto_service.go b/client/cli_crypto_service.go index 62273998cb..3a6c9839e6 100644 --- a/client/cli_crypto_service.go +++ b/client/cli_crypto_service.go @@ -14,11 +14,15 @@ import ( "github.com/endophage/gotuf/data" ) +// CryptoService implements Sign and Create, holding a specific GUN and keystore to +// operate on type CryptoService struct { gun string keyStore *trustmanager.KeyFileStore } +// RootCryptoService implements Sign and Create and operates on a rootKeyStore, +// taking in a passphrase and calling decrypt when signing. type RootCryptoService struct { // TODO(diogo): support multiple passphrases per key passphrase string @@ -87,7 +91,7 @@ func (rcs *RootCryptoService) Create(role string) (*data.PublicKey, error) { // Sign returns the signatures for data with the given root Key ID, falling back // if not rootKeyID is found // TODO(diogo): This code has 1 line change from the Sign from Crypto service. DRY it up. -func (ccs *RootCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { +func (rcs *RootCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { // Create hasher and hash data hash := crypto.SHA256 hashed := sha256.Sum256(payload) @@ -95,7 +99,7 @@ func (ccs *RootCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Sign signatures := make([]data.Signature, 0, len(keyIDs)) for _, fingerprint := range keyIDs { // Read PrivateKey from file - privKey, err := ccs.rootKeyStore.GetDecryptedKey(fingerprint, ccs.passphrase) + privKey, err := rcs.rootKeyStore.GetDecryptedKey(fingerprint, rcs.passphrase) if err != nil { // TODO(diogo): This error should be returned to the user in someway continue diff --git a/client/client.go b/client/client.go index ea55b8c3c9..6916daf355 100644 --- a/client/client.go +++ b/client/client.go @@ -24,32 +24,41 @@ import ( "github.com/endophage/gotuf/store" ) +// ErrRepoNotInitialized is returned when trying to can publish on an uninitialized +// notary repository type ErrRepoNotInitialized struct{} type passwordRetriever func() (string, error) +// ErrRepoNotInitialized is returned when trying to can publish on an uninitialized +// notary repository func (err *ErrRepoNotInitialized) Error() string { return "Repository has not been initialized" } // Default paths should end with a '/' so directory creation works correctly const ( - trustDir string = "/trusted_certificates/" - privDir string = "/private/" - tufDir string = "/tuf/" - rootKeysDir string = privDir + "/root_keys/" + trustDir string = "/trusted_certificates/" + privDir string = "/private/" + tufDir string = "/tuf/" + rootKeysDir string = privDir + "/root_keys/" + rsaKeySize int = 2048 // Used for snapshots and targets keys + rsaRootKeySize int = 4096 // Used for new root keys ) -const rsaKeySize int = 2048 // ErrRepositoryNotExist gets returned when trying to make an action over a repository -/// that doesn't exist +/// that doesn't exist. var ErrRepositoryNotExist = errors.New("repository does not exist") +// UnlockedSigner encapsulates a private key and a signer that uses that private key, +// providing convinience methods for generation of certificates. type UnlockedSigner struct { privKey *data.PrivateKey signer *signed.Signer } +// NotaryRepository stores all the information needed to operate on a notary +// repository. type NotaryRepository struct { baseDir string Gun string @@ -65,7 +74,8 @@ type NotaryRepository struct { rootSigner *UnlockedSigner } -// Target represents a simplified version of the data TUF operates on. +// Target represents a simplified version of the data TUF operates on, so external +// applications don't have to depend on tuf data types. type Target struct { Name string Hashes data.Hashes @@ -87,9 +97,9 @@ func NewTarget(targetName string, targetPath string) (*Target, error) { return &Target{Name: targetName, Hashes: meta.Hashes, Length: meta.Length}, nil } -// NewClient is a helper method that returns a new notary Client, given a config -// file. It makes the assumption that the base directory for the config file will -// be the place where trust information is being cached locally. +// NewNotaryRepository is a helper method that returns a new notary repository. +// It takes the base directory under where all the trust files will be stored +// (usually ~/.docker/trust/). func NewNotaryRepository(baseDir, gun, baseURL string) (*NotaryRepository, error) { trustDir := filepath.Join(baseDir, trustDir) rootKeysDir := filepath.Join(baseDir, rootKeysDir) @@ -251,7 +261,7 @@ func (r *NotaryRepository) ListTargets() ([]*Target, error) { return nil, err } - targetList := make([]*Target, 0) + var targetList []*Target for name, meta := range r.tufRepo.Targets["targets"].Signed.Targets { target := &Target{Name: name, Hashes: meta.Hashes, Length: meta.Length} targetList = append(targetList, target) @@ -467,7 +477,7 @@ func (r *NotaryRepository) saveMetadata(rootSigner *signed.Signer) error { func (r *NotaryRepository) snapshot() error { fmt.Println("Saving changes to Trusted Collection.") - for t, _ := range r.tufRepo.Targets { + for t := range r.tufRepo.Targets { signedTargets, err := r.tufRepo.SignTargets(t, data.DefaultExpires("targets"), nil) if err != nil { return err @@ -598,41 +608,41 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { ), nil } -// ListPrivateKeys lists all available root keys. Does not include private key -// material -func (c *NotaryRepository) ListRootKeys() []string { - return c.rootKeyStore.ListKeys() +// ListRootKeys returns the IDs for all of the root keys. It ignores symlinks +// if any exist. +func (r *NotaryRepository) ListRootKeys() []string { + return r.rootKeyStore.ListKeys() } // GenRootKey generates a new root key protected by a given passphrase -func (c *NotaryRepository) GenRootKey(passphrase string) (string, error) { - privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize) +func (r *NotaryRepository) GenRootKey(passphrase string) (string, error) { + privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaRootKeySize) if err != nil { return "", fmt.Errorf("failed to convert private key: ", err) } - c.rootKeyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase) + r.rootKeyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase) return privKey.ID(), nil } // GetRootSigner retreives a root key that includes the ID and a signer -func (c *NotaryRepository) GetRootSigner(rootKeyID, passphrase string) (*UnlockedSigner, error) { - privKey, err := c.rootKeyStore.GetDecryptedKey(rootKeyID, passphrase) +func (r *NotaryRepository) GetRootSigner(rootKeyID, passphrase string) (*UnlockedSigner, error) { + privKey, err := r.rootKeyStore.GetDecryptedKey(rootKeyID, passphrase) if err != nil { return nil, fmt.Errorf("could not get decrypted root key: %v", err) } // This signer will be used for all of the normal TUF operations, except for // when a root key is needed. - signer := signed.NewSigner(NewRootCryptoService(c.rootKeyStore, passphrase)) + signer := signed.NewSigner(NewRootCryptoService(r.rootKeyStore, passphrase)) return &UnlockedSigner{ privKey: privKey, signer: signer}, nil } -func (c *NotaryRepository) loadKeys(trustDir, rootKeysDir string) error { +func (r *NotaryRepository) loadKeys(trustDir, rootKeysDir string) error { // Load all CAs that aren't expired and don't use SHA1 caStore, err := trustmanager.NewX509FilteredFileStore(trustDir, func(cert *x509.Certificate) bool { return cert.IsCA && cert.BasicConstraintsValid && cert.SubjectKeyId != nil && @@ -663,9 +673,9 @@ func (c *NotaryRepository) loadKeys(trustDir, rootKeysDir string) error { return err } - c.caStore = caStore - c.certificateStore = certificateStore - c.rootKeyStore = rootKeyStore + r.caStore = caStore + r.certificateStore = certificateStore + r.rootKeyStore = rootKeyStore return nil } diff --git a/server/handlers/default.go b/server/handlers/default.go index fce87200e2..3e0cf1572f 100644 --- a/server/handlers/default.go +++ b/server/handlers/default.go @@ -35,7 +35,7 @@ func MainHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *e return nil } -// AddHandler adds the provided json data for the role and GUN specified in the URL +// UpdateHandler adds the provided json data for the role and GUN specified in the URL func UpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError { defer r.Body.Close() s := ctx.Value("metaStore") @@ -154,6 +154,7 @@ func DeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) return nil } +// GetTimestampHandler returns a timestamp.json given a GUN func GetTimestampHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError { s := ctx.Value("metaStore") store, ok := s.(storage.MetaStore) @@ -198,6 +199,8 @@ func GetTimestampHandler(ctx context.Context, w http.ResponseWriter, r *http.Req return nil } +// GetTimestampKeyHandler returns a timestamp public key, creating a new key-pair +// it if it doesn't yet exist func GetTimestampKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError { s := ctx.Value("metaStore") store, ok := s.(storage.MetaStore) diff --git a/server/server.go b/server/server.go index dd42a28b90..d65262e8d2 100644 --- a/server/server.go +++ b/server/server.go @@ -58,7 +58,7 @@ func Run(ctx context.Context, addr, tlsCertFile, tlsKeyFile string, trust signed lsnr = tls.NewListener(lsnr, tlsConfig) } - var ac auth.AccessController = nil + var ac auth.AccessController hand := utils.RootHandlerFactory(ac, ctx, trust) r := mux.NewRouter() diff --git a/server/storage/database.go b/server/storage/database.go index d23ea278e1..c3cdd554d6 100644 --- a/server/storage/database.go +++ b/server/storage/database.go @@ -27,13 +27,14 @@ type MySQLStorage struct { sql.DB } +// NewMySQLStorage is a convenience method to create a MySQLStorage func NewMySQLStorage(db *sql.DB) *MySQLStorage { return &MySQLStorage{ DB: *db, } } -// Update multiple TUF records in a single transaction. +// UpdateCurrent updates multiple TUF records in a single transaction. // Always insert a new row. The unique constraint will ensure there is only ever func (db *MySQLStorage) UpdateCurrent(gun, role string, version int, data []byte) error { checkStmt := "SELECT count(*) FROM `tuf_files` WHERE `gun`=? AND `role`=? AND `version`>=?;" @@ -62,7 +63,7 @@ func (db *MySQLStorage) UpdateCurrent(gun, role string, version int, data []byte return nil } -// Get a specific TUF record +// GetCurrent gets a specific TUF record func (db *MySQLStorage) GetCurrent(gun, tufRole string) (data []byte, err error) { stmt := "SELECT `data` FROM `tuf_files` WHERE `gun`=? AND `role`=? ORDER BY `version` DESC LIMIT 1;" rows, err := db.Query(stmt, gun, tufRole) // this should be a QueryRow() @@ -82,11 +83,14 @@ func (db *MySQLStorage) GetCurrent(gun, tufRole string) (data []byte, err error) return data, nil } +// Delete deletes all the records for a specific GUN func (db *MySQLStorage) Delete(gun string) error { stmt := "DELETE FROM `tuf_files` WHERE `gun`=?;" _, err := db.Exec(stmt, gun) return err } + +// GetTimestampKey returns the timestamps Public Key data func (db *MySQLStorage) GetTimestampKey(gun string) (cipher string, public []byte, err error) { stmt := "SELECT `cipher`, `public` FROM `timestamp_keys` WHERE `gun`=?;" row := db.QueryRow(stmt, gun) @@ -100,6 +104,8 @@ func (db *MySQLStorage) GetTimestampKey(gun string) (cipher string, public []byt return cipher, public, err } + +// SetTimestampKey attempts to write a TimeStamp key and returns an error if it already exists func (db *MySQLStorage) SetTimestampKey(gun, cipher string, public []byte) error { stmt := "INSERT INTO `timestamp_keys` (`gun`, `cipher`, `public`) VALUES (?,?,?);" _, err := db.Exec(stmt, gun, cipher, public) diff --git a/server/storage/errors.go b/server/storage/errors.go index 14be8ab02b..f2d58c3a3e 100644 --- a/server/storage/errors.go +++ b/server/storage/errors.go @@ -4,30 +4,38 @@ import ( "fmt" ) +// ErrOldVersion is returned when a newer version of TUF metadada is already available type ErrOldVersion struct{} +// ErrOldVersion is returned when a newer version of TUF metadada is already available func (err ErrOldVersion) Error() string { return fmt.Sprintf("Error updating metadata. A newer version is already available") } +// ErrNotFound is returned when TUF metadata isn't found for a specific record type ErrNotFound struct{} +// Error implements error func (err ErrNotFound) Error() string { return fmt.Sprintf("No record found") } +// ErrTimestampKeyExists is returned when a timestamp key already exists type ErrTimestampKeyExists struct { gun string } +// ErrTimestampKeyExists is returned when a timestamp key already exists func (err ErrTimestampKeyExists) Error() string { return fmt.Sprintf("Error, timestamp key already exists for %s", err.gun) } +// ErrNoKey is returned when no timestamp key is found type ErrNoKey struct { gun string } +// ErrNoKey is returned when no timestamp key is found func (err ErrNoKey) Error() string { return fmt.Sprintf("Error, no timestamp key found for %s", err.gun) } diff --git a/server/storage/interface.go b/server/storage/interface.go index 2fefbdafe0..25ee4f644e 100644 --- a/server/storage/interface.go +++ b/server/storage/interface.go @@ -1,5 +1,6 @@ package storage +// MetaStore holds the methods that are used for a Metadata Store type MetaStore interface { UpdateCurrent(gun, role string, version int, data []byte) error GetCurrent(gun, tufRole string) (data []byte, err error) diff --git a/server/storage/memory.go b/server/storage/memory.go index 6d3233437c..a4d4bfe441 100644 --- a/server/storage/memory.go +++ b/server/storage/memory.go @@ -16,23 +16,24 @@ type ver struct { data []byte } -// memStorage is really just designed for dev and testing. It is very +// MemStorage is really just designed for dev and testing. It is very // inefficient in many scenarios -type memStorage struct { +type MemStorage struct { lock sync.Mutex tufMeta map[string][]*ver tsKeys map[string]*key } // NewMemStorage instantiates a memStorage instance -func NewMemStorage() *memStorage { - return &memStorage{ +func NewMemStorage() *MemStorage { + return &MemStorage{ tufMeta: make(map[string][]*ver), tsKeys: make(map[string]*key), } } -func (st *memStorage) UpdateCurrent(gun, role string, version int, data []byte) error { +// UpdateCurrent updates the meta data for a specific role +func (st *MemStorage) UpdateCurrent(gun, role string, version int, data []byte) error { id := entryKey(gun, role) st.lock.Lock() defer st.lock.Unlock() @@ -47,7 +48,8 @@ func (st *memStorage) UpdateCurrent(gun, role string, version int, data []byte) return nil } -func (st *memStorage) GetCurrent(gun, role string) (data []byte, err error) { +// GetCurrent returns the metadada for a given role, under a GUN +func (st *MemStorage) GetCurrent(gun, role string) (data []byte, err error) { id := entryKey(gun, role) st.lock.Lock() defer st.lock.Unlock() @@ -58,10 +60,11 @@ func (st *memStorage) GetCurrent(gun, role string) (data []byte, err error) { return space[len(st.tufMeta[id])-1].data, nil } -func (st *memStorage) Delete(gun string) error { +// Delete delets all the metadata for a given GUN +func (st *MemStorage) Delete(gun string) error { st.lock.Lock() defer st.lock.Unlock() - for k, _ := range st.tufMeta { + for k := range st.tufMeta { if strings.HasPrefix(k, gun) { delete(st.tufMeta, k) } @@ -69,17 +72,20 @@ func (st *memStorage) Delete(gun string) error { return nil } -func (st *memStorage) GetTimestampKey(gun string) (cipher string, public []byte, err error) { +// GetTimestampKey returns the public key material of the timestamp key of a given gun +func (st *MemStorage) GetTimestampKey(gun string) (cipher string, public []byte, err error) { // no need for lock. It's ok to return nil if an update // wasn't observed - if k, ok := st.tsKeys[gun]; !ok { + k, ok := st.tsKeys[gun] + if !ok { return "", nil, &ErrNoKey{gun: gun} - } else { - return k.cipher, k.public, nil } + + return k.cipher, k.public, nil } -func (st *memStorage) SetTimestampKey(gun, cipher string, public []byte) error { +// SetTimestampKey sets a Timestamp key under a gun +func (st *MemStorage) SetTimestampKey(gun, cipher string, public []byte) error { k := &key{cipher: cipher, public: public} st.lock.Lock() defer st.lock.Unlock() diff --git a/signer/rufus_trust.go b/signer/rufus_trust.go index ce51457f5d..adf90b9bba 100644 --- a/signer/rufus_trust.go +++ b/signer/rufus_trust.go @@ -17,6 +17,7 @@ type RufusSigner struct { sClient pb.SignerClient } +// NewRufusSigner is a convinience method that returns RufusSigner func NewRufusSigner(hostname string, port string, tlscafile string) *RufusSigner { var opts []grpc.DialOption netAddr := net.JoinHostPort(hostname, port) diff --git a/trustmanager/filestore.go b/trustmanager/filestore.go index 44ad6b2774..3701ebece6 100644 --- a/trustmanager/filestore.go +++ b/trustmanager/filestore.go @@ -29,7 +29,7 @@ type SimpleFileStore struct { perms os.FileMode } -// NewFileStore creates a directory with 755 permissions +// NewSimpleFileStore creates a directory with 755 permissions func NewSimpleFileStore(baseDir string, fileExt string) (FileStore, error) { if err := CreateDirectory(baseDir); err != nil { return nil, err @@ -42,7 +42,7 @@ func NewSimpleFileStore(baseDir string, fileExt string) (FileStore, error) { }, nil } -// NewPrivateFileStore creates a directory with 700 permissions +// NewPrivateSimpleFileStore creates a directory with 700 permissions func NewPrivateSimpleFileStore(baseDir string, fileExt string) (FileStore, error) { if err := CreatePrivateDirectory(baseDir); err != nil { return nil, err @@ -103,12 +103,12 @@ func (f *SimpleFileStore) GetPath(name string) string { return f.genFilePath(name) } -// List lists all the files inside of a store +// ListFiles lists all the files inside of a store func (f *SimpleFileStore) ListFiles(symlinks bool) []string { return f.list(f.baseDir, symlinks) } -// List lists all the files inside of a directory identified by a name +// ListDir lists all the files inside of a directory identified by a name func (f *SimpleFileStore) ListDir(name string, symlinks bool) []string { fullPath := filepath.Join(f.baseDir, name) return f.list(fullPath, symlinks) diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go index c734f035bb..6418139037 100644 --- a/trustmanager/keyfilestore.go +++ b/trustmanager/keyfilestore.go @@ -48,7 +48,7 @@ func (s *KeyFileStore) GetKey(name string) (*data.PrivateKey, error) { return privKey, nil } -// AddEncrypted stores the contents of a PEM-encoded private key as an encrypted PEM block +// AddEncryptedKey stores the contents of a PEM-encoded private key as an encrypted PEM block func (s *KeyFileStore) AddEncryptedKey(name string, privKey *data.PrivateKey, passphrase string) error { encryptedPrivKey, err := EncryptPrivateKey(privKey, passphrase) if err != nil { @@ -58,7 +58,7 @@ func (s *KeyFileStore) AddEncryptedKey(name string, privKey *data.PrivateKey, pa return s.Add(name, encryptedPrivKey) } -// GetDecrypted decrypts and returns the PEM Encoded private key given a flename +// GetDecryptedKey decrypts and returns the PEM Encoded private key given a flename // and a passphrase func (s *KeyFileStore) GetDecryptedKey(name string, passphrase string) (*data.PrivateKey, error) { keyBytes, err := s.Get(name) diff --git a/trustmanager/x509utils.go b/trustmanager/x509utils.go index fb408019ae..3bfd817d63 100644 --- a/trustmanager/x509utils.go +++ b/trustmanager/x509utils.go @@ -92,7 +92,7 @@ func EncryptPrivateKey(key *data.PrivateKey, passphrase string) ([]byte, error) return pem.EncodeToMemory(encryptedPEMBlock), nil } -// loadCertFromPEM returns the first certificate found in a bunch of bytes or error +// LoadCertFromPEM returns the first certificate found in a bunch of bytes or error // if nothing is found. Taken from https://golang.org/src/crypto/x509/cert_pool.go#L85. func LoadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) { for len(pemBytes) > 0 { From 06a28c89eec5ec4c197dee389338edd252000307 Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Thu, 9 Jul 2015 18:56:06 -0700 Subject: [PATCH 38/39] Added root key creation if non-existing to notary Signed-off-by: Diogo Monica --- client/client.go | 2 +- cmd/notary/tuf.go | 112 ++++++-------------- q | 197 +++++++++++++++++++++++++++++++++++ trustmanager/keyfilestore.go | 14 ++- 4 files changed, 244 insertions(+), 81 deletions(-) create mode 100644 q diff --git a/client/client.go b/client/client.go index 6916daf355..80299960fc 100644 --- a/client/client.go +++ b/client/client.go @@ -618,7 +618,7 @@ func (r *NotaryRepository) ListRootKeys() []string { func (r *NotaryRepository) GenRootKey(passphrase string) (string, error) { privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaRootKeySize) if err != nil { - return "", fmt.Errorf("failed to convert private key: ", err) + return "", fmt.Errorf("failed to convert private key: %v", err) } r.rootKeyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase) diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index af21933410..46b12affd0 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -2,14 +2,13 @@ package main import ( "crypto/sha256" + "errors" "fmt" "io/ioutil" "os" "github.com/Sirupsen/logrus" notaryclient "github.com/docker/notary/client" - "github.com/endophage/gotuf/data" - "github.com/endophage/gotuf/keys" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -107,13 +106,30 @@ func tufInit(cmd *cobra.Command, args []string) { fatalf(err.Error()) } - // TODO(diogo): We don't want to generate a new root every time. Ask the user - // which key she wants to use if there > 0 root keys available. - rootKeyID, err := nRepo.GenRootKey("passphrase") - if err != nil { - fatalf(err.Error()) + keysList := nRepo.ListRootKeys() + var passphrase string + var rootKeyID string + if len(keysList) < 1 { + fmt.Println("No root keys found. Generating a new root key...") + passphrase, err = passphraseRetriever() + if err != nil { + fatalf(err.Error()) + } + rootKeyID, err = nRepo.GenRootKey(passphrase) + if err != nil { + fatalf(err.Error()) + } + } else { + rootKeyID = keysList[0] + fmt.Println("Root key found.") + fmt.Printf("Enter passphrase for: %s (%d)\n", rootKeyID, len(rootKeyID)) + passphrase, err = passphraseRetriever() + if err != nil { + fatalf(err.Error()) + } } - rootSigner, err := nRepo.GetRootSigner(rootKeyID, "passphrase") + + rootSigner, err := nRepo.GetRootSigner(rootKeyID, passphrase) if err != nil { fatalf(err.Error()) } @@ -185,7 +201,7 @@ func tufPublish(cmd *cobra.Command, args []string) { fatalf(err.Error()) } - err = repo.Publish(passwordRetriever) + err = repo.Publish(passphraseRetriever) if err != nil { fatalf(err.Error()) } @@ -249,76 +265,16 @@ func verify(cmd *cobra.Command, args []string) { return } -//func generateKeys(kdb *keys.KeyDB, signer *signed.Signer, remote store.RemoteStore) (string, string, string, string, error) { -// rawTSKey, err := remote.GetKey("timestamp") -// if err != nil { -// return "", "", "", "", err -// } -// fmt.Println("RawKey: ", string(rawTSKey)) -// parsedKey := &data.TUFKey{} -// err = json.Unmarshal(rawTSKey, parsedKey) -// if err != nil { -// return "", "", "", "", err -// } -// timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public()) -// -// rootKey, err := signer.Create("root") -// if err != nil { -// return "", "", "", "", err -// } -// targetsKey, err := signer.Create("targets") -// if err != nil { -// return "", "", "", "", err -// } -// snapshotKey, err := signer.Create("snapshot") -// if err != nil { -// return "", "", "", "", err -// } -// -// kdb.AddKey(rootKey) -// kdb.AddKey(targetsKey) -// kdb.AddKey(snapshotKey) -// kdb.AddKey(timestampKey) -// return rootKey.ID(), targetsKey.ID(), snapshotKey.ID(), timestampKey.ID(), nil -//} - -func generateRoles(kdb *keys.KeyDB, rootKeyID, targetsKeyID, snapshotKeyID, timestampKeyID string) error { - rootRole, err := data.NewRole("root", 1, []string{rootKeyID}, nil, nil) +func passphraseRetriever() (string, error) { + fmt.Println("Please provide a passphrase for this root key: ") + var passphrase string + _, err := fmt.Scanln(&passphrase) if err != nil { - return err + return "", err } - targetsRole, err := data.NewRole("targets", 1, []string{targetsKeyID}, nil, nil) - if err != nil { - return err + if len(passphrase) < 8 { + fmt.Println("Please use a password manager to generate and store a good random passphrase.") + return "", errors.New("Passphrase too short") } - snapshotRole, err := data.NewRole("snapshot", 1, []string{snapshotKeyID}, nil, nil) - if err != nil { - return err - } - timestampRole, err := data.NewRole("timestamp", 1, []string{timestampKeyID}, nil, nil) - if err != nil { - return err - } - - err = kdb.AddRole(rootRole) - if err != nil { - return err - } - err = kdb.AddRole(targetsRole) - if err != nil { - return err - } - err = kdb.AddRole(snapshotRole) - if err != nil { - return err - } - err = kdb.AddRole(timestampRole) - if err != nil { - return err - } - return nil -} - -func passwordRetriever() (string, error) { - return "passphrase", nil + return passphrase, nil } diff --git a/q b/q new file mode 100644 index 0000000000..c5a8699982 --- /dev/null +++ b/q @@ -0,0 +1,197 @@ +diff --git a/client/client.go b/client/client.go +index 6916daf..8029996 100644 +--- a/client/client.go ++++ b/client/client.go +@@ -618,7 +618,7 @@ func (r *NotaryRepository) ListRootKeys() []string { + func (r *NotaryRepository) GenRootKey(passphrase string) (string, error) { + privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaRootKeySize) + if err != nil { +- return "", fmt.Errorf("failed to convert private key: ", err) ++ return "", fmt.Errorf("failed to convert private key: %v", err) + } +  + r.rootKeyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase) +diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go +index af21933..7825170 100644 +--- a/cmd/notary/tuf.go ++++ b/cmd/notary/tuf.go +@@ -2,14 +2,13 @@ package main +  + import ( + "crypto/sha256" ++ "errors" + "fmt" + "io/ioutil" + "os" +  + "github.com/Sirupsen/logrus" + notaryclient "github.com/docker/notary/client" +- "github.com/endophage/gotuf/data" +- "github.com/endophage/gotuf/keys" + "github.com/spf13/cobra" + "github.com/spf13/viper" + ) +@@ -107,13 +106,30 @@ func tufInit(cmd *cobra.Command, args []string) { + fatalf(err.Error()) + } +  +- // TODO(diogo): We don't want to generate a new root every time. Ask the user +- // which key she wants to use if there > 0 root keys available. +- rootKeyID, err := nRepo.GenRootKey("passphrase") +- if err != nil { +- fatalf(err.Error()) ++ keysList := nRepo.ListRootKeys() ++ var passphrase string ++ var rootKeyID string ++ if len(keysList) < 1 { ++ fmt.Println("No root keys found. Generating a new root key...") ++ passphrase, err = passphraseRetriever() ++ if err != nil { ++ fatalf(err.Error()) ++ } ++ rootKeyID, err = nRepo.GenRootKey(passphrase) ++ if err != nil { ++ fatalf(err.Error()) ++ } ++ } else { ++ rootKeyID = keysList[0] ++ fmt.Println("Root key found.") ++ fmt.Printf("Enter passphrase for: %s (%d)\n", rootKeyID, len(rootKeyID)) ++ passphrase, err = passphraseRetriever() ++ if err != nil { ++ fatalf(err.Error()) ++ } + } +- rootSigner, err := nRepo.GetRootSigner(rootKeyID, "passphrase") ++ ++ rootSigner, err := nRepo.GetRootSigner(rootKeyID, passphrase) + if err != nil { + fatalf(err.Error()) + } +@@ -185,7 +201,7 @@ func tufPublish(cmd *cobra.Command, args []string) { + fatalf(err.Error()) + } +  +- err = repo.Publish(passwordRetriever) ++ err = repo.Publish(passphraseRetriever) + if err != nil { + fatalf(err.Error()) + } +@@ -249,76 +265,20 @@ func verify(cmd *cobra.Command, args []string) { + return + } +  +-//func generateKeys(kdb *keys.KeyDB, signer *signed.Signer, remote store.RemoteStore) (string, string, string, string, error) { +-// rawTSKey, err := remote.GetKey("timestamp") +-// if err != nil { +-// return "", "", "", "", err +-// } +-// fmt.Println("RawKey: ", string(rawTSKey)) +-// parsedKey := &data.TUFKey{} +-// err = json.Unmarshal(rawTSKey, parsedKey) +-// if err != nil { +-// return "", "", "", "", err +-// } +-// timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public()) +-// +-// rootKey, err := signer.Create("root") +-// if err != nil { +-// return "", "", "", "", err +-// } +-// targetsKey, err := signer.Create("targets") +-// if err != nil { +-// return "", "", "", "", err +-// } +-// snapshotKey, err := signer.Create("snapshot") +-// if err != nil { +-// return "", "", "", "", err +-// } +-// +-// kdb.AddKey(rootKey) +-// kdb.AddKey(targetsKey) +-// kdb.AddKey(snapshotKey) +-// kdb.AddKey(timestampKey) +-// return rootKey.ID(), targetsKey.ID(), snapshotKey.ID(), timestampKey.ID(), nil +-//} +- +-func generateRoles(kdb *keys.KeyDB, rootKeyID, targetsKeyID, snapshotKeyID, timestampKeyID string) error { +- rootRole, err := data.NewRole("root", 1, []string{rootKeyID}, nil, nil) +- if err != nil { +- return err +- } +- targetsRole, err := data.NewRole("targets", 1, []string{targetsKeyID}, nil, nil) +- if err != nil { +- return err +- } +- snapshotRole, err := data.NewRole("snapshot", 1, []string{snapshotKeyID}, nil, nil) +- if err != nil { +- return err +- } +- timestampRole, err := data.NewRole("timestamp", 1, []string{timestampKeyID}, nil, nil) +- if err != nil { +- return err +- } ++// func passwordRetriever() (string, error) { ++// return "passphrase", nil ++// } +  +- err = kdb.AddRole(rootRole) ++func passphraseRetriever() (string, error) { ++ fmt.Println("Please provide a passphrase for this root key: ") ++ var passphrase string ++ _, err := fmt.Scanln(&passphrase) + if err != nil { +- return err ++ return "", err + } +- err = kdb.AddRole(targetsRole) +- if err != nil { +- return err +- } +- err = kdb.AddRole(snapshotRole) +- if err != nil { +- return err ++ if len(passphrase) < 8 { ++ fmt.Println("Please use a password manager to generate and store a good random passphrase.") ++ return "", errors.New("Passphrase too short") + } +- err = kdb.AddRole(timestampRole) +- if err != nil { +- return err +- } +- return nil +-} +- +-func passwordRetriever() (string, error) { +- return "passphrase", nil ++ return passphrase, nil + } +diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go +index 6418139..f076c79 100644 +--- a/trustmanager/keyfilestore.go ++++ b/trustmanager/keyfilestore.go +@@ -1,6 +1,11 @@ + package trustmanager +  +-import "github.com/endophage/gotuf/data" ++import ( ++ "path/filepath" ++ "strings" ++ ++ "github.com/endophage/gotuf/data" ++) +  + const ( + keyExtension = "key" +@@ -79,5 +84,10 @@ func (s *KeyFileStore) GetDecryptedKey(name string, passphrase string) (*data.Pr + // There might be symlinks associating Certificate IDs to Public Keys, so this + // method only returns the IDs that aren't symlinks + func (s *KeyFileStore) ListKeys() []string { +- return s.ListFiles(false) ++ var keyIDList []string ++ for _, f := range s.ListFiles(false) { ++ keyID := strings.TrimSpace(strings.TrimSuffix(filepath.Base(f), filepath.Ext(f))) ++ keyIDList = append(keyIDList, keyID) ++ } ++ return keyIDList + } diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go index 6418139037..f076c794d1 100644 --- a/trustmanager/keyfilestore.go +++ b/trustmanager/keyfilestore.go @@ -1,6 +1,11 @@ package trustmanager -import "github.com/endophage/gotuf/data" +import ( + "path/filepath" + "strings" + + "github.com/endophage/gotuf/data" +) const ( keyExtension = "key" @@ -79,5 +84,10 @@ func (s *KeyFileStore) GetDecryptedKey(name string, passphrase string) (*data.Pr // There might be symlinks associating Certificate IDs to Public Keys, so this // method only returns the IDs that aren't symlinks func (s *KeyFileStore) ListKeys() []string { - return s.ListFiles(false) + var keyIDList []string + for _, f := range s.ListFiles(false) { + keyID := strings.TrimSpace(strings.TrimSuffix(filepath.Base(f), filepath.Ext(f))) + keyIDList = append(keyIDList, keyID) + } + return keyIDList } From 9ae667c9aa153be6140ab055edbdabfe5faab1f0 Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Thu, 9 Jul 2015 18:57:53 -0700 Subject: [PATCH 39/39] Adding new vendored gotuf Signed-off-by: Diogo Monica --- Godeps/Godeps.json | 2 +- .../github.com/endophage/gotuf/CONTRIBUTORS | 3 + .../endophage/gotuf/signed/sign_test.go | 55 ++++++++++++------- .../endophage/gotuf/signed/verifiers_test.go | 8 +-- .../endophage/gotuf/signed/verify_test.go | 28 +++++----- 5 files changed, 58 insertions(+), 38 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/endophage/gotuf/CONTRIBUTORS diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 82541cf4d2..00d93dafab 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -47,7 +47,7 @@ }, { "ImportPath": "github.com/endophage/gotuf", - "Rev": "060b7012cc28711473151872690ce9a0be9ab501" + "Rev": "66da486b58ef378c96433af965f61ca0efaccb9a" }, { "ImportPath": "github.com/go-sql-driver/mysql", diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/CONTRIBUTORS b/Godeps/_workspace/src/github.com/endophage/gotuf/CONTRIBUTORS new file mode 100644 index 0000000000..5f484889cb --- /dev/null +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/CONTRIBUTORS @@ -0,0 +1,3 @@ +Aaron Lehmann (github: aaronlehmann) +Lewis Marshall (github: lmars) +Jonathan Rudenberg (github: titanous) diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign_test.go b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign_test.go index 3cfb9e5018..9c204644c5 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign_test.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign_test.go @@ -1,14 +1,22 @@ package signed import ( + "encoding/pem" "testing" "github.com/endophage/gotuf/data" - "github.com/endophage/gotuf/keys" +) + +const ( + testKeyPEM1 = "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnKuXZeefa2LmgxaL5NsM\nzKOHNe+x/nL6ik+lDBCTV6OdcwAhHQS+PONGhrChIUVR6Vth3hUCrreLzPO73Oo5\nVSCuRJ53UronENl6lsa5mFKP8StYLvIDITNvkoT3j52BJIjyNUK9UKY9As2TNqDf\nBEPIRp28ev/NViwGOEkBu2UAbwCIdnDXm8JQErCZA0Ydm7PKGgjLbFsFGrVzqXHK\n6pdzJXlhr9yap3UpgQ/iO9JtoEYB2EXsnSrPc9JRjR30bNHHtnVql3fvinXrAEwq\n3xmN4p+R4VGzfdQN+8Kl/IPjqWB535twhFYEG/B7Ze8IwbygBjK3co/KnOPqMUrM\nBI8ztvPiogz+MvXb8WvarZ6TMTh8ifZI96r7zzqyzjR1hJulEy3IsMGvz8XS2J0X\n7sXoaqszEtXdq5ef5zKVxkiyIQZcbPgmpHLq4MgfdryuVVc/RPASoRIXG4lKaTJj\n1ANMFPxDQpHudCLxwCzjCb+sVa20HBRPTnzo8LSZkI6jAgMBAAE=\n-----END PUBLIC KEY-----" + testKeyID1 = "51324b59d4888faa91219ebbe5a3876bb4efb21f0602ddf363cd4c3996ded3d4" + + testKeyPEM2 = "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEArvqUPYb6JJROPJQglPTj\n5uDrsxQKl34Mo+3pSlBVuD6puE4lDnG649a2YksJy+C8ZIPJgokn5w+C3alh+dMe\nzbdWHHxrY1h9CLpYz5cbMlE16303ubkt1rvwDqEezG0HDBzPaKj4oP9YJ9x7wbsq\ndvFcy+Qc3wWd7UWcieo6E0ihbJkYcY8chRXVLg1rL7EfZ+e3bq5+ojA2ECM5JqzZ\nzgDpqCv5hTCYYZp72MZcG7dfSPAHrcSGIrwg7whzz2UsEtCOpsJTuCl96FPN7kAu\n4w/WyM3+SPzzr4/RQXuY1SrLCFD8ebM2zHt/3ATLhPnGmyG5I0RGYoegFaZ2AViw\nlqZDOYnBtgDvKP0zakMtFMbkh2XuNBUBO7Sjs0YcZMjLkh9gYUHL1yWS3Aqus1Lw\nlI0gHS22oyGObVBWkZEgk/Foy08sECLGao+5VvhmGpfVuiz9OKFUmtPVjWzRE4ng\niekEu4drSxpH41inLGSvdByDWLpcTvWQI9nkgclh3AT/AgMBAAE=\n-----END PUBLIC KEY-----" + testKeyID2 = "26f2f5c0fbfa98823bf1ad39d5f3b32575895793baf80f1df675597d5b95dba8" ) type MockCryptoService struct { - testKey keys.PublicKey + testKey data.PublicKey } func (mts *MockCryptoService) Sign(keyIDs []string, _ []byte) ([]data.Signature, error) { @@ -19,12 +27,12 @@ func (mts *MockCryptoService) Sign(keyIDs []string, _ []byte) ([]data.Signature, return sigs, nil } -func (mts *MockCryptoService) Create() (*keys.PublicKey, error) { +func (mts *MockCryptoService) Create(_ string) (*data.PublicKey, error) { return &mts.testKey, nil } -func (mts *MockCryptoService) PublicKeys(keyIDs ...string) (map[string]*keys.PublicKey, error) { - keys := map[string]*keys.PublicKey{"testID": &mts.testKey} +func (mts *MockCryptoService) PublicKeys(keyIDs ...string) (map[string]*data.PublicKey, error) { + keys := map[string]*data.PublicKey{"testID": &mts.testKey} return keys, nil } @@ -32,8 +40,10 @@ var _ CryptoService = &MockCryptoService{} // Test signing and ensure the expected signature is added func TestBasicSign(t *testing.T) { + testKey, _ := pem.Decode([]byte(testKeyPEM1)) + k := data.NewPublicKey("rsa", testKey.Bytes) signer := Signer{&MockCryptoService{ - testKey: keys.PublicKey{ID: "testID"}, + testKey: *k, }} key, err := signer.Create("root") if err != nil { @@ -47,7 +57,7 @@ func TestBasicSign(t *testing.T) { t.Fatalf("Incorrect number of signatures: %d", len(testData.Signatures)) } - if testData.Signatures[0].KeyID != "testID" { + if testData.Signatures[0].KeyID != testKeyID1 { t.Fatalf("Wrong signature ID returned: %s", testData.Signatures[0].KeyID) } @@ -57,20 +67,21 @@ func TestBasicSign(t *testing.T) { // for the key (N.B. MockCryptoService.Sign will still be called again, but Signer.Sign // should be cleaning previous signatures by the KeyID when asked to sign again) func TestReSign(t *testing.T) { + testKey, _ := pem.Decode([]byte(testKeyPEM1)) + k := data.NewPublicKey("rsa", testKey.Bytes) signer := Signer{&MockCryptoService{ - testKey: keys.PublicKey{}, + testKey: *k, }} - key := keys.PublicKey{ID: "testID"} testData := data.Signed{} - signer.Sign(&testData, &key) - signer.Sign(&testData, &key) + signer.Sign(&testData, k) + signer.Sign(&testData, k) if len(testData.Signatures) != 1 { t.Fatalf("Incorrect number of signatures: %d", len(testData.Signatures)) } - if testData.Signatures[0].KeyID != "testID" { + if testData.Signatures[0].KeyID != testKeyID1 { t.Fatalf("Wrong signature ID returned: %s", testData.Signatures[0].KeyID) } @@ -78,19 +89,21 @@ func TestReSign(t *testing.T) { func TestMultiSign(t *testing.T) { signer := Signer{&MockCryptoService{}} - key := keys.PublicKey{ID: "testID1"} testData := data.Signed{} - signer.Sign(&testData, &key) + testKey, _ := pem.Decode([]byte(testKeyPEM1)) + key := data.NewPublicKey("rsa", testKey.Bytes) + signer.Sign(&testData, key) - key = keys.PublicKey{ID: "testID2"} - signer.Sign(&testData, &key) + testKey, _ = pem.Decode([]byte(testKeyPEM2)) + key = data.NewPublicKey("rsa", testKey.Bytes) + signer.Sign(&testData, key) if len(testData.Signatures) != 2 { t.Fatalf("Incorrect number of signatures: %d", len(testData.Signatures)) } - keyIDs := map[string]struct{}{"testID1": struct{}{}, "testID2": struct{}{}} + keyIDs := map[string]struct{}{testKeyID1: struct{}{}, testKeyID2: struct{}{}} for _, sig := range testData.Signatures { if _, ok := keyIDs[sig.KeyID]; !ok { t.Fatalf("Got a signature we didn't expect: %s", sig.KeyID) @@ -100,8 +113,10 @@ func TestMultiSign(t *testing.T) { } func TestCreate(t *testing.T) { + testKey, _ := pem.Decode([]byte(testKeyPEM1)) + k := data.NewPublicKey("rsa", testKey.Bytes) signer := Signer{&MockCryptoService{ - testKey: keys.PublicKey{ID: "testID"}, + testKey: *k, }} key, err := signer.Create("root") @@ -109,7 +124,7 @@ func TestCreate(t *testing.T) { if err != nil { t.Fatal(err) } - if key.ID != "testID" { - t.Fatalf("Expected key ID not found: %s", key.ID) + if key.ID() != testKeyID1 { + t.Fatalf("Expected key ID not found: %s", key.ID()) } } diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers_test.go b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers_test.go index b51ae0c15d..4f57d2ea6b 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers_test.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers_test.go @@ -1,10 +1,10 @@ package signed import ( - "crypto" - "crypto/rsa" - "crypto/sha256" - "crypto/x509" + _ "crypto" + _ "crypto/rsa" + _ "crypto/sha256" + _ "crypto/x509" "testing" ) diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify_test.go b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify_test.go index b2dde99cd7..f9f5748d3f 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify_test.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify_test.go @@ -23,7 +23,7 @@ func (VerifySuite) Test(c *C) { signer := NewSigner(trust) type test struct { name string - keys []*keys.PublicKey + keys []*data.PublicKey roles map[string]*data.Role s *data.Signed ver int @@ -81,7 +81,7 @@ func (VerifySuite) Test(c *C) { k, _ := signer.Create("root") signer.Sign(t.s, k) t.keys = append(t.keys, k) - t.roles["root"].KeyIDs = append(t.roles["root"].KeyIDs, k.ID) + t.roles["root"].KeyIDs = append(t.roles["root"].KeyIDs, k.ID()) }, }, { @@ -139,7 +139,7 @@ func (VerifySuite) Test(c *C) { { name: "expired", exp: &expiredTime, - err: ErrExpired{expiredTime}, + err: ErrExpired{expiredTime.Format("2006-01-02 15:04:05 MST")}, }, } for _, t := range tests { @@ -154,24 +154,27 @@ func (VerifySuite) Test(c *C) { t.exp = &expires } if t.typ == "" { - t.typ = t.role + t.typ = data.TUFTypes[t.role] } if t.keys == nil && t.s == nil { k, _ := signer.Create("root") - meta := &signedMeta{Type: t.typ, Version: t.ver, Expires: *t.exp} + meta := &signedMeta{Type: t.typ, Version: t.ver, Expires: t.exp.Format("2006-01-02 15:04:05 MST")} b, err := cjson.Marshal(meta) c.Assert(err, IsNil) s := &data.Signed{Signed: b} signer.Sign(s, k) t.s = s - t.keys = []*keys.PublicKey{k} + t.keys = []*data.PublicKey{k} } if t.roles == nil { t.roles = map[string]*data.Role{ "root": &data.Role{ - KeyIDs: []string{t.keys[0].ID}, - Threshold: 1, + RootRole: data.RootRole{ + KeyIDs: []string{t.keys[0].ID()}, + Threshold: 1, + }, + Name: "root", }, } } @@ -181,11 +184,10 @@ func (VerifySuite) Test(c *C) { db := keys.NewDB() for _, k := range t.keys { - err := db.AddKey(k) - c.Assert(err, IsNil) + db.AddKey(k) } - for n, r := range t.roles { - err := db.AddRole(n, r) + for _, r := range t.roles { + err := db.AddRole(r) c.Assert(err, IsNil) } @@ -203,5 +205,5 @@ func assertErrExpired(c *C, err error, expected ErrExpired) { if !ok { c.Fatalf("expected err to have type ErrExpired, got %T", err) } - c.Assert(actual.Expired.Unix(), Equals, expected.Expired.Unix()) + c.Assert(actual.Expired, Equals, expected.Expired) }