Skip to content

Commit

Permalink
Support wildcard CNs in root leaf certificates
Browse files Browse the repository at this point in the history
Signed-off-by: Evan Cordell <cordell.evan@gmail.com>
  • Loading branch information
ecordell committed Jan 26, 2017
1 parent 905fffb commit fb428da
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 2 deletions.
15 changes: 13 additions & 2 deletions trustpinning/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,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 {
wildcard := "*"
if strings.HasSuffix(commonName, wildcard) {
prefix := commonName[:len(commonName)-len(wildcard)]
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 +194,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
186 changes: 186 additions & 0 deletions trustpinning/certs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1299,3 +1299,189 @@ func TestValidateRootWithExpiredIntermediate(t *testing.T) {
)
require.Error(t, err, "failed to invalidate expired intermediate certificate")
}

func TestCheckingWilcardCert(t *testing.T) {
now := time.Now()
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
memStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
cs := cryptoservice.NewCryptoService(memStore)

// generate CA cert
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
require.NoError(t, err)
caTmpl := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "notary testing CA",
},
NotBefore: now.Add(-time.Hour),
NotAfter: now.Add(time.Hour),
KeyUsage: x509.KeyUsageCertSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 3,
}
caPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
_, err = x509.CreateCertificate(
rand.Reader,
&caTmpl,
&caTmpl,
caPrivKey.Public(),
caPrivKey,
)
require.NoError(t, err)

// generate expired intermediate
intTmpl := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "valid testing intermediate",
},
NotBefore: now.Add(-2 * notary.Year),
NotAfter: now.Add(notary.Year),
KeyUsage: x509.KeyUsageCertSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 2,
}
intPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
intCert, err := x509.CreateCertificate(
rand.Reader,
&intTmpl,
&caTmpl,
intPrivKey.Public(),
caPrivKey,
)
require.NoError(t, err)

// generate leaf
serialNumber, err = rand.Int(rand.Reader, serialNumberLimit)
require.NoError(t, err)
leafTmpl := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "docker.io/notary/*",
},
NotBefore: now.Add(-time.Hour),
NotAfter: now.Add(time.Hour),

KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
BasicConstraintsValid: true,
}

leafPubKey, err := cs.Create("root", "docker.io/notary/test", data.ECDSAKey)
require.NoError(t, err)
leafPrivKey, _, err := cs.GetPrivateKey(leafPubKey.ID())
require.NoError(t, err)
signer := leafPrivKey.CryptoSigner()
leafCert, err := x509.CreateCertificate(
rand.Reader,
&leafTmpl,
&intTmpl,
signer.Public(),
intPrivKey,
)
require.NoError(t, err)

rootBundleWriter := bytes.NewBuffer(nil)
pem.Encode(
rootBundleWriter,
&pem.Block{
Type: "CERTIFICATE",
Bytes: leafCert,
},
)
pem.Encode(
rootBundleWriter,
&pem.Block{
Type: "CERTIFICATE",
Bytes: intCert,
},
)

rootBundle := rootBundleWriter.Bytes()

ecdsax509Key := data.NewECDSAx509PublicKey(rootBundle)

otherKey, err := cs.Create("targets", "docker.io/notary/test", data.ED25519Key)
require.NoError(t, err)

root := data.SignedRoot{
Signatures: make([]data.Signature, 0),
Signed: data.Root{
SignedCommon: data.SignedCommon{
Type: "Root",
Expires: now.Add(time.Hour),
Version: 1,
},
Keys: map[string]data.PublicKey{
ecdsax509Key.ID(): ecdsax509Key,
otherKey.ID(): otherKey,
},
Roles: map[string]*data.RootRole{
"root": {
KeyIDs: []string{ecdsax509Key.ID()},
Threshold: 1,
},
"targets": {
KeyIDs: []string{otherKey.ID()},
Threshold: 1,
},
"snapshot": {
KeyIDs: []string{otherKey.ID()},
Threshold: 1,
},
"timestamp": {
KeyIDs: []string{otherKey.ID()},
Threshold: 1,
},
},
},
Dirty: true,
}

signedRoot, err := root.ToSigned()
require.NoError(t, err)
err = signed.Sign(cs, signedRoot, []data.PublicKey{ecdsax509Key}, 1, nil)
require.NoError(t, err)

tempBaseDir, err := ioutil.TempDir("", "notary-test-")
defer os.RemoveAll(tempBaseDir)
require.NoError(t, err, "failed to create a temporary directory: %s", err)

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

_, err = trustpinning.ValidateRoot(
nil,
signedRoot,
"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},
{"test/*/wild", "test/test/wild", false},
{"*/all", "test/all", false},
{"docker.com/*/*", "docker.com/notary/test", false},
{"*", "docker.com/any", true},
}
for _, tt := range wildcardTests {
require.Equal(t, trustpinning.MatchCNToGun(tt.CN, tt.gun), tt.out)
}
}

0 comments on commit fb428da

Please sign in to comment.