mirror of https://github.com/docker/docs.git
293 lines
9.4 KiB
Go
293 lines
9.4 KiB
Go
package keystoremanager
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/notary/cryptoservice"
|
|
"github.com/docker/notary/trustmanager"
|
|
"github.com/endophage/gotuf/data"
|
|
"github.com/endophage/gotuf/signed"
|
|
)
|
|
|
|
// KeyStoreManager is an abstraction around the root and non-root key stores,
|
|
// and related CA stores
|
|
type KeyStoreManager struct {
|
|
rootKeyStore *trustmanager.KeyFileStore
|
|
nonRootKeyStore *trustmanager.KeyFileStore
|
|
|
|
trustedCAStore trustmanager.X509Store
|
|
trustedCertificateStore trustmanager.X509Store
|
|
}
|
|
|
|
const (
|
|
trustDir = "trusted_certificates"
|
|
privDir = "private"
|
|
rootKeysSubdir = "root_keys"
|
|
nonRootKeysSubdir = "tuf_keys"
|
|
rsaRootKeySize = 4096 // Used for new root keys
|
|
)
|
|
|
|
// NewKeyStoreManager returns an initialized KeyStoreManager, or an error
|
|
// if it fails to create the KeyFileStores or load certificates
|
|
func NewKeyStoreManager(baseDir string) (*KeyStoreManager, error) {
|
|
nonRootKeysPath := filepath.Join(baseDir, privDir, nonRootKeysSubdir)
|
|
nonRootKeyStore, err := trustmanager.NewKeyFileStore(nonRootKeysPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Load the keystore that will hold all of our encrypted Root Private Keys
|
|
rootKeysPath := filepath.Join(baseDir, privDir, rootKeysSubdir)
|
|
rootKeyStore, err := trustmanager.NewKeyFileStore(rootKeysPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
trustPath := filepath.Join(baseDir, trustDir)
|
|
|
|
// Load all CAs that aren't expired and don't use SHA1
|
|
trustedCAStore, err := trustmanager.NewX509FilteredFileStore(trustPath, func(cert *x509.Certificate) bool {
|
|
return cert.IsCA && cert.BasicConstraintsValid && cert.SubjectKeyId != nil &&
|
|
time.Now().Before(cert.NotAfter) &&
|
|
cert.SignatureAlgorithm != x509.SHA1WithRSA &&
|
|
cert.SignatureAlgorithm != x509.DSAWithSHA1 &&
|
|
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Load all individual (non-CA) certificates that aren't expired and don't use SHA1
|
|
trustedCertificateStore, err := trustmanager.NewX509FilteredFileStore(trustPath, func(cert *x509.Certificate) bool {
|
|
return !cert.IsCA &&
|
|
time.Now().Before(cert.NotAfter) &&
|
|
cert.SignatureAlgorithm != x509.SHA1WithRSA &&
|
|
cert.SignatureAlgorithm != x509.DSAWithSHA1 &&
|
|
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &KeyStoreManager{
|
|
rootKeyStore: rootKeyStore,
|
|
nonRootKeyStore: nonRootKeyStore,
|
|
trustedCAStore: trustedCAStore,
|
|
trustedCertificateStore: trustedCertificateStore,
|
|
}, nil
|
|
}
|
|
|
|
// RootKeyStore returns the root key store being managed by this
|
|
// KeyStoreManager
|
|
func (km *KeyStoreManager) RootKeyStore() *trustmanager.KeyFileStore {
|
|
return km.rootKeyStore
|
|
}
|
|
|
|
// NonRootKeyStore returns the non-root key store being managed by this
|
|
// KeyStoreManager
|
|
func (km *KeyStoreManager) NonRootKeyStore() *trustmanager.KeyFileStore {
|
|
return km.nonRootKeyStore
|
|
}
|
|
|
|
// TrustedCertificateStore returns the trusted certificate store being managed
|
|
// by this KeyStoreManager
|
|
func (km *KeyStoreManager) TrustedCertificateStore() trustmanager.X509Store {
|
|
return km.trustedCertificateStore
|
|
}
|
|
|
|
// TrustedCAStore returns the CA store being managed by this KeyStoreManager
|
|
func (km *KeyStoreManager) TrustedCAStore() trustmanager.X509Store {
|
|
return km.trustedCAStore
|
|
}
|
|
|
|
// AddTrustedCert adds a cert to the trusted certificate store (not the CA
|
|
// store)
|
|
func (km *KeyStoreManager) AddTrustedCert(cert *x509.Certificate) {
|
|
km.trustedCertificateStore.AddCert(cert)
|
|
}
|
|
|
|
// AddTrustedCACert adds a cert to the trusted CA certificate store
|
|
func (km *KeyStoreManager) AddTrustedCACert(cert *x509.Certificate) {
|
|
km.trustedCAStore.AddCert(cert)
|
|
}
|
|
|
|
// GenRootKey generates a new root key protected by a given passphrase
|
|
// TODO(diogo): show not create keys manually, should use a cryptoservice instead
|
|
func (km *KeyStoreManager) GenRootKey(algorithm, passphrase string) (string, error) {
|
|
var err error
|
|
var privKey data.PrivateKey
|
|
|
|
// We don't want external API callers to rely on internal TUF data types, so
|
|
// the API here should continue to receive a string algorithm, and ensure
|
|
// that it is downcased
|
|
switch data.KeyAlgorithm(strings.ToLower(algorithm)) {
|
|
case data.RSAKey:
|
|
privKey, err = trustmanager.GenerateRSAKey(rand.Reader, rsaRootKeySize)
|
|
case data.ECDSAKey:
|
|
privKey, err = trustmanager.GenerateECDSAKey(rand.Reader)
|
|
default:
|
|
return "", fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", algorithm)
|
|
|
|
}
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to generate private key: %v", err)
|
|
}
|
|
|
|
// Changing the root
|
|
km.rootKeyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase)
|
|
|
|
return privKey.ID(), nil
|
|
}
|
|
|
|
// GetRootCryptoService retreives a root key and a cryptoservice to use with it
|
|
func (km *KeyStoreManager) GetRootCryptoService(rootKeyID, passphrase string) (*cryptoservice.UnlockedCryptoService, error) {
|
|
privKey, err := km.rootKeyStore.GetDecryptedKey(rootKeyID, passphrase)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not get decrypted root key with keyID: %s, %v", rootKeyID, err)
|
|
}
|
|
|
|
cryptoService := cryptoservice.NewCryptoService("", km.rootKeyStore, passphrase)
|
|
|
|
return cryptoservice.NewUnlockedCryptoService(privKey, cryptoService), nil
|
|
}
|
|
|
|
/*
|
|
ValidateRoot iterates over every root key included in the TUF data and
|
|
attempts to validate the certificate by first checking for an exact match on
|
|
the certificate store, and subsequently trying to find a valid chain on the
|
|
trustedCAStore.
|
|
|
|
When this is being used with a notary repository, the dnsName parameter should
|
|
be the GUN associated with the repository.
|
|
|
|
Example TUF Content for root role:
|
|
"roles" : {
|
|
"root" : {
|
|
"threshold" : 1,
|
|
"keyids" : [
|
|
"e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38"
|
|
]
|
|
},
|
|
...
|
|
}
|
|
|
|
Example TUF Content for root key:
|
|
"e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38" : {
|
|
"keytype" : "RSA",
|
|
"keyval" : {
|
|
"private" : "",
|
|
"public" : "Base64-encoded, PEM encoded x509 Certificate"
|
|
}
|
|
}
|
|
*/
|
|
func (km *KeyStoreManager) ValidateRoot(root *data.Signed, dnsName string) error {
|
|
rootSigned, err := data.RootFromSigned(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// iterate over every keyID for the root role inside of roots.json
|
|
validKeys := make(map[string]*data.PublicKey)
|
|
for _, keyID := range rootSigned.Signed.Roles["root"].KeyIDs {
|
|
// decode all the x509 certificates that were bundled with this
|
|
// specific root key
|
|
decodedCerts, err := trustmanager.LoadCertBundleFromPEM([]byte(rootSigned.Signed.Keys[keyID].Public()))
|
|
if err != nil {
|
|
logrus.Debugf("error while parsing root certificate with keyID: %s, %v", keyID, err)
|
|
continue
|
|
}
|
|
|
|
// get all non-CA certificates in the decoded certificates
|
|
leafCerts := trustmanager.GetLeafCerts(decodedCerts)
|
|
|
|
// gf we got no leaf certificates or we got more than one, fail
|
|
if len(leafCerts) != 1 {
|
|
logrus.Debugf("error while parsing root certificate with keyID: %s, %v", keyID, err)
|
|
continue
|
|
}
|
|
|
|
// get the ID of the leaf certificate
|
|
leafCert := leafCerts[0]
|
|
leafID, err := trustmanager.FingerprintCert(leafCert)
|
|
if err != nil {
|
|
logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", keyID, err)
|
|
continue
|
|
}
|
|
|
|
// retrieve all the trusted certificates that match this dns Name
|
|
certsForCN, err := km.certificateStore.GetCertificatesByCN(dnsName)
|
|
|
|
// if there are no certificates with this CN, lets TOFU!
|
|
// note that this logic should only exist in docker 1.8
|
|
if len(certsForCN) == 0 {
|
|
km.certificateStore.AddCert(leafCert)
|
|
certsForCN = append(certsForCN, leafCert)
|
|
}
|
|
|
|
// iterate over all known certificates for this CN and see if any are trusted
|
|
for _, cert := range certsForCN {
|
|
// Check to see if there is an exact match of this certificate.
|
|
certID, err := trustmanager.FingerprintCert(cert)
|
|
if err == nil && certID == leafID {
|
|
validKeys[keyID] = rootSigned.Signed.Keys[keyID]
|
|
}
|
|
}
|
|
|
|
// Check to see if this leafCertificate has a chain to one of the Root
|
|
// CAs of our CA Store.
|
|
err = trustmanager.Verify(km.caStore, dnsName, decodedCerts)
|
|
if err == nil {
|
|
validKeys[keyID] = rootSigned.Signed.Keys[keyID]
|
|
}
|
|
}
|
|
|
|
if len(validKeys) < 1 {
|
|
return errors.New("could not validate the path to a trusted root")
|
|
}
|
|
|
|
// TODO(david): change hardcoded minversion on TUF.
|
|
newRootKey, err := signed.VerifyRoot(root, 0, validKeys, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// VerifyRoot returns a non-nil value if there is a root key rotation happening
|
|
// if this happens, we should replace the old root of trust with the new one
|
|
if newRootKey != nil {
|
|
// retrieve all the certificates associated with the new root key
|
|
keyID := newRootKey.ID()
|
|
decodedCerts, err := trustmanager.LoadCertBundleFromPEM([]byte(rootSigned.Signed.Keys[keyID].Public()))
|
|
if err != nil {
|
|
logrus.Debugf("error while parsing root certificate with keyID: %s, %v", keyID, err)
|
|
return err
|
|
}
|
|
|
|
// adds trust on the certificate of the new root key
|
|
leafCerts := trustmanager.GetLeafCerts(decodedCerts)
|
|
err = km.certificateStore.AddCert(leafCerts[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// iterate over all old valid keys and removes the associated certificates
|
|
// were previously valid
|
|
for _, key := range validKeys {
|
|
cert, err := km.certificateStore.GetCertificateByCertID(key.ID())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Remove the old certificate
|
|
km.certificateStore.RemoveCert(cert)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|