From 712ff83945a89da027bd909cc8795d45869ce8c1 Mon Sep 17 00:00:00 2001 From: Diogo Monica Date: Wed, 17 Jun 2015 16:05:16 -0700 Subject: [PATCH] Added cliCryptoService --- cmd/notary/cli_crypto_service.go | 130 +++++++++++++++++++++++++++++++ cmd/notary/keys.go | 66 +++------------- cmd/notary/main.go | 30 +++---- cmd/notary/tuf.go | 11 +-- trustmanager/X509FileStore.go | 10 ++- trustmanager/X509Utils.go | 16 +++- 6 files changed, 184 insertions(+), 79 deletions(-) create mode 100644 cmd/notary/cli_crypto_service.go diff --git a/cmd/notary/cli_crypto_service.go b/cmd/notary/cli_crypto_service.go new file mode 100644 index 0000000000..316d95dac1 --- /dev/null +++ b/cmd/notary/cli_crypto_service.go @@ -0,0 +1,130 @@ +package main + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/pem" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/docker/vetinari/trustmanager" + "github.com/endophage/gotuf/data" + "github.com/spf13/viper" +) + +type cliCryptoService struct { + privateKeys map[string]*data.PrivateKey + gun string +} + +func NewCryptoService(gun string) *cliCryptoService { + return &cliCryptoService{privateKeys: make(map[string]*data.PrivateKey), gun: gun} +} + +// Create is used to generate keys for targets, snapshots and timestamps +func (ccs *cliCryptoService) Create() (*data.PublicKey, error) { + _, cert, err := generateKeyAndCert(ccs.gun) + if err != nil { + return nil, err + } + + return data.NewPublicKey("RSA", string(cert)), nil +} + +// Sign returns the signatures for data with the given keyIDs +func (ccs *cliCryptoService) 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 _, kID := range keyIDs { + // Get the PrivateKey filename + privKeyFilename := filepath.Join(viper.GetString("privDir"), ccs.gun, kID+".key") + // Read PrivateKey from file + privPEMBytes, err := ioutil.ReadFile(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[:]) + if err != nil { + return nil, err + } + + // Append signatures to result array + signatures = append(signatures, data.Signature{ + KeyID: kID, + Method: "RSASSA-PKCS1-V1_5-SIGN", + Signature: sig[:], + }) + } + return signatures, nil +} + +//TODO (diogo): Add support for EC P384 +func generateKeyAndCert(gun string) (crypto.PrivateKey, []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) + } + + keyBytes := x509.MarshalPKCS1PrivateKey(key) + + // 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 := 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 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + + // Create new TUF Key so we can compute the TUF-compliant ID + tufKey := data.NewTUFKey("RSA", string(cert), "") + + // The key is going to be stored in the private directory, using the GUN and + // the filename will be the TUF-compliant ID + privKeyFilename := filepath.Join(viper.GetString("privDir"), gun, tufKey.ID()+".key") + + // If GUN is in the form of 'foo/bar' ensures that private key is stored in the + // adequate sub-directory + err = trustmanager.CreateDirectory(privKeyFilename) + if err != nil { + return nil, nil, fmt.Errorf("could not create directory for private key: %v", err) + } + + // Opens a FD to the file with the correct permissions + keyOut, err := os.OpenFile(privKeyFilename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return nil, nil, fmt.Errorf("could not write privatekey: %v", err) + } + defer keyOut.Close() + + // Encodes the private key as PEM and writes it + err = pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}) + if err != nil { + return nil, nil, fmt.Errorf("failed to encode key: %v", err) + } + + return key, cert, nil +} diff --git a/cmd/notary/keys.go b/cmd/notary/keys.go index e29b281f4b..7763c437f2 100644 --- a/cmd/notary/keys.go +++ b/cmd/notary/keys.go @@ -1,24 +1,19 @@ package main import ( - "crypto" - "crypto/ecdsa" - "crypto/elliptic" "crypto/rand" "crypto/x509" "crypto/x509/pkix" - "encoding/pem" "fmt" "math" "math/big" "net/url" "os" - "path/filepath" "time" "github.com/docker/vetinari/trustmanager" + "github.com/spf13/cobra" - "github.com/spf13/viper" ) var subjectKeyID string @@ -99,7 +94,7 @@ func keysTrust(cmd *cobra.Command, args []string) { fatalf("not enough arguments provided") } - qualifiedDN := args[0] + gun := args[0] certLocationStr := args[1] // Verify if argument is a valid URL url, err := url.Parse(certLocationStr) @@ -109,9 +104,9 @@ func keysTrust(cmd *cobra.Command, args []string) { if err != nil { fatalf("error retreiving certificate from url (%s): %v", certLocationStr, err) } - err = cert.VerifyHostname(qualifiedDN) + err = cert.VerifyHostname(gun) if err != nil { - fatalf("certificate does not match the Qualified Docker Name: %v", err) + fatalf("certificate does not match the Global Unique Name: %v", err) } err = caStore.AddCert(cert) if err != nil { @@ -156,33 +151,17 @@ func keysGenerate(cmd *cobra.Command, args []string) { } // (diogo): Validate GUNs - qualifiedDN := args[0] + gun := args[0] - key, err := generateKey(qualifiedDN) + _, cert, err := generateKeyAndCert(gun) if err != nil { fatalf("could not generate key: %v", err) } - template := newCertificate(qualifiedDN, qualifiedDN) - derBytes, err := x509.CreateCertificate(rand.Reader, template, template, key.(crypto.Signer).Public(), key) - if err != nil { - fatalf("failed to generate certificate: %s", err) - } - - certName := filepath.Join(viper.GetString("privDir"), qualifiedDN+".crt") - certOut, err := os.Create(certName) - if err != nil { - fatalf("failed to save certificate: %s", err) - } - - defer certOut.Close() - err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) - if err != nil { - fatalf("failed to save certificate: %s", err) - } + caStore.AddCertFromPEM(cert) } -func newCertificate(qualifiedDN, organization string) *x509.Certificate { +func newCertificate(gun, organization string) *x509.Certificate { notBefore := time.Now() notAfter := notBefore.Add(time.Hour * 24 * 365 * 2) @@ -196,7 +175,7 @@ func newCertificate(qualifiedDN, organization string) *x509.Certificate { SerialNumber: serialNumber, Subject: pkix.Name{ Organization: []string{organization}, - CommonName: qualifiedDN, + CommonName: gun, }, NotBefore: notBefore, NotAfter: notAfter, @@ -207,33 +186,6 @@ func newCertificate(qualifiedDN, organization string) *x509.Certificate { } } -func generateKey(qualifiedDN string) (crypto.PrivateKey, error) { - curve := elliptic.P384() - key, err := ecdsa.GenerateKey(curve, rand.Reader) - if err != nil { - return nil, fmt.Errorf("could not generate private key: %v", err) - } - - keyBytes, err := x509.MarshalECPrivateKey(key) - if err != nil { - return nil, fmt.Errorf("could not marshal private key: %v", err) - } - - keyName := filepath.Join(viper.GetString("privDir"), qualifiedDN+".key") - keyOut, err := os.OpenFile(keyName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return nil, fmt.Errorf("could not write privatekey: %v", err) - } - defer keyOut.Close() - - err = pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}) - if err != nil { - return nil, fmt.Errorf("failed to encode key: %v", err) - } - - return key, nil -} - func printCert(cert *x509.Certificate) { timeDifference := cert.NotAfter.Sub(time.Now()) subjectKeyID := trustmanager.FingerprintCert(cert) diff --git a/cmd/notary/main.go b/cmd/notary/main.go index 7f27d05bc3..67dd6bc252 100644 --- a/cmd/notary/main.go +++ b/cmd/notary/main.go @@ -15,9 +15,11 @@ import ( ) const configFileName string = "config" + +// Default paths should end with a '/' so directory creation works correctly const configPath string = ".docker/trust/" -const caDir string = ".docker/trust/certificate_authorities/" -const privDir string = ".docker/trust/private/" +const trustDir string = configPath + "repository_certificates/" +const privDir string = configPath + "private/" var caStore trustmanager.X509Store var privStore trustmanager.X509Store @@ -45,26 +47,32 @@ func init() { if err != nil { // Ignore if the configuration file doesn't exist, we can use the defaults if !os.IsNotExist(err) { - panic(fmt.Errorf("Fatal error config file: %s \n", err)) + fatalf("fatal error config file: %v", err) } } // Set up the defaults for our config - viper.SetDefault("caDir", path.Join(homeDir, path.Dir(caDir))) + viper.SetDefault("trustDir", path.Join(homeDir, path.Dir(trustDir))) viper.SetDefault("privDir", path.Join(homeDir, path.Dir(privDir))) // Get the final value for the CA directory - finalcaDir := viper.GetString("caDir") + finalTrustDir := viper.GetString("trustDir") finalPrivDir := viper.GetString("privDir") // Ensure the existence of the CAs directory - createDirectory(finalcaDir) - createDirectory(finalPrivDir) + err = trustmanager.CreateDirectory(finalTrustDir) + if err != nil { + fatalf("could not create directory: %v", err) + } + err = trustmanager.CreateDirectory(finalPrivDir) + if err != nil { + fatalf("could not create directory: %v", err) + } // Load all CAs that aren't expired and don't use SHA1 // We could easily add "return cert.IsCA && cert.BasicConstraintsValid" in order // to have only valid CA certificates being loaded - caStore = trustmanager.NewX509FilteredFileStore(finalcaDir, func(cert *x509.Certificate) bool { + caStore = trustmanager.NewX509FilteredFileStore(finalTrustDir, func(cert *x509.Certificate) bool { return time.Now().Before(cert.NotAfter) && cert.SignatureAlgorithm != x509.SHA1WithRSA && cert.SignatureAlgorithm != x509.DSAWithSHA1 && @@ -95,9 +103,3 @@ func fatalf(format string, args ...interface{}) { fmt.Printf("* fatal: "+format+"\n", args...) os.Exit(1) } - -func createDirectory(dir string) { - if err := os.MkdirAll(dir, 0700); err != nil { - fatalf("cannot create directory: %v", err) - } -} diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 082f45cc94..db43854d04 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -48,35 +48,35 @@ var cmdTufAdd = &cobra.Command{ var cmdTufRemove = &cobra.Command{ Use: "remove [ GUN ] ", Short: "Removes a target from the TUF repo.", - Long: "removes a target from the local TUF repo identified by a Qualified Docker Name.", + Long: "removes a target from the local TUF repo identified by a Global Unique Name.", Run: tufRemove, } var cmdTufInit = &cobra.Command{ Use: "init [ GUN ]", Short: "initializes the local TUF repository.", - Long: "creates locally the initial set of TUF metadata for the Qualified Docker Name.", + Long: "creates locally the initial set of TUF metadata for the Global Unique Name.", Run: tufInit, } var cmdTufList = &cobra.Command{ Use: "list [ GUN ]", Short: "Lists all targets in a TUF repository.", - Long: "lists all the targets in the TUF repository identified by the Qualified Docker Name.", + Long: "lists all the targets in the TUF repository identified by the Global Unique Name.", Run: tufList, } var cmdTufLookup = &cobra.Command{ Use: "lookup [ GUN ] ", Short: "Looks up a specific TUF target in a repository.", - Long: "looks up a TUF target in a repository given a Qualified Docker Name.", + Long: "looks up a TUF target in a repository given a Global Unique Name.", Run: tufLookup, } var cmdTufPush = &cobra.Command{ Use: "push [ GUN ]", Short: "initializes the local TUF repository.", - Long: "creates locally the initial set of TUF metadata for the Qualified Docker Name.", + Long: "creates locally the initial set of TUF metadata for the Global Unique Name.", Run: tufPush, } @@ -161,6 +161,7 @@ func tufInit(cmd *cobra.Command, args []string) { } gun := args[0] + // cryptoService := NewCryptoService(gun) kdb := keys.NewDB() repo := tuf.NewTufRepo(kdb, nil) diff --git a/trustmanager/X509FileStore.go b/trustmanager/X509FileStore.go index e25bff881d..c4a878f5b1 100644 --- a/trustmanager/X509FileStore.go +++ b/trustmanager/X509FileStore.go @@ -57,8 +57,14 @@ func (s X509FileStore) AddCert(cert *x509.Certificate) error { return errors.New("adding nil Certificate to X509Store") } - fingerprint := FingerprintCert(cert) - filename := path.Join(s.baseDir, string(fingerprint)+certExtension) + var filename string + if cert.Subject.CommonName != "" { + filename = path.Join(s.baseDir, cert.Subject.CommonName+certExtension) + } else { + fingerprint := FingerprintCert(cert) + filename = path.Join(s.baseDir, string(fingerprint)+certExtension) + } + if err := s.addNamedCert(cert, filename); err != nil { return err } diff --git a/trustmanager/X509Utils.go b/trustmanager/X509Utils.go index 3cfa761a47..c9fcbe4c91 100644 --- a/trustmanager/X509Utils.go +++ b/trustmanager/X509Utils.go @@ -10,6 +10,7 @@ import ( "io/ioutil" "net/http" "net/url" + "os" "path" "path/filepath" ) @@ -54,7 +55,12 @@ func saveCertificate(cert *x509.Certificate, filename string) error { block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw} pemdata := string(pem.EncodeToMemory(&block)) - err := ioutil.WriteFile(filename, []byte(pemdata), 0600) + err := CreateDirectory(filename) + if err != nil { + return err + } + + err = ioutil.WriteFile(filename, []byte(pemdata), 0600) if err != nil { return err } @@ -124,3 +130,11 @@ func loadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) { return nil, errors.New("no certificates found in PEM data") } + +func CreateDirectory(dir string) error { + cleanDir := filepath.Dir(dir) + if err := os.MkdirAll(cleanDir, 0700); err != nil { + return fmt.Errorf("cannot create directory: %v", err) + } + return nil +}