// Generates steps to test out various role permutations func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep { roleVals := roleEntry{ MaxTTL: "12h", KeyType: "rsa", KeyBits: 2048, } issueVals := certutil.IssueData{} ret := []logicaltest.TestStep{} roleTestStep := logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "roles/test", } var issueTestStep logicaltest.TestStep if useCSRs { issueTestStep = logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "sign/test", } } else { issueTestStep = logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "issue/test", } } genericErrorOkCheck := func(resp *logical.Response) error { if resp.IsError() { return nil } return fmt.Errorf("Expected an error, but did not seem to get one") } // Adds tests with the currently configured issue/role information addTests := func(testCheck logicaltest.TestCheckFunc) { //fmt.Printf("role vals: %#v\n", roleVals) //fmt.Printf("issue vals: %#v\n", issueTestStep) roleTestStep.Data = structs.New(roleVals).Map() ret = append(ret, roleTestStep) issueTestStep.Data = structs.New(issueVals).Map() switch { case issueTestStep.ErrorOk: issueTestStep.Check = genericErrorOkCheck case testCheck != nil: issueTestStep.Check = testCheck default: issueTestStep.Check = nil } ret = append(ret, issueTestStep) } // Returns a TestCheckFunc that performs various validity checks on the // returned certificate information, mostly within checkCertsAndPrivateKey getCnCheck := func(name string, role roleEntry, key crypto.Signer, usage certUsage, validity time.Duration) logicaltest.TestCheckFunc { var certBundle certutil.CertBundle return func(resp *logical.Response) error { err := mapstructure.Decode(resp.Data, &certBundle) if err != nil { return err } parsedCertBundle, err := checkCertsAndPrivateKey(role.KeyType, key, usage, validity, &certBundle) if err != nil { return fmt.Errorf("Error checking generated certificate: %s", err) } cert := parsedCertBundle.Certificate if cert.Subject.CommonName != name { return fmt.Errorf("Error: returned certificate has CN of %s but %s was requested", cert.Subject.CommonName, name) } if strings.Contains(cert.Subject.CommonName, "@") { if len(cert.DNSNames) != 0 || len(cert.EmailAddresses) != 1 { return fmt.Errorf("Error: found more than one DNS SAN or not one Email SAN but only one was requested, cert.DNSNames = %#v, cert.EmailAddresses = %#v", cert.DNSNames, cert.EmailAddresses) } } else { if len(cert.DNSNames) != 1 || len(cert.EmailAddresses) != 0 { return fmt.Errorf("Error: found more than one Email SAN or not one DNS SAN but only one was requested, cert.DNSNames = %#v, cert.EmailAddresses = %#v", cert.DNSNames, cert.EmailAddresses) } } var retName string if len(cert.DNSNames) > 0 { retName = cert.DNSNames[0] } if len(cert.EmailAddresses) > 0 { retName = cert.EmailAddresses[0] } if retName != name { return fmt.Errorf("Error: returned certificate has a DNS SAN of %s but %s was requested", retName, name) } return nil } } // Common names to test with the various role flags toggled var commonNames struct { Localhost bool `structs:"localhost"` BareDomain bool `structs:"example.com"` SecondDomain bool `structs:"foobar.com"` SubDomain bool `structs:"foo.example.com"` Wildcard bool `structs:"*.example.com"` SubSubdomain bool `structs:"foo.bar.example.com"` SubSubdomainWildcard bool `structs:"*.bar.example.com"` NonHostname bool `structs:"daɪˈɛrɨsɨs"` AnyHost bool `structs:"porkslap.beer"` } // Adds a series of tests based on the current selection of // allowed common names; contains some (seeded) randomness // // This allows for a variety of common names to be tested in various // combinations with allowed toggles of the role addCnTests := func() { cnMap := structs.New(commonNames).Map() // For the number of tests being run, this is known to hit all // of the various values below mathRand := mathrand.New(mathrand.NewSource(1)) for name, allowedInt := range cnMap { roleVals.KeyType = "rsa" roleVals.KeyBits = 2048 if mathRand.Int()%2 == 1 { roleVals.KeyType = "ec" roleVals.KeyBits = 224 } roleVals.ServerFlag = false roleVals.ClientFlag = false roleVals.CodeSigningFlag = false roleVals.EmailProtectionFlag = false var usage certUsage i := mathRand.Int() switch { case i%5 == 0: usage = emailProtectionUsage roleVals.EmailProtectionFlag = true case i%3 == 0: usage = serverUsage roleVals.ServerFlag = true case i%2 == 0: usage = clientUsage roleVals.ClientFlag = true default: usage = codeSigningUsage roleVals.CodeSigningFlag = true } allowed := allowedInt.(bool) issueVals.CommonName = name if roleVals.EmailProtectionFlag { if !strings.HasPrefix(name, "*") { issueVals.CommonName = "user@" + issueVals.CommonName } } if allowed { issueTestStep.ErrorOk = false } else { issueTestStep.ErrorOk = true } validity, _ := time.ParseDuration(roleVals.MaxTTL) if useCSRs { var privKey crypto.Signer switch roleVals.KeyType { case "rsa": privKey, _ = rsa.GenerateKey(rand.Reader, roleVals.KeyBits) case "ec": var curve elliptic.Curve switch roleVals.KeyBits { case 224: curve = elliptic.P224() case 256: curve = elliptic.P256() case 384: curve = elliptic.P384() case 521: curve = elliptic.P521() } privKey, _ = ecdsa.GenerateKey(curve, rand.Reader) } templ := &x509.CertificateRequest{ Subject: pkix.Name{ CommonName: issueVals.CommonName, }, } csr, err := x509.CreateCertificateRequest(rand.Reader, templ, privKey) if err != nil { t.Fatalf("Error creating certificate request: %s", err) } block := pem.Block{ Type: "CERTIFICATE REQUEST", Bytes: csr, } issueVals.CSR = strings.TrimSpace(string(pem.EncodeToMemory(&block))) addTests(getCnCheck(issueVals.CommonName, roleVals, privKey, usage, validity)) } else { addTests(getCnCheck(issueVals.CommonName, roleVals, nil, usage, validity)) } } } // Common Name tests { // common_name not provided issueVals.CommonName = "" issueTestStep.ErrorOk = true addTests(nil) // Nothing is allowed addCnTests() roleVals.AllowLocalhost = true commonNames.Localhost = true addCnTests() roleVals.AllowedDomains = "foobar.com" addCnTests() roleVals.AllowedDomains = "example.com" roleVals.AllowSubdomains = true commonNames.SubDomain = true commonNames.Wildcard = true commonNames.SubSubdomain = true commonNames.SubSubdomainWildcard = true addCnTests() roleVals.AllowedDomains = "foobar.com,example.com" commonNames.SecondDomain = true roleVals.AllowBareDomains = true commonNames.BareDomain = true addCnTests() roleVals.AllowAnyName = true roleVals.EnforceHostnames = true commonNames.AnyHost = true addCnTests() roleVals.EnforceHostnames = false commonNames.NonHostname = true addCnTests() } // IP SAN tests { issueVals.IPSANs = "127.0.0.1,::1" issueTestStep.ErrorOk = true addTests(nil) roleVals.AllowIPSANs = true issueTestStep.ErrorOk = false addTests(nil) issueVals.IPSANs = "foobar" issueTestStep.ErrorOk = true addTests(nil) issueTestStep.ErrorOk = false issueVals.IPSANs = "" } // Lease tests { roleTestStep.ErrorOk = true roleVals.Lease = "" roleVals.MaxTTL = "" addTests(nil) roleVals.Lease = "12h" roleVals.MaxTTL = "6h" addTests(nil) roleTestStep.ErrorOk = false } return ret }
// Generates steps to test out various role permutations func generateRoleSteps(t *testing.T, useCSRs bool) []logicaltest.TestStep { roleVals := roleEntry{ MaxTTL: "12h", KeyType: "rsa", KeyBits: 2048, } issueVals := certutil.IssueData{} ret := []logicaltest.TestStep{} roleTestStep := logicaltest.TestStep{ Operation: logical.UpdateOperation, Path: "roles/test", } var issueTestStep logicaltest.TestStep if useCSRs { issueTestStep = logicaltest.TestStep{ Operation: logical.UpdateOperation, Path: "sign/test", } } else { issueTestStep = logicaltest.TestStep{ Operation: logical.UpdateOperation, Path: "issue/test", } } /* // For the number of tests being run, a seed of 1 has been tested // to hit all of the various values below. However, for normal // testing we use a randomized time for maximum fuzziness. */ var seed int64 = 1 fixedSeed := os.Getenv("VAULT_PKITESTS_FIXED_SEED") if len(fixedSeed) == 0 { seed = time.Now().UnixNano() } else { var err error seed, err = strconv.ParseInt(fixedSeed, 10, 64) if err != nil { t.Fatalf("error parsing fixed seed of %s: %v", fixedSeed, err) } } mathRand := mathrand.New(mathrand.NewSource(seed)) t.Logf("seed under test: %v", seed) // Used by tests not toggling common names to turn off the behavior of random key bit fuzziness keybitSizeRandOff := false genericErrorOkCheck := func(resp *logical.Response) error { if resp.IsError() { return nil } return fmt.Errorf("Expected an error, but did not seem to get one") } // Adds tests with the currently configured issue/role information addTests := func(testCheck logicaltest.TestCheckFunc) { stepCount += 1 //t.Logf("test step %d\nrole vals: %#v\n", stepCount, roleVals) stepCount += 1 //t.Logf("test step %d\nissue vals: %#v\n", stepCount, issueTestStep) roleTestStep.Data = structs.New(roleVals).Map() ret = append(ret, roleTestStep) issueTestStep.Data = structs.New(issueVals).Map() switch { case issueTestStep.ErrorOk: issueTestStep.Check = genericErrorOkCheck case testCheck != nil: issueTestStep.Check = testCheck default: issueTestStep.Check = nil } ret = append(ret, issueTestStep) } // Returns a TestCheckFunc that performs various validity checks on the // returned certificate information, mostly within checkCertsAndPrivateKey getCnCheck := func(name string, role roleEntry, key crypto.Signer, usage certUsage, validity time.Duration) logicaltest.TestCheckFunc { var certBundle certutil.CertBundle return func(resp *logical.Response) error { err := mapstructure.Decode(resp.Data, &certBundle) if err != nil { return err } parsedCertBundle, err := checkCertsAndPrivateKey(role.KeyType, key, usage, validity, &certBundle) if err != nil { return fmt.Errorf("Error checking generated certificate: %s", err) } cert := parsedCertBundle.Certificate if cert.Subject.CommonName != name { return fmt.Errorf("Error: returned certificate has CN of %s but %s was requested", cert.Subject.CommonName, name) } if strings.Contains(cert.Subject.CommonName, "@") { if len(cert.DNSNames) != 0 || len(cert.EmailAddresses) != 1 { return fmt.Errorf("Error: found more than one DNS SAN or not one Email SAN but only one was requested, cert.DNSNames = %#v, cert.EmailAddresses = %#v", cert.DNSNames, cert.EmailAddresses) } } else { if len(cert.DNSNames) != 1 || len(cert.EmailAddresses) != 0 { return fmt.Errorf("Error: found more than one Email SAN or not one DNS SAN but only one was requested, cert.DNSNames = %#v, cert.EmailAddresses = %#v", cert.DNSNames, cert.EmailAddresses) } } var retName string if len(cert.DNSNames) > 0 { retName = cert.DNSNames[0] } if len(cert.EmailAddresses) > 0 { retName = cert.EmailAddresses[0] } if retName != name { return fmt.Errorf("Error: returned certificate has a DNS SAN of %s but %s was requested", retName, name) } return nil } } // Common names to test with the various role flags toggled var commonNames struct { Localhost bool `structs:"localhost"` BareDomain bool `structs:"example.com"` SecondDomain bool `structs:"foobar.com"` SubDomain bool `structs:"foo.example.com"` Wildcard bool `structs:"*.example.com"` SubSubdomain bool `structs:"foo.bar.example.com"` SubSubdomainWildcard bool `structs:"*.bar.example.com"` NonHostname bool `structs:"daɪˈɛrɨsɨs"` AnyHost bool `structs:"porkslap.beer"` } // Adds a series of tests based on the current selection of // allowed common names; contains some (seeded) randomness // // This allows for a variety of common names to be tested in various // combinations with allowed toggles of the role addCnTests := func() { cnMap := structs.New(commonNames).Map() for name, allowedInt := range cnMap { roleVals.KeyType = "rsa" roleVals.KeyBits = 2048 if mathRand.Int()%2 == 1 { roleVals.KeyType = "ec" roleVals.KeyBits = 224 } roleVals.ServerFlag = false roleVals.ClientFlag = false roleVals.CodeSigningFlag = false roleVals.EmailProtectionFlag = false var usage certUsage i := mathRand.Int() switch { case i%5 == 0: usage = emailProtectionUsage roleVals.EmailProtectionFlag = true case i%3 == 0: usage = serverUsage roleVals.ServerFlag = true case i%2 == 0: usage = clientUsage roleVals.ClientFlag = true default: usage = codeSigningUsage roleVals.CodeSigningFlag = true } allowed := allowedInt.(bool) issueVals.CommonName = name if roleVals.EmailProtectionFlag { if !strings.HasPrefix(name, "*") { issueVals.CommonName = "user@" + issueVals.CommonName } } issueTestStep.ErrorOk = !allowed validity, _ := time.ParseDuration(roleVals.MaxTTL) var testBitSize int if useCSRs { rsaKeyBits := []int{2048, 4096} ecKeyBits := []int{224, 256, 384, 521} var privKey crypto.Signer switch roleVals.KeyType { case "rsa": roleVals.KeyBits = rsaKeyBits[mathRand.Int()%2] // If we don't expect an error already, randomly choose a // key size and expect an error if it's less than the role // setting testBitSize = roleVals.KeyBits if !keybitSizeRandOff && !issueTestStep.ErrorOk { testBitSize = rsaKeyBits[mathRand.Int()%2] } if testBitSize < roleVals.KeyBits { issueTestStep.ErrorOk = true } privKey, _ = rsa.GenerateKey(rand.Reader, testBitSize) case "ec": roleVals.KeyBits = ecKeyBits[mathRand.Int()%4] var curve elliptic.Curve // If we don't expect an error already, randomly choose a // key size and expect an error if it's less than the role // setting testBitSize = roleVals.KeyBits if !keybitSizeRandOff && !issueTestStep.ErrorOk { testBitSize = ecKeyBits[mathRand.Int()%4] } switch testBitSize { case 224: curve = elliptic.P224() case 256: curve = elliptic.P256() case 384: curve = elliptic.P384() case 521: curve = elliptic.P521() } if curve.Params().BitSize < roleVals.KeyBits { issueTestStep.ErrorOk = true } privKey, _ = ecdsa.GenerateKey(curve, rand.Reader) } templ := &x509.CertificateRequest{ Subject: pkix.Name{ CommonName: issueVals.CommonName, }, } csr, err := x509.CreateCertificateRequest(rand.Reader, templ, privKey) if err != nil { t.Fatalf("Error creating certificate request: %s", err) } block := pem.Block{ Type: "CERTIFICATE REQUEST", Bytes: csr, } issueVals.CSR = strings.TrimSpace(string(pem.EncodeToMemory(&block))) addTests(getCnCheck(issueVals.CommonName, roleVals, privKey, usage, validity)) } else { addTests(getCnCheck(issueVals.CommonName, roleVals, nil, usage, validity)) } } } // Common Name tests { // common_name not provided issueVals.CommonName = "" issueTestStep.ErrorOk = true addTests(nil) // Nothing is allowed addCnTests() roleVals.AllowLocalhost = true commonNames.Localhost = true addCnTests() roleVals.AllowedDomains = "foobar.com" addCnTests() roleVals.AllowedDomains = "example.com" roleVals.AllowSubdomains = true commonNames.SubDomain = true commonNames.Wildcard = true commonNames.SubSubdomain = true commonNames.SubSubdomainWildcard = true addCnTests() roleVals.AllowedDomains = "foobar.com,example.com" commonNames.SecondDomain = true roleVals.AllowBareDomains = true commonNames.BareDomain = true addCnTests() roleVals.AllowAnyName = true roleVals.EnforceHostnames = true commonNames.AnyHost = true addCnTests() roleVals.EnforceHostnames = false commonNames.NonHostname = true addCnTests() // Ensure that we end up with acceptable key sizes since they won't be // toggled any longer keybitSizeRandOff = true addCnTests() } // IP SAN tests { issueVals.IPSANs = "127.0.0.1,::1" issueTestStep.ErrorOk = true addTests(nil) roleVals.AllowIPSANs = true issueTestStep.ErrorOk = false addTests(nil) issueVals.IPSANs = "foobar" issueTestStep.ErrorOk = true addTests(nil) issueTestStep.ErrorOk = false issueVals.IPSANs = "" } // Lease tests { roleTestStep.ErrorOk = true roleVals.Lease = "" roleVals.MaxTTL = "" addTests(nil) roleVals.Lease = "12h" roleVals.MaxTTL = "6h" addTests(nil) roleTestStep.ErrorOk = false } // Listing test ret = append(ret, logicaltest.TestStep{ Operation: logical.ListOperation, Path: "roles/", Check: func(resp *logical.Response) error { if resp.Data == nil { return fmt.Errorf("nil data") } keysRaw, ok := resp.Data["keys"] if !ok { return fmt.Errorf("no keys found") } keys, ok := keysRaw.([]string) if !ok { return fmt.Errorf("could not convert keys to a string list") } if len(keys) != 1 { return fmt.Errorf("unexpected keys length of %d", len(keys)) } if keys[0] != "test" { return fmt.Errorf("unexpected key value of %d", keys[0]) } return nil }, }) return ret }