diff --git a/certs/certs.go b/certs/certs.go index 8d3aba2d3b..2b53b90f8b 100644 --- a/certs/certs.go +++ b/certs/certs.go @@ -8,9 +8,11 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/notary" "github.com/docker/notary/trustmanager" "github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/signed" + "github.com/docker/notary/tuf/utils" ) // ErrValidationFail is returned when there is no valid trusted certificates @@ -58,25 +60,35 @@ 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 succeeds, or in the case where we found no trusted -certificates for this particular GUN, we check the integrity of the root by +If the previous validation succeeds, 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. +However, if we do not have any current trusted certificates for this GUN, we +check if there are any pinned certificates specified in the trust_pinning section +of the notary client config. If this section specifies a Certs section with this +GUN, we attempt to validate that the certificates present in the downloaded root +file match the pinned ID. -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. +If the Certs section is empty are empty for this GUN, we check if the trust_pinning +section specifies a CA section specified in the config for this GUN. If so, we check +that the specified CA is valid and has signed a certificate included in the downloaded +root file. + +If both the Certs and CA configs do not match this GUN, we fall back to the TOFU +section in the config: if true, we trust certificates specified in the root for +this GUN. 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 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. + +Validation failure at any step will result in an ErrValidationFailed error. */ -func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun string) error { +func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun string, trustPinning notary.TrustPinConfig) error { logrus.Debugf("entered ValidateRoot with dns: %s", gun) signedRoot, err := data.RootFromSigned(root) if err != nil { @@ -101,7 +113,6 @@ func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun strin 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(trustedCerts) != 0 { @@ -114,9 +125,72 @@ func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun strin } } else { logrus.Debugf("found no currently valid root certificates for %s", gun) + logrus.Debugf("using trust_pinning config to bootstrap trust: %v", trustPinning) + // First, check if the Certs section is specified for our GUN. + // If so, we try to find a matching Cert that is pinned to bootstrap trust from + if pinnedID, ok := trustPinning.Certs[gun]; ok { + foundCertIDMatch := false + for _, cert := range certsFromRoot { + // Try to match by CertID or public key ID + certID, err := trustmanager.FingerprintCert(cert) + if err != nil { + continue + } + if certID == pinnedID { + // If we found our pinned cert, only use that one certificate as allValidCerts for verification + certsFromRoot = []*x509.Certificate{cert} + foundCertIDMatch = true + break + } + } + // If we didn't find any entries under our GUN in Certs with a matching certificate ID, fail validation + if !foundCertIDMatch { + return &ErrValidationFail{Reason: "failed to find matching certificate ID "} + } + } else if utils.ContainsKeyPrefix(trustPinning.CA, gun) { + // Next, check if the CA section is specified with a GUN that prefixes our GUN. If so, we use this CA to bootstrap trust: + // We attempt to add the CA PEM if it's valid, and all certs to our certStore that are from this CA + if len(trustPinning.CA) > 0 { + for caGunPrefix, caFilepath := range trustPinning.CA { + if strings.HasPrefix(gun, caGunPrefix) { + // Try to add the CA cert to our certificate store, + // and use it to validate certs in the root.json later + caCert, err := trustmanager.LoadCertFromFile(caFilepath) + if err != nil { + return &ErrValidationFail{Reason: "failed to load specified CA trust pin"} + } + if err = trustmanager.ValidateCertificate(caCert); err != nil { + return &ErrValidationFail{Reason: "failed to validate specified CA trust pin"} + } + if err = certStore.AddCertFromFile(caFilepath); err != nil { + return &ErrValidationFail{Reason: "failed to add CA trust pin to certificate store"} + } + opts, err := certStore.GetVerifyOptions(gun) + if err != nil { + return &ErrValidationFail{Reason: "failed to add CA trust pin to certificate store for verification"} + } + // Now only consider certificates that are direct children from this CA cert, overwriting allValidCerts + validCertsForCA := []*x509.Certificate{} + for _, cert := range certsFromRoot { + if _, err := cert.Verify(opts); err == nil { + validCertsForCA = append(validCertsForCA, cert) + } + } + certsFromRoot = validCertsForCA + } + } + } + } else if !trustPinning.TOFU { + // If we reach this if-case, it means that we didn't find any local certs/CAs for this GUN, + // nor did we specify any specifications for how to bootstrap trust in trust_pinning using TOFU + // If TOFU is true, we fall through and consider all certificates + return &ErrValidationFail{Reason: "could not bootstrap trust without trust_pinning configuration"} + } } // Validate the integrity of the new root (does it have valid signatures) + // Note that certsFromRoot is guaranteed to be unchanged only if we had prior cert data for this GUN or enabled TOFUS + // If we attempted to pin a certain certificate or CA, certsFromRoot could have been pruned accordingly err = signed.VerifyRoot(root, 0, trustmanager.CertsToKeys(certsFromRoot)) if err != nil { logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err) diff --git a/certs/certs_test.go b/certs/certs_test.go index 84f1ec26d9..1996aa83e3 100644 --- a/certs/certs_test.go +++ b/certs/certs_test.go @@ -32,6 +32,10 @@ const validIntermediateAndCertRSA = `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUl const signedRSARootTemplate = `{"signed":{"_type":"Root","consistent_snapshot":false,"expires":"2016-07-16T23:34:13.389129622-07:00","keys":{"1fc4fdc38f66558658c5c59b67f1716bdc6a74ef138b023ae5931db69f51d670":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nIgzLigo5D47dWQe1IUjzHXxvyx0j/OL16VQymuloWsgVDxxT6+mH3CeviMAs+/McnEPE9exnm6SQGR5x3XMw=="}},"23c29cc372109c819e081bc953b7657d05e3f968f03c21d0d75ea457590f3d14":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEClUFVWkc85OQScfTQRS02VaLIEaeCmxdwYS/hcTLVoTxlFfRfs7HyalTwXGAGO79XZZS+koE6s8D0xGcCJQkLQ=="}},"49cf5c6404a35fa41d5a5aa2ce539dfee0d7a2176d0da488914a38603b1f4292":{"keytype":"rsa-x509","keyval":{"private":null,"public":"{{.RootPem}}"}},"e3a5a4fdaf11ea1ec58f5efed6f3639b39cd4cfa1418c8b55c9a8c2447ace5d9":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgl3rzMPMEKhS1k/AX16MM4PdidpjJr+z4pj0Td+30QnpbOIARgpyR1PiFztU8BZlqG3cUazvFclr2q/xHvfrqw=="}}},"roles":{"root":{"keyids":["49cf5c6404a35fa41d5a5aa2ce539dfee0d7a2176d0da488914a38603b1f4292"],"threshold":1},"snapshot":{"keyids":["23c29cc372109c819e081bc953b7657d05e3f968f03c21d0d75ea457590f3d14"],"threshold":1},"targets":{"keyids":["1fc4fdc38f66558658c5c59b67f1716bdc6a74ef138b023ae5931db69f51d670"],"threshold":1},"timestamp":{"keyids":["e3a5a4fdaf11ea1ec58f5efed6f3639b39cd4cfa1418c8b55c9a8c2447ace5d9"],"threshold":1}},"version":2},"signatures":[{"keyid":"49cf5c6404a35fa41d5a5aa2ce539dfee0d7a2176d0da488914a38603b1f4292","method":"rsapss","sig":"YlZwtCj028Xc23+KHfj6govFEY6hMbBXO5HT20F0I5ZeIPb1l7OmkjEiwp9ZHusClY+QeqiP1CFh\n/AfCbv4tLanqMkXPtm8UJJ1hMZVq86coieB32PQDj9k6x1hErHzvPUbOzTRW2BQkFFMZFkLDAd06\npH8lmxyPLOhdkVE8qIT7sBCy/4bQIGfvEX6yCDz84MZdcLNX5B9mzGi9A7gDloh9IEZxA8UgoI18\nSYpv/fYeSZSqM/ws2G+kiELGgTWhcZ+gOlF7ArM/DOlcC/NYqcvY1ugE6Gn7G8opre6NOofdRp3w\n603A2rMMvYTwqKLY6oX/d+07A2+WGHXPUy5otCAybWOw2hIZ35Jjmh12g6Dc6Qk4K2zXwAgvWwBU\nWlT8MlP1Tf7f80jnGjh0aARlHI4LCxlYU5L/pCaYuHgynujvLuzoOuiiPfJv7sYvKoQ8UieE1w//\nHc8E6tWtV5G2FguKLurMoKZ9FBWcanDO0fg5AWuG3qcgUJdvh9acQ33EKer1fqBxs6LSAUWo8rDt\nQkg+b55AW0YBukAW9IAfMySQGAS2e3mHZ8nK/ijaygCRu7/P+NgKY9/zpmfL2xgcNslLcANcSOOt\nhiJS6yqYM9i9G0af0yw/TxAT4ntwjVm8u52UyR/hXIiUc/mjZcYRbSmJOHws902+i+Z/qv72knk="}]}` +const rootPubKeyID = "49cf5c6404a35fa41d5a5aa2ce539dfee0d7a2176d0da488914a38603b1f4292" + +const targetsPubKeyID = "1fc4fdc38f66558658c5c59b67f1716bdc6a74ef138b023ae5931db69f51d670" + func TestCertsToRemove(t *testing.T) { // Get a few certificates to test with cert1, err := trustmanager.LoadCertFromFile("../fixtures/secure.example.com.crt") @@ -138,12 +142,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") + err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{TOFU: true}) 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") + err = ValidateRoot(certStore, &testSignedRoot, "diogomonica.com/notary", notary.TrustPinConfig{TOFU: true}) require.Error(t, err, "An error was expected") require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}) @@ -157,7 +161,7 @@ func TestValidateRoot(t *testing.T) { // Unmarshal our signedroot json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary") + err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{TOFU: true}) require.Error(t, err, "illegal base64 data at input byte") // @@ -170,7 +174,7 @@ func TestValidateRoot(t *testing.T) { // Unmarshal our signedroot json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary") + err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{TOFU: true}) require.Error(t, err, "An error was expected") require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}) @@ -185,7 +189,7 @@ func TestValidateRoot(t *testing.T) { // Unmarshal our signedroot json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary") + err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{TOFU: true}) require.Error(t, err, "An error was expected") require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}) @@ -203,11 +207,152 @@ func TestValidateRoot(t *testing.T) { // Unmarshal our signedroot json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) - err = ValidateRoot(certStore, &testSignedRoot, "secure.example.com") + err = ValidateRoot(certStore, &testSignedRoot, "secure.example.com", notary.TrustPinConfig{TOFU: true}) require.Error(t, err, "An error was expected") require.Equal(t, err, &ErrValidationFail{Reason: "failed to validate integrity of roots"}) } +func TestValidateRootWithoutTOFUS(t *testing.T) { + var testSignedRoot data.Signed + var signedRootBytes bytes.Buffer + + // Temporary directory where test files will be created + tempBaseDir, err := ioutil.TempDir("", "notary-test-") + defer os.RemoveAll(tempBaseDir) + assert.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, + ) + assert.NoError(t, err) + + // Execute our template + 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 since we are explicitly disabling TOFU and have no local certs + err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{TOFU: false}) + assert.Error(t, err) +} + +func TestValidateRootWithPinnedCert(t *testing.T) { + var testSignedRoot data.Signed + var signedRootBytes bytes.Buffer + + // Temporary directory where test files will be created + tempBaseDir, err := ioutil.TempDir("", "notary-test-") + defer os.RemoveAll(tempBaseDir) + assert.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, + ) + assert.NoError(t, err) + + // Execute our template + templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) + templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) + + // Unmarshal our signedroot + 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", notary.TrustPinConfig{Certs: map[string]string{"docker.com/notary": rootPubKeyID}}) + assert.NoError(t, err) +} + +func TestValidateRootFailuresWithPinnedCert(t *testing.T) { + var testSignedRoot data.Signed + var signedRootBytes bytes.Buffer + + // Temporary directory where test files will be created + tempBaseDir, err := ioutil.TempDir("", "notary-test-") + defer os.RemoveAll(tempBaseDir) + assert.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, + ) + assert.NoError(t, err) + + // Execute our template + templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) + templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) + + // Unmarshal our signedroot + json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) + + // This call to ValidateRoot should fail due to an incorrect cert ID + err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{Certs: map[string]string{"docker.com/notary": "ABSOLUTELY NOT A CERT ID"}}) + assert.Error(t, err) + certStore.RemoveAll() + + // This call to ValidateRoot should fail due to an empty cert ID + err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{Certs: map[string]string{"docker.com/notary": ""}}) + assert.Error(t, err) + certStore.RemoveAll() + + // This call to ValidateRoot should fail due to an invalid GUN (even though the cert ID is correct), and TOFUS defaults to false + err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{Certs: map[string]string{"not_a_gun": rootPubKeyID}, TOFU: false}) + assert.Error(t, err) + certStore.RemoveAll() + + // 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", notary.TrustPinConfig{Certs: map[string]string{"docker.com/notary": targetsPubKeyID}}) + assert.Error(t, err) + certStore.RemoveAll() + + // This call to ValidateRoot should succeed because we fall through to TOFUS even though we have no matching GUNs under Certs + err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{Certs: map[string]string{"not_a_gun": rootPubKeyID}, TOFU: true}) + assert.NoError(t, err) + certStore.RemoveAll() +} + +func TestValidateRootWithPinnedCA(t *testing.T) { + var testSignedRoot data.Signed + var signedRootBytes bytes.Buffer + + // Temporary directory where test files will be created + tempBaseDir, err := ioutil.TempDir("", "notary-test-") + defer os.RemoveAll(tempBaseDir) + assert.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, + ) + assert.NoError(t, err) + + // Execute our template + 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", notary.TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}}) + assert.Error(t, err) + + // This call to ValidateRoot will fail because we have no valid GUNs to use + err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}}) + assert.Error(t, err) +} + // TestValidateSuccessfulRootRotation runs through a full root certificate rotation // We test this with both an RSA and ECDSA root certificate func TestValidateSuccessfulRootRotation(t *testing.T) { @@ -296,7 +441,7 @@ 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) + err = ValidateRoot(certStore, signedTestRoot, gun, notary.TrustPinConfig{TOFU: true}) require.NoError(t, err) // Finally, validate the only trusted certificate that exists is the new one @@ -357,7 +502,7 @@ 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) + err = ValidateRoot(certStore, signedTestRoot, gun, notary.TrustPinConfig{TOFU: true}) require.Error(t, err, "insuficient signatures on root") // Finally, validate the only trusted certificate that exists is still @@ -421,7 +566,7 @@ 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) + err = ValidateRoot(certStore, signedTestRoot, gun, notary.TrustPinConfig{TOFU: true}) require.Error(t, err, "insuficient signatures on root") // Finally, validate the only trusted certificate that exists is still diff --git a/client/backwards_compatibility_test.go b/client/backwards_compatibility_test.go index 09abd061e3..f69639609c 100644 --- a/client/backwards_compatibility_test.go +++ b/client/backwards_compatibility_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/docker/notary" "github.com/docker/notary/passphrase" "github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/store" @@ -90,7 +91,7 @@ func Test0Dot1RepoFormat(t *testing.T) { defer ts.Close() repo, err := NewNotaryRepository(tmpDir, gun, ts.URL, http.DefaultTransport, - passphrase.ConstantRetriever(passwd)) + passphrase.ConstantRetriever(passwd), notary.TrustPinConfig{TOFU: true}) require.NoError(t, err, "error creating repo: %s", err) // targets should have 1 target, and it should be readable offline @@ -152,7 +153,7 @@ func TestDownloading0Dot1RepoFormat(t *testing.T) { defer os.RemoveAll(repoDir) repo, err := NewNotaryRepository(repoDir, gun, ts.URL, http.DefaultTransport, - passphrase.ConstantRetriever(passwd)) + passphrase.ConstantRetriever(passwd), notary.TrustPinConfig{TOFU: true}) require.NoError(t, err, "error creating repo: %s", err) err = repo.Update(true) diff --git a/client/client.go b/client/client.go index 8f434dc794..eb6f923ab9 100644 --- a/client/client.go +++ b/client/client.go @@ -88,13 +88,14 @@ type NotaryRepository struct { tufRepo *tuf.Repo roundTrip http.RoundTripper CertStore trustmanager.X509Store + trustPinning notary.TrustPinConfig } // repositoryFromKeystores is a helper function for NewNotaryRepository that // takes some basic NotaryRepository parameters as well as keystores (in order // of usage preference), and returns a NotaryRepository. func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper, - keyStores []trustmanager.KeyStore) (*NotaryRepository, error) { + keyStores []trustmanager.KeyStore, trustPin notary.TrustPinConfig) (*NotaryRepository, error) { certPath := filepath.Join(baseDir, notary.TrustedCertsDir) certStore, err := trustmanager.NewX509FilteredFileStore( @@ -115,6 +116,7 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper, CryptoService: cryptoService, roundTrip: rt, CertStore: certStore, + trustPinning: trustPin, } fileStore, err := store.NewFilesystemStore( @@ -873,7 +875,7 @@ func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, erro return nil, err } - err = certs.ValidateRoot(r.CertStore, root, r.gun) + err = certs.ValidateRoot(r.CertStore, root, r.gun, r.trustPinning) if err != nil { return nil, err } diff --git a/client/client_test.go b/client/client_test.go index e779432525..84dced87f7 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -191,7 +191,7 @@ func createRepoAndKey(t *testing.T, rootType, tempBaseDir, gun, url string) ( rec := newRoleRecorder() repo, err := NewNotaryRepository( - tempBaseDir, gun, url, http.DefaultTransport, rec.retriever) + tempBaseDir, gun, url, http.DefaultTransport, rec.retriever, notary.TrustPinConfig{TOFU: true}) require.NoError(t, err, "error creating repo: %s", err) rootPubKey, err := repo.CryptoService.Create("root", repo.gun, rootType) @@ -222,7 +222,7 @@ func newRepoToTestRepo(t *testing.T, existingRepo *NotaryRepository, newDir bool rec := newRoleRecorder() repo, err := NewNotaryRepository( repoDir, existingRepo.gun, existingRepo.baseURL, - http.DefaultTransport, rec.retriever) + http.DefaultTransport, rec.retriever, notary.TrustPinConfig{TOFU: true}) require.NoError(t, err, "error creating repository: %s", err) if err != nil && newDir { defer os.RemoveAll(repoDir) @@ -574,7 +574,7 @@ func testInitRepoAttemptsExceeded(t *testing.T, rootType string) { defer ts.Close() retriever := passphrase.ConstantRetriever("password") - repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, retriever) + repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, retriever, notary.TrustPinConfig{TOFU: true}) require.NoError(t, err, "error creating repo: %s", err) rootPubKey, err := repo.CryptoService.Create("root", repo.gun, rootType) require.NoError(t, err, "error generating root key: %s", err) @@ -582,7 +582,7 @@ func testInitRepoAttemptsExceeded(t *testing.T, rootType string) { retriever = passphrase.ConstantRetriever("incorrect password") // repo.CryptoService’s FileKeyStore caches the unlocked private key, so to test // private key unlocking we need a new repo instance. - repo, err = NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, retriever) + repo, err = NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, retriever, notary.TrustPinConfig{TOFU: true}) require.NoError(t, err, "error creating repo: %s", err) err = repo.Initialize(rootPubKey.ID()) require.EqualError(t, err, trustmanager.ErrAttemptsExceeded{}.Error()) @@ -612,14 +612,14 @@ func testInitRepoPasswordInvalid(t *testing.T, rootType string) { defer ts.Close() retriever := passphrase.ConstantRetriever("password") - repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, retriever) + repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, retriever, notary.TrustPinConfig{TOFU: true}) require.NoError(t, err, "error creating repo: %s", err) rootPubKey, err := repo.CryptoService.Create("root", repo.gun, rootType) require.NoError(t, err, "error generating root key: %s", err) // repo.CryptoService’s FileKeyStore caches the unlocked private key, so to test // private key unlocking we need a new repo instance. - repo, err = NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, giveUpPassphraseRetriever) + repo, err = NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, giveUpPassphraseRetriever, notary.TrustPinConfig{TOFU: true}) require.NoError(t, err, "error creating repo: %s", err) err = repo.Initialize(rootPubKey.ID()) require.EqualError(t, err, trustmanager.ErrPasswordInvalid{}.Error()) @@ -1641,7 +1641,7 @@ func TestPublishUninitializedRepo(t *testing.T) { defer os.RemoveAll(tempBaseDir) repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, - http.DefaultTransport, passphraseRetriever) + http.DefaultTransport, passphraseRetriever, notary.TrustPinConfig{TOFU: true}) require.NoError(t, err, "error creating repository: %s", err) err = repo.Publish() require.Error(t, err) @@ -2005,7 +2005,7 @@ func TestPublishSnapshotLocalKeysCreatedFirst(t *testing.T) { defer ts.Close() repo, err := NewNotaryRepository( - tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever) + tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever, notary.TrustPinConfig{TOFU: true}) require.NoError(t, err, "error creating repo: %s", err) cs := cryptoservice.NewCryptoService(trustmanager.NewKeyMemoryStore(passphraseRetriever)) @@ -2863,7 +2863,7 @@ func TestRemoteServerUnavailableNoLocalCache(t *testing.T) { defer ts.Close() repo, err := NewNotaryRepository(tempBaseDir, "docker.com/notary", - ts.URL, http.DefaultTransport, passphraseRetriever) + ts.URL, http.DefaultTransport, passphraseRetriever, notary.TrustPinConfig{TOFU: true}) require.NoError(t, err, "error creating repo: %s", err) _, err = repo.ListTargets(data.CanonicalTargetsRole) @@ -3173,6 +3173,7 @@ func TestBootstrapClientBadURL(t *testing.T) { "http://localhost:9998", http.DefaultTransport, passphraseRetriever, + notary.TrustPinConfig{TOFU: true}, ) require.NoError(t, err, "error creating repo: %s", err) @@ -3202,6 +3203,7 @@ func TestBootstrapClientInvalidURL(t *testing.T) { "#!*)&!)#*^%!#)%^!#", http.DefaultTransport, passphraseRetriever, + notary.TrustPinConfig{TOFU: true}, ) require.NoError(t, err, "error creating repo: %s", err) diff --git a/client/client_update_test.go b/client/client_update_test.go index 426e4d00f8..df66a82a56 100644 --- a/client/client_update_test.go +++ b/client/client_update_test.go @@ -14,6 +14,7 @@ import ( "testing" "time" + "github.com/docker/notary" "github.com/docker/notary/certs" "github.com/docker/notary/passphrase" "github.com/docker/notary/tuf/client" @@ -31,7 +32,7 @@ func newBlankRepo(t *testing.T, url string) *NotaryRepository { require.NoError(t, err, "failed to create a temporary directory: %s", err) repo, err := NewNotaryRepository(tempBaseDir, "docker.com/notary", url, - http.DefaultTransport, passphrase.ConstantRetriever("pass")) + http.DefaultTransport, passphrase.ConstantRetriever("pass"), notary.TrustPinConfig{TOFU: true}) require.NoError(t, err) return repo } diff --git a/client/repo.go b/client/repo.go index d9931891ff..6e5df7bad8 100644 --- a/client/repo.go +++ b/client/repo.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" + "github.com/docker/notary" "github.com/docker/notary/passphrase" "github.com/docker/notary/trustmanager" ) @@ -14,7 +15,7 @@ import ( // It takes the base directory under where all the trust files will be stored // (usually ~/.docker/trust/). func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper, - retriever passphrase.Retriever) ( + retriever passphrase.Retriever, trustPinning notary.TrustPinConfig) ( *NotaryRepository, error) { fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever) @@ -23,5 +24,5 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper, } return repositoryFromKeystores(baseDir, gun, baseURL, rt, - []trustmanager.KeyStore{fileKeyStore}) + []trustmanager.KeyStore{fileKeyStore}, trustPinning) } diff --git a/client/repo_pkcs11.go b/client/repo_pkcs11.go index 60ae18f410..78dd88888a 100644 --- a/client/repo_pkcs11.go +++ b/client/repo_pkcs11.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" + "github.com/docker/notary" "github.com/docker/notary/passphrase" "github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager/yubikey" @@ -15,7 +16,7 @@ import ( // It takes the base directory under where all the trust files will be stored // (usually ~/.docker/trust/). func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper, - retriever passphrase.Retriever) ( + retriever passphrase.Retriever, trustPinning notary.TrustPinConfig) ( *NotaryRepository, error) { fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever) @@ -29,5 +30,5 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper, keyStores = []trustmanager.KeyStore{yubiKeyStore, fileKeyStore} } - return repositoryFromKeystores(baseDir, gun, baseURL, rt, keyStores) + return repositoryFromKeystores(baseDir, gun, baseURL, rt, keyStores, trustPinning) } diff --git a/cmd/notary/cert.go b/cmd/notary/cert.go index 2922850d4e..a840ef4bf5 100644 --- a/cmd/notary/cert.go +++ b/cmd/notary/cert.go @@ -139,7 +139,7 @@ func (c *certCommander) certRemove(cmd *cobra.Command, args []string) error { // Remove all TUF data, so call RemoveTrustData on a NotaryRepository with the GUN // no online operations are performed so the transport argument is nil nRepo, err := notaryclient.NewNotaryRepository( - trustDir, c.certRemoveGUN, getRemoteTrustServer(config), nil, c.retriever) + trustDir, c.certRemoveGUN, getRemoteTrustServer(config), nil, c.retriever, config.Get("trust_pinning").(notary.TrustPinConfig)) if err != nil { return fmt.Errorf("Could not establish trust data for GUN %s", c.certRemoveGUN) } diff --git a/cmd/notary/delegations.go b/cmd/notary/delegations.go index d92a3f8769..26373d46fe 100644 --- a/cmd/notary/delegations.go +++ b/cmd/notary/delegations.go @@ -4,6 +4,7 @@ import ( "fmt" "io/ioutil" + "github.com/docker/notary" notaryclient "github.com/docker/notary/client" "github.com/docker/notary/passphrase" "github.com/docker/notary/trustmanager" @@ -85,7 +86,7 @@ func (d *delegationCommander) delegationsList(cmd *cobra.Command, args []string) // initialize repo with transport to get latest state of the world before listing delegations nRepo, err := notaryclient.NewNotaryRepository( - config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, d.retriever) + config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, d.retriever, config.Get("trust_pinning").(notary.TrustPinConfig)) if err != nil { return err } @@ -140,7 +141,7 @@ func (d *delegationCommander) delegationRemove(cmd *cobra.Command, args []string // no online operations are performed by add so the transport argument // should be nil nRepo, err := notaryclient.NewNotaryRepository( - config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, d.retriever) + config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, d.retriever, config.Get("trust_pinning").(notary.TrustPinConfig)) if err != nil { return err } @@ -246,7 +247,7 @@ func (d *delegationCommander) delegationAdd(cmd *cobra.Command, args []string) e // no online operations are performed by add so the transport argument // should be nil nRepo, err := notaryclient.NewNotaryRepository( - config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, d.retriever) + config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, d.retriever, config.Get("trust_pinning").(notary.TrustPinConfig)) if err != nil { return err } diff --git a/cmd/notary/delegations_test.go b/cmd/notary/delegations_test.go index e80f5ca460..1ac9e021ad 100644 --- a/cmd/notary/delegations_test.go +++ b/cmd/notary/delegations_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/docker/notary" "github.com/docker/notary/cryptoservice" "github.com/docker/notary/trustmanager" "github.com/spf13/viper" @@ -21,6 +22,7 @@ func setup() *delegationCommander { configGetter: func() (*viper.Viper, error) { mainViper := viper.New() mainViper.Set("trust_dir", testTrustDir) + mainViper.Set("trust_pinning", notary.TrustPinConfig{TOFU: true}) return mainViper, nil }, retriever: nil, diff --git a/cmd/notary/keys.go b/cmd/notary/keys.go index 56d3e54753..ab8fc4cd9b 100644 --- a/cmd/notary/keys.go +++ b/cmd/notary/keys.go @@ -420,7 +420,7 @@ func (k *keyCommander) keysRotate(cmd *cobra.Command, args []string) error { nRepo, err := notaryclient.NewNotaryRepository( config.GetString("trust_dir"), gun, getRemoteTrustServer(config), - rt, k.getRetriever()) + rt, k.getRetriever(), config.Get("trust_pinning").(notary.TrustPinConfig)) if err != nil { return err } diff --git a/cmd/notary/keys_test.go b/cmd/notary/keys_test.go index 20a3b80d41..f821dc098f 100644 --- a/cmd/notary/keys_test.go +++ b/cmd/notary/keys_test.go @@ -249,7 +249,11 @@ func TestRotateKeyInvalidRoles(t *testing.T) { for _, role := range invalids { for _, serverManaged := range []bool{true, false} { k := &keyCommander{ - configGetter: func() (*viper.Viper, error) { return viper.New(), nil }, + configGetter: func() (*viper.Viper, error) { + v := viper.New() + v.Set("trust_pinning", notary.TrustPinConfig{TOFU: true}) + return v, nil + }, getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") }, rotateKeyRole: role, rotateKeyServerManaged: serverManaged, @@ -270,7 +274,11 @@ func TestRotateKeyInvalidRoles(t *testing.T) { func TestRotateKeyTargetCannotBeServerManaged(t *testing.T) { setUp(t) k := &keyCommander{ - configGetter: func() (*viper.Viper, error) { return viper.New(), nil }, + configGetter: func() (*viper.Viper, error) { + v := viper.New() + v.Set("trust_pinning", notary.TrustPinConfig{TOFU: true}) + return v, nil + }, getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") }, rotateKeyRole: data.CanonicalTargetsRole, rotateKeyServerManaged: true, @@ -284,7 +292,11 @@ func TestRotateKeyTargetCannotBeServerManaged(t *testing.T) { func TestRotateKeyTimestampCannotBeLocallyManaged(t *testing.T) { setUp(t) k := &keyCommander{ - configGetter: func() (*viper.Viper, error) { return viper.New(), nil }, + configGetter: func() (*viper.Viper, error) { + v := viper.New() + v.Set("trust_pinning", notary.TrustPinConfig{TOFU: true}) + return v, nil + }, getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") }, rotateKeyRole: data.CanonicalTimestampRole, rotateKeyServerManaged: false, @@ -329,7 +341,7 @@ func setUpRepo(t *testing.T, tempBaseDir, gun string, ret passphrase.Retriever) ts := httptest.NewServer(server.RootHandler(nil, ctx, cryptoService, nil, nil)) repo, err := client.NewNotaryRepository( - tempBaseDir, gun, ts.URL, http.DefaultTransport, ret) + tempBaseDir, gun, ts.URL, http.DefaultTransport, ret, notary.TrustPinConfig{TOFU: true}) require.NoError(t, err, "error creating repo: %s", err) rootPubKey, err := repo.CryptoService.Create("root", "", data.ECDSAKey) @@ -364,6 +376,7 @@ func TestRotateKeyRemoteServerManagesKey(t *testing.T) { v := viper.New() v.SetDefault("trust_dir", tempBaseDir) v.SetDefault("remote_server.url", ts.URL) + v.SetDefault("trust_pinning", notary.TrustPinConfig{TOFU: true}) return v, nil }, getRetriever: func() passphrase.Retriever { return ret }, @@ -371,7 +384,7 @@ func TestRotateKeyRemoteServerManagesKey(t *testing.T) { } require.NoError(t, k.keysRotate(&cobra.Command{}, []string{gun, role, "-r"})) - repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, ret) + repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, ret, notary.TrustPinConfig{TOFU: true}) require.NoError(t, err, "error creating repo: %s", err) cl, err := repo.GetChangelist() @@ -395,7 +408,6 @@ func TestRotateKeyRemoteServerManagesKey(t *testing.T) { require.True(t, ok) } } - } } @@ -419,6 +431,7 @@ func TestRotateKeyBothKeys(t *testing.T) { v := viper.New() v.SetDefault("trust_dir", tempBaseDir) v.SetDefault("remote_server.url", ts.URL) + v.Set("trust_pinning", notary.TrustPinConfig{TOFU: true}) return v, nil }, getRetriever: func() passphrase.Retriever { return ret }, @@ -426,7 +439,7 @@ func TestRotateKeyBothKeys(t *testing.T) { require.NoError(t, k.keysRotate(&cobra.Command{}, []string{gun, data.CanonicalTargetsRole})) require.NoError(t, k.keysRotate(&cobra.Command{}, []string{gun, data.CanonicalSnapshotRole})) - repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, nil, ret) + repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, nil, ret, notary.TrustPinConfig{TOFU: true}) require.NoError(t, err, "error creating repo: %s", err) cl, err := repo.GetChangelist() diff --git a/cmd/notary/main.go b/cmd/notary/main.go index 6fa2960064..fbb90394cc 100644 --- a/cmd/notary/main.go +++ b/cmd/notary/main.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/Sirupsen/logrus" + "github.com/docker/notary" "github.com/docker/notary/passphrase" "github.com/docker/notary/tuf/data" "github.com/docker/notary/version" @@ -97,6 +98,7 @@ func (n *notaryCommander) parseConfig() (*viper.Viper, error) { // Setup the configuration details into viper config.SetDefault("trust_dir", defaultTrustDir) config.SetDefault("remote_server", map[string]string{"url": defaultServerURL}) + config.SetDefault("trust_pinning", notary.TrustPinConfig{TOFU: true}) // Find and read the config file if err := config.ReadInConfig(); err != nil { @@ -109,6 +111,13 @@ func (n *notaryCommander) parseConfig() (*viper.Viper, error) { } } + // Check that the trust_pinning section was a valid notary.TrustPinConfig object + // We parse this here so that parsing and usage are separated in code, we then use type assertions later + if _, ok := config.Get("trust_pinning").(notary.TrustPinConfig); !ok { + // Return a noisy error if the user passed an invalid trust_pinning section instead of sticking to default TOFUS + return nil, fmt.Errorf("error parsing trust_pinning section of config") + } + // At this point we either have the default value or the one set by the config. // Either way, some command-line flags have precedence and overwrites the value if n.trustDir != "" { diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 397ea39cf3..019d54755d 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -16,6 +16,7 @@ import ( "github.com/docker/distribution/registry/client/transport" "github.com/docker/docker/pkg/term" "github.com/docker/go-connections/tlsconfig" + "github.com/docker/notary" notaryclient "github.com/docker/notary/client" "github.com/docker/notary/passphrase" "github.com/docker/notary/tuf/data" @@ -119,7 +120,7 @@ func (t *tufCommander) tufAdd(cmd *cobra.Command, args []string) error { // no online operations are performed by add so the transport argument // should be nil nRepo, err := notaryclient.NewNotaryRepository( - config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever) + config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig)) if err != nil { return err } @@ -156,7 +157,7 @@ func (t *tufCommander) tufInit(cmd *cobra.Command, args []string) error { } nRepo, err := notaryclient.NewNotaryRepository( - config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever) + config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig)) if err != nil { return err } @@ -201,7 +202,7 @@ func (t *tufCommander) tufList(cmd *cobra.Command, args []string) error { } nRepo, err := notaryclient.NewNotaryRepository( - config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever) + config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig)) if err != nil { return err } @@ -236,7 +237,7 @@ func (t *tufCommander) tufLookup(cmd *cobra.Command, args []string) error { } nRepo, err := notaryclient.NewNotaryRepository( - config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever) + config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig)) if err != nil { return err } @@ -263,7 +264,7 @@ func (t *tufCommander) tufStatus(cmd *cobra.Command, args []string) error { gun := args[0] nRepo, err := notaryclient.NewNotaryRepository( - config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever) + config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig)) if err != nil { return err } @@ -307,7 +308,7 @@ func (t *tufCommander) tufPublish(cmd *cobra.Command, args []string) error { } nRepo, err := notaryclient.NewNotaryRepository( - config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever) + config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig)) if err != nil { return err } @@ -333,7 +334,7 @@ func (t *tufCommander) tufRemove(cmd *cobra.Command, args []string) error { // no online operation are performed by remove so the transport argument // should be nil. repo, err := notaryclient.NewNotaryRepository( - config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever) + config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig)) if err != nil { return err } @@ -372,7 +373,7 @@ func (t *tufCommander) tufVerify(cmd *cobra.Command, args []string) error { } nRepo, err := notaryclient.NewNotaryRepository( - config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever) + config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig)) if err != nil { return err } diff --git a/const.go b/const.go index f3d834ad16..3724bb99b5 100644 --- a/const.go +++ b/const.go @@ -64,3 +64,11 @@ var NotaryDefaultExpiries = map[string]time.Duration{ "snapshot": NotarySnapshotExpiry, "timestamp": NotaryTimestampExpiry, } + +// TrustPinConfig represents the configuration under the trust_pinning section of the config file +// This struct represents the preferred way to bootstrap trust for this repository +type TrustPinConfig struct { + CA map[string]string + Certs map[string]string + TOFU bool +} diff --git a/tuf/utils/utils.go b/tuf/utils/utils.go index 8de72b6797..4a8639da23 100644 --- a/tuf/utils/utils.go +++ b/tuf/utils/utils.go @@ -67,6 +67,16 @@ func StrSliceContainsI(ss []string, s string) bool { return false } +// ContainsKeyPrefix checks if the given string map contains a key that prefixes the desired key +func ContainsKeyPrefix(smap map[string]string, s string) bool { + for k := range smap { + if strings.HasPrefix(s, k) { + return true + } + } + return false +} + // FileExists returns true if a file (or dir) exists at the given path, // false otherwise func FileExists(path string) bool {