mirror of https://github.com/docker/docs.git
Merge pull request #83 from docker/cooking-the-tofu-to-support-rotations
Cooking the tofu to support rotations
This commit is contained in:
commit
9d31d343f3
|
@ -63,7 +63,7 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/endophage/gotuf",
|
||||
"Rev": "a8a23ab6e67bd0e9fbaf563aabd9e6ee7ea344d2"
|
||||
"Rev": "9640c9b3f2ff0ba75baf7d1a57632e16cb78d5e6"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||
|
|
|
@ -52,7 +52,13 @@ func VerifyRoot(s *data.Signed, minVersion int, keys map[string]data.PublicKey)
|
|||
continue
|
||||
}
|
||||
|
||||
if err := verifier.Verify(keys[sig.KeyID], sig.Signature, msg); err != nil {
|
||||
key, ok := keys[sig.KeyID]
|
||||
if !ok {
|
||||
logrus.Debugf("continuing b/c signing key isn't present in keys: %s\n", sig.KeyID)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := verifier.Verify(key, sig.Signature, msg); err != nil {
|
||||
logrus.Debugf("continuing b/c signature was invalid\n")
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package client
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
|
@ -125,7 +124,7 @@ func validateRootSuccessfully(t *testing.T, rootType data.KeyAlgorithm) {
|
|||
assert.Len(t, repo.KeyStoreManager.TrustedCertificateStore().GetCertificates(), 1)
|
||||
|
||||
//
|
||||
// Test certificate mismatch logic. We remove all certs, and a different cert to the
|
||||
// Test certificate mismatch logic. We remove all certs, add a different cert to the
|
||||
// same CN, and expect ValidateRoot to fail
|
||||
//
|
||||
|
||||
|
@ -142,7 +141,7 @@ func validateRootSuccessfully(t *testing.T, rootType data.KeyAlgorithm) {
|
|||
// in the store for the dnsName docker.com/notary, so TOFUS doesn't apply
|
||||
_, err = repo.ListTargets()
|
||||
if assert.Error(t, err, "An error was expected") {
|
||||
assert.Equal(t, err, keystoremanager.ErrValidationFail)
|
||||
assert.Equal(t, err, &keystoremanager.ErrValidationFail{Reason: "failed to validate integrity of roots"})
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -187,7 +186,7 @@ func validateRootSuccessfully(t *testing.T, rootType data.KeyAlgorithm) {
|
|||
//
|
||||
err = repo.KeyStoreManager.ValidateRoot(&testSignedRoot, "secure.example.com")
|
||||
if assert.Error(t, err, "An error was expected") {
|
||||
assert.Equal(t, err, errors.New("tuf: valid signatures did not meet threshold"))
|
||||
assert.Equal(t, err, &keystoremanager.ErrValidationFail{Reason: "failed to validate integrity of roots"})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,7 +223,7 @@ func TestValidateRootWithInvalidData(t *testing.T) {
|
|||
//
|
||||
err = keyStoreManager.ValidateRoot(&testSignedRoot, "diogomonica.com/notary")
|
||||
if assert.Error(t, err, "An error was expected") {
|
||||
assert.Equal(t, err, keystoremanager.ErrValidationFail)
|
||||
assert.Equal(t, err, &keystoremanager.ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -252,7 +251,7 @@ func TestValidateRootWithInvalidData(t *testing.T) {
|
|||
|
||||
err = keyStoreManager.ValidateRoot(&testSignedRoot, "docker.com/notary")
|
||||
if assert.Error(t, err, "An error was expected") {
|
||||
assert.Equal(t, err, keystoremanager.ErrValidationFail)
|
||||
assert.Equal(t, err, &keystoremanager.ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -268,6 +267,6 @@ func TestValidateRootWithInvalidData(t *testing.T) {
|
|||
|
||||
err = keyStoreManager.ValidateRoot(&testSignedRoot, "docker.com/notary")
|
||||
if assert.Error(t, err, "An error was expected") {
|
||||
assert.Equal(t, err, keystoremanager.ErrValidationFail)
|
||||
assert.Equal(t, err, &keystoremanager.ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,14 +34,29 @@ const (
|
|||
rsaRootKeySize = 4096 // Used for new root keys
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrValidationFail is returned when there is no trusted certificate in any of the
|
||||
// root keys available in the roots.json
|
||||
ErrValidationFail = errors.New("could not validate the path to a trusted root")
|
||||
// ErrRootRotationFail is returned when we fail to do a full root key rotation
|
||||
// by either failing to add the new root certificate, or delete the old ones
|
||||
ErrRootRotationFail = errors.New("could not rotate trust to a new trusted root")
|
||||
)
|
||||
// ErrValidationFail is returned when there is no valid trusted certificates
|
||||
// being served inside of the roots.json
|
||||
type ErrValidationFail struct {
|
||||
Reason string
|
||||
}
|
||||
|
||||
// ErrValidationFail is returned when there is no valid trusted certificates
|
||||
// being served inside of the roots.json
|
||||
func (err ErrValidationFail) Error() string {
|
||||
return fmt.Sprintf("could not validate the path to a trusted root: %s", err.Reason)
|
||||
}
|
||||
|
||||
// ErrRootRotationFail is returned when we fail to do a full root key rotation
|
||||
// by either failing to add the new root certificate, or delete the old ones
|
||||
type ErrRootRotationFail struct {
|
||||
Reason string
|
||||
}
|
||||
|
||||
// ErrRootRotationFail is returned when we fail to do a full root key rotation
|
||||
// by either failing to add the new root certificate, or delete the old ones
|
||||
func (err ErrRootRotationFail) Error() string {
|
||||
return fmt.Sprintf("could not rotate trust to a new trusted root: %s", err.Reason)
|
||||
}
|
||||
|
||||
// NewKeyStoreManager returns an initialized KeyStoreManager, or an error
|
||||
// if it fails to create the KeyFileStores or load certificates
|
||||
|
@ -168,172 +183,241 @@ func (km *KeyStoreManager) GetRootCryptoService(rootKeyID, passphrase string) (*
|
|||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
ValidateRoot receives a new root, validates its correctness and attempts to
|
||||
do root key rotation if needed.
|
||||
|
||||
Currently this method operates on a Trust On First Use (TOFU) model: if we
|
||||
First we list the current trusted certificates we have for a particular GUN. If
|
||||
that list is non-empty means that we've already seen this repository before, and
|
||||
have a list of trusted certificates for it. In this case, we use this list of
|
||||
certificates to attempt to validate this root file.
|
||||
|
||||
If the previous validation suceeds, or in the case where we found no trusted
|
||||
certificates for this particular GUN, we check the integrity of the root by
|
||||
making sure that it is validated by itself. This means that we will attempt to
|
||||
validate the root data with the certificates that are included in the root keys
|
||||
themselves.
|
||||
|
||||
If this last steps succeeds, we attempt to do root rotation, by ensuring that
|
||||
we only trust the certificates that are present in the new root.
|
||||
|
||||
This mechanism of operation is essentially Trust On First Use (TOFU): if we
|
||||
have never seen a certificate for a particular CN, we trust it. If later we see
|
||||
a different certificate for that certificate, we return an ErrValidationFailed error.
|
||||
|
||||
Note that since we only allow trust data to be downloaded over an HTTPS channel
|
||||
we are using the current web-of-trust to validate the first download of the certificate
|
||||
we are using the current public PKI to validate the first download of the certificate
|
||||
adding an extra layer of security over the normal (SSH style) trust model.
|
||||
We shall call this: TOFUS.
|
||||
|
||||
ValidateRoot also supports root key rotation, trusting a new certificate that has
|
||||
been included in the roots.json, and removing trust in the old one.
|
||||
*/
|
||||
func (km *KeyStoreManager) ValidateRoot(root *data.Signed, dnsName string) error {
|
||||
logrus.Debugf("entered ValidateRoot with dns: %s", dnsName)
|
||||
rootSigned, err := data.RootFromSigned(root)
|
||||
func (km *KeyStoreManager) ValidateRoot(root *data.Signed, gun string) error {
|
||||
logrus.Debugf("entered ValidateRoot with dns: %s", gun)
|
||||
signedRoot, err := data.RootFromSigned(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validKeys will store all the keys that were considered valid either by
|
||||
// direct certificate match, or CA chain path
|
||||
validKeys := make(map[string]data.PublicKey)
|
||||
|
||||
// allCerts will keep a list of all leafCerts that were found, and is used
|
||||
// to aid on root certificate rotation
|
||||
allCerts := make(map[string]*x509.Certificate)
|
||||
|
||||
// Before we loop through all root keys available, make sure any exist
|
||||
rootRoles, ok := rootSigned.Signed.Roles["root"]
|
||||
if !ok {
|
||||
return errors.New("no root roles found in tuf metadata")
|
||||
// Retrieve all the leaf certificates in root for which the CN matches the GUN
|
||||
allValidCerts, err := validRootLeafCerts(signedRoot, gun)
|
||||
if err != nil {
|
||||
logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err)
|
||||
return &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}
|
||||
}
|
||||
|
||||
logrus.Debugf("found the following root keys in roots.json: %v", rootRoles.KeyIDs)
|
||||
// Retrieve all the trusted certificates that match this gun
|
||||
certsForCN, err := km.trustedCertificateStore.GetCertificatesByCN(gun)
|
||||
if err != nil {
|
||||
// If the error that we get back is different than ErrNoCertificatesFound
|
||||
// we couldn't check if there are any certificates with this CN already
|
||||
// trusted. Let's take the conservative approach and return a failed validation
|
||||
if _, ok := err.(*trustmanager.ErrNoCertificatesFound); !ok {
|
||||
logrus.Debugf("error retrieving trusted certificates for: %s, %v", gun, err)
|
||||
return &ErrValidationFail{Reason: "unable to retrieve trusted certificates"}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have certificates that match this specific GUN, let's make sure to
|
||||
// use them first to validate that this new root is valid.
|
||||
if len(certsForCN) != 0 {
|
||||
logrus.Debugf("found %d valid certificates for %s", len(certsForCN), gun)
|
||||
err = signed.VerifyRoot(root, 0, trustmanager.CertsToKeys(certsForCN))
|
||||
if err != nil {
|
||||
logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
|
||||
return &ErrValidationFail{Reason: "failed to validate integrity of roots"}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the integrity of the new root (does it have valid signatures)
|
||||
err = signed.VerifyRoot(root, 0, trustmanager.CertsToKeys(allValidCerts))
|
||||
if err != nil {
|
||||
logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
|
||||
return &ErrValidationFail{Reason: "failed to validate integrity of roots"}
|
||||
}
|
||||
|
||||
// Getting here means A) we had trusted certificates and both the
|
||||
// old and new validated this root; or B) we had no trusted certificates but
|
||||
// the new set of certificates has integrity (self-signed)
|
||||
logrus.Debugf("entering root certificate rotation for: %s", gun)
|
||||
|
||||
// Do root certificate rotation: we trust only the certs present in the new root
|
||||
// First we add all the new certificates (even if they already exist)
|
||||
for _, cert := range allValidCerts {
|
||||
err := km.trustedCertificateStore.AddCert(cert)
|
||||
if err != nil {
|
||||
// If the error is already exists we don't fail the rotation
|
||||
if _, ok := err.(*trustmanager.ErrCertExists); ok {
|
||||
continue
|
||||
}
|
||||
logrus.Debugf("error adding new trusted certificate for: %s, %v", gun, err)
|
||||
}
|
||||
// Getting the CertID for debug only, don't care about the error
|
||||
certID, _ := trustmanager.FingerprintCert(cert)
|
||||
logrus.Debugf("adding trust certificate with certID %s for %s", certID, gun)
|
||||
}
|
||||
|
||||
// Now we delete old certificates that aren't present in the new root
|
||||
for certID, cert := range certsToRemove(certsForCN, allValidCerts) {
|
||||
logrus.Debugf("removing certificate with certID: %s", certID)
|
||||
err = km.trustedCertificateStore.RemoveCert(cert)
|
||||
if err != nil {
|
||||
logrus.Debugf("failed to remove trusted certificate with keyID: %s, %v", certID, err)
|
||||
return &ErrRootRotationFail{Reason: "failed to rotate root keys"}
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Debugf("Root validation succeeded for %s", gun)
|
||||
return nil
|
||||
}
|
||||
|
||||
// validRootLeafCerts returns a list of non-exipired, non-sha1 certificates whoose
|
||||
// Common-Names match the provided GUN
|
||||
func validRootLeafCerts(root *data.SignedRoot, gun string) ([]*x509.Certificate, error) {
|
||||
// Get a list of all of the leaf certificates present in root
|
||||
allLeafCerts, _ := parseAllCerts(root)
|
||||
var validLeafCerts []*x509.Certificate
|
||||
|
||||
// Go through every leaf certificate and check that the CN matches the gun
|
||||
for _, cert := range allLeafCerts {
|
||||
// Validate that this leaf certificate has a CN that matches the exact gun
|
||||
if cert.Subject.CommonName != gun {
|
||||
logrus.Debugf("error leaf certificate CN: %s doesn't match the given GUN: %s", cert.Subject.CommonName)
|
||||
continue
|
||||
}
|
||||
// Make sure the certificate is not expired
|
||||
if time.Now().After(cert.NotAfter) {
|
||||
logrus.Debugf("error leaf certificate is expired")
|
||||
continue
|
||||
}
|
||||
|
||||
// We don't allow root certificates that use SHA1
|
||||
if cert.SignatureAlgorithm == x509.SHA1WithRSA ||
|
||||
cert.SignatureAlgorithm == x509.DSAWithSHA1 ||
|
||||
cert.SignatureAlgorithm == x509.ECDSAWithSHA1 {
|
||||
|
||||
logrus.Debugf("error certificate uses deprecated hashing algorithm (SHA1)")
|
||||
continue
|
||||
}
|
||||
|
||||
validLeafCerts = append(validLeafCerts, cert)
|
||||
}
|
||||
|
||||
if len(validLeafCerts) < 1 {
|
||||
return nil, errors.New("no valid leaf certificates found in any of the root keys")
|
||||
}
|
||||
|
||||
return validLeafCerts, nil
|
||||
}
|
||||
|
||||
// parseAllCerts returns two maps, one with all of the leafCertificates and one
|
||||
// with all the intermediate certificates found in signedRoot
|
||||
func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, map[string][]*x509.Certificate) {
|
||||
leafCerts := make(map[string]*x509.Certificate)
|
||||
intCerts := make(map[string][]*x509.Certificate)
|
||||
|
||||
// Before we loop through all root keys available, make sure any exist
|
||||
rootRoles, ok := signedRoot.Signed.Roles["root"]
|
||||
if !ok {
|
||||
logrus.Debugf("tried to parse certificates from invalid root signed data")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
logrus.Debugf("found the following root keys: %v", rootRoles.KeyIDs)
|
||||
// Iterate over every keyID for the root role inside of roots.json
|
||||
for _, keyID := range rootRoles.KeyIDs {
|
||||
// check that the key exists in the signed root keys map
|
||||
key, ok := signedRoot.Signed.Keys[keyID]
|
||||
if !ok {
|
||||
logrus.Debugf("error while getting data for keyID: %s", keyID)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode all the x509 certificates that were bundled with this
|
||||
// Specific root key
|
||||
decodedCerts, err := trustmanager.LoadCertBundleFromPEM([]byte(rootSigned.Signed.Keys[keyID].Public()))
|
||||
decodedCerts, err := trustmanager.LoadCertBundleFromPEM(key.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)
|
||||
leafCertList := trustmanager.GetLeafCerts(decodedCerts)
|
||||
|
||||
// If we got no leaf certificates or we got more than one, fail
|
||||
if len(leafCerts) != 1 {
|
||||
logrus.Debugf("wasn't able to find a leaf certificate in the chain of keyID: %s", keyID)
|
||||
if len(leafCertList) != 1 {
|
||||
logrus.Debugf("invalid chain due to leaf certificate missing or too many leaf certificates for keyID: %s", keyID)
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the ID of the leaf certificate
|
||||
leafCert := leafCerts[0]
|
||||
leafCert := leafCertList[0]
|
||||
leafID, err := trustmanager.FingerprintCert(leafCert)
|
||||
if err != nil {
|
||||
logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", keyID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Validate that this leaf certificate has a CN that matches the exact gun
|
||||
if leafCert.Subject.CommonName != dnsName {
|
||||
logrus.Debugf("error leaf certificate CN: %s doesn't match the given dns name: %s", leafCert.Subject.CommonName, dnsName)
|
||||
// Store the leaf cert in the map
|
||||
leafCerts[leafID] = leafCert
|
||||
|
||||
// Get all the remainder certificates marked as a CA to be used as intermediates
|
||||
intermediateCerts := trustmanager.GetIntermediateCerts(decodedCerts)
|
||||
intCerts[leafID] = intermediateCerts
|
||||
}
|
||||
|
||||
return leafCerts, intCerts
|
||||
}
|
||||
|
||||
// certsToRemove returns all the certifificates from oldCerts that aren't present
|
||||
// in newCerts
|
||||
func certsToRemove(oldCerts, newCerts []*x509.Certificate) map[string]*x509.Certificate {
|
||||
certsToRemove := make(map[string]*x509.Certificate)
|
||||
|
||||
// If no newCerts were provided
|
||||
if len(newCerts) == 0 {
|
||||
return certsToRemove
|
||||
}
|
||||
|
||||
// Populate a map with all the IDs from newCert
|
||||
var newCertMap = make(map[string]struct{})
|
||||
for _, cert := range newCerts {
|
||||
certID, err := trustmanager.FingerprintCert(cert)
|
||||
if err != nil {
|
||||
logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", certID, err)
|
||||
continue
|
||||
}
|
||||
newCertMap[certID] = struct{}{}
|
||||
}
|
||||
|
||||
// Add all the valid leafs to the certificates map so we can refer to them later
|
||||
allCerts[leafID] = leafCert
|
||||
|
||||
// Retrieve all the trusted certificates that match this dns Name
|
||||
certsForCN, err := km.trustedCertificateStore.GetCertificatesByCN(dnsName)
|
||||
// Iterate over all the old certificates and check to see if we should remove them
|
||||
for _, cert := range oldCerts {
|
||||
certID, err := trustmanager.FingerprintCert(cert)
|
||||
if err != nil {
|
||||
// If the error that we get back is different than ErrNoCertificatesFound
|
||||
// we couldn't check if there are any certificates with this CN already
|
||||
// trusted. Let's take the conservative approach and not trust this key
|
||||
if _, ok := err.(*trustmanager.ErrNoCertificatesFound); !ok {
|
||||
logrus.Debugf("error retrieving certificates for: %s, %v", dnsName, err)
|
||||
continue
|
||||
}
|
||||
logrus.Debugf("error while fingerprinting root certificate with certID: %s, %v", certID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// If there are no certificates with this CN, lets TOFUS!
|
||||
// Note that this logic should only exist in docker 1.8
|
||||
if len(certsForCN) == 0 {
|
||||
km.trustedCertificateStore.AddCert(leafCert)
|
||||
certsForCN = append(certsForCN, leafCert)
|
||||
logrus.Debugf("using TOFUS on %s with keyID: %s", dnsName, leafID)
|
||||
}
|
||||
|
||||
// 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]
|
||||
logrus.Debugf("found an exact match for %s with keyID: %s", dnsName, keyID)
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if this leafCertificate has a chain to one of the Root
|
||||
// CAs of our CA Store.
|
||||
err = trustmanager.Verify(km.trustedCAStore, dnsName, decodedCerts)
|
||||
if err == nil {
|
||||
validKeys[keyID] = rootSigned.Signed.Keys[keyID]
|
||||
logrus.Debugf("found a CA path for %s with keyID: %s", dnsName, keyID)
|
||||
if _, ok := newCertMap[certID]; !ok {
|
||||
certsToRemove[certID] = cert
|
||||
}
|
||||
}
|
||||
|
||||
if len(validKeys) < 1 {
|
||||
logrus.Debugf("wasn't able to trust any of the root keys")
|
||||
return ErrValidationFail
|
||||
}
|
||||
|
||||
// TODO(david): change hardcoded minversion on TUF.
|
||||
err = signed.VerifyRoot(root, 0, validKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var newRootKey data.PublicKey
|
||||
// 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 {
|
||||
logrus.Debugf("got a new root key to rotate to: %s", newRootKey.ID())
|
||||
|
||||
// Retrieve the certificate associated with the new root key and trust it
|
||||
newRootKeyCert, ok := allCerts[newRootKey.ID()]
|
||||
// Paranoid check for the certificate still being in the map
|
||||
if !ok {
|
||||
logrus.Debugf("error while retrieving new root certificate with keyID: %s, %v", newRootKey.ID(), err)
|
||||
return ErrRootRotationFail
|
||||
}
|
||||
|
||||
// Add the new root certificate to our certificate store
|
||||
err := km.trustedCertificateStore.AddCert(newRootKeyCert)
|
||||
if err != nil {
|
||||
// Ignore the error if the certificate already exists
|
||||
if _, ok := err.(*trustmanager.ErrCertExists); !ok {
|
||||
logrus.Debugf("error while adding new root certificate with keyID: %s, %v", newRootKey.ID(), err)
|
||||
return ErrRootRotationFail
|
||||
}
|
||||
logrus.Debugf("root certificate already exists in keystore: %s", newRootKey.ID())
|
||||
}
|
||||
|
||||
// Remove the new root certificate from the certificate mapping so we
|
||||
// can remove trust from all of the remaining ones
|
||||
delete(allCerts, newRootKey.ID())
|
||||
|
||||
// Iterate over all old valid certificates and remove them, essentially
|
||||
// finishing the rotation of the currently trusted root certificate
|
||||
for _, cert := range allCerts {
|
||||
err := km.trustedCertificateStore.RemoveCert(cert)
|
||||
if err != nil {
|
||||
logrus.Debugf("error while removing old root certificate: %v", err)
|
||||
return ErrRootRotationFail
|
||||
}
|
||||
logrus.Debugf("removed trust from old root certificate")
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Debugf("Root validation succeeded")
|
||||
return nil
|
||||
return certsToRemove
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@ func (s *X509FileStore) RemoveCert(cert *x509.Certificate) error {
|
|||
filename := s.fileMap[certID]
|
||||
delete(s.fileMap, certID)
|
||||
|
||||
name := string(cert.RawSubject)
|
||||
name := string(cert.Subject.CommonName)
|
||||
|
||||
// Filter the fingerprint out of this name entry
|
||||
fpList := s.nameMap[name]
|
||||
|
|
|
@ -114,8 +114,6 @@ func fingerprintCert(cert *x509.Certificate) (CertID, error) {
|
|||
// Create new TUF Key so we can compute the TUF-compliant CertID
|
||||
tufKey := data.NewPublicKey(keyType, pemdata)
|
||||
|
||||
logrus.Debugf("certificate fingerprint generated for key type %s: %s", keyType, tufKey.ID())
|
||||
|
||||
return CertID(tufKey.ID()), nil
|
||||
}
|
||||
|
||||
|
@ -189,6 +187,17 @@ func GetLeafCerts(certs []*x509.Certificate) []*x509.Certificate {
|
|||
return leafCerts
|
||||
}
|
||||
|
||||
// GetIntermediateCerts parses a list of x509 Certificates and returns all of the
|
||||
// ones marked as a CA, to be used as intermediates
|
||||
func GetIntermediateCerts(certs []*x509.Certificate) (intCerts []*x509.Certificate) {
|
||||
for _, cert := range certs {
|
||||
if cert.IsCA {
|
||||
intCerts = append(intCerts, cert)
|
||||
}
|
||||
}
|
||||
return intCerts
|
||||
}
|
||||
|
||||
// ParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It
|
||||
// only supports RSA (PKCS#1) and attempts to decrypt using the passphrase, if encrypted.
|
||||
func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, error) {
|
||||
|
@ -420,6 +429,32 @@ func EncryptPrivateKey(key data.PrivateKey, passphrase string) ([]byte, error) {
|
|||
return pem.EncodeToMemory(encryptedPEMBlock), nil
|
||||
}
|
||||
|
||||
// CertsToKeys transforms each of the input certificates into it's corresponding
|
||||
// PublicKey
|
||||
func CertsToKeys(certs []*x509.Certificate) map[string]data.PublicKey {
|
||||
keys := make(map[string]data.PublicKey)
|
||||
for _, cert := range certs {
|
||||
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
|
||||
pemdata := pem.EncodeToMemory(&block)
|
||||
|
||||
var keyType data.KeyAlgorithm
|
||||
switch cert.PublicKeyAlgorithm {
|
||||
case x509.RSA:
|
||||
keyType = data.RSAx509Key
|
||||
case x509.ECDSA:
|
||||
keyType = data.ECDSAx509Key
|
||||
default:
|
||||
logrus.Debugf("unknown certificate type found, ignoring")
|
||||
}
|
||||
|
||||
// Create new the appropriate PublicKey
|
||||
newKey := data.NewPublicKey(keyType, pemdata)
|
||||
keys[newKey.ID()] = newKey
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
// NewCertificate returns an X509 Certificate following a template, given a GUN.
|
||||
func NewCertificate(gun string) (*x509.Certificate, error) {
|
||||
notBefore := time.Now()
|
||||
|
@ -436,8 +471,7 @@ func NewCertificate(gun string) (*x509.Certificate, error) {
|
|||
return &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{gun},
|
||||
CommonName: gun,
|
||||
CommonName: gun,
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
package trustmanager
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCertsToKeys(t *testing.T) {
|
||||
// Get root certificate
|
||||
rootCA, err := LoadCertFromFile("../fixtures/root-ca.crt")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Get intermediate certificate
|
||||
intermediateCA, err := LoadCertFromFile("../fixtures/intermediate-ca.crt")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Get leaf certificate
|
||||
leafCert, err := LoadCertFromFile("../fixtures/secure.example.com.crt")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Get our certList with Leaf Cert and Intermediate
|
||||
certList := []*x509.Certificate{leafCert, intermediateCA, rootCA}
|
||||
|
||||
// Call CertsToKEys
|
||||
keys := CertsToKeys(certList)
|
||||
assert.NotNil(t, keys)
|
||||
assert.Len(t, keys, 3)
|
||||
|
||||
// Call GetLeafCerts
|
||||
newKeys := GetLeafCerts(certList)
|
||||
assert.NotNil(t, newKeys)
|
||||
assert.Len(t, newKeys, 1)
|
||||
|
||||
// Call GetIntermediateCerts (checks for certs with IsCA true)
|
||||
newKeys = GetIntermediateCerts(certList)
|
||||
assert.NotNil(t, newKeys)
|
||||
assert.Len(t, newKeys, 2)
|
||||
}
|
||||
|
||||
func TestNewCertificate(t *testing.T) {
|
||||
cert, err := NewCertificate("docker.com/alpine")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, cert.Subject.CommonName, "docker.com/alpine")
|
||||
assert.True(t, time.Now().Before(cert.NotAfter))
|
||||
}
|
||||
|
||||
func TestKeyOperations(t *testing.T) {
|
||||
// Generate our ED25519 private key
|
||||
edKey, err := GenerateED25519Key(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Generate our EC private key
|
||||
ecKey, err := GenerateECDSAKey(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Generate our RSA private key
|
||||
rsaKey, err := GenerateRSAKey(rand.Reader, 512)
|
||||
|
||||
// Encode our ED private key
|
||||
edPEM, err := KeyToPEM(edKey)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Encode our EC private key
|
||||
ecPEM, err := KeyToPEM(ecKey)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Encode our RSA private key
|
||||
rsaPEM, err := KeyToPEM(rsaKey)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check to see if ED key it is encoded
|
||||
stringEncodedEDKey := string(edPEM)
|
||||
assert.True(t, strings.Contains(stringEncodedEDKey, "-----BEGIN ED25519 PRIVATE KEY-----"))
|
||||
|
||||
// Check to see if EC key it is encoded
|
||||
stringEncodedECKey := string(ecPEM)
|
||||
assert.True(t, strings.Contains(stringEncodedECKey, "-----BEGIN EC PRIVATE KEY-----"))
|
||||
|
||||
// Check to see if RSA key it is encoded
|
||||
stringEncodedRSAKey := string(rsaPEM)
|
||||
assert.True(t, strings.Contains(stringEncodedRSAKey, "-----BEGIN RSA PRIVATE KEY-----"))
|
||||
|
||||
// Decode our ED Key
|
||||
decodedEDKey, err := ParsePEMPrivateKey(edPEM, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, edKey.Private(), decodedEDKey.Private())
|
||||
|
||||
// Decode our EC Key
|
||||
decodedECKey, err := ParsePEMPrivateKey(ecPEM, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ecKey.Private(), decodedECKey.Private())
|
||||
|
||||
// Decode our RSA Key
|
||||
decodedRSAKey, err := ParsePEMPrivateKey(rsaPEM, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rsaKey.Private(), decodedRSAKey.Private())
|
||||
|
||||
// Encrypt our ED Key
|
||||
encryptedEDKey, err := EncryptPrivateKey(edKey, "ponies")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Encrypt our EC Key
|
||||
encryptedECKey, err := EncryptPrivateKey(ecKey, "ponies")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Encrypt our RSA Key
|
||||
encryptedRSAKey, err := EncryptPrivateKey(rsaKey, "ponies")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check to see if ED key it is encrypted
|
||||
stringEncryptedEDKey := string(encryptedEDKey)
|
||||
assert.True(t, strings.Contains(stringEncryptedEDKey, "-----BEGIN ED25519 PRIVATE KEY-----"))
|
||||
assert.True(t, strings.Contains(stringEncryptedEDKey, "Proc-Type: 4,ENCRYPTED"))
|
||||
|
||||
// Check to see if EC key it is encrypted
|
||||
stringEncryptedECKey := string(encryptedECKey)
|
||||
assert.True(t, strings.Contains(stringEncryptedECKey, "-----BEGIN EC PRIVATE KEY-----"))
|
||||
assert.True(t, strings.Contains(stringEncryptedECKey, "Proc-Type: 4,ENCRYPTED"))
|
||||
|
||||
// Check to see if RSA key it is encrypted
|
||||
stringEncryptedRSAKey := string(encryptedRSAKey)
|
||||
assert.True(t, strings.Contains(stringEncryptedRSAKey, "-----BEGIN RSA PRIVATE KEY-----"))
|
||||
assert.True(t, strings.Contains(stringEncryptedRSAKey, "Proc-Type: 4,ENCRYPTED"))
|
||||
|
||||
// Decrypt our ED Key
|
||||
decryptedEDKey, err := ParsePEMPrivateKey(encryptedEDKey, "ponies")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, edKey.Private(), decryptedEDKey.Private())
|
||||
|
||||
// Decrypt our EC Key
|
||||
decryptedECKey, err := ParsePEMPrivateKey(encryptedECKey, "ponies")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ecKey.Private(), decryptedECKey.Private())
|
||||
|
||||
// Decrypt our RSA Key
|
||||
decryptedRSAKey, err := ParsePEMPrivateKey(encryptedRSAKey, "ponies")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rsaKey.Private(), decryptedRSAKey.Private())
|
||||
|
||||
}
|
Loading…
Reference in New Issue