Merge pull request #154 from docker/diogo-add-remove-cert

Added cli cert command, changed keylisting to be a map
This commit is contained in:
Aaron Lehmann 2015-07-28 20:13:32 -07:00
commit 2ac1d44be1
9 changed files with 259 additions and 84 deletions

139
cmd/notary/cert.go Normal file
View File

@ -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))
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)

View File

@ -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)) {

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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
}