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
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)
caStore.AddCertFromPEM(cert)
}
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)
}
}
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)

View File

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

View File

@ -48,35 +48,35 @@ var cmdTufAdd = &cobra.Command{
var cmdTufRemove = &cobra.Command{
Use: "remove [ GUN ] <target>",
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 ] <target name>",
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)

View File

@ -57,8 +57,14 @@ func (s X509FileStore) AddCert(cert *x509.Certificate) error {
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)
filename := path.Join(s.baseDir, string(fingerprint)+certExtension)
filename = path.Join(s.baseDir, string(fingerprint)+certExtension)
}
if err := s.addNamedCert(cert, filename); err != nil {
return err
}

View File

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