Example #1
0
// NewTrustPinChecker returns a new certChecker function from a TrustPinConfig for a GUN
func NewTrustPinChecker(trustPinConfig TrustPinConfig, gun string) (CertChecker, error) {
	t := trustPinChecker{gun: gun, config: trustPinConfig}
	// Determine the mode, and if it's even valid
	if pinnedCerts, ok := trustPinConfig.Certs[gun]; ok {
		t.pinnedCertIDs = pinnedCerts
		return t.certsCheck, nil
	}

	if caFilepath, err := getPinnedCAFilepathByPrefix(gun, trustPinConfig); err == nil {
		// Try to add the CA certs from its bundle file to our certificate store,
		// and use it to validate certs in the root.json later
		caCerts, err := trustmanager.LoadCertBundleFromFile(caFilepath)
		if err != nil {
			return nil, fmt.Errorf("could not load root cert from CA path")
		}
		// Now only consider certificates that are direct children from this CA cert chain
		caRootPool := x509.NewCertPool()
		for _, caCert := range caCerts {
			if err = trustmanager.ValidateCertificate(caCert); err != nil {
				continue
			}
			caRootPool.AddCert(caCert)
		}
		// If we didn't have any valid CA certs, error out
		if len(caRootPool.Subjects()) == 0 {
			return nil, fmt.Errorf("invalid CA certs provided")
		}
		t.pinnedCAPool = caRootPool
		return t.caCheck, nil
	}

	if !trustPinConfig.DisableTOFU {
		return t.tofusCheck, nil
	}
	return nil, fmt.Errorf("invalid trust pinning specified")
}
Example #2
0
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)
	require.NoError(t, err, "failed to create a temporary directory: %s", err)

	templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
	templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
	// Unmarshal our signedRoot
	json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
	typedSignedRoot, err := data.RootFromSigned(&testSignedRoot)
	require.NoError(t, err)

	// This call to trustpinning.ValidateRoot will fail because we have an invalid path for the CA
	_, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}})
	require.Error(t, err)

	// This call to trustpinning.ValidateRoot will fail because we have no valid GUNs to use, and TOFUS is disabled
	_, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: true})
	require.Error(t, err)

	// This call to trustpinning.ValidateRoot will succeed because we have no valid GUNs to use and we fall back to enabled TOFUS
	validatedRoot, err := trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: false})
	require.NoError(t, err)
	generateRootKeyIDs(typedSignedRoot)
	require.Equal(t, typedSignedRoot, validatedRoot)

	// 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 trustpinning.ValidateRoot
	_, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.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 = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.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
	leafCert, err := trustmanager.LoadCertFromFile("../fixtures/notary-signer.crt")
	require.NoError(t, err)

	intermediateCert, err := trustmanager.LoadCertFromFile("../fixtures/intermediate-ca.crt")
	require.NoError(t, err)

	pemChainBytes, err := trustmanager.CertChainToPEM([]*x509.Certificate{leafCert, intermediateCert})
	require.NoError(t, err)

	newRootKey := data.NewPublicKey(data.RSAx509Key, pemChainBytes)

	rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{newRootKey.ID()}, nil)
	require.NoError(t, err)

	testRoot, err := data.NewRoot(
		map[string]data.PublicKey{newRootKey.ID(): newRootKey},
		map[string]*data.RootRole{
			data.CanonicalRootRole:      &rootRole.RootRole,
			data.CanonicalTimestampRole: &rootRole.RootRole,
			data.CanonicalTargetsRole:   &rootRole.RootRole,
			data.CanonicalSnapshotRole:  &rootRole.RootRole},
		false,
	)
	testRoot.Signed.Version = 1
	require.NoError(t, err, "Failed to create new root")

	keyReader, err := os.Open("../fixtures/notary-signer.key")
	require.NoError(t, err, "could not open key file")
	pemBytes, err := ioutil.ReadAll(keyReader)
	require.NoError(t, err, "could not read key file")
	privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "")
	require.NoError(t, err)

	store, err := trustmanager.NewKeyFileStore(tempBaseDir, passphraseRetriever)
	require.NoError(t, err)
	cs := cryptoservice.NewCryptoService(store)

	err = store.AddKey(trustmanager.KeyInfo{Role: data.CanonicalRootRole, Gun: "notary-signer"}, privKey)
	require.NoError(t, err)

	newTestSignedRoot, err := testRoot.ToSigned()
	require.NoError(t, err)

	err = signed.Sign(cs, newTestSignedRoot, []data.PublicKey{newRootKey}, 1, nil)
	require.NoError(t, err)

	newTypedSignedRoot, err := data.RootFromSigned(newTestSignedRoot)
	require.NoError(t, err)

	// Check that we validate correctly against a pinned CA and provided bundle
	validatedRoot, err = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.TrustPinConfig{CA: map[string]string{"notary-signer": validCAFilepath}, DisableTOFU: true})
	require.NoError(t, err)
	generateRootKeyIDs(newTypedSignedRoot)
	require.Equal(t, newTypedSignedRoot, validatedRoot)

	// Add an expired CA for the same gun to our previous pinned bundle, ensure that we still validate correctly
	goodRootCABundle, err := trustmanager.LoadCertBundleFromFile(validCAFilepath)
	require.NoError(t, err)
	memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
	cryptoService := cryptoservice.NewCryptoService(memKeyStore)
	testPubKey, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey)
	require.NoError(t, err)
	testPrivKey, _, err := memKeyStore.GetKey(testPubKey.ID())
	require.NoError(t, err)
	expiredCert, err := generateExpiredTestingCertificate(testPrivKey, "notary-signer")
	require.NoError(t, err)
	bundleWithExpiredCert, err := trustmanager.CertChainToPEM(append(goodRootCABundle, expiredCert))
	require.NoError(t, err)
	bundleWithExpiredCertPath := filepath.Join(tempBaseDir, "bundle_with_expired_cert.pem")
	require.NoError(t, ioutil.WriteFile(bundleWithExpiredCertPath, bundleWithExpiredCert, 0644))

	// Check that we validate correctly against a pinned CA and provided bundle
	validatedRoot, err = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithExpiredCertPath}, DisableTOFU: true})
	require.NoError(t, err)
	require.Equal(t, newTypedSignedRoot, validatedRoot)

	testPubKey2, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey)
	require.NoError(t, err)
	testPrivKey2, _, err := memKeyStore.GetKey(testPubKey2.ID())
	require.NoError(t, err)
	expiredCert2, err := generateExpiredTestingCertificate(testPrivKey2, "notary-signer")
	require.NoError(t, err)
	allExpiredCertBundle, err := trustmanager.CertChainToPEM([]*x509.Certificate{expiredCert, expiredCert2})
	require.NoError(t, err)
	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 = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.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
	testPubKey3, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey)
	require.NoError(t, err)
	testPrivKey3, _, err := memKeyStore.GetKey(testPubKey3.ID())
	require.NoError(t, err)
	validCert, err := cryptoservice.GenerateCertificate(testPrivKey3, "notary-signer", time.Now(), time.Now().AddDate(1, 0, 0))
	require.NoError(t, err)
	bundleWithWrongCert, err := trustmanager.CertChainToPEM([]*x509.Certificate{validCert})
	require.NoError(t, err)
	bundleWithWrongCertPath := filepath.Join(tempBaseDir, "bundle_with_expired_cert.pem")
	require.NoError(t, ioutil.WriteFile(bundleWithWrongCertPath, bundleWithWrongCert, 0644))
	_, err = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithWrongCertPath}, DisableTOFU: true})
	require.Error(t, err)
}