Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support wildcard CNs in root leaf certificates #1088

Merged
merged 1 commit into from
Jan 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions trustpinning/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"github.com/docker/notary/tuf/utils"
)

const wildcard = "*"

// ErrValidationFail is returned when there is no valid trusted certificates
// being served inside of the roots.json
type ErrValidationFail struct {
Expand Down Expand Up @@ -175,6 +177,17 @@ func ValidateRoot(prevRoot *data.SignedRoot, root *data.Signed, gun string, trus
return data.RootFromSigned(root)
}

// MatchCNToGun checks that the common name in a cert is valid for the given gun.
// This allows wildcards as suffixes, e.g. `namespace/*`
func MatchCNToGun(commonName, gun string) bool {
if strings.HasSuffix(commonName, wildcard) {
prefix := strings.TrimRight(commonName, wildcard)
logrus.Debugf("checking gun %s against wildcard prefix %s", gun, prefix)
return strings.HasPrefix(gun, prefix)
}
return commonName == gun
}

// 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.
Expand All @@ -183,8 +196,8 @@ func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string, c

// Go through every leaf certificate and check that the CN matches the gun
for id, cert := range allLeafCerts {
// Validate that this leaf certificate has a CN that matches the exact gun
if cert.Subject.CommonName != gun {
// Validate that this leaf certificate has a CN that matches the gun
if !MatchCNToGun(cert.Subject.CommonName, gun) {
logrus.Debugf("error leaf certificate CN: %s doesn't match the given GUN: %s",
cert.Subject.CommonName, gun)
continue
Expand Down
76 changes: 76 additions & 0 deletions trustpinning/certs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1299,3 +1299,79 @@ func TestValidateRootWithExpiredIntermediate(t *testing.T) {
)
require.Error(t, err, "failed to invalidate expired intermediate certificate")
}

func TestCheckingWildcardCert(t *testing.T) {
memStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
cs := cryptoservice.NewCryptoService(memStore)
testPubKey, err := cs.Create(data.CanonicalRootRole, "docker.io/notary/*", data.ECDSAKey)
require.NoError(t, err)
testPrivKey, _, err := memStore.GetKey(testPubKey.ID())
require.NoError(t, err)

testCert, err := generateTestingCertificate(testPrivKey, "docker.io/notary/*", notary.Year)
require.NoError(t, err)
testCertPubKey, err := utils.ParsePEMPublicKey(utils.CertToPEM(testCert))
require.NoError(t, err)

rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{testCertPubKey.ID()}, nil)
require.NoError(t, err)
testRoot, err := data.NewRoot(
map[string]data.PublicKey{testCertPubKey.ID(): testCertPubKey},
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")

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

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

_, err = trustpinning.ValidateRoot(
nil,
signedTestRoot,
"docker.io/notary/test",
trustpinning.TrustPinConfig{},
)
require.NoError(t, err, "expected wildcard cert to validate")

_, err = trustpinning.ValidateRoot(
nil,
signedTestRoot,
"docker.io/not-a-match",
trustpinning.TrustPinConfig{},
)
require.Error(t, err, "expected wildcard cert not to validate")
}

func TestWildcardMatching(t *testing.T) {
var wildcardTests = []struct {
CN string
gun string
out bool
}{
{"docker.com/*", "docker.com/notary", true},
{"docker.com/**", "docker.com/notary", true},
{"*", "docker.com/any", true},
{"*", "", true},
{"**", "docker.com/any", true},
{"test/*******", "test/many/wildcard", true},
{"test/**/*/", "test/test", false},
{"test/*/wild", "test/test/wild", false},
{"*/all", "test/all", false},
{"docker.com/*/*", "docker.com/notary/test", false},
{"docker.com/*/**", "docker.com/notary/test", false},
{"", "*", false},
{"*abc*", "abc", false},
{"test/*/wild*", "test/test/wild", false},
}
for _, tt := range wildcardTests {
require.Equal(t, trustpinning.MatchCNToGun(tt.CN, tt.gun), tt.out)
}
}