mirror of https://github.com/docker/docs.git
Foundation for removing certstore
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
parent
dc445b4a3a
commit
9da40f07da
|
@ -812,7 +812,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
|||
rootJSON, cachedRootErr := r.fileStore.GetMeta(data.CanonicalRootRole, -1)
|
||||
|
||||
if cachedRootErr == nil {
|
||||
signedRoot, cachedRootErr = r.validateRoot(rootJSON)
|
||||
signedRoot, cachedRootErr = r.validateRoot(rootJSON, false)
|
||||
}
|
||||
|
||||
remote, remoteErr := getRemoteStore(r.baseURL, r.gun, r.roundTrip)
|
||||
|
@ -833,7 +833,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
|||
if cachedRootErr != nil {
|
||||
// we always want to use the downloaded root if there was a cache
|
||||
// error.
|
||||
signedRoot, err = r.validateRoot(tmpJSON)
|
||||
signedRoot, err = r.validateRoot(tmpJSON, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -868,7 +868,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
|||
// signatures of the root based on known keys, not expiry or other metadata.
|
||||
// This is so that an out of date root can be loaded to be used in a rotation
|
||||
// should the TUF update process detect a problem.
|
||||
func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, error) {
|
||||
func (r *NotaryRepository) validateRoot(rootJSON []byte, fromRemote bool) (*data.SignedRoot, error) {
|
||||
// can't just unmarshal into SignedRoot because validate root
|
||||
// needs the root.Signed field to still be []byte for signature
|
||||
// validation
|
||||
|
@ -878,7 +878,25 @@ func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, erro
|
|||
return nil, err
|
||||
}
|
||||
|
||||
err = trustpinning.ValidateRoot(r.CertStore, root, r.gun, r.trustPinning)
|
||||
// If we're downloading a root from a remote source, attempt to load a local root
|
||||
// to ensure that we consider old roots when validating this new one
|
||||
var prevRoot *data.SignedRoot
|
||||
if fromRemote {
|
||||
prevRootJSON, err := r.fileStore.GetMeta(data.CanonicalRootRole, -1)
|
||||
if err == nil {
|
||||
prevSignedRoot := &data.Signed{}
|
||||
err = json.Unmarshal(prevRootJSON, prevSignedRoot)
|
||||
if err == nil {
|
||||
prevRoot, err = data.RootFromSigned(prevSignedRoot)
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we had any errors while trying to retrieve the previous root, just set it to nil
|
||||
if err != nil {
|
||||
prevRoot = nil
|
||||
}
|
||||
|
||||
err = trustpinning.ValidateRoot(prevRoot, root, r.gun, r.trustPinning)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ We shall call this: TOFUS.
|
|||
|
||||
Validation failure at any step will result in an ErrValidationFailed error.
|
||||
*/
|
||||
func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun string, trustPinning TrustPinConfig) error {
|
||||
func ValidateRoot(prevRoot *data.SignedRoot, root *data.Signed, gun string, trustPinning TrustPinConfig) error {
|
||||
logrus.Debugf("entered ValidateRoot with dns: %s", gun)
|
||||
signedRoot, err := data.RootFromSigned(root)
|
||||
if err != nil {
|
||||
|
@ -100,30 +100,23 @@ func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun strin
|
|||
|
||||
// Retrieve all the leaf and intermediate certificates in root for which the CN matches the GUN
|
||||
allLeafCerts, allIntCerts := parseAllCerts(signedRoot)
|
||||
certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun)
|
||||
certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun, true)
|
||||
if err != nil {
|
||||
logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err)
|
||||
return &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}
|
||||
}
|
||||
|
||||
// Retrieve all the trusted certificates that match this gun
|
||||
trustedCerts, err := certStore.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"}
|
||||
}
|
||||
}
|
||||
// Retrieve all the trusted certificates from our previous root
|
||||
// Note that we do not validate expiries here since our originally trusted root might have expired certs
|
||||
allTrustedLeafCerts, allTrustedIntCerts := parseAllCerts(prevRoot)
|
||||
trustedLeafCerts, err := validRootLeafCerts(allTrustedLeafCerts, gun, false)
|
||||
// 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(trustedCerts) != 0 {
|
||||
logrus.Debugf("found %d valid root certificates for %s: %s", len(trustedCerts), gun,
|
||||
prettyFormatCertIDs(trustedCerts))
|
||||
if len(trustedLeafCerts) != 0 {
|
||||
logrus.Debugf("found %d valid root leaf certificates for %s: %s", len(trustedLeafCerts), gun,
|
||||
prettyFormatCertIDs(trustedLeafCerts))
|
||||
err = signed.VerifySignatures(
|
||||
root, data.BaseRole{Keys: trustmanager.CertsToKeys(trustedCerts, allIntCerts), Threshold: 1})
|
||||
root, data.BaseRole{Keys: trustmanager.CertsToKeys(trustedLeafCerts, allTrustedIntCerts), Threshold: 1})
|
||||
if err != nil {
|
||||
logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
|
||||
return &ErrValidationFail{Reason: "failed to validate data with current trusted certificates"}
|
||||
|
@ -162,49 +155,14 @@ func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun strin
|
|||
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 certsFromRoot {
|
||||
err := certStore.AddCert(cert)
|
||||
if err != nil {
|
||||
// If the error is already exists we don't fail the rotation
|
||||
if _, ok := err.(*trustmanager.ErrCertExists); ok {
|
||||
logrus.Debugf("ignoring certificate addition to: %s", gun)
|
||||
continue
|
||||
}
|
||||
logrus.Debugf("error adding new trusted certificate for: %s, %v", gun, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Now we delete old certificates that aren't present in the new root
|
||||
oldCertsToRemove, err := certsToRemove(trustedCerts, certsFromRoot)
|
||||
if err != nil {
|
||||
logrus.Debugf("inconsistency when removing old certificates: %v", err)
|
||||
return err
|
||||
}
|
||||
for certID, cert := range oldCertsToRemove {
|
||||
logrus.Debugf("removing certificate with certID: %s", certID)
|
||||
err = certStore.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-expired, non-sha1 certificates
|
||||
// validRootLeafCerts returns a list of possibly (if checkExpiry is true) non-expired, non-sha1 certificates
|
||||
// found in root whose Common-Names match the provided GUN. Note that this
|
||||
// "validity" alone does not imply any measure of trust.
|
||||
func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string) ([]*x509.Certificate, error) {
|
||||
func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string, checkExpiry bool) ([]*x509.Certificate, error) {
|
||||
var validLeafCerts []*x509.Certificate
|
||||
|
||||
// Go through every leaf certificate and check that the CN matches the gun
|
||||
|
@ -215,8 +173,8 @@ func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string) (
|
|||
cert.Subject.CommonName, gun)
|
||||
continue
|
||||
}
|
||||
// Make sure the certificate is not expired
|
||||
if time.Now().After(cert.NotAfter) {
|
||||
// Make sure the certificate is not expired if checkExpiry is true
|
||||
if checkExpiry && time.Now().After(cert.NotAfter) {
|
||||
logrus.Debugf("error leaf certificate is expired")
|
||||
continue
|
||||
}
|
||||
|
@ -246,6 +204,10 @@ func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string) (
|
|||
// 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) {
|
||||
if signedRoot == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
leafCerts := make(map[string]*x509.Certificate)
|
||||
intCerts := make(map[string][]*x509.Certificate)
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ import (
|
|||
|
||||
"time"
|
||||
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/cryptoservice"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
|
@ -133,14 +132,6 @@ func TestValidateRoot(t *testing.T) {
|
|||
defer os.RemoveAll(tempBaseDir)
|
||||
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
// Create a X509Store
|
||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
||||
trustPath,
|
||||
trustmanager.FilterCertsExpiredSha1,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Execute our template
|
||||
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
||||
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
||||
|
@ -150,12 +141,12 @@ func TestValidateRoot(t *testing.T) {
|
|||
|
||||
// This call to ValidateRoot will succeed since we are using a valid PEM
|
||||
// encoded certificate, and have no other certificates for this CN
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// This call to ValidateRoot will fail since we are passing in a dnsName that
|
||||
// doesn't match the CN of the certificate.
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "diogomonica.com/notary", TrustPinConfig{})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "diogomonica.com/notary", TrustPinConfig{})
|
||||
require.Error(t, err, "An error was expected")
|
||||
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
||||
|
||||
|
@ -169,7 +160,7 @@ func TestValidateRoot(t *testing.T) {
|
|||
// Unmarshal our signedroot
|
||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
||||
require.Error(t, err, "illegal base64 data at input byte")
|
||||
|
||||
//
|
||||
|
@ -182,7 +173,7 @@ func TestValidateRoot(t *testing.T) {
|
|||
// Unmarshal our signedroot
|
||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
||||
require.Error(t, err, "An error was expected")
|
||||
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
||||
|
||||
|
@ -197,7 +188,7 @@ func TestValidateRoot(t *testing.T) {
|
|||
// Unmarshal our signedroot
|
||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
||||
require.Error(t, err, "An error was expected")
|
||||
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
||||
|
||||
|
@ -216,7 +207,7 @@ func TestValidateRoot(t *testing.T) {
|
|||
// Unmarshal our signedroot
|
||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "secure.example.com", TrustPinConfig{})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "secure.example.com", TrustPinConfig{})
|
||||
require.Error(t, err, "An error was expected")
|
||||
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
||||
}
|
||||
|
@ -230,14 +221,6 @@ func TestValidateRootWithoutTOFUS(t *testing.T) {
|
|||
defer os.RemoveAll(tempBaseDir)
|
||||
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
// Create a X509Store
|
||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
||||
trustPath,
|
||||
trustmanager.FilterCertsExpiredSha1,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Execute our template
|
||||
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
||||
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
||||
|
@ -246,7 +229,7 @@ func TestValidateRootWithoutTOFUS(t *testing.T) {
|
|||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
// This call to ValidateRoot will fail since we are explicitly disabling TOFU and have no local certs
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{DisableTOFU: true})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{DisableTOFU: true})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
|
@ -259,14 +242,6 @@ func TestValidateRootWithPinnedCert(t *testing.T) {
|
|||
defer os.RemoveAll(tempBaseDir)
|
||||
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
// Create a X509Store
|
||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
||||
trustPath,
|
||||
trustmanager.FilterCertsExpiredSha1,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Execute our template
|
||||
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
||||
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
||||
|
@ -275,14 +250,11 @@ func TestValidateRootWithPinnedCert(t *testing.T) {
|
|||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
// This call to ValidateRoot should succeed with the correct Cert ID (same as root public key ID)
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {rootPubKeyID}}, DisableTOFU: true})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {rootPubKeyID}}, DisableTOFU: true})
|
||||
require.NoError(t, err)
|
||||
|
||||
// purge the cert store to check another valid certs trust pinning
|
||||
certStore.RemoveAll()
|
||||
|
||||
// This call to ValidateRoot should also succeed with the correct Cert ID (same as root public key ID), even though we passed an extra bad one
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {rootPubKeyID, "invalidID"}}, DisableTOFU: true})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {rootPubKeyID, "invalidID"}}, DisableTOFU: true})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
@ -439,16 +411,9 @@ func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) {
|
|||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
// Create a X509Store
|
||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
||||
trustPath,
|
||||
trustmanager.FilterCertsExpiredSha1,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ValidateRoot(
|
||||
certStore,
|
||||
nil,
|
||||
signedRoot,
|
||||
"docker.io/notary/test",
|
||||
TrustPinConfig{
|
||||
|
@ -470,14 +435,6 @@ func TestValidateRootFailuresWithPinnedCert(t *testing.T) {
|
|||
defer os.RemoveAll(tempBaseDir)
|
||||
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
// Create a X509Store
|
||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
||||
trustPath,
|
||||
trustmanager.FilterCertsExpiredSha1,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Execute our template
|
||||
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
||||
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
||||
|
@ -486,23 +443,23 @@ func TestValidateRootFailuresWithPinnedCert(t *testing.T) {
|
|||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
// This call to ValidateRoot should fail due to an incorrect cert ID
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"ABSOLUTELY NOT A CERT ID"}}, DisableTOFU: true})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"ABSOLUTELY NOT A CERT ID"}}, DisableTOFU: true})
|
||||
require.Error(t, err)
|
||||
|
||||
// This call to ValidateRoot should fail due to an empty cert ID
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {""}}, DisableTOFU: true})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {""}}, DisableTOFU: true})
|
||||
require.Error(t, err)
|
||||
|
||||
// This call to ValidateRoot should fail due to an invalid GUN (even though the cert ID is correct), and TOFUS is set to false
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: true})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: true})
|
||||
require.Error(t, err)
|
||||
|
||||
// This call to ValidateRoot should fail due to an invalid cert ID, even though it's a valid key ID for targets
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {targetsPubKeyID}}, DisableTOFU: true})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {targetsPubKeyID}}, DisableTOFU: true})
|
||||
require.Error(t, err)
|
||||
|
||||
// This call to ValidateRoot should succeed because we fall through to TOFUS because we have no matching GUNs under Certs
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: false})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: false})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
@ -515,44 +472,35 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
|
|||
defer os.RemoveAll(tempBaseDir)
|
||||
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
// Create a X509Store
|
||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
||||
trustPath,
|
||||
trustmanager.FilterCertsExpiredSha1,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
||||
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
||||
// Unmarshal our signedRoot
|
||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
// This call to ValidateRoot will fail because we have an invalid path for the CA
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}})
|
||||
require.Error(t, err)
|
||||
|
||||
// This call to ValidateRoot will fail because we have no valid GUNs to use, and TOFUS is disabled
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: true})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: true})
|
||||
require.Error(t, err)
|
||||
|
||||
// This call to ValidateRoot will succeed because we have no valid GUNs to use and we fall back to enabled TOFUS
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: false})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: false})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, certStore.RemoveAll())
|
||||
|
||||
// Write an invalid CA cert (not even a PEM) to the tempDir and ensure validation fails when using it
|
||||
invalidCAFilepath := filepath.Join(tempBaseDir, "invalid.ca")
|
||||
require.NoError(t, ioutil.WriteFile(invalidCAFilepath, []byte("ABSOLUTELY NOT A PEM"), 0644))
|
||||
|
||||
// Using this invalid CA cert should fail on ValidateRoot
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": invalidCAFilepath}, DisableTOFU: true})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": invalidCAFilepath}, DisableTOFU: true})
|
||||
require.Error(t, err)
|
||||
|
||||
validCAFilepath := "../fixtures/root-ca.crt"
|
||||
|
||||
// If we pass an invalid Certs entry in addition to this valid CA entry, since Certs has priority for pinning we will fail
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"invalidID"}}, CA: map[string]string{"docker.com/notary": validCAFilepath}, DisableTOFU: true})
|
||||
err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"invalidID"}}, CA: map[string]string{"docker.com/notary": validCAFilepath}, DisableTOFU: true})
|
||||
require.Error(t, err)
|
||||
|
||||
// Now construct a new root with a valid cert chain, such that signatures are correct over the 'notary-signer' GUN. Pin the root-ca and validate
|
||||
|
@ -602,11 +550,10 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
// Check that we validate correctly against a pinned CA and provided bundle
|
||||
err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": validCAFilepath}, DisableTOFU: true})
|
||||
err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": validCAFilepath}, DisableTOFU: true})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add an expired CA for the same gun to our previous pinned bundle, ensure that we still validate correctly
|
||||
certStore.RemoveAll()
|
||||
goodRootCABundle, err := trustmanager.LoadCertBundleFromFile(validCAFilepath)
|
||||
require.NoError(t, err)
|
||||
memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
|
||||
|
@ -623,10 +570,9 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
|
|||
require.NoError(t, ioutil.WriteFile(bundleWithExpiredCertPath, bundleWithExpiredCert, 0644))
|
||||
|
||||
// Check that we validate correctly against a pinned CA and provided bundle
|
||||
err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithExpiredCertPath}, DisableTOFU: true})
|
||||
err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithExpiredCertPath}, DisableTOFU: true})
|
||||
require.NoError(t, err)
|
||||
|
||||
certStore.RemoveAll()
|
||||
testPubKey2, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey)
|
||||
require.NoError(t, err)
|
||||
testPrivKey2, _, err := memKeyStore.GetKey(testPubKey2.ID())
|
||||
|
@ -638,11 +584,10 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
|
|||
allExpiredCertPath := filepath.Join(tempBaseDir, "all_expired_cert.pem")
|
||||
require.NoError(t, ioutil.WriteFile(allExpiredCertPath, allExpiredCertBundle, 0644))
|
||||
// Now only use expired certs in the bundle, we should fail
|
||||
err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": allExpiredCertPath}, DisableTOFU: true})
|
||||
err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": allExpiredCertPath}, DisableTOFU: true})
|
||||
require.Error(t, err)
|
||||
|
||||
// Add a CA cert for a that won't validate against the root leaf certificate
|
||||
certStore.RemoveAll()
|
||||
testPubKey3, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey)
|
||||
require.NoError(t, err)
|
||||
testPrivKey3, _, err := memKeyStore.GetKey(testPubKey3.ID())
|
||||
|
@ -653,7 +598,7 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
bundleWithWrongCertPath := filepath.Join(tempBaseDir, "bundle_with_expired_cert.pem")
|
||||
require.NoError(t, ioutil.WriteFile(bundleWithWrongCertPath, bundleWithWrongCert, 0644))
|
||||
err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithWrongCertPath}, DisableTOFU: true})
|
||||
err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithWrongCertPath}, DisableTOFU: true})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
|
@ -666,11 +611,10 @@ func TestValidateSuccessfulRootRotation(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// Generates a X509Store in a temporary directory and returns the
|
||||
// store and certificates for two keys which have been added to the keystore.
|
||||
// Generates certificates for two keys which have been added to the keystore.
|
||||
// Also returns the temporary directory so it can be cleaned up.
|
||||
func filestoreWithTwoCerts(t *testing.T, gun, keyAlg string) (
|
||||
string, trustmanager.X509Store, *cryptoservice.CryptoService, []*x509.Certificate) {
|
||||
func generateTwoCerts(t *testing.T, gun, keyAlg string) (
|
||||
string, *cryptoservice.CryptoService, []*x509.Certificate) {
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
|
@ -679,14 +623,6 @@ func filestoreWithTwoCerts(t *testing.T, gun, keyAlg string) (
|
|||
|
||||
cryptoService := cryptoservice.NewCryptoService(fileKeyStore)
|
||||
|
||||
// Create a store
|
||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
||||
trustPath,
|
||||
trustmanager.FilterCertsExpiredSha1,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
certificates := make([]*x509.Certificate, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
pubKey, err := cryptoService.Create("root", gun, keyAlg)
|
||||
|
@ -700,21 +636,18 @@ func filestoreWithTwoCerts(t *testing.T, gun, keyAlg string) (
|
|||
|
||||
certificates[i] = cert
|
||||
}
|
||||
return tempBaseDir, certStore, cryptoService, certificates
|
||||
return tempBaseDir, cryptoService, certificates
|
||||
}
|
||||
|
||||
func testValidateSuccessfulRootRotation(t *testing.T, keyAlg, rootKeyType string) {
|
||||
// The gun to test
|
||||
gun := "docker.com/notary"
|
||||
|
||||
tempBaseDir, certStore, cs, certificates := filestoreWithTwoCerts(t, gun, keyAlg)
|
||||
tempBaseDir, cs, certificates := generateTwoCerts(t, gun, keyAlg)
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
origRootCert := certificates[0]
|
||||
replRootCert := certificates[1]
|
||||
|
||||
// Add the old root cert part of trustedCertificates
|
||||
certStore.AddCert(origRootCert)
|
||||
|
||||
// We need the PEM representation of the replacement key to put it into the TUF data
|
||||
origRootPEMCert := trustmanager.CertToPEM(origRootCert)
|
||||
replRootPEMCert := trustmanager.CertToPEM(replRootCert)
|
||||
|
@ -745,13 +678,8 @@ func testValidateSuccessfulRootRotation(t *testing.T, keyAlg, rootKeyType string
|
|||
|
||||
// This call to ValidateRoot will succeed since we are using a valid PEM
|
||||
// encoded certificate, and have no other certificates for this CN
|
||||
err = ValidateRoot(certStore, signedTestRoot, gun, TrustPinConfig{})
|
||||
err = ValidateRoot(nil, signedTestRoot, gun, TrustPinConfig{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Finally, validate the only trusted certificate that exists is the new one
|
||||
certificates = certStore.GetCertificates()
|
||||
require.Len(t, certificates, 1)
|
||||
require.Equal(t, certificates[0], replRootCert)
|
||||
}
|
||||
|
||||
// TestValidateRootRotationMissingOrigSig runs through a full root certificate rotation
|
||||
|
@ -767,14 +695,42 @@ func TestValidateRootRotationMissingOrigSig(t *testing.T) {
|
|||
func testValidateRootRotationMissingOrigSig(t *testing.T, keyAlg, rootKeyType string) {
|
||||
gun := "docker.com/notary"
|
||||
|
||||
tempBaseDir, certStore, cryptoService, certificates := filestoreWithTwoCerts(
|
||||
tempBaseDir, cryptoService, certificates := generateTwoCerts(
|
||||
t, gun, keyAlg)
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
origRootCert := certificates[0]
|
||||
replRootCert := certificates[1]
|
||||
|
||||
// Add the old root cert part of trustedCertificates
|
||||
certStore.AddCert(origRootCert)
|
||||
// Set up the previous root prior to rotating
|
||||
// We need the PEM representation of the original key to put it into the TUF data
|
||||
origRootPEMCert := trustmanager.CertToPEM(origRootCert)
|
||||
|
||||
// Tuf key with PEM-encoded x509 certificate
|
||||
origRootKey := data.NewPublicKey(rootKeyType, origRootPEMCert)
|
||||
|
||||
origRootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{origRootKey.ID()}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
origTestRoot, err := data.NewRoot(
|
||||
map[string]data.PublicKey{origRootKey.ID(): origRootKey},
|
||||
map[string]*data.RootRole{
|
||||
data.CanonicalRootRole: &origRootRole.RootRole,
|
||||
data.CanonicalTargetsRole: &origRootRole.RootRole,
|
||||
data.CanonicalSnapshotRole: &origRootRole.RootRole,
|
||||
data.CanonicalTimestampRole: &origRootRole.RootRole,
|
||||
},
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err, "Failed to create new root")
|
||||
|
||||
signedOrigTestRoot, err := origTestRoot.ToSigned()
|
||||
require.NoError(t, err)
|
||||
|
||||
// We only sign with the new key, and not with the original one.
|
||||
err = signed.Sign(cryptoService, signedOrigTestRoot, []data.PublicKey{origRootKey}, 1, nil)
|
||||
require.NoError(t, err)
|
||||
prevRoot, err := data.RootFromSigned(signedOrigTestRoot)
|
||||
require.NoError(t, err)
|
||||
|
||||
// We need the PEM representation of the replacement key to put it into the TUF data
|
||||
replRootPEMCert := trustmanager.CertToPEM(replRootCert)
|
||||
|
@ -806,14 +762,8 @@ func testValidateRootRotationMissingOrigSig(t *testing.T, keyAlg, rootKeyType st
|
|||
|
||||
// This call to ValidateRoot will succeed since we are using a valid PEM
|
||||
// encoded certificate, and have no other certificates for this CN
|
||||
err = ValidateRoot(certStore, signedTestRoot, gun, TrustPinConfig{})
|
||||
err = ValidateRoot(prevRoot, signedTestRoot, gun, TrustPinConfig{})
|
||||
require.Error(t, err, "insuficient signatures on root")
|
||||
|
||||
// Finally, validate the only trusted certificate that exists is still
|
||||
// the old one
|
||||
certificates = certStore.GetCertificates()
|
||||
require.Len(t, certificates, 1)
|
||||
require.Equal(t, certificates[0], origRootCert)
|
||||
}
|
||||
|
||||
// TestValidateRootRotationMissingNewSig runs through a full root certificate rotation
|
||||
|
@ -829,21 +779,47 @@ func TestValidateRootRotationMissingNewSig(t *testing.T) {
|
|||
func testValidateRootRotationMissingNewSig(t *testing.T, keyAlg, rootKeyType string) {
|
||||
gun := "docker.com/notary"
|
||||
|
||||
tempBaseDir, certStore, cryptoService, certificates := filestoreWithTwoCerts(
|
||||
tempBaseDir, cryptoService, certificates := generateTwoCerts(
|
||||
t, gun, keyAlg)
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
origRootCert := certificates[0]
|
||||
replRootCert := certificates[1]
|
||||
|
||||
// Add the old root cert part of trustedCertificates
|
||||
certStore.AddCert(origRootCert)
|
||||
|
||||
// We need the PEM representation of the replacement key to put it into the TUF data
|
||||
origRootPEMCert := trustmanager.CertToPEM(origRootCert)
|
||||
replRootPEMCert := trustmanager.CertToPEM(replRootCert)
|
||||
|
||||
// Set up the previous root prior to rotating
|
||||
// We need the PEM representation of the original key to put it into the TUF data
|
||||
origRootPEMCert := trustmanager.CertToPEM(origRootCert)
|
||||
|
||||
// Tuf key with PEM-encoded x509 certificate
|
||||
origRootKey := data.NewPublicKey(rootKeyType, origRootPEMCert)
|
||||
|
||||
origRootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{origRootKey.ID()}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
origTestRoot, err := data.NewRoot(
|
||||
map[string]data.PublicKey{origRootKey.ID(): origRootKey},
|
||||
map[string]*data.RootRole{
|
||||
data.CanonicalRootRole: &origRootRole.RootRole,
|
||||
data.CanonicalTargetsRole: &origRootRole.RootRole,
|
||||
data.CanonicalSnapshotRole: &origRootRole.RootRole,
|
||||
data.CanonicalTimestampRole: &origRootRole.RootRole,
|
||||
},
|
||||
false,
|
||||
)
|
||||
require.NoError(t, err, "Failed to create new root")
|
||||
|
||||
signedOrigTestRoot, err := origTestRoot.ToSigned()
|
||||
require.NoError(t, err)
|
||||
|
||||
// We only sign with the new key, and not with the original one.
|
||||
err = signed.Sign(cryptoService, signedOrigTestRoot, []data.PublicKey{origRootKey}, 1, nil)
|
||||
require.NoError(t, err)
|
||||
prevRoot, err := data.RootFromSigned(signedOrigTestRoot)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Tuf key with PEM-encoded x509 certificate
|
||||
replRootKey := data.NewPublicKey(rootKeyType, replRootPEMCert)
|
||||
|
||||
rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil)
|
||||
|
@ -870,14 +846,8 @@ func testValidateRootRotationMissingNewSig(t *testing.T, keyAlg, rootKeyType str
|
|||
|
||||
// This call to ValidateRoot will succeed since we are using a valid PEM
|
||||
// encoded certificate, and have no other certificates for this CN
|
||||
err = ValidateRoot(certStore, signedTestRoot, gun, TrustPinConfig{})
|
||||
err = ValidateRoot(prevRoot, signedTestRoot, gun, TrustPinConfig{})
|
||||
require.Error(t, err, "insuficient signatures on root")
|
||||
|
||||
// Finally, validate the only trusted certificate that exists is still
|
||||
// the old one
|
||||
certificates = certStore.GetCertificates()
|
||||
require.Len(t, certificates, 1)
|
||||
require.Equal(t, certificates[0], origRootCert)
|
||||
}
|
||||
|
||||
func generateTestingCertificate(rootKey data.PrivateKey, gun string) (*x509.Certificate, error) {
|
||||
|
|
Loading…
Reference in New Issue