Added cliCryptoService

This commit is contained in:
Diogo Monica 2015-06-17 16:05:16 -07:00
parent ff169897b6
commit 712ff83945
6 changed files with 184 additions and 79 deletions

View File

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

View File

@ -1,24 +1,19 @@
package main package main
import ( import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem"
"fmt" "fmt"
"math" "math"
"math/big" "math/big"
"net/url" "net/url"
"os" "os"
"path/filepath"
"time" "time"
"github.com/docker/vetinari/trustmanager" "github.com/docker/vetinari/trustmanager"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
) )
var subjectKeyID string var subjectKeyID string
@ -99,7 +94,7 @@ func keysTrust(cmd *cobra.Command, args []string) {
fatalf("not enough arguments provided") fatalf("not enough arguments provided")
} }
qualifiedDN := args[0] gun := args[0]
certLocationStr := args[1] certLocationStr := args[1]
// Verify if argument is a valid URL // Verify if argument is a valid URL
url, err := url.Parse(certLocationStr) url, err := url.Parse(certLocationStr)
@ -109,9 +104,9 @@ func keysTrust(cmd *cobra.Command, args []string) {
if err != nil { if err != nil {
fatalf("error retreiving certificate from url (%s): %v", certLocationStr, err) fatalf("error retreiving certificate from url (%s): %v", certLocationStr, err)
} }
err = cert.VerifyHostname(qualifiedDN) err = cert.VerifyHostname(gun)
if err != nil { 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) err = caStore.AddCert(cert)
if err != nil { if err != nil {
@ -156,33 +151,17 @@ func keysGenerate(cmd *cobra.Command, args []string) {
} }
// (diogo): Validate GUNs // (diogo): Validate GUNs
qualifiedDN := args[0] gun := args[0]
key, err := generateKey(qualifiedDN) _, cert, err := generateKeyAndCert(gun)
if err != nil { if err != nil {
fatalf("could not generate key: %v", err) fatalf("could not generate key: %v", err)
} }
template := newCertificate(qualifiedDN, qualifiedDN) caStore.AddCertFromPEM(cert)
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") func newCertificate(gun, organization string) *x509.Certificate {
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)
}
}
func newCertificate(qualifiedDN, organization string) *x509.Certificate {
notBefore := time.Now() notBefore := time.Now()
notAfter := notBefore.Add(time.Hour * 24 * 365 * 2) notAfter := notBefore.Add(time.Hour * 24 * 365 * 2)
@ -196,7 +175,7 @@ func newCertificate(qualifiedDN, organization string) *x509.Certificate {
SerialNumber: serialNumber, SerialNumber: serialNumber,
Subject: pkix.Name{ Subject: pkix.Name{
Organization: []string{organization}, Organization: []string{organization},
CommonName: qualifiedDN, CommonName: gun,
}, },
NotBefore: notBefore, NotBefore: notBefore,
NotAfter: notAfter, 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) { func printCert(cert *x509.Certificate) {
timeDifference := cert.NotAfter.Sub(time.Now()) timeDifference := cert.NotAfter.Sub(time.Now())
subjectKeyID := trustmanager.FingerprintCert(cert) subjectKeyID := trustmanager.FingerprintCert(cert)

View File

@ -15,9 +15,11 @@ import (
) )
const configFileName string = "config" const configFileName string = "config"
// Default paths should end with a '/' so directory creation works correctly
const configPath string = ".docker/trust/" const configPath string = ".docker/trust/"
const caDir string = ".docker/trust/certificate_authorities/" const trustDir string = configPath + "repository_certificates/"
const privDir string = ".docker/trust/private/" const privDir string = configPath + "private/"
var caStore trustmanager.X509Store var caStore trustmanager.X509Store
var privStore trustmanager.X509Store var privStore trustmanager.X509Store
@ -45,26 +47,32 @@ func init() {
if err != nil { if err != nil {
// Ignore if the configuration file doesn't exist, we can use the defaults // Ignore if the configuration file doesn't exist, we can use the defaults
if !os.IsNotExist(err) { 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 // 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))) viper.SetDefault("privDir", path.Join(homeDir, path.Dir(privDir)))
// Get the final value for the CA directory // Get the final value for the CA directory
finalcaDir := viper.GetString("caDir") finalTrustDir := viper.GetString("trustDir")
finalPrivDir := viper.GetString("privDir") finalPrivDir := viper.GetString("privDir")
// Ensure the existence of the CAs directory // Ensure the existence of the CAs directory
createDirectory(finalcaDir) err = trustmanager.CreateDirectory(finalTrustDir)
createDirectory(finalPrivDir) 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 // Load all CAs that aren't expired and don't use SHA1
// We could easily add "return cert.IsCA && cert.BasicConstraintsValid" in order // We could easily add "return cert.IsCA && cert.BasicConstraintsValid" in order
// to have only valid CA certificates being loaded // 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) && return time.Now().Before(cert.NotAfter) &&
cert.SignatureAlgorithm != x509.SHA1WithRSA && cert.SignatureAlgorithm != x509.SHA1WithRSA &&
cert.SignatureAlgorithm != x509.DSAWithSHA1 && cert.SignatureAlgorithm != x509.DSAWithSHA1 &&
@ -95,9 +103,3 @@ func fatalf(format string, args ...interface{}) {
fmt.Printf("* fatal: "+format+"\n", args...) fmt.Printf("* fatal: "+format+"\n", args...)
os.Exit(1) os.Exit(1)
} }
func createDirectory(dir string) {
if err := os.MkdirAll(dir, 0700); err != nil {
fatalf("cannot create directory: %v", err)
}
}

View File

@ -48,35 +48,35 @@ var cmdTufAdd = &cobra.Command{
var cmdTufRemove = &cobra.Command{ var cmdTufRemove = &cobra.Command{
Use: "remove [ GUN ] <target>", Use: "remove [ GUN ] <target>",
Short: "Removes a target from the TUF repo.", 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, Run: tufRemove,
} }
var cmdTufInit = &cobra.Command{ var cmdTufInit = &cobra.Command{
Use: "init [ GUN ]", Use: "init [ GUN ]",
Short: "initializes the local TUF repository.", 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, Run: tufInit,
} }
var cmdTufList = &cobra.Command{ var cmdTufList = &cobra.Command{
Use: "list [ GUN ]", Use: "list [ GUN ]",
Short: "Lists all targets in a TUF repository.", 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, Run: tufList,
} }
var cmdTufLookup = &cobra.Command{ var cmdTufLookup = &cobra.Command{
Use: "lookup [ GUN ] <target name>", Use: "lookup [ GUN ] <target name>",
Short: "Looks up a specific TUF target in a repository.", 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, Run: tufLookup,
} }
var cmdTufPush = &cobra.Command{ var cmdTufPush = &cobra.Command{
Use: "push [ GUN ]", Use: "push [ GUN ]",
Short: "initializes the local TUF repository.", 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, Run: tufPush,
} }
@ -161,6 +161,7 @@ func tufInit(cmd *cobra.Command, args []string) {
} }
gun := args[0] gun := args[0]
// cryptoService := NewCryptoService(gun)
kdb := keys.NewDB() kdb := keys.NewDB()
repo := tuf.NewTufRepo(kdb, nil) repo := tuf.NewTufRepo(kdb, nil)

View File

@ -57,8 +57,14 @@ func (s X509FileStore) AddCert(cert *x509.Certificate) error {
return errors.New("adding nil Certificate to X509Store") return errors.New("adding nil Certificate to X509Store")
} }
var filename string
if cert.Subject.CommonName != "" {
filename = path.Join(s.baseDir, cert.Subject.CommonName+certExtension)
} else {
fingerprint := FingerprintCert(cert) fingerprint := FingerprintCert(cert)
filename := path.Join(s.baseDir, string(fingerprint)+certExtension) filename = path.Join(s.baseDir, string(fingerprint)+certExtension)
}
if err := s.addNamedCert(cert, filename); err != nil { if err := s.addNamedCert(cert, filename); err != nil {
return err return err
} }

View File

@ -10,6 +10,7 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"os"
"path" "path"
"path/filepath" "path/filepath"
) )
@ -54,7 +55,12 @@ func saveCertificate(cert *x509.Certificate, filename string) error {
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw} block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
pemdata := string(pem.EncodeToMemory(&block)) 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 { if err != nil {
return err return err
} }
@ -124,3 +130,11 @@ func loadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) {
return nil, errors.New("no certificates found in PEM data") 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
}