diff --git a/cmd/notary/cert.go b/cmd/notary/cert.go new file mode 100644 index 0000000000..b73dd34d27 --- /dev/null +++ b/cmd/notary/cert.go @@ -0,0 +1,139 @@ +package main + +import ( + "crypto/x509" + "fmt" + "math" + "os" + "time" + + "github.com/docker/notary/keystoremanager" + "github.com/docker/notary/trustmanager" + + "github.com/spf13/cobra" +) + +func init() { + cmdCert.AddCommand(cmdCertList) + + cmdCertRemove.Flags().StringVarP(&certRemoveGUN, "gun", "g", "", "Globally unique name to delete certificates for") + cmdCert.AddCommand(cmdCertRemove) +} + +var cmdCert = &cobra.Command{ + Use: "cert", + Short: "Operates on certificates.", + Long: `operations on certificates.`, +} + +var cmdCertList = &cobra.Command{ + Use: "list", + Short: "Lists certificates.", + Long: "lists root certificates known to notary.", + Run: certList, +} + +var certRemoveGUN string + +var cmdCertRemove = &cobra.Command{ + Use: "remove [ certID ]", + Short: "Removes the certificate with the given cert ID.", + Long: "remove the certificate with the given cert ID from the local host.", + Run: certRemove, +} + +// certRemove deletes a certificate given a cert ID or a gun +func certRemove(cmd *cobra.Command, args []string) { + // If the user hasn't provided -g with a gun, or a cert ID, show usage + // If the user provided -g and a cert ID, also show usage + if (len(args) < 1 && certRemoveGUN == "") || (len(args) > 0 && certRemoveGUN != "") { + cmd.Usage() + fatalf("must specify the cert ID or the GUN of the certificates to remove") + } + + parseConfig() + + keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, retriever) + if err != nil { + fatalf("failed to create a new truststore manager with directory: %s", trustDir) + } + + var certsToRemove []*x509.Certificate + + // If there is no GUN, we expect a cert ID + if certRemoveGUN == "" { + certID := args[0] + // This is an invalid ID + if len(certID) != idSize { + fatalf("invalid certificate ID provided: %s", certID) + } + // Attempt to find this certificates + cert, err := keyStoreManager.TrustedCertificateStore().GetCertificateByCertID(certID) + if err != nil { + fatalf("unable to retrieve certificate with cert ID: %s", certID) + } + certsToRemove = append(certsToRemove, cert) + } else { + // We got the -g flag, it's a GUN + certs, err := keyStoreManager.TrustedCertificateStore().GetCertificatesByCN(certRemoveGUN) + if err != nil { + fatalf("%v", err) + } + certsToRemove = append(certsToRemove, certs...) + } + + // List all the keys about to be removed + fmt.Printf("The following certificates will be removed:\n\n") + for _, cert := range certsToRemove { + // This error can't occur because we're getting certs off of an + // x509 store that indexes by ID. + certID, _ := trustmanager.FingerprintCert(cert) + fmt.Printf("%s - %s\n", cert.Subject.CommonName, certID) + } + fmt.Println("\nAre you sure you want to remove these certificates? (yes/no)") + + // Ask for confirmation before removing certificates + confirmed := askConfirm() + if !confirmed { + fatalf("aborting action.") + } + + // Remove all the certs + for _, cert := range certsToRemove { + err = keyStoreManager.TrustedCertificateStore().RemoveCert(cert) + if err != nil { + fatalf("failed to remove root certificate for %s", cert.Subject.CommonName) + } + } +} + +func certList(cmd *cobra.Command, args []string) { + if len(args) > 0 { + cmd.Usage() + os.Exit(1) + } + + 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 := keyStoreManager.TrustedCertificateStore().GetCertificates() + for _, c := range trustedCerts { + printCert(c) + } +} + +func printCert(cert *x509.Certificate) { + timeDifference := cert.NotAfter.Sub(time.Now()) + certID, err := trustmanager.FingerprintCert(cert) + if err != nil { + fatalf("could not fingerprint certificate: %v", err) + } + + fmt.Printf("%s %s (expires in: %v days)\n", cert.Subject.CommonName, certID, math.Floor(timeDifference.Hours()/24)) +} diff --git a/cmd/notary/keys.go b/cmd/notary/keys.go index 6583e16e8b..a4277865eb 100644 --- a/cmd/notary/keys.go +++ b/cmd/notary/keys.go @@ -2,13 +2,11 @@ package main import ( "archive/zip" - "crypto/x509" "fmt" - "math" "os" "path/filepath" + "sort" "strings" - "time" "github.com/docker/notary/keystoremanager" "github.com/docker/notary/pkg/passphrase" @@ -19,10 +17,12 @@ import ( func init() { cmdKey.AddCommand(cmdKeyList) - cmdKey.AddCommand(cmdKeyRemoveRootKey) + cmdKey.AddCommand(cmdKeyRemoveKey) + cmdKeyRemoveKey.Flags().StringVarP(&keyRemoveGUN, "gun", "g", "", "Globally unique name to remove keys for") + cmdKeyRemoveKey.Flags().BoolVarP(&keyRemoveRoot, "root", "r", false, "Remove root keys") cmdKey.AddCommand(cmdKeyGenerateRootKey) - cmdKeyExport.Flags().StringVarP(&keysExportGUN, "gun", "g", "", "Globally unique name to export keys for.") + cmdKeyExport.Flags().StringVarP(&keysExportGUN, "gun", "g", "", "Globally unique name to export keys for") cmdKey.AddCommand(cmdKeyExport) cmdKey.AddCommand(cmdKeyExportRoot) cmdKeyExportRoot.Flags().BoolVarP(&keysExportRootChangePassphrase, "change-passphrase", "c", false, "set a new passphrase for the key being exported") @@ -33,7 +33,7 @@ func init() { var cmdKey = &cobra.Command{ Use: "key", Short: "Operates on keys.", - Long: "operations on private keys.", + Long: `operations on private keys.`, } var cmdKeyList = &cobra.Command{ @@ -43,11 +43,14 @@ var cmdKeyList = &cobra.Command{ Run: keysList, } -var cmdKeyRemoveRootKey = &cobra.Command{ +var keyRemoveGUN string +var keyRemoveRoot bool + +var cmdKeyRemoveKey = &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, + Short: "Removes the key with the given keyID.", + Long: "remove the key with the given keyID from the local host.", + Run: keysRemoveKey, } var cmdKeyGenerateRootKey = &cobra.Command{ @@ -89,17 +92,13 @@ var cmdKeyImportRoot = &cobra.Command{ Run: keysImportRoot, } -// keysRemoveRootKey deletes a root private key based on ID -func keysRemoveRootKey(cmd *cobra.Command, args []string) { +// keysRemoveKey deletes a private key based on ID +func keysRemoveKey(cmd *cobra.Command, args []string) { if len(args) < 1 { cmd.Usage() - fatalf("must specify the key ID of the root key to remove") + fatalf("must specify the key ID of the key to remove") } - keyID := args[0] - if len(keyID) != 64 { - fatalf("please enter a valid root key ID") - } parseConfig() keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, retriever) @@ -107,22 +106,53 @@ func keysRemoveRootKey(cmd *cobra.Command, args []string) { fatalf("failed to create a new truststore manager with directory: %s", trustDir) } - // List all the keys about to be removed - fmt.Printf("Are you sure you want to remove the following key?\n%s\n (yes/no)\n", keyID) + keyID := args[0] - // Ask for confirmation before removing keys + // This is an invalid ID + if len(keyID) != idSize { + fatalf("invalid key ID provided: %s", keyID) + } + + // List the key about to be removed + fmt.Println("Are you sure you want to remove the following key?") + fmt.Printf("%s\n(yes/no)\n", keyID) + + // Ask for confirmation before removing the key confirmed := askConfirm() if !confirmed { fatalf("aborting action.") } - // Remove all the keys under the Global Unique Name - err = keyStoreManager.RootKeyStore().RemoveKey(keyID) - if err != nil { - fatalf("failed to remove root key with key ID: %s", keyID) + // Choose the correct filestore to remove the key from + var keyStoreToRemove *trustmanager.KeyFileStore + var keyMap map[string]string + if keyRemoveRoot { + keyStoreToRemove = keyStoreManager.RootKeyStore() + keyMap = keyStoreManager.RootKeyStore().ListKeys() + } else { + keyStoreToRemove = keyStoreManager.NonRootKeyStore() + keyMap = keyStoreManager.NonRootKeyStore().ListKeys() } - fmt.Printf("Root key %s removed\n", keyID) + // Attempt to find the full GUN to the key in the map + // This is irrelevant for removing root keys, but does no harm + var keyWithGUN string + for k := range keyMap { + if filepath.Base(k) == keyID { + keyWithGUN = k + } + } + + // If empty, we didn't find any matches + if keyWithGUN == "" { + fatalf("key with key ID: %s not found\n", keyID) + } + + // Attempt to remove the key + err = keyStoreToRemove.RemoveKey(keyWithGUN) + if err != nil { + fatalf("failed to remove key with key ID: %s, %v", keyID, err) + } } func keysList(cmd *cobra.Command, args []string) { @@ -138,23 +168,29 @@ func keysList(cmd *cobra.Command, args []string) { fatalf("failed to create a new truststore manager with directory: %s", trustDir) } - fmt.Println("") - fmt.Println("# Trusted Certificates:") - trustedCerts := keyStoreManager.TrustedCertificateStore().GetCertificates() - for _, c := range trustedCerts { - printCert(c) - } - fmt.Println("") fmt.Println("# Root keys: ") - for _, k := range keyStoreManager.RootKeyStore().ListKeys() { + for k := range keyStoreManager.RootKeyStore().ListKeys() { fmt.Println(k) } fmt.Println("") fmt.Println("# Signing keys: ") - for _, k := range keyStoreManager.NonRootKeyStore().ListKeys() { - printKey(k) + + // Get a map of all the keys/roles + keysMap := keyStoreManager.NonRootKeyStore().ListKeys() + + // Get a list of all the keys + var sortedKeys []string + for k := range keysMap { + sortedKeys = append(sortedKeys, k) + } + // Sort the list of all the keys + sort.Strings(sortedKeys) + + // Print a sorted list of the key/role + for _, k := range sortedKeys { + printKey(k, keysMap[k]) } } @@ -237,7 +273,7 @@ func keysExportRoot(cmd *cobra.Command, args []string) { keyID := args[0] exportFilename := args[1] - if len(keyID) != 64 { + if len(keyID) != idSize { fatalf("please specify a valid root key ID") } @@ -306,7 +342,7 @@ func keysImportRoot(cmd *cobra.Command, args []string) { keyID := args[0] importFilename := args[1] - if len(keyID) != 64 { + if len(keyID) != idSize { fatalf("please specify a valid root key ID") } @@ -330,30 +366,8 @@ func keysImportRoot(cmd *cobra.Command, args []string) { } } -func printCert(cert *x509.Certificate) { - timeDifference := cert.NotAfter.Sub(time.Now()) - certID, err := trustmanager.FingerprintCert(cert) - if err != nil { - fatalf("could not fingerprint certificate: %v", err) - } - - fmt.Printf("%s %s (expires in: %v days)\n", cert.Subject.CommonName, certID, math.Floor(timeDifference.Hours()/24)) -} - -func printKey(keyPath string) { +func printKey(keyPath, alias string) { keyID := filepath.Base(keyPath) gun := filepath.Dir(keyPath) - fmt.Printf("%s %s\n", gun, keyID) -} - -func askConfirm() bool { - var res string - _, err := fmt.Scanln(&res) - if err != nil { - return false - } - if strings.EqualFold(res, "y") || strings.EqualFold(res, "yes") { - return true - } - return false + fmt.Printf("%s - %s - %s\n", gun, alias, keyID) } diff --git a/cmd/notary/main.go b/cmd/notary/main.go index 9156686b79..20f7358170 100644 --- a/cmd/notary/main.go +++ b/cmd/notary/main.go @@ -5,6 +5,7 @@ import ( "os" "os/user" "path/filepath" + "strings" "github.com/Sirupsen/logrus" "github.com/spf13/cobra" @@ -17,6 +18,7 @@ import ( const configFileName string = "config" const defaultTrustDir string = ".notary/" const defaultServerURL = "https://notary-server:4443" +const idSize = 64 var rawOutput bool var trustDir string @@ -91,6 +93,7 @@ func main() { notaryCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output") notaryCmd.AddCommand(cmdKey) + notaryCmd.AddCommand(cmdCert) notaryCmd.AddCommand(cmdTufInit) cmdTufInit.Flags().StringVarP(&remoteTrustServer, "server", "s", defaultServerURL, "Remote trust server location") notaryCmd.AddCommand(cmdTufList) @@ -112,3 +115,15 @@ func fatalf(format string, args ...interface{}) { fmt.Printf("* fatal: "+format+"\n", args...) os.Exit(1) } + +func askConfirm() bool { + var res string + _, err := fmt.Scanln(&res) + if err != nil { + return false + } + if strings.EqualFold(res, "y") || strings.EqualFold(res, "yes") { + return true + } + return false +} diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 2b3f1734f9..3cc96428db 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -107,17 +107,22 @@ func tufInit(cmd *cobra.Command, args []string) { fatalf(err.Error()) } - keysList := nRepo.KeyStoreManager.RootKeyStore().ListKeys() + keysMap := nRepo.KeyStoreManager.RootKeyStore().ListKeys() + var rootKeyID string - if len(keysList) < 1 { + if len(keysMap) < 1 { fmt.Println("No root keys found. Generating a new root key...") rootKeyID, err = nRepo.KeyStoreManager.GenRootKey("ECDSA") if err != nil { fatalf(err.Error()) } } else { - rootKeyID = keysList[0] - fmt.Println("Root key found.") + // TODO(diogo): ask which root key to use + for keyID := range keysMap { + rootKeyID = keyID + } + + fmt.Printf("Root key found, using: %s\n", rootKeyID) } rootCryptoService, err := nRepo.KeyStoreManager.GetRootCryptoService(rootKeyID) diff --git a/keystoremanager/import_export.go b/keystoremanager/import_export.go index 667ed8449d..55c736cdad 100644 --- a/keystoremanager/import_export.go +++ b/keystoremanager/import_export.go @@ -113,7 +113,7 @@ func (km *KeyStoreManager) ImportRootKey(source io.Reader, keyID string) error { func moveKeys(oldKeyStore, newKeyStore *trustmanager.KeyFileStore) error { // List all files but no symlinks - for _, f := range oldKeyStore.ListKeys() { + for f := range oldKeyStore.ListKeys() { privateKey, alias, err := oldKeyStore.GetKey(f) if err != nil { return err @@ -280,7 +280,7 @@ func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader) error { func moveKeysByGUN(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, gun string) error { // List all files but no symlinks - for _, relKeyPath := range oldKeyStore.ListKeys() { + for relKeyPath := range oldKeyStore.ListKeys() { // Skip keys that aren't associated with this GUN if !strings.HasPrefix(relKeyPath, filepath.FromSlash(gun)) { diff --git a/keystoremanager/import_export_test.go b/keystoremanager/import_export_test.go index 53b58ef1d0..71a85cce1e 100644 --- a/keystoremanager/import_export_test.go +++ b/keystoremanager/import_export_test.go @@ -83,8 +83,8 @@ func TestImportExportZip(t *testing.T) { // Add non-root keys to the map. These should use the new passphrase // because the passwords were chosen by the newPassphraseRetriever. - privKeyList := repo.KeyStoreManager.NonRootKeyStore().ListKeys() - for _, privKeyName := range privKeyList { + privKeyMap := repo.KeyStoreManager.NonRootKeyStore().ListKeys() + for privKeyName := range privKeyMap { _, alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKey(privKeyName) assert.NoError(t, err, "privKey %s has no alias", privKeyName) @@ -155,7 +155,7 @@ func TestImportExportZip(t *testing.T) { // Look for keys in private. The filenames should match the key IDs // in the repo's private key store. - for _, privKeyName := range privKeyList { + for privKeyName := range privKeyMap { _, alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKey(privKeyName) assert.NoError(t, err, "privKey %s has no alias", privKeyName) @@ -219,8 +219,8 @@ func TestImportExportGUN(t *testing.T) { // Add keys non-root keys to the map. These should use the new passphrase // because they were formerly unencrypted. - privKeyList := repo.KeyStoreManager.NonRootKeyStore().ListKeys() - for _, privKeyName := range privKeyList { + privKeyMap := repo.KeyStoreManager.NonRootKeyStore().ListKeys() + for privKeyName := range privKeyMap { _, alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKey(privKeyName) if err != nil { t.Fatalf("privKey %s has no alias", privKeyName) @@ -289,7 +289,7 @@ func TestImportExportGUN(t *testing.T) { // Look for keys in private. The filenames should match the key IDs // in the repo's private key store. - for _, privKeyName := range privKeyList { + for privKeyName := range privKeyMap { _, alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKey(privKeyName) if err != nil { t.Fatalf("privKey %s has no alias", privKeyName) diff --git a/trustmanager/keydbstore.go b/trustmanager/keydbstore.go index 9a42eda3d3..6e1af47d2e 100644 --- a/trustmanager/keydbstore.go +++ b/trustmanager/keydbstore.go @@ -132,7 +132,7 @@ func (s *KeyDBStore) GetKey(name string) (data.PrivateKey, string, error) { } // ListKeys always returns nil. This method is here to satisfy the KeyStore interface -func (s *KeyDBStore) ListKeys() []string { +func (s *KeyDBStore) ListKeys() map[string]string { return nil } diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go index effff89717..fc68463dbd 100644 --- a/trustmanager/keyfilestore.go +++ b/trustmanager/keyfilestore.go @@ -56,7 +56,7 @@ func (s *KeyFileStore) GetKey(name string) (data.PrivateKey, string, error) { // 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 { +func (s *KeyFileStore) ListKeys() map[string]string { return listKeys(s) } @@ -94,7 +94,7 @@ func (s *KeyMemoryStore) GetKey(name string) (data.PrivateKey, string, error) { // 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 *KeyMemoryStore) ListKeys() []string { +func (s *KeyMemoryStore) ListKeys() map[string]string { return listKeys(s) } @@ -206,18 +206,20 @@ func getKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached return privKey, keyAlias, nil } -// ListKeys returns a list of unique PublicKeys present on the KeyFileStore. +// ListKeys returns a map of unique PublicKeys present on the KeyFileStore and +// their corresponding aliases. // There might be symlinks associating Certificate IDs to Public Keys, so this // method only returns the IDs that aren't symlinks -func listKeys(s LimitedFileStore) []string { - var keyIDList []string +func listKeys(s LimitedFileStore) map[string]string { + keyIDMap := make(map[string]string) for _, f := range s.ListFiles(false) { - keyID := strings.TrimSpace(strings.TrimSuffix(f, filepath.Ext(f))) - keyID = keyID[:strings.LastIndex(keyID, "_")] - keyIDList = append(keyIDList, keyID) + keyIDFull := strings.TrimSpace(strings.TrimSuffix(f, filepath.Ext(f))) + keyID := keyIDFull[:strings.LastIndex(keyIDFull, "_")] + keyAlias := keyIDFull[strings.LastIndex(keyIDFull, "_")+1:] + keyIDMap[keyID] = keyAlias } - return keyIDList + return keyIDMap } // RemoveKey removes the key from the keyfilestore diff --git a/trustmanager/keystore.go b/trustmanager/keystore.go index 66de343236..ba5fb1a1a4 100644 --- a/trustmanager/keystore.go +++ b/trustmanager/keystore.go @@ -42,7 +42,7 @@ const ( type KeyStore interface { AddKey(name, alias string, privKey data.PrivateKey) error GetKey(name string) (data.PrivateKey, string, error) - ListKeys() []string + ListKeys() map[string]string RemoveKey(name string) error }