diff --git a/cmd/notary/keys.go b/cmd/notary/keys.go index 62b9e7a47c..e0943834dd 100644 --- a/cmd/notary/keys.go +++ b/cmd/notary/keys.go @@ -4,104 +4,63 @@ import ( "crypto/x509" "fmt" "math" - "net/url" "os" "path/filepath" "strings" "time" + "github.com/docker/notary/keystoremanager" "github.com/docker/notary/trustmanager" "github.com/spf13/cobra" - "github.com/spf13/viper" ) -var subjectKeyID string +func init() { + cmdKeys.AddCommand(cmdKeysRemoveRootKey) + cmdKeys.AddCommand(cmdKeysGenerateRootKey) +} var cmdKeys = &cobra.Command{ Use: "keys", - Short: "Operates on keys.", - Long: "operations on signature keys and trusted certificate authorities.", + Short: "Operates on root keys.", + Long: "operations on private root keys.", Run: keysList, } -func init() { - cmdKeys.AddCommand(cmdKeysTrust) - cmdKeys.AddCommand(cmdKeysRemove) - cmdKeys.AddCommand(cmdKeysGenerate) +var cmdKeysRemoveRootKey = &cobra.Command{ + Use: "remove [ keyID ]", + Short: "Removes the root key with the given keyID.", + Long: "remove the root key with the given keyID from the local host.", + Run: keysRemoveRootKey, } -var cmdKeysRemove = &cobra.Command{ - Use: "remove [ Subject Key ID ]", - Short: "Removes trust from a specific certificate authority or certificate.", - Long: "remove trust from a specific certificate authority.", - Run: keysRemove, +var cmdKeysGenerateRootKey = &cobra.Command{ + Use: "generate [ algorithm ]", + Short: "Generates a new root key with a given algorithm.", + Long: "generates a new root key with a given algorithm.", + Run: keysGenerateRootKey, } -var cmdKeysTrust = &cobra.Command{ - Use: "trust [ certificate ]", - Short: "Trusts a new certificate.", - Long: "adds a the certificate to the trusted certificate authority list.", - Run: keysTrust, -} - -var cmdKeysGenerate = &cobra.Command{ - Use: "generate [ GUN ]", - Short: "Generates a new key for a specific GUN.", - Long: "generates a new key for a specific Global Unique Name.", - Run: keysGenerate, -} - -// keysRemove deletes Certificates based on hash and Private Keys -// based on GUNs. -func keysRemove(cmd *cobra.Command, args []string) { +// keysRemoveRootKey deletes a root private key based on ID +func keysRemoveRootKey(cmd *cobra.Command, args []string) { if len(args) < 1 { cmd.Usage() - fatalf("must specify a SHA256 SubjectKeyID of the certificate") + fatalf("must specify the key ID of the root key to remove") } - //TODO (diogo): Validate Global Unique Name. We probably want to reject 1 char GUNs. - gunOrID := args[0] - - // Try to retrieve the ID from the CA store. - cert, err := caStore.GetCertificateByCertID(gunOrID) - if err == nil { - fmt.Printf("Removing: ") - printCert(cert) - - // If the ID is found, remove it. - err = caStore.RemoveCert(cert) - if err != nil { - fatalf("failed to remove certificate from KeyStore") - } - return + keyID := args[0] + if len(keyID) != 64 { + fatalf("please enter a valid root key ID") } + parseConfig() - // Try to retrieve the ID from the Certificate store. - cert, err = certificateStore.GetCertificateByCertID(gunOrID) - if err == nil { - fmt.Printf("Removing: ") - printCert(cert) - - // If the ID is found, remove it. - err = certificateStore.RemoveCert(cert) - if err != nil { - fatalf("failed to remove certificate from KeyStore") - } - return - } - - // We didn't find a certificate with this ID, let's try to see if we can find keys. - keyList := privKeyStore.ListDir(gunOrID, true) - if len(keyList) < 1 { - fatalf("no Private Keys found under Global Unique Name: %s", gunOrID) + keyStoreManager, err := keystoremanager.NewKeyStoreManager(TrustDir, retriever) + if err != nil { + fatalf("failed to create a new truststore manager with directory: %s", TrustDir) } // List all the keys about to be removed - fmt.Println("Are you sure you want to remove the following keys? (yes/no)", gunOrID) - for _, k := range keyList { - printKey(k) - } + fmt.Printf("Are you sure you want to remove the following key? (yes/no)\n%s\n", keyID) // Ask for confirmation before removing keys confirmed := askConfirm() @@ -110,60 +69,12 @@ func keysRemove(cmd *cobra.Command, args []string) { } // Remove all the keys under the Global Unique Name - err = privKeyStore.RemoveDir(gunOrID) + err = keyStoreManager.RootKeyStore().RemoveKey(keyID) if err != nil { - fatalf("failed to remove all Private keys under Global Unique Name: %s", gunOrID) - } - fmt.Printf("Removing all Private keys from: %s \n", gunOrID) -} - -//TODO (diogo): Ask the use if she wants to trust the GUN in the cert -func keysTrust(cmd *cobra.Command, args []string) { - if len(args) < 1 { - cmd.Usage() - fatalf("please provide a URL or filename to a certificate") + fatalf("failed to remove root key with key ID: %s", keyID) } - certLocationStr := args[0] - var cert *x509.Certificate - - // Verify if argument is a valid URL - url, err := url.Parse(certLocationStr) - if err == nil && url.Scheme != "" { - cert, err = trustmanager.GetCertFromURL(certLocationStr) - if err != nil { - fatalf("error retrieving certificate from url (%s): %v", certLocationStr, err) - } - } else if _, err := os.Stat(certLocationStr); err == nil { - // Try to load the certificate from the file - cert, err = trustmanager.LoadCertFromFile(certLocationStr) - if err != nil { - fatalf("error adding certificate from file: %v", err) - } - } else { - fatalf("please provide a file location or URL for CA certificate.") - } - - // Ask for confirmation before adding certificate into repository - fmt.Printf("Are you sure you want to add trust for: %s? (yes/no)\n", cert.Subject.CommonName) - confirmed := askConfirm() - if !confirmed { - fatalf("aborting action.") - } - - err = nil - if cert.IsCA { - err = caStore.AddCert(cert) - } else { - err = certificateStore.AddCert(cert) - } - if err != nil { - fatalf("error adding certificate from file: %v", err) - } - - fmt.Printf("Adding: ") - printCert(cert) - + fmt.Printf("Root key %s removed\n", keyID) } func keysList(cmd *cobra.Command, args []string) { @@ -172,46 +83,62 @@ func keysList(cmd *cobra.Command, args []string) { os.Exit(1) } - fmt.Println("# Trusted CAs:") - trustedCAs := caStore.GetCertificates() - for _, c := range trustedCAs { - printCert(c) + parseConfig() + + keyStoreManager, err := keystoremanager.NewKeyStoreManager(TrustDir, retriever) + if err != nil { + fatalf("failed to create a new truststore manager with directory: %s", TrustDir) } fmt.Println("") fmt.Println("# Trusted Certificates:") - trustedCerts := certificateStore.GetCertificates() + trustedCerts := keyStoreManager.TrustedCertificateStore().GetCertificates() for _, c := range trustedCerts { printCert(c) } + fmt.Println("") + fmt.Println("# Root keys: ") + for _, k := range keyStoreManager.RootKeyStore().ListKeys() { + fmt.Println(k) + } + fmt.Println("") fmt.Println("# Signing keys: ") - for _, k := range privKeyStore.ListFiles(true) { + for _, k := range keyStoreManager.NonRootKeyStore().ListKeys() { printKey(k) } } -func keysGenerate(cmd *cobra.Command, args []string) { +func keysGenerateRootKey(cmd *cobra.Command, args []string) { if len(args) < 1 { cmd.Usage() - fatalf("must specify a GUN") + fatalf("must specify an Algorithm (RSA, ECDSA)") } - //TODO (diogo): Validate GUNs. Don't allow '/' or '\' for now. - gun := args[0] - if gun[0:1] == "/" || gun[0:1] == "\\" { - fatalf("invalid Global Unique Name: %s", gun) + algorithm := args[0] + allowedCiphers := map[string]bool{ + "rsa": true, + "ecdsa": true, } - // _, cert, err := generateKeyAndCert(gun) - // if err != nil { - // fatalf("could not generate key: %v", err) - // } + if !allowedCiphers[strings.ToLower(algorithm)] { + fatalf("algorithm not allowed, possible values are: RSA, ECDSA") + } - // certificateStore.AddCert(cert) - // fingerprint := trustmanager.FingerprintCert(cert) - // fmt.Println("Generated new keypair with ID: ", fingerprint) + parseConfig() + + keyStoreManager, err := keystoremanager.NewKeyStoreManager(TrustDir, retriever) + if err != nil { + fatalf("failed to create a new truststore manager with directory: %s", TrustDir) + } + + keyID, err := keyStoreManager.GenRootKey(algorithm) + if err != nil { + fatalf("failed to create a new root key: %v", err) + } + + fmt.Printf("Generated new %s key with keyID: %s\n", algorithm, keyID) } func printCert(cert *x509.Certificate) { @@ -225,11 +152,8 @@ func printCert(cert *x509.Certificate) { } func printKey(keyPath string) { - keyPath = strings.TrimSuffix(keyPath, filepath.Ext(keyPath)) - keyPath = strings.TrimPrefix(keyPath, viper.GetString("privDir")) - keyID := filepath.Base(keyPath) - gun := filepath.Dir(keyPath)[1:] + gun := filepath.Dir(keyPath) fmt.Printf("%s %s\n", gun, keyID) } diff --git a/cmd/notary/main.go b/cmd/notary/main.go index 5ecf6327b5..e8bd8bcba3 100644 --- a/cmd/notary/main.go +++ b/cmd/notary/main.go @@ -1,96 +1,71 @@ package main import ( - "crypto/x509" "fmt" "os" "os/user" - "path" - "time" + "path/filepath" "github.com/Sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/docker/notary/trustmanager" + "github.com/docker/notary/pkg/passphrase" ) const configFileName string = "config" -const configPath string = ".docker/trust/" -const trustDir string = "trusted_certificates/" -const privDir string = "private/" -const rootKeysDir string = "root_keys/" +const defaultTrustDir string = ".notary/" +const defaultServerURL = "https://notary-server:4443" var rawOutput bool -var caStore trustmanager.X509Store -var certificateStore trustmanager.X509Store -var privKeyStore trustmanager.FileStore +var TrustDir string +var RemoteTrustServer string +var Verbose bool +var retriever passphrase.Retriever func init() { - logrus.SetLevel(logrus.DebugLevel) - logrus.SetOutput(os.Stderr) - // Retrieve current user to get home directory - usr, err := user.Current() - if err != nil { - fatalf("cannot get current user: %v", err) + retriever = passphrase.PromptRetriever() +} + +func parseConfig() { + if Verbose { + logrus.SetLevel(logrus.DebugLevel) + logrus.SetOutput(os.Stderr) } - // Get home directory for current user - homeDir := usr.HomeDir - if homeDir == "" { - fatalf("cannot get current user home directory") + if TrustDir == "" { + // Retrieve current user to get home directory + usr, err := user.Current() + if err != nil { + fatalf("cannot get current user: %v", err) + } + + // Get home directory for current user + homeDir := usr.HomeDir + if homeDir == "" { + fatalf("cannot get current user home directory") + } + TrustDir = filepath.Join(homeDir, filepath.Dir(defaultTrustDir)) + + logrus.Debugf("no trust directory provided, using default: %s", TrustDir) + } else { + logrus.Debugf("trust directory provided: %s", TrustDir) } // Setup the configuration details viper.SetConfigName(configFileName) - viper.AddConfigPath(path.Join(homeDir, path.Dir(configPath))) + viper.AddConfigPath(TrustDir) viper.SetConfigType("json") // Find and read the config file - err = viper.ReadInConfig() + err := viper.ReadInConfig() if err != nil { + logrus.Debugf("configuration file not found, using defaults") // Ignore if the configuration file doesn't exist, we can use the defaults if !os.IsNotExist(err) { fatalf("fatal error config file: %v", err) } } - - // Set up the defaults for our config - viper.SetDefault("baseTrustDir", path.Join(homeDir, path.Dir(configPath))) - - // Get the final value for the CA directory - 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 { - 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 { - fatalf("could not create CA X509FileStore: %v", err) - } - - // 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) && - cert.SignatureAlgorithm != x509.SHA1WithRSA && - cert.SignatureAlgorithm != x509.DSAWithSHA1 && - cert.SignatureAlgorithm != x509.ECDSAWithSHA1 - }) - if err != nil { - fatalf("could not create Certificate X509FileStore: %v", err) - } - - privKeyStore, err = trustmanager.NewKeyFileStore(finalPrivDir, - func(string, string, bool, int) (string, bool, error) { return "", false, nil }) - if err != nil { - fatalf("could not create KeyFileStore: %v", err) - } } func main() { @@ -100,17 +75,22 @@ func main() { Long: "notary allows the creation and management of collections of signed targets, allowing the signing and validation of arbitrary content.", } + NotaryCmd.PersistentFlags().StringVarP(&TrustDir, "trustdir", "d", "", "directory where the trust data is persisted to") + NotaryCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") + NotaryCmd.AddCommand(cmdKeys) NotaryCmd.AddCommand(cmdTufInit) + cmdTufInit.Flags().StringVarP(&RemoteTrustServer, "server", "s", defaultServerURL, "Remote trust server location") NotaryCmd.AddCommand(cmdTufList) 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.") + cmdTufList.Flags().StringVarP(&RemoteTrustServer, "server", "s", defaultServerURL, "Remote trust server location") NotaryCmd.AddCommand(cmdTufAdd) NotaryCmd.AddCommand(cmdTufRemove) NotaryCmd.AddCommand(cmdTufPublish) - cmdTufPublish.Flags().StringVarP(&remoteTrustServer, "remote", "r", "", "Remote trust server location") + cmdTufPublish.Flags().StringVarP(&RemoteTrustServer, "server", "s", defaultServerURL, "Remote trust server location") NotaryCmd.AddCommand(cmdTufLookup) 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") + cmdTufLookup.Flags().StringVarP(&RemoteTrustServer, "server", "s", defaultServerURL, "Remote trust server location") NotaryCmd.AddCommand(cmdVerify) NotaryCmd.Execute() diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 05951740c0..08df78c251 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -9,24 +9,13 @@ import ( "os" "crypto/subtle" + "github.com/Sirupsen/logrus" notaryclient "github.com/docker/notary/client" - "github.com/docker/notary/pkg/passphrase" "github.com/spf13/cobra" "github.com/spf13/viper" ) -// FIXME: This should not be hardcoded -const hardcodedBaseURL = "http://notary-server:4443" - -var retriever passphrase.Retriever - -func init() { - retriever = passphrase.PromptRetriever() -} - -var remoteTrustServer string - var cmdTufList = &cobra.Command{ Use: "list [ GUN ]", Short: "Lists targets for a trusted collection.", @@ -86,8 +75,9 @@ func tufAdd(cmd *cobra.Command, args []string) { targetName := args[1] targetPath := args[2] - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, - getTransport(), retriever) + parseConfig() + + nRepo, err := notaryclient.NewNotaryRepository(TrustDir, gun, RemoteTrustServer, getTransport(), retriever) if err != nil { fatalf(err.Error()) } @@ -96,7 +86,7 @@ func tufAdd(cmd *cobra.Command, args []string) { if err != nil { fatalf(err.Error()) } - err = repo.AddTarget(target) + err = nRepo.AddTarget(target) if err != nil { fatalf(err.Error()) } @@ -110,9 +100,9 @@ func tufInit(cmd *cobra.Command, args []string) { } gun := args[0] + parseConfig() - nRepo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, - getTransport(), retriever) + nRepo, err := notaryclient.NewNotaryRepository(TrustDir, gun, RemoteTrustServer, getTransport(), retriever) if err != nil { fatalf(err.Error()) } @@ -147,14 +137,15 @@ func tufList(cmd *cobra.Command, args []string) { fatalf("must specify a GUN") } gun := args[0] - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, - getTransport(), retriever) + parseConfig() + + nRepo, err := notaryclient.NewNotaryRepository(TrustDir, gun, RemoteTrustServer, getTransport(), retriever) if err != nil { fatalf(err.Error()) } // Retreive the remote list of signed targets - targetList, err := repo.ListTargets() + targetList, err := nRepo.ListTargets() if err != nil { fatalf(err.Error()) } @@ -172,15 +163,14 @@ func tufLookup(cmd *cobra.Command, args []string) { } gun := args[0] targetName := args[1] + parseConfig() - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, - getTransport(), retriever) + nRepo, err := notaryclient.NewNotaryRepository(TrustDir, gun, RemoteTrustServer, getTransport(), retriever) if err != nil { fatalf(err.Error()) } - // TODO(diogo): Parse Targets and print them - target, err := repo.GetTargetByName(targetName) + target, err := nRepo.GetTargetByName(targetName) if err != nil { fatalf(err.Error()) } @@ -195,16 +185,16 @@ func tufPublish(cmd *cobra.Command, args []string) { } gun := args[0] + parseConfig() fmt.Println("Pushing changes to ", gun, ".") - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, - getTransport(), retriever) + nRepo, err := notaryclient.NewNotaryRepository(TrustDir, gun, RemoteTrustServer, getTransport(), retriever) if err != nil { fatalf(err.Error()) } - err = repo.Publish() + err = nRepo.Publish() if err != nil { fatalf(err.Error()) } @@ -217,8 +207,9 @@ func tufRemove(cmd *cobra.Command, args []string) { } gun := args[0] targetName := args[1] + parseConfig() - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, remoteTrustServer, getTransport(), retriever) if err != nil { fatalf(err.Error()) @@ -234,25 +225,22 @@ func verify(cmd *cobra.Command, args []string) { cmd.Usage() fatalf("must specify a GUN and target") } + parseConfig() // Reads all of the data on STDIN - //TODO (diogo): Change this to do a streaming hash payload, err := ioutil.ReadAll(os.Stdin) if err != nil { fatalf("error reading content from STDIN: %v", err) } - //TODO (diogo): This code is copy/pasted from lookup. gun := args[0] targetName := args[1] - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, - getTransport(), retriever) + nRepo, err := notaryclient.NewNotaryRepository(TrustDir, gun, RemoteTrustServer, getTransport(), retriever) if err != nil { fatalf(err.Error()) } - // TODO(diogo): Parse Targets and print them - target, err := repo.GetTargetByName(targetName) + target, err := nRepo.GetTargetByName(targetName) if err != nil { logrus.Error("notary: data not present in the trusted collection.") os.Exit(-11)