mirror of https://github.com/docker/docs.git
Added cliCryptoService
This commit is contained in:
parent
ff169897b6
commit
712ff83945
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue