2107 lines
64 KiB
Go
2107 lines
64 KiB
Go
package pki
|
||
|
||
import (
|
||
"crypto"
|
||
"crypto/ecdsa"
|
||
"crypto/elliptic"
|
||
"crypto/rand"
|
||
"crypto/rsa"
|
||
"crypto/x509"
|
||
"crypto/x509/pkix"
|
||
"encoding/base64"
|
||
"encoding/pem"
|
||
"fmt"
|
||
"math"
|
||
mathrand "math/rand"
|
||
"net"
|
||
"os"
|
||
"reflect"
|
||
"strconv"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/fatih/structs"
|
||
"github.com/hashicorp/vault/helper/certutil"
|
||
"github.com/hashicorp/vault/helper/strutil"
|
||
"github.com/hashicorp/vault/logical"
|
||
logicaltest "github.com/hashicorp/vault/logical/testing"
|
||
"github.com/mitchellh/mapstructure"
|
||
)
|
||
|
||
var (
|
||
stepCount = 0
|
||
serialUnderTest string
|
||
parsedKeyUsageUnderTest int
|
||
)
|
||
|
||
// Performs basic tests on CA functionality
|
||
// Uses the RSA CA key
|
||
func TestBackend_RSAKey(t *testing.T) {
|
||
defaultLeaseTTLVal := time.Hour * 24
|
||
maxLeaseTTLVal := time.Hour * 24 * 32
|
||
b, err := Factory(&logical.BackendConfig{
|
||
Logger: nil,
|
||
System: &logical.StaticSystemView{
|
||
DefaultLeaseTTLVal: defaultLeaseTTLVal,
|
||
MaxLeaseTTLVal: maxLeaseTTLVal,
|
||
},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Unable to create backend: %s", err)
|
||
}
|
||
|
||
testCase := logicaltest.TestCase{
|
||
Backend: b,
|
||
Steps: []logicaltest.TestStep{},
|
||
}
|
||
|
||
stepCount = len(testCase.Steps)
|
||
|
||
intdata := map[string]interface{}{}
|
||
reqdata := map[string]interface{}{}
|
||
testCase.Steps = append(testCase.Steps, generateCATestingSteps(t, rsaCACert, rsaCAKey, ecCACert, intdata, reqdata)...)
|
||
|
||
logicaltest.Test(t, testCase)
|
||
}
|
||
|
||
// Performs basic tests on CA functionality
|
||
// Uses the EC CA key
|
||
func TestBackend_ECKey(t *testing.T) {
|
||
defaultLeaseTTLVal := time.Hour * 24
|
||
maxLeaseTTLVal := time.Hour * 24 * 32
|
||
b, err := Factory(&logical.BackendConfig{
|
||
Logger: nil,
|
||
System: &logical.StaticSystemView{
|
||
DefaultLeaseTTLVal: defaultLeaseTTLVal,
|
||
MaxLeaseTTLVal: maxLeaseTTLVal,
|
||
},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Unable to create backend: %s", err)
|
||
}
|
||
|
||
testCase := logicaltest.TestCase{
|
||
Backend: b,
|
||
Steps: []logicaltest.TestStep{},
|
||
}
|
||
|
||
stepCount = len(testCase.Steps)
|
||
|
||
intdata := map[string]interface{}{}
|
||
reqdata := map[string]interface{}{}
|
||
testCase.Steps = append(testCase.Steps, generateCATestingSteps(t, ecCACert, ecCAKey, rsaCACert, intdata, reqdata)...)
|
||
|
||
logicaltest.Test(t, testCase)
|
||
}
|
||
|
||
func TestBackend_CSRValues(t *testing.T) {
|
||
defaultLeaseTTLVal := time.Hour * 24
|
||
maxLeaseTTLVal := time.Hour * 24 * 32
|
||
b, err := Factory(&logical.BackendConfig{
|
||
Logger: nil,
|
||
System: &logical.StaticSystemView{
|
||
DefaultLeaseTTLVal: defaultLeaseTTLVal,
|
||
MaxLeaseTTLVal: maxLeaseTTLVal,
|
||
},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Unable to create backend: %s", err)
|
||
}
|
||
|
||
testCase := logicaltest.TestCase{
|
||
Backend: b,
|
||
Steps: []logicaltest.TestStep{},
|
||
}
|
||
|
||
stepCount = len(testCase.Steps)
|
||
|
||
intdata := map[string]interface{}{}
|
||
reqdata := map[string]interface{}{}
|
||
testCase.Steps = append(testCase.Steps, generateCSRSteps(t, ecCACert, ecCAKey, intdata, reqdata)...)
|
||
|
||
logicaltest.Test(t, testCase)
|
||
}
|
||
|
||
func TestBackend_URLsCRUD(t *testing.T) {
|
||
defaultLeaseTTLVal := time.Hour * 24
|
||
maxLeaseTTLVal := time.Hour * 24 * 32
|
||
b, err := Factory(&logical.BackendConfig{
|
||
Logger: nil,
|
||
System: &logical.StaticSystemView{
|
||
DefaultLeaseTTLVal: defaultLeaseTTLVal,
|
||
MaxLeaseTTLVal: maxLeaseTTLVal,
|
||
},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Unable to create backend: %s", err)
|
||
}
|
||
|
||
testCase := logicaltest.TestCase{
|
||
Backend: b,
|
||
Steps: []logicaltest.TestStep{},
|
||
}
|
||
|
||
stepCount = len(testCase.Steps)
|
||
|
||
intdata := map[string]interface{}{}
|
||
reqdata := map[string]interface{}{}
|
||
testCase.Steps = append(testCase.Steps, generateURLSteps(t, ecCACert, ecCAKey, intdata, reqdata)...)
|
||
|
||
logicaltest.Test(t, testCase)
|
||
}
|
||
|
||
// Generates and tests steps that walk through the various possibilities
|
||
// of role flags to ensure that they are properly restricted
|
||
// Uses the RSA CA key
|
||
func TestBackend_RSARoles(t *testing.T) {
|
||
defaultLeaseTTLVal := time.Hour * 24
|
||
maxLeaseTTLVal := time.Hour * 24 * 32
|
||
b, err := Factory(&logical.BackendConfig{
|
||
Logger: nil,
|
||
System: &logical.StaticSystemView{
|
||
DefaultLeaseTTLVal: defaultLeaseTTLVal,
|
||
MaxLeaseTTLVal: maxLeaseTTLVal,
|
||
},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Unable to create backend: %s", err)
|
||
}
|
||
|
||
testCase := logicaltest.TestCase{
|
||
Backend: b,
|
||
Steps: []logicaltest.TestStep{
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "config/ca",
|
||
Data: map[string]interface{}{
|
||
"pem_bundle": rsaCAKey + rsaCACert,
|
||
},
|
||
},
|
||
},
|
||
}
|
||
|
||
stepCount = len(testCase.Steps)
|
||
|
||
testCase.Steps = append(testCase.Steps, generateRoleSteps(t, false)...)
|
||
if len(os.Getenv("VAULT_VERBOSE_PKITESTS")) > 0 {
|
||
for i, v := range testCase.Steps {
|
||
fmt.Printf("Step %d:\n%+v\n\n", i+stepCount, v)
|
||
}
|
||
}
|
||
|
||
logicaltest.Test(t, testCase)
|
||
}
|
||
|
||
// Generates and tests steps that walk through the various possibilities
|
||
// of role flags to ensure that they are properly restricted
|
||
// Uses the RSA CA key
|
||
func TestBackend_RSARoles_CSR(t *testing.T) {
|
||
defaultLeaseTTLVal := time.Hour * 24
|
||
maxLeaseTTLVal := time.Hour * 24 * 32
|
||
b, err := Factory(&logical.BackendConfig{
|
||
Logger: nil,
|
||
System: &logical.StaticSystemView{
|
||
DefaultLeaseTTLVal: defaultLeaseTTLVal,
|
||
MaxLeaseTTLVal: maxLeaseTTLVal,
|
||
},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Unable to create backend: %s", err)
|
||
}
|
||
|
||
testCase := logicaltest.TestCase{
|
||
Backend: b,
|
||
Steps: []logicaltest.TestStep{
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "config/ca",
|
||
Data: map[string]interface{}{
|
||
"pem_bundle": rsaCAKey + rsaCACert + rsaCAChain,
|
||
},
|
||
},
|
||
},
|
||
}
|
||
|
||
stepCount = len(testCase.Steps)
|
||
|
||
testCase.Steps = append(testCase.Steps, generateRoleSteps(t, false)...)
|
||
if len(os.Getenv("VAULT_VERBOSE_PKITESTS")) > 0 {
|
||
for i, v := range testCase.Steps {
|
||
fmt.Printf("Step %d:\n%+v\n\n", i+stepCount, v)
|
||
}
|
||
}
|
||
|
||
logicaltest.Test(t, testCase)
|
||
}
|
||
|
||
// Generates and tests steps that walk through the various possibilities
|
||
// of role flags to ensure that they are properly restricted
|
||
// Uses the EC CA key
|
||
func TestBackend_ECRoles(t *testing.T) {
|
||
defaultLeaseTTLVal := time.Hour * 24
|
||
maxLeaseTTLVal := time.Hour * 24 * 32
|
||
b, err := Factory(&logical.BackendConfig{
|
||
Logger: nil,
|
||
System: &logical.StaticSystemView{
|
||
DefaultLeaseTTLVal: defaultLeaseTTLVal,
|
||
MaxLeaseTTLVal: maxLeaseTTLVal,
|
||
},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Unable to create backend: %s", err)
|
||
}
|
||
|
||
testCase := logicaltest.TestCase{
|
||
Backend: b,
|
||
Steps: []logicaltest.TestStep{
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "config/ca",
|
||
Data: map[string]interface{}{
|
||
"pem_bundle": ecCAKey + ecCACert,
|
||
},
|
||
},
|
||
},
|
||
}
|
||
|
||
stepCount = len(testCase.Steps)
|
||
|
||
testCase.Steps = append(testCase.Steps, generateRoleSteps(t, false)...)
|
||
if len(os.Getenv("VAULT_VERBOSE_PKITESTS")) > 0 {
|
||
for i, v := range testCase.Steps {
|
||
fmt.Printf("Step %d:\n%+v\n\n", i+stepCount, v)
|
||
}
|
||
}
|
||
|
||
logicaltest.Test(t, testCase)
|
||
}
|
||
|
||
// Generates and tests steps that walk through the various possibilities
|
||
// of role flags to ensure that they are properly restricted
|
||
// Uses the EC CA key
|
||
func TestBackend_ECRoles_CSR(t *testing.T) {
|
||
defaultLeaseTTLVal := time.Hour * 24
|
||
maxLeaseTTLVal := time.Hour * 24 * 32
|
||
b, err := Factory(&logical.BackendConfig{
|
||
Logger: nil,
|
||
System: &logical.StaticSystemView{
|
||
DefaultLeaseTTLVal: defaultLeaseTTLVal,
|
||
MaxLeaseTTLVal: maxLeaseTTLVal,
|
||
},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("Unable to create backend: %s", err)
|
||
}
|
||
|
||
testCase := logicaltest.TestCase{
|
||
Backend: b,
|
||
Steps: []logicaltest.TestStep{
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "config/ca",
|
||
Data: map[string]interface{}{
|
||
"pem_bundle": ecCAKey + ecCACert,
|
||
},
|
||
},
|
||
},
|
||
}
|
||
|
||
stepCount = len(testCase.Steps)
|
||
|
||
testCase.Steps = append(testCase.Steps, generateRoleSteps(t, true)...)
|
||
if len(os.Getenv("VAULT_VERBOSE_PKITESTS")) > 0 {
|
||
for i, v := range testCase.Steps {
|
||
fmt.Printf("Step %d:\n%+v\n\n", i+stepCount, v)
|
||
}
|
||
}
|
||
|
||
logicaltest.Test(t, testCase)
|
||
}
|
||
|
||
// Performs some validity checking on the returned bundles
|
||
func checkCertsAndPrivateKey(keyType string, key crypto.Signer, usage x509.KeyUsage, extUsage x509.ExtKeyUsage, validity time.Duration, certBundle *certutil.CertBundle) (*certutil.ParsedCertBundle, error) {
|
||
parsedCertBundle, err := certBundle.ToParsedCertBundle()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("Error parsing cert bundle: %s", err)
|
||
}
|
||
|
||
if key != nil {
|
||
switch keyType {
|
||
case "rsa":
|
||
parsedCertBundle.PrivateKeyType = certutil.RSAPrivateKey
|
||
parsedCertBundle.PrivateKey = key
|
||
parsedCertBundle.PrivateKeyBytes = x509.MarshalPKCS1PrivateKey(key.(*rsa.PrivateKey))
|
||
case "ec":
|
||
parsedCertBundle.PrivateKeyType = certutil.ECPrivateKey
|
||
parsedCertBundle.PrivateKey = key
|
||
parsedCertBundle.PrivateKeyBytes, err = x509.MarshalECPrivateKey(key.(*ecdsa.PrivateKey))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("Error parsing EC key: %s", err)
|
||
}
|
||
}
|
||
}
|
||
|
||
switch {
|
||
case parsedCertBundle.Certificate == nil:
|
||
return nil, fmt.Errorf("Did not find a certificate in the cert bundle")
|
||
case len(parsedCertBundle.CAChain) == 0 || parsedCertBundle.CAChain[0].Certificate == nil:
|
||
return nil, fmt.Errorf("Did not find a CA in the cert bundle")
|
||
case parsedCertBundle.PrivateKey == nil:
|
||
return nil, fmt.Errorf("Did not find a private key in the cert bundle")
|
||
case parsedCertBundle.PrivateKeyType == certutil.UnknownPrivateKey:
|
||
return nil, fmt.Errorf("Could not figure out type of private key")
|
||
}
|
||
|
||
switch {
|
||
case parsedCertBundle.PrivateKeyType == certutil.RSAPrivateKey && keyType != "rsa":
|
||
fallthrough
|
||
case parsedCertBundle.PrivateKeyType == certutil.ECPrivateKey && keyType != "ec":
|
||
return nil, fmt.Errorf("Given key type does not match type found in bundle")
|
||
}
|
||
|
||
cert := parsedCertBundle.Certificate
|
||
|
||
if usage != cert.KeyUsage {
|
||
return nil, fmt.Errorf("Expected usage of %#v, got %#v; ext usage is %#v", usage, cert.KeyUsage, cert.ExtKeyUsage)
|
||
}
|
||
|
||
// There should only be one ext usage type, because only one is requested
|
||
// in the tests
|
||
if len(cert.ExtKeyUsage) != 1 {
|
||
return nil, fmt.Errorf("Got wrong size key usage in generated cert; expected 1, values are %#v", cert.ExtKeyUsage)
|
||
}
|
||
switch extUsage {
|
||
case x509.ExtKeyUsageEmailProtection:
|
||
if cert.ExtKeyUsage[0] != x509.ExtKeyUsageEmailProtection {
|
||
return nil, fmt.Errorf("Bad extended key usage")
|
||
}
|
||
case x509.ExtKeyUsageServerAuth:
|
||
if cert.ExtKeyUsage[0] != x509.ExtKeyUsageServerAuth {
|
||
return nil, fmt.Errorf("Bad extended key usage")
|
||
}
|
||
case x509.ExtKeyUsageClientAuth:
|
||
if cert.ExtKeyUsage[0] != x509.ExtKeyUsageClientAuth {
|
||
return nil, fmt.Errorf("Bad extended key usage")
|
||
}
|
||
case x509.ExtKeyUsageCodeSigning:
|
||
if cert.ExtKeyUsage[0] != x509.ExtKeyUsageCodeSigning {
|
||
return nil, fmt.Errorf("Bad extended key usage")
|
||
}
|
||
}
|
||
|
||
// 40 seconds since we add 30 second slack for clock skew
|
||
if math.Abs(float64(time.Now().Unix()-cert.NotBefore.Unix())) > 40 {
|
||
return nil, fmt.Errorf("Validity period starts out of range")
|
||
}
|
||
if !cert.NotBefore.Before(time.Now().Add(-10 * time.Second)) {
|
||
return nil, fmt.Errorf("Validity period not far enough in the past")
|
||
}
|
||
|
||
if math.Abs(float64(time.Now().Add(validity).Unix()-cert.NotAfter.Unix())) > 10 {
|
||
return nil, fmt.Errorf("Validity period of %d too large vs max of 10", cert.NotAfter.Unix())
|
||
}
|
||
|
||
return parsedCertBundle, nil
|
||
}
|
||
|
||
func generateURLSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[string]interface{}) []logicaltest.TestStep {
|
||
expected := urlEntries{
|
||
IssuingCertificates: []string{
|
||
"http://example.com/ca1",
|
||
"http://example.com/ca2",
|
||
},
|
||
CRLDistributionPoints: []string{
|
||
"http://example.com/crl1",
|
||
"http://example.com/crl2",
|
||
},
|
||
OCSPServers: []string{
|
||
"http://example.com/ocsp1",
|
||
"http://example.com/ocsp2",
|
||
},
|
||
}
|
||
csrTemplate := x509.CertificateRequest{
|
||
Subject: pkix.Name{
|
||
CommonName: "my@example.com",
|
||
},
|
||
}
|
||
|
||
priv1024, _ := rsa.GenerateKey(rand.Reader, 1024)
|
||
csr1024, _ := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, priv1024)
|
||
csrPem1024 := pem.EncodeToMemory(&pem.Block{
|
||
Type: "CERTIFICATE REQUEST",
|
||
Bytes: csr1024,
|
||
})
|
||
|
||
priv2048, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||
csr2048, _ := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, priv2048)
|
||
csrPem2048 := pem.EncodeToMemory(&pem.Block{
|
||
Type: "CERTIFICATE REQUEST",
|
||
Bytes: csr2048,
|
||
})
|
||
|
||
ret := []logicaltest.TestStep{
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "root/generate/exported",
|
||
Data: map[string]interface{}{
|
||
"common_name": "Root Cert",
|
||
"ttl": "180h",
|
||
},
|
||
Check: func(resp *logical.Response) error {
|
||
if resp.Secret != nil && resp.Secret.LeaseID != "" {
|
||
return fmt.Errorf("root returned with a lease")
|
||
}
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "config/urls",
|
||
Data: map[string]interface{}{
|
||
"issuing_certificates": strings.Join(expected.IssuingCertificates, ","),
|
||
"crl_distribution_points": strings.Join(expected.CRLDistributionPoints, ","),
|
||
"ocsp_servers": strings.Join(expected.OCSPServers, ","),
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
Path: "config/urls",
|
||
Check: func(resp *logical.Response) error {
|
||
if resp.Data == nil {
|
||
return fmt.Errorf("no data returned")
|
||
}
|
||
var entries urlEntries
|
||
err := mapstructure.Decode(resp.Data, &entries)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if !reflect.DeepEqual(entries, expected) {
|
||
return fmt.Errorf("expected urls\n%#v\ndoes not match provided\n%#v\n", expected, entries)
|
||
}
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "root/sign-intermediate",
|
||
Data: map[string]interface{}{
|
||
"common_name": "Intermediate Cert",
|
||
"csr": string(csrPem1024),
|
||
"format": "der",
|
||
},
|
||
ErrorOk: true,
|
||
Check: func(resp *logical.Response) error {
|
||
if !resp.IsError() {
|
||
return fmt.Errorf("expected an error response but did not get one")
|
||
}
|
||
if !strings.Contains(resp.Data["error"].(string), "2048") {
|
||
return fmt.Errorf("recieved an error but not about a 1024-bit key, error was: %s", resp.Data["error"].(string))
|
||
}
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "root/sign-intermediate",
|
||
Data: map[string]interface{}{
|
||
"common_name": "Intermediate Cert",
|
||
"csr": string(csrPem2048),
|
||
"format": "der",
|
||
},
|
||
Check: func(resp *logical.Response) error {
|
||
certString := resp.Data["certificate"].(string)
|
||
if certString == "" {
|
||
return fmt.Errorf("no certificate returned")
|
||
}
|
||
if resp.Secret != nil && resp.Secret.LeaseID != "" {
|
||
return fmt.Errorf("signed intermediate returned with a lease")
|
||
}
|
||
certBytes, _ := base64.StdEncoding.DecodeString(certString)
|
||
certs, err := x509.ParseCertificates(certBytes)
|
||
if err != nil {
|
||
return fmt.Errorf("returned cert cannot be parsed: %v", err)
|
||
}
|
||
if len(certs) != 1 {
|
||
return fmt.Errorf("unexpected returned length of certificates: %d", len(certs))
|
||
}
|
||
cert := certs[0]
|
||
|
||
switch {
|
||
case !reflect.DeepEqual(expected.IssuingCertificates, cert.IssuingCertificateURL):
|
||
return fmt.Errorf("expected\n%#v\ngot\n%#v\n", expected.IssuingCertificates, cert.IssuingCertificateURL)
|
||
case !reflect.DeepEqual(expected.CRLDistributionPoints, cert.CRLDistributionPoints):
|
||
return fmt.Errorf("expected\n%#v\ngot\n%#v\n", expected.CRLDistributionPoints, cert.CRLDistributionPoints)
|
||
case !reflect.DeepEqual(expected.OCSPServers, cert.OCSPServer):
|
||
return fmt.Errorf("expected\n%#v\ngot\n%#v\n", expected.OCSPServers, cert.OCSPServer)
|
||
case !reflect.DeepEqual([]string{"Intermediate Cert"}, cert.DNSNames):
|
||
return fmt.Errorf("expected\n%#v\ngot\n%#v\n", []string{"Intermediate Cert"}, cert.DNSNames)
|
||
}
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// Same as above but exclude adding to sans
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "root/sign-intermediate",
|
||
Data: map[string]interface{}{
|
||
"common_name": "Intermediate Cert",
|
||
"csr": string(csrPem2048),
|
||
"format": "der",
|
||
"exclude_cn_from_sans": true,
|
||
},
|
||
Check: func(resp *logical.Response) error {
|
||
certString := resp.Data["certificate"].(string)
|
||
if certString == "" {
|
||
return fmt.Errorf("no certificate returned")
|
||
}
|
||
if resp.Secret != nil && resp.Secret.LeaseID != "" {
|
||
return fmt.Errorf("signed intermediate returned with a lease")
|
||
}
|
||
certBytes, _ := base64.StdEncoding.DecodeString(certString)
|
||
certs, err := x509.ParseCertificates(certBytes)
|
||
if err != nil {
|
||
return fmt.Errorf("returned cert cannot be parsed: %v", err)
|
||
}
|
||
if len(certs) != 1 {
|
||
return fmt.Errorf("unexpected returned length of certificates: %d", len(certs))
|
||
}
|
||
cert := certs[0]
|
||
|
||
switch {
|
||
case !reflect.DeepEqual(expected.IssuingCertificates, cert.IssuingCertificateURL):
|
||
return fmt.Errorf("expected\n%#v\ngot\n%#v\n", expected.IssuingCertificates, cert.IssuingCertificateURL)
|
||
case !reflect.DeepEqual(expected.CRLDistributionPoints, cert.CRLDistributionPoints):
|
||
return fmt.Errorf("expected\n%#v\ngot\n%#v\n", expected.CRLDistributionPoints, cert.CRLDistributionPoints)
|
||
case !reflect.DeepEqual(expected.OCSPServers, cert.OCSPServer):
|
||
return fmt.Errorf("expected\n%#v\ngot\n%#v\n", expected.OCSPServers, cert.OCSPServer)
|
||
case !reflect.DeepEqual([]string(nil), cert.DNSNames):
|
||
return fmt.Errorf("expected\n%#v\ngot\n%#v\n", []string(nil), cert.DNSNames)
|
||
}
|
||
|
||
return nil
|
||
},
|
||
},
|
||
}
|
||
return ret
|
||
}
|
||
|
||
func generateCSRSteps(t *testing.T, caCert, caKey string, intdata, reqdata map[string]interface{}) []logicaltest.TestStep {
|
||
csrTemplate := x509.CertificateRequest{
|
||
Subject: pkix.Name{
|
||
Country: []string{"MyCountry"},
|
||
PostalCode: []string{"MyPostalCode"},
|
||
SerialNumber: "MySerialNumber",
|
||
CommonName: "my@example.com",
|
||
},
|
||
DNSNames: []string{
|
||
"name1.example.com",
|
||
"name2.example.com",
|
||
"name3.example.com",
|
||
},
|
||
EmailAddresses: []string{
|
||
"name1@example.com",
|
||
"name2@example.com",
|
||
"name3@example.com",
|
||
},
|
||
IPAddresses: []net.IP{
|
||
net.ParseIP("::ff:1:2:3:4"),
|
||
net.ParseIP("::ff:5:6:7:8"),
|
||
},
|
||
}
|
||
|
||
priv, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||
csr, _ := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, priv)
|
||
csrPem := pem.EncodeToMemory(&pem.Block{
|
||
Type: "CERTIFICATE REQUEST",
|
||
Bytes: csr,
|
||
})
|
||
|
||
ret := []logicaltest.TestStep{
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "root/generate/exported",
|
||
Data: map[string]interface{}{
|
||
"common_name": "Root Cert",
|
||
"ttl": "180h",
|
||
"max_path_length": 0,
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "root/sign-intermediate",
|
||
Data: map[string]interface{}{
|
||
"use_csr_values": true,
|
||
"csr": string(csrPem),
|
||
"format": "der",
|
||
},
|
||
ErrorOk: true,
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "root/generate/exported",
|
||
Data: map[string]interface{}{
|
||
"common_name": "Root Cert",
|
||
"ttl": "180h",
|
||
"max_path_length": 1,
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "root/sign-intermediate",
|
||
Data: map[string]interface{}{
|
||
"use_csr_values": true,
|
||
"csr": string(csrPem),
|
||
"format": "der",
|
||
},
|
||
Check: func(resp *logical.Response) error {
|
||
certString := resp.Data["certificate"].(string)
|
||
if certString == "" {
|
||
return fmt.Errorf("no certificate returned")
|
||
}
|
||
certBytes, _ := base64.StdEncoding.DecodeString(certString)
|
||
certs, err := x509.ParseCertificates(certBytes)
|
||
if err != nil {
|
||
return fmt.Errorf("returned cert cannot be parsed: %v", err)
|
||
}
|
||
if len(certs) != 1 {
|
||
return fmt.Errorf("unexpected returned length of certificates: %d", len(certs))
|
||
}
|
||
cert := certs[0]
|
||
|
||
if cert.MaxPathLen != 0 {
|
||
return fmt.Errorf("max path length of %d does not match the requested of 3", cert.MaxPathLen)
|
||
}
|
||
if !cert.MaxPathLenZero {
|
||
return fmt.Errorf("max path length zero is not set")
|
||
}
|
||
|
||
// We need to set these as they are filled in with unparsed values in the final cert
|
||
csrTemplate.Subject.Names = cert.Subject.Names
|
||
csrTemplate.Subject.ExtraNames = cert.Subject.ExtraNames
|
||
|
||
switch {
|
||
case !reflect.DeepEqual(cert.Subject, csrTemplate.Subject):
|
||
return fmt.Errorf("cert subject\n%#v\ndoes not match csr subject\n%#v\n", cert.Subject, csrTemplate.Subject)
|
||
case !reflect.DeepEqual(cert.DNSNames, csrTemplate.DNSNames):
|
||
return fmt.Errorf("cert dns names\n%#v\ndoes not match csr dns names\n%#v\n", cert.DNSNames, csrTemplate.DNSNames)
|
||
case !reflect.DeepEqual(cert.EmailAddresses, csrTemplate.EmailAddresses):
|
||
return fmt.Errorf("cert email addresses\n%#v\ndoes not match csr email addresses\n%#v\n", cert.EmailAddresses, csrTemplate.EmailAddresses)
|
||
case !reflect.DeepEqual(cert.IPAddresses, csrTemplate.IPAddresses):
|
||
return fmt.Errorf("cert ip addresses\n%#v\ndoes not match csr ip addresses\n%#v\n", cert.IPAddresses, csrTemplate.IPAddresses)
|
||
}
|
||
return nil
|
||
},
|
||
},
|
||
}
|
||
return ret
|
||
}
|
||
|
||
// Generates steps to test out CA configuration -- certificates + CRL expiry,
|
||
// and ensure that the certificates are readable after storing them
|
||
func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, intdata, reqdata map[string]interface{}) []logicaltest.TestStep {
|
||
setSerialUnderTest := func(req *logical.Request) error {
|
||
req.Path = serialUnderTest
|
||
return nil
|
||
}
|
||
|
||
ret := []logicaltest.TestStep{
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "config/ca",
|
||
Data: map[string]interface{}{
|
||
"pem_bundle": caKey + caCert,
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "config/crl",
|
||
Data: map[string]interface{}{
|
||
"expiry": "16h",
|
||
},
|
||
},
|
||
|
||
// Ensure we can fetch it back via unauthenticated means, in various formats
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
Path: "cert/ca",
|
||
Unauthenticated: true,
|
||
Check: func(resp *logical.Response) error {
|
||
if resp.Data["certificate"].(string) != caCert {
|
||
return fmt.Errorf("CA certificate:\n%s\ndoes not match original:\n%s\n", resp.Data["certificate"].(string), caCert)
|
||
}
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
Path: "ca/pem",
|
||
Unauthenticated: true,
|
||
Check: func(resp *logical.Response) error {
|
||
rawBytes := resp.Data["http_raw_body"].([]byte)
|
||
if string(rawBytes) != caCert {
|
||
return fmt.Errorf("CA certificate:\n%s\ndoes not match original:\n%s\n", string(rawBytes), caCert)
|
||
}
|
||
if resp.Data["http_content_type"].(string) != "application/pkix-cert" {
|
||
return fmt.Errorf("Expected application/pkix-cert as content-type, but got %s", resp.Data["http_content_type"].(string))
|
||
}
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
Path: "ca",
|
||
Unauthenticated: true,
|
||
Check: func(resp *logical.Response) error {
|
||
rawBytes := resp.Data["http_raw_body"].([]byte)
|
||
pemBytes := pem.EncodeToMemory(&pem.Block{
|
||
Type: "CERTIFICATE",
|
||
Bytes: rawBytes,
|
||
})
|
||
if string(pemBytes) != caCert {
|
||
return fmt.Errorf("CA certificate:\n%s\ndoes not match original:\n%s\n", string(pemBytes), caCert)
|
||
}
|
||
if resp.Data["http_content_type"].(string) != "application/pkix-cert" {
|
||
return fmt.Errorf("Expected application/pkix-cert as content-type, but got %s", resp.Data["http_content_type"].(string))
|
||
}
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
Path: "config/crl",
|
||
Check: func(resp *logical.Response) error {
|
||
if resp.Data["expiry"].(string) != "16h" {
|
||
return fmt.Errorf("CRL lifetimes do not match (got %s)", resp.Data["expiry"].(string))
|
||
}
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// Ensure that both parts of the PEM bundle are required
|
||
// Here, just the cert
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "config/ca",
|
||
Data: map[string]interface{}{
|
||
"pem_bundle": caCert,
|
||
},
|
||
ErrorOk: true,
|
||
},
|
||
|
||
// Here, just the key
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "config/ca",
|
||
Data: map[string]interface{}{
|
||
"pem_bundle": caKey,
|
||
},
|
||
ErrorOk: true,
|
||
},
|
||
|
||
// Ensure we can fetch it back via unauthenticated means, in various formats
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
Path: "cert/ca",
|
||
Unauthenticated: true,
|
||
Check: func(resp *logical.Response) error {
|
||
if resp.Data["certificate"].(string) != caCert {
|
||
return fmt.Errorf("CA certificate:\n%s\ndoes not match original:\n%s\n", resp.Data["certificate"].(string), caCert)
|
||
}
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
Path: "ca/pem",
|
||
Unauthenticated: true,
|
||
Check: func(resp *logical.Response) error {
|
||
rawBytes := resp.Data["http_raw_body"].([]byte)
|
||
if string(rawBytes) != caCert {
|
||
return fmt.Errorf("CA certificate:\n%s\ndoes not match original:\n%s\n", string(rawBytes), caCert)
|
||
}
|
||
if resp.Data["http_content_type"].(string) != "application/pkix-cert" {
|
||
return fmt.Errorf("Expected application/pkix-cert as content-type, but got %s", resp.Data["http_content_type"].(string))
|
||
}
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
Path: "ca",
|
||
Unauthenticated: true,
|
||
Check: func(resp *logical.Response) error {
|
||
rawBytes := resp.Data["http_raw_body"].([]byte)
|
||
pemBytes := pem.EncodeToMemory(&pem.Block{
|
||
Type: "CERTIFICATE",
|
||
Bytes: rawBytes,
|
||
})
|
||
if string(pemBytes) != caCert {
|
||
return fmt.Errorf("CA certificate:\n%s\ndoes not match original:\n%s\n", string(pemBytes), caCert)
|
||
}
|
||
if resp.Data["http_content_type"].(string) != "application/pkix-cert" {
|
||
return fmt.Errorf("Expected application/pkix-cert as content-type, but got %s", resp.Data["http_content_type"].(string))
|
||
}
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// Test a bunch of generation stuff
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "root/generate/exported",
|
||
Data: map[string]interface{}{
|
||
"common_name": "Root Cert",
|
||
"ttl": "180h",
|
||
},
|
||
Check: func(resp *logical.Response) error {
|
||
intdata["root"] = resp.Data["certificate"].(string)
|
||
intdata["rootkey"] = resp.Data["private_key"].(string)
|
||
reqdata["pem_bundle"] = intdata["root"].(string) + "\n" + intdata["rootkey"].(string)
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "intermediate/generate/exported",
|
||
Data: map[string]interface{}{
|
||
"common_name": "Intermediate Cert",
|
||
},
|
||
Check: func(resp *logical.Response) error {
|
||
intdata["intermediatecsr"] = resp.Data["csr"].(string)
|
||
intdata["intermediatekey"] = resp.Data["private_key"].(string)
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// Re-load the root key in so we can sign it
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "config/ca",
|
||
Data: reqdata,
|
||
Check: func(resp *logical.Response) error {
|
||
delete(reqdata, "pem_bundle")
|
||
delete(reqdata, "ttl")
|
||
reqdata["csr"] = intdata["intermediatecsr"].(string)
|
||
reqdata["common_name"] = "Intermediate Cert"
|
||
reqdata["ttl"] = "10s"
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "root/sign-intermediate",
|
||
Data: reqdata,
|
||
Check: func(resp *logical.Response) error {
|
||
delete(reqdata, "csr")
|
||
delete(reqdata, "common_name")
|
||
delete(reqdata, "ttl")
|
||
intdata["intermediatecert"] = resp.Data["certificate"].(string)
|
||
reqdata["serial_number"] = resp.Data["serial_number"].(string)
|
||
reqdata["rsa_int_serial_number"] = resp.Data["serial_number"].(string)
|
||
reqdata["certificate"] = resp.Data["certificate"].(string)
|
||
reqdata["pem_bundle"] = intdata["intermediatekey"].(string) + "\n" + resp.Data["certificate"].(string)
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// First load in this way to populate the private key
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "config/ca",
|
||
Data: reqdata,
|
||
Check: func(resp *logical.Response) error {
|
||
delete(reqdata, "pem_bundle")
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// Now test setting the intermediate, signed CA cert
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "intermediate/set-signed",
|
||
Data: reqdata,
|
||
Check: func(resp *logical.Response) error {
|
||
delete(reqdata, "certificate")
|
||
|
||
serialUnderTest = "cert/" + reqdata["rsa_int_serial_number"].(string)
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// We expect to find a zero revocation time
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
PreFlight: setSerialUnderTest,
|
||
Check: func(resp *logical.Response) error {
|
||
if resp.Data["error"] != nil && resp.Data["error"].(string) != "" {
|
||
return fmt.Errorf("got an error: %s", resp.Data["error"].(string))
|
||
}
|
||
|
||
if resp.Data["revocation_time"].(int64) != 0 {
|
||
return fmt.Errorf("expected a zero revocation time")
|
||
}
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "revoke",
|
||
Data: reqdata,
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
Path: "crl",
|
||
Data: reqdata,
|
||
Check: func(resp *logical.Response) error {
|
||
crlBytes := resp.Data["http_raw_body"].([]byte)
|
||
certList, err := x509.ParseCRL(crlBytes)
|
||
if err != nil {
|
||
t.Fatalf("err: %s", err)
|
||
}
|
||
revokedList := certList.TBSCertList.RevokedCertificates
|
||
if len(revokedList) != 1 {
|
||
t.Fatalf("length of revoked list not 1; %d", len(revokedList))
|
||
}
|
||
revokedString := certutil.GetHexFormatted(revokedList[0].SerialNumber.Bytes(), ":")
|
||
if revokedString != reqdata["serial_number"].(string) {
|
||
t.Fatalf("got serial %s, expecting %s", revokedString, reqdata["serial_number"].(string))
|
||
}
|
||
delete(reqdata, "serial_number")
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// Do it all again, with EC keys and DER format
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "root/generate/exported",
|
||
Data: map[string]interface{}{
|
||
"common_name": "Root Cert",
|
||
"ttl": "180h",
|
||
"key_type": "ec",
|
||
"key_bits": 384,
|
||
"format": "der",
|
||
},
|
||
Check: func(resp *logical.Response) error {
|
||
certBytes, _ := base64.StdEncoding.DecodeString(resp.Data["certificate"].(string))
|
||
certPem := pem.EncodeToMemory(&pem.Block{
|
||
Type: "CERTIFICATE",
|
||
Bytes: certBytes,
|
||
})
|
||
keyBytes, _ := base64.StdEncoding.DecodeString(resp.Data["private_key"].(string))
|
||
keyPem := pem.EncodeToMemory(&pem.Block{
|
||
Type: "EC PRIVATE KEY",
|
||
Bytes: keyBytes,
|
||
})
|
||
intdata["root"] = string(certPem)
|
||
intdata["rootkey"] = string(keyPem)
|
||
reqdata["pem_bundle"] = string(certPem) + "\n" + string(keyPem)
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "intermediate/generate/exported",
|
||
Data: map[string]interface{}{
|
||
"format": "der",
|
||
"key_type": "ec",
|
||
"key_bits": 384,
|
||
"common_name": "Intermediate Cert",
|
||
},
|
||
Check: func(resp *logical.Response) error {
|
||
csrBytes, _ := base64.StdEncoding.DecodeString(resp.Data["csr"].(string))
|
||
csrPem := pem.EncodeToMemory(&pem.Block{
|
||
Type: "CERTIFICATE REQUEST",
|
||
Bytes: csrBytes,
|
||
})
|
||
keyBytes, _ := base64.StdEncoding.DecodeString(resp.Data["private_key"].(string))
|
||
keyPem := pem.EncodeToMemory(&pem.Block{
|
||
Type: "EC PRIVATE KEY",
|
||
Bytes: keyBytes,
|
||
})
|
||
intdata["intermediatecsr"] = string(csrPem)
|
||
intdata["intermediatekey"] = string(keyPem)
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "config/ca",
|
||
Data: reqdata,
|
||
Check: func(resp *logical.Response) error {
|
||
delete(reqdata, "pem_bundle")
|
||
delete(reqdata, "ttl")
|
||
reqdata["csr"] = intdata["intermediatecsr"].(string)
|
||
reqdata["common_name"] = "Intermediate Cert"
|
||
reqdata["ttl"] = "10s"
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "root/sign-intermediate",
|
||
Data: reqdata,
|
||
Check: func(resp *logical.Response) error {
|
||
delete(reqdata, "csr")
|
||
delete(reqdata, "common_name")
|
||
delete(reqdata, "ttl")
|
||
intdata["intermediatecert"] = resp.Data["certificate"].(string)
|
||
reqdata["serial_number"] = resp.Data["serial_number"].(string)
|
||
reqdata["ec_int_serial_number"] = resp.Data["serial_number"].(string)
|
||
reqdata["certificate"] = resp.Data["certificate"].(string)
|
||
reqdata["pem_bundle"] = intdata["intermediatekey"].(string) + "\n" + resp.Data["certificate"].(string)
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// First load in this way to populate the private key
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "config/ca",
|
||
Data: reqdata,
|
||
Check: func(resp *logical.Response) error {
|
||
delete(reqdata, "pem_bundle")
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// Now test setting the intermediate, signed CA cert
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "intermediate/set-signed",
|
||
Data: reqdata,
|
||
Check: func(resp *logical.Response) error {
|
||
delete(reqdata, "certificate")
|
||
|
||
serialUnderTest = "cert/" + reqdata["ec_int_serial_number"].(string)
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// We expect to find a zero revocation time
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
PreFlight: setSerialUnderTest,
|
||
Check: func(resp *logical.Response) error {
|
||
if resp.Data["error"] != nil && resp.Data["error"].(string) != "" {
|
||
return fmt.Errorf("got an error: %s", resp.Data["error"].(string))
|
||
}
|
||
|
||
if resp.Data["revocation_time"].(int64) != 0 {
|
||
return fmt.Errorf("expected a zero revocation time")
|
||
}
|
||
|
||
return nil
|
||
},
|
||
},
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "revoke",
|
||
Data: reqdata,
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
Path: "crl",
|
||
Data: reqdata,
|
||
Check: func(resp *logical.Response) error {
|
||
crlBytes := resp.Data["http_raw_body"].([]byte)
|
||
certList, err := x509.ParseCRL(crlBytes)
|
||
if err != nil {
|
||
t.Fatalf("err: %s", err)
|
||
}
|
||
revokedList := certList.TBSCertList.RevokedCertificates
|
||
if len(revokedList) != 2 {
|
||
t.Fatalf("length of revoked list not 2; %d", len(revokedList))
|
||
}
|
||
found := false
|
||
for _, revEntry := range revokedList {
|
||
revokedString := certutil.GetHexFormatted(revEntry.SerialNumber.Bytes(), ":")
|
||
if revokedString == reqdata["serial_number"].(string) {
|
||
found = true
|
||
}
|
||
}
|
||
if !found {
|
||
t.Fatalf("did not find %s in CRL", reqdata["serial_number"].(string))
|
||
}
|
||
delete(reqdata, "serial_number")
|
||
|
||
serialUnderTest = "cert/" + reqdata["rsa_int_serial_number"].(string)
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// Make sure both serial numbers we expect to find are found
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
PreFlight: setSerialUnderTest,
|
||
Check: func(resp *logical.Response) error {
|
||
if resp.Data["error"] != nil && resp.Data["error"].(string) != "" {
|
||
return fmt.Errorf("got an error: %s", resp.Data["error"].(string))
|
||
}
|
||
|
||
if resp.Data["revocation_time"].(int64) == 0 {
|
||
return fmt.Errorf("expected a non-zero revocation time")
|
||
}
|
||
|
||
serialUnderTest = "cert/" + reqdata["ec_int_serial_number"].(string)
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
PreFlight: setSerialUnderTest,
|
||
Check: func(resp *logical.Response) error {
|
||
if resp.Data["error"] != nil && resp.Data["error"].(string) != "" {
|
||
return fmt.Errorf("got an error: %s", resp.Data["error"].(string))
|
||
}
|
||
|
||
if resp.Data["revocation_time"].(int64) == 0 {
|
||
return fmt.Errorf("expected a non-zero revocation time")
|
||
}
|
||
|
||
// Give time for the certificates to pass the safety buffer
|
||
t.Logf("Sleeping for 15 seconds to allow safety buffer time to pass before testing tidying")
|
||
time.Sleep(15 * time.Second)
|
||
|
||
serialUnderTest = "cert/" + reqdata["rsa_int_serial_number"].(string)
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// This shouldn't do anything since the safety buffer is too long
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "tidy",
|
||
Data: map[string]interface{}{
|
||
"safety_buffer": "3h",
|
||
"tidy_cert_store": true,
|
||
"tidy_revocation_list": true,
|
||
},
|
||
},
|
||
|
||
// We still expect to find these
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
PreFlight: setSerialUnderTest,
|
||
Check: func(resp *logical.Response) error {
|
||
if resp.Data["error"] != nil && resp.Data["error"].(string) != "" {
|
||
return fmt.Errorf("got an error: %s", resp.Data["error"].(string))
|
||
}
|
||
|
||
serialUnderTest = "cert/" + reqdata["ec_int_serial_number"].(string)
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
PreFlight: setSerialUnderTest,
|
||
Check: func(resp *logical.Response) error {
|
||
if resp.Data["error"] != nil && resp.Data["error"].(string) != "" {
|
||
return fmt.Errorf("got an error: %s", resp.Data["error"].(string))
|
||
}
|
||
|
||
serialUnderTest = "cert/" + reqdata["rsa_int_serial_number"].(string)
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// Both should appear in the CRL
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
Path: "crl",
|
||
Data: reqdata,
|
||
Check: func(resp *logical.Response) error {
|
||
crlBytes := resp.Data["http_raw_body"].([]byte)
|
||
certList, err := x509.ParseCRL(crlBytes)
|
||
if err != nil {
|
||
t.Fatalf("err: %s", err)
|
||
}
|
||
revokedList := certList.TBSCertList.RevokedCertificates
|
||
if len(revokedList) != 2 {
|
||
t.Fatalf("length of revoked list not 2; %d", len(revokedList))
|
||
}
|
||
foundRsa := false
|
||
foundEc := false
|
||
for _, revEntry := range revokedList {
|
||
revokedString := certutil.GetHexFormatted(revEntry.SerialNumber.Bytes(), ":")
|
||
if revokedString == reqdata["rsa_int_serial_number"].(string) {
|
||
foundRsa = true
|
||
}
|
||
if revokedString == reqdata["ec_int_serial_number"].(string) {
|
||
foundEc = true
|
||
}
|
||
}
|
||
if !foundRsa || !foundEc {
|
||
t.Fatalf("did not find an expected entry in CRL")
|
||
}
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// This shouldn't do anything since the boolean values default to false
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "tidy",
|
||
Data: map[string]interface{}{
|
||
"safety_buffer": "1s",
|
||
},
|
||
},
|
||
|
||
// We still expect to find these
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
PreFlight: setSerialUnderTest,
|
||
Check: func(resp *logical.Response) error {
|
||
if resp.Data["error"] != nil && resp.Data["error"].(string) != "" {
|
||
return fmt.Errorf("got an error: %s", resp.Data["error"].(string))
|
||
}
|
||
|
||
serialUnderTest = "cert/" + reqdata["ec_int_serial_number"].(string)
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
PreFlight: setSerialUnderTest,
|
||
Check: func(resp *logical.Response) error {
|
||
if resp.Data["error"] != nil && resp.Data["error"].(string) != "" {
|
||
return fmt.Errorf("got an error: %s", resp.Data["error"].(string))
|
||
}
|
||
|
||
serialUnderTest = "cert/" + reqdata["rsa_int_serial_number"].(string)
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// This should remove the values since the safety buffer is short
|
||
logicaltest.TestStep{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "tidy",
|
||
Data: map[string]interface{}{
|
||
"safety_buffer": "1s",
|
||
"tidy_cert_store": true,
|
||
"tidy_revocation_list": true,
|
||
},
|
||
},
|
||
|
||
// We do *not* expect to find these
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
PreFlight: setSerialUnderTest,
|
||
Check: func(resp *logical.Response) error {
|
||
if resp.Data["error"] == nil || resp.Data["error"].(string) == "" {
|
||
return fmt.Errorf("didn't get an expected error")
|
||
}
|
||
|
||
serialUnderTest = "cert/" + reqdata["ec_int_serial_number"].(string)
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
PreFlight: setSerialUnderTest,
|
||
Check: func(resp *logical.Response) error {
|
||
if resp.Data["error"] == nil || resp.Data["error"].(string) == "" {
|
||
return fmt.Errorf("didn't get an expected error")
|
||
}
|
||
|
||
serialUnderTest = "cert/" + reqdata["rsa_int_serial_number"].(string)
|
||
|
||
return nil
|
||
},
|
||
},
|
||
|
||
// Both should be gone from the CRL
|
||
logicaltest.TestStep{
|
||
Operation: logical.ReadOperation,
|
||
Path: "crl",
|
||
Data: reqdata,
|
||
Check: func(resp *logical.Response) error {
|
||
crlBytes := resp.Data["http_raw_body"].([]byte)
|
||
certList, err := x509.ParseCRL(crlBytes)
|
||
if err != nil {
|
||
t.Fatalf("err: %s", err)
|
||
}
|
||
revokedList := certList.TBSCertList.RevokedCertificates
|
||
if len(revokedList) != 0 {
|
||
t.Fatalf("length of revoked list not 0; %d", len(revokedList))
|
||
}
|
||
|
||
return nil
|
||
},
|
||
},
|
||
}
|
||
|
||
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",
|
||
}
|
||
}
|
||
|
||
generatedRSAKeys := map[int]crypto.Signer{}
|
||
generatedECKeys := map[int]crypto.Signer{}
|
||
|
||
/*
|
||
// 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++
|
||
//t.Logf("test step %d\nrole vals: %#v\n", stepCount, roleVals)
|
||
stepCount++
|
||
//t.Logf("test step %d\nissue vals: %#v\n", stepCount, issueTestStep)
|
||
roleTestStep.Data = structs.New(roleVals).Map()
|
||
roleTestStep.Data["generate_lease"] = false
|
||
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)
|
||
}
|
||
|
||
getOuCheck := func(role roleEntry) 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 := certBundle.ToParsedCertBundle()
|
||
if err != nil {
|
||
return fmt.Errorf("Error checking generated certificate: %s", err)
|
||
}
|
||
cert := parsedCertBundle.Certificate
|
||
|
||
expected := strutil.ParseDedupAndSortStrings(role.OU, ",")
|
||
if !reflect.DeepEqual(cert.Subject.OrganizationalUnit, expected) {
|
||
return fmt.Errorf("Error: returned certificate has OU of %s but %s was specified in the role.", cert.Subject.OrganizationalUnit, expected)
|
||
}
|
||
return nil
|
||
}
|
||
}
|
||
|
||
getOrganizationCheck := func(role roleEntry) 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 := certBundle.ToParsedCertBundle()
|
||
if err != nil {
|
||
return fmt.Errorf("Error checking generated certificate: %s", err)
|
||
}
|
||
cert := parsedCertBundle.Certificate
|
||
|
||
expected := strutil.ParseDedupAndSortStrings(role.Organization, ",")
|
||
if !reflect.DeepEqual(cert.Subject.Organization, expected) {
|
||
return fmt.Errorf("Error: returned certificate has Organization of %s but %s was specified in the role.", cert.Subject.Organization, expected)
|
||
}
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 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 x509.KeyUsage, extUsage x509.ExtKeyUsage, 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, extUsage, 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 string
|
||
if mathRand.Int()%2 == 1 {
|
||
usage = usage + ",DigitalSignature"
|
||
}
|
||
if mathRand.Int()%2 == 1 {
|
||
usage = usage + ",ContentCoMmitment"
|
||
}
|
||
if mathRand.Int()%2 == 1 {
|
||
usage = usage + ",KeyEncipherment"
|
||
}
|
||
if mathRand.Int()%2 == 1 {
|
||
usage = usage + ",DataEncipherment"
|
||
}
|
||
if mathRand.Int()%2 == 1 {
|
||
usage = usage + ",KeyAgreemEnt"
|
||
}
|
||
if mathRand.Int()%2 == 1 {
|
||
usage = usage + ",CertSign"
|
||
}
|
||
if mathRand.Int()%2 == 1 {
|
||
usage = usage + ",CRLSign"
|
||
}
|
||
if mathRand.Int()%2 == 1 {
|
||
usage = usage + ",EncipherOnly"
|
||
}
|
||
if mathRand.Int()%2 == 1 {
|
||
usage = usage + ",DecipherOnly"
|
||
}
|
||
|
||
roleVals.KeyUsage = usage
|
||
parsedKeyUsage := parseKeyUsages(roleVals.KeyUsage)
|
||
if parsedKeyUsage == 0 && usage != "" {
|
||
panic("parsed key usages was zero")
|
||
}
|
||
parsedKeyUsageUnderTest = parsedKeyUsage
|
||
|
||
var extUsage x509.ExtKeyUsage
|
||
i := mathRand.Int() % 4
|
||
switch {
|
||
case i == 0:
|
||
extUsage = x509.ExtKeyUsageEmailProtection
|
||
roleVals.EmailProtectionFlag = true
|
||
case i == 1:
|
||
extUsage = x509.ExtKeyUsageServerAuth
|
||
roleVals.ServerFlag = true
|
||
case i == 2:
|
||
extUsage = x509.ExtKeyUsageClientAuth
|
||
roleVals.ClientFlag = true
|
||
default:
|
||
extUsage = x509.ExtKeyUsageCodeSigning
|
||
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
|
||
var ok bool
|
||
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, ok = generatedRSAKeys[testBitSize]
|
||
if !ok {
|
||
privKey, _ = rsa.GenerateKey(rand.Reader, testBitSize)
|
||
generatedRSAKeys[testBitSize] = privKey
|
||
}
|
||
|
||
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, ok = generatedECKeys[testBitSize]
|
||
if !ok {
|
||
privKey, _ = ecdsa.GenerateKey(curve, rand.Reader)
|
||
generatedECKeys[testBitSize] = privKey
|
||
}
|
||
}
|
||
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, x509.KeyUsage(parsedKeyUsage), extUsage, validity))
|
||
} else {
|
||
addTests(getCnCheck(issueVals.CommonName, roleVals, nil, x509.KeyUsage(parsedKeyUsage), extUsage, 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()
|
||
}
|
||
// OU tests
|
||
{
|
||
roleVals.OU = "foo"
|
||
addTests(getOuCheck(roleVals))
|
||
|
||
roleVals.OU = "foo,bar"
|
||
addTests(getOuCheck(roleVals))
|
||
}
|
||
// Organization tests
|
||
{
|
||
roleVals.Organization = "system:masters"
|
||
addTests(getOrganizationCheck(roleVals))
|
||
|
||
roleVals.Organization = "foo,bar"
|
||
addTests(getOrganizationCheck(roleVals))
|
||
}
|
||
// 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 %s", keys[0])
|
||
}
|
||
|
||
return nil
|
||
},
|
||
})
|
||
|
||
return ret
|
||
}
|
||
|
||
func TestBackend_PathFetchCertList(t *testing.T) {
|
||
// create the backend
|
||
config := logical.TestBackendConfig()
|
||
storage := &logical.InmemStorage{}
|
||
config.StorageView = storage
|
||
|
||
b := Backend()
|
||
_, err := b.Setup(config)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
// generate root
|
||
rootData := map[string]interface{}{
|
||
"common_name": "test.com",
|
||
"ttl": "6h",
|
||
}
|
||
|
||
resp, err := b.HandleRequest(&logical.Request{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "root/generate/internal",
|
||
Storage: storage,
|
||
Data: rootData,
|
||
})
|
||
if resp != nil && resp.IsError() {
|
||
t.Fatalf("failed to generate root, %#v", resp)
|
||
}
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
// config urls
|
||
urlsData := map[string]interface{}{
|
||
"issuing_certificates": "http://127.0.0.1:8200/v1/pki/ca",
|
||
"crl_distribution_points": "http://127.0.0.1:8200/v1/pki/crl",
|
||
}
|
||
|
||
resp, err = b.HandleRequest(&logical.Request{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "config/urls",
|
||
Storage: storage,
|
||
Data: urlsData,
|
||
})
|
||
if resp != nil && resp.IsError() {
|
||
t.Fatalf("failed to config urls, %#v", resp)
|
||
}
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
// create a role entry
|
||
roleData := map[string]interface{}{
|
||
"allowed_domains": "test.com",
|
||
"allow_subdomains": "true",
|
||
"max_ttl": "4h",
|
||
}
|
||
|
||
resp, err = b.HandleRequest(&logical.Request{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "roles/test-example",
|
||
Storage: storage,
|
||
Data: roleData,
|
||
})
|
||
if resp != nil && resp.IsError() {
|
||
t.Fatalf("failed to create a role, %#v", resp)
|
||
}
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
// issue some certs
|
||
i := 1
|
||
for i < 10 {
|
||
certData := map[string]interface{}{
|
||
"common_name": "example.test.com",
|
||
}
|
||
resp, err = b.HandleRequest(&logical.Request{
|
||
Operation: logical.UpdateOperation,
|
||
Path: "issue/test-example",
|
||
Storage: storage,
|
||
Data: certData,
|
||
})
|
||
if resp != nil && resp.IsError() {
|
||
t.Fatalf("failed to issue a cert, %#v", resp)
|
||
}
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
i = i + 1
|
||
}
|
||
|
||
// list certs
|
||
resp, err = b.HandleRequest(&logical.Request{
|
||
Operation: logical.ListOperation,
|
||
Path: "certs",
|
||
Storage: storage,
|
||
})
|
||
if resp != nil && resp.IsError() {
|
||
t.Fatalf("failed to list certs, %#v", resp)
|
||
}
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
// check that the root and 9 additional certs are all listed
|
||
if len(resp.Data["keys"].([]string)) != 10 {
|
||
t.Fatalf("failed to list all 10 certs")
|
||
}
|
||
|
||
// list certs/
|
||
resp, err = b.HandleRequest(&logical.Request{
|
||
Operation: logical.ListOperation,
|
||
Path: "certs/",
|
||
Storage: storage,
|
||
})
|
||
if resp != nil && resp.IsError() {
|
||
t.Fatalf("failed to list certs, %#v", resp)
|
||
}
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
// check that the root and 9 additional certs are all listed
|
||
if len(resp.Data["keys"].([]string)) != 10 {
|
||
t.Fatalf("failed to list all 10 certs")
|
||
}
|
||
}
|
||
|
||
const (
|
||
rsaCAKey string = `-----BEGIN RSA PRIVATE KEY-----
|
||
MIIEogIBAAKCAQEAmPQlK7xD5p+E8iLQ8XlVmll5uU2NKMxKY3UF5tbh+0vkc+Fy
|
||
XmutLxxXAyYRPoztZ1g7ocr8XBFYsQPK26TFc3TzrLL7bBEYHQArd8M+VUHjziB7
|
||
zwwpbV7tG8WPqIScDKMNncavDcT8sDg3DUqb8/zWkBD8WEYmsVr1VfKY5pFdxIZU
|
||
kHP3/MkDpGmfrED9K5qPu17dIHTL2VYi4KxKhtIryapZTk6vDwRNfIYJD23QbQnt
|
||
Si1j0X9MTRUf3BIcd0Ch60aGvv0VSL+1NTafsZQD+z1RY/zNp9IUHz5bNIiePZ6l
|
||
JrlddodAAXZ4sN1CMetf4bA2RXssxBEIb5FyiQIDAQABAoIBAGMScSk9DvZJCUIV
|
||
zyU6JHqPzkp6sx5kBSMa37HAKiwt4lI1C3GhaVIEl0/Qzoannfa8rhOEeaXhDoPK
|
||
IxHWTpcUf+mzHSvIfsf6Hi2655stzLLtU4SvKf5P6GF+vCi5jKKa0u0JjsXqfIpg
|
||
Pzh6xT1q3kf+2JUNC28Brbv4IZXmPmqWwu21VN+t3GsMGYgOnEOzBjXMhvNnm9kN
|
||
kznV9Y2y0UIcT4dhbe2VRs4Dp8dGEyrFM7/Ovb3hIJrTkPcxjBbL5eMqpXnIkiW2
|
||
7NyPMWFvX2lGnGdZ1Erh65SVtMjnHFwnSJ8jD+x9RAH9c1LQrYASws3MvMV8Bdzg
|
||
2iljNqECgYEAw3Ow0clLx2alj9qFXcS2ap1lUCJxXZ9UiIU5lOcPxpCpHPloua14
|
||
46rj2EJ9SD1L2kyB5gCq4nGK5uUIx37AJryy1SGzUmtmIVxQLnm6XK6zKnTBk0gx
|
||
gevS6D7fHLDiVGGl3oGw4evibUFCk7dFOb/I/uBRb1zyaJrqOIlDS7UCgYEAyFYi
|
||
RYQbYJJ0k18fUWDKy/P/Rl7uy9D67Qa9+wxoYN2Kh/aQwnNxYHAbwG7Pupd0oGcW
|
||
Yl4bgUliAX3IFGs/cCkPJAIHzwWBPjUDhsJ020TGxKfL4SWP9OaxOpN5TOAixvBY
|
||
ar9aSaKEl7QShmzc/Dknxu58LcoZUwI82pKIGAUCgYAxaHJ/ZcpxOsKJjez+2jZe
|
||
1zEAQ+SyjQ96f2sh+BMl1/XYLDhMD80qiE2WoqA2/b/KDGMd+Hc6TQeW/LjubV03
|
||
raXreNxy7lFgB40BYqY4vbTu+5rfl3VkaW/kY9hU0WY1fIXIrLJBOjb/9WpWGxM1
|
||
2QR/YcdURoPE67xf1FsdrQKBgE8KdNEakzah8e6nLBMOblTTutcH4410sVvNOi2P
|
||
sqrtHZgRNwIRTB0xfjGJRtomoXQb2CANYyq6SjmuZ79upQPan0ekqXILiPeDMRX9
|
||
KN/OHeI/FdiJ2mdUkX476zLih7YX47qSLsw4m7nC6UAyOWomHsSFGWdzglRW4K2X
|
||
/KwFAoGAYQUEWhXp5vpKzAly1ivSH9+sGC59Cujdy50oJSjaw9J+W1fM5WO9z+MH
|
||
CoEpRt8epIgvCBBP2IM7uJUu8i2jQgJ/rrn3NTJgZn2UEPzyxUxbuWnSyueyUsD6
|
||
uhTwBDf8LWOpvdZHMI4CPZ5WJwxAGkvde9xtlzuZUSAlyI2X8m0=
|
||
-----END RSA PRIVATE KEY-----
|
||
`
|
||
rsaCACert string = `-----BEGIN CERTIFICATE-----
|
||
MIIDljCCAn6gAwIBAgIUQVapfgyAeDH9rAmpw3PQrhMcjRMwDQYJKoZIhvcNAQEL
|
||
BQAwMzExMC8GA1UEAxMoVmF1bHQgVGVzdGluZyBJbnRlcm1lZGlhdGUgU3ViIEF1
|
||
dGhvcml0eTAeFw0xNjA4MDcyMjUzNTRaFw0yNjA3MjQxMDU0MjRaMDcxNTAzBgNV
|
||
BAMTLFZhdWx0IFRlc3RpbmcgSW50ZXJtZWRpYXRlIFN1YiBTdWIgQXV0aG9yaXR5
|
||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmPQlK7xD5p+E8iLQ8XlV
|
||
mll5uU2NKMxKY3UF5tbh+0vkc+FyXmutLxxXAyYRPoztZ1g7ocr8XBFYsQPK26TF
|
||
c3TzrLL7bBEYHQArd8M+VUHjziB7zwwpbV7tG8WPqIScDKMNncavDcT8sDg3DUqb
|
||
8/zWkBD8WEYmsVr1VfKY5pFdxIZUkHP3/MkDpGmfrED9K5qPu17dIHTL2VYi4KxK
|
||
htIryapZTk6vDwRNfIYJD23QbQntSi1j0X9MTRUf3BIcd0Ch60aGvv0VSL+1NTaf
|
||
sZQD+z1RY/zNp9IUHz5bNIiePZ6lJrlddodAAXZ4sN1CMetf4bA2RXssxBEIb5Fy
|
||
iQIDAQABo4GdMIGaMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0G
|
||
A1UdDgQWBBRMeQTX9VkLqb1wzrvN/vFG09yhUTAfBgNVHSMEGDAWgBR0Oq2VTUBE
|
||
dOm6a1sKJTvdZMV5LjA3BgNVHREEMDAugixWYXVsdCBUZXN0aW5nIEludGVybWVk
|
||
aWF0ZSBTdWIgU3ViIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAQEAagYM7uFa
|
||
tUziraBkuU7cIyX83y7lYFsDhUse2hkpqmgO14oEOwFsDox1Jg2QGt4FEfJoCOXf
|
||
oCZZN8XmaWdSrfgs1nDmtE0xwXiX1z7JuJZ+Ygt3dcRHO1zs5tmuHLxrvMnKfIfG
|
||
bsGmES4mknt0qQ7tGhpyC+KgEmcVL1QQJXNjzCrw5iQ9sgvQt+oCqV28pxOUSYkq
|
||
FdrozmNdJwMgVADywiY/FqYJWgkixlFHQkPR7eiXwpahON+zRMk1JSgr/8N8fRDj
|
||
aqVBRppPzVU9joUME0vOc8cK3VozNe4iRkKNZFelHU2NPPJSDjRLVH9tJ7jPVOEA
|
||
/k6w2PwdoRom7Q==
|
||
-----END CERTIFICATE-----
|
||
`
|
||
|
||
rsaCAChain string = `-----BEGIN CERTIFICATE-----
|
||
MIIDijCCAnKgAwIBAgIUOiGo/1EOhRhuupTRGDYnqdALk/swDQYJKoZIhvcNAQEL
|
||
BQAwLzEtMCsGA1UEAxMkVmF1bHQgVGVzdGluZyBJbnRlcm1lZGlhdGUgQXV0aG9y
|
||
aXR5MB4XDTE2MDgwNzIyNTA1MloXDTI2MDcyODE0NTEyMlowMzExMC8GA1UEAxMo
|
||
VmF1bHQgVGVzdGluZyBJbnRlcm1lZGlhdGUgU3ViIEF1dGhvcml0eTCCASIwDQYJ
|
||
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMTPRQREwW3BEifNcm0XElMRB0GNTXHr
|
||
XCuNoFVsVBlIEsNVQkka+SHZcmNBdEcZLBXP/W3tBT82B48GVN8jyxAGfYZ5hoOQ
|
||
ed3GVft1A7lAnxcGvf5e9kfecKDcBB4G4rBhqdDNcAtklS2hV4uZUcVcEJKggpsQ
|
||
a1wZkCn8eg6sqEYG/SxPouwL52PblxIN+Dd57sBeqx4qdL297XR8LuLkxqftwUCZ
|
||
l2iFBnSDID/06ZmHDXA38I0n3jT2ZGjgPGFnIFKxRGq1vpVc3F5ga8qk+u66ybBu
|
||
xWHzINQrrryjELbl2YBTr6i0R9HnZle6OPcXMWp0JuGjtDC1xb5NmnkCAwEAAaOB
|
||
mTCBljAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
|
||
dDqtlU1ARHTpumtbCiU73WTFeS4wHwYDVR0jBBgwFoAU+UO/nrlKr4COZCxLZSY/
|
||
ul+YMvMwMwYDVR0RBCwwKoIoVmF1bHQgVGVzdGluZyBJbnRlcm1lZGlhdGUgU3Vi
|
||
IEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAQEAjgCuTXsLFkf0DVkfSsKReNwI
|
||
U/yBcP8Ttbx/ltanJGIVfD5TZoCnNTWm6RkML29ohfxI27sHTUhj+/6Ba0MRiLeI
|
||
FXdclXmHOU2dTHlrmUa0m/4cb5uYoiiEnpmyWL5k94fqPOZAvJcFHnP3db4vsaUW
|
||
47YcOvJbPSJqFXZHadqnsf3Fur5NCeTkIk6yZSvwTaZJT0JIWcqfE5LK3mYAMMC3
|
||
iPaIa1cYqOZhWx9ilQfW6u6WxWeOphGuDIusP7Q4qc2Dr9sekyD59dfIYsroK5TP
|
||
QVJb69nIYINpYdg3l3VNmmkY4G30N9QNs6acaH49rYzLcRX6tLBgPklO6d+TPA==
|
||
-----END CERTIFICATE-----
|
||
-----BEGIN CERTIFICATE-----
|
||
MIIDejCCAmKgAwIBAgIUULdIdrdK4Y8d+XM9fuOpDlNcJIYwDQYJKoZIhvcNAQEL
|
||
BQAwJzElMCMGA1UEAxMcVmF1bHQgVGVzdGluZyBSb290IEF1dGhvcml0eTAeFw0x
|
||
NjA4MDcyMjUwNTFaFw0yNjA4MDExODUxMjFaMC8xLTArBgNVBAMTJFZhdWx0IFRl
|
||
c3RpbmcgSW50ZXJtZWRpYXRlIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQAD
|
||
ggEPADCCAQoCggEBANXa6U+MDiUrryeZeGxgkmAZdrm9wCKz/6SmxYSebKr8aZwD
|
||
nfbsPLRFxU6BXp9Nc6pP7e8HLBv6PtFTQG389zxOBwAHxZQvUsFESumUd64oTLRG
|
||
J+AErTh7rtSWbLZsgDtQVvpx+6mKkvm53f/aKcq+DbqAFOg6slYOaQix0ZvP/qL0
|
||
iWGIPr1JZk9uBJOUuIUBJdbsgTk+KQqJL9M6up8bCnM0noCafwrNKwZWtsbkfOZE
|
||
OLSycdzCEBeHejpHTIU0vgAkdj63oEy2AbK3hMPxKzNthL3DX6W0tssoVgL//92i
|
||
oSfpDTxiXqqdr+J3accpsAvA+F+D2TqaxdAfjLcCAwEAAaOBlTCBkjAOBgNVHQ8B
|
||
Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU+UO/nrlKr4COZCxL
|
||
ZSY/ul+YMvMwHwYDVR0jBBgwFoAUA3jY4OUWi1Y7zQgM7S9QeXjNgIQwLwYDVR0R
|
||
BCgwJoIkVmF1bHQgVGVzdGluZyBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MA0GCSqG
|
||
SIb3DQEBCwUAA4IBAQA9VJt92LsOOegtAx35rr41LSSfPWB2SCKg0fphL2gMPO5y
|
||
fE2u8O5TF5dJWJ1fF3cg9/XK30Ohdl1/ujfHbVcX+O6Sb3cgwKTQQOhTdsAZKFRT
|
||
RPHaf/Ja8uqAXMITApxOp7YiYQwukwZr+OsKi66s+zhlfI790PoQbC3UJvgmNDmv
|
||
V5oP63mw4yhqRNPn4NOjzoC/hJSIM0AIdRB1nx2rsSUw0P354R1j9gO43L/Lj33S
|
||
NEaPmw+SC3Tbcx4yxeKnTvGdu3sw/ndmZkCjaq5jxgTy9FONqT45TPJOyk29o5gl
|
||
+AVQz5fD2M3C1L/sZIPH2OQbXxePHcsvUZVgaKyk
|
||
-----END CERTIFICATE-----
|
||
`
|
||
|
||
ecCAKey string = `-----BEGIN EC PRIVATE KEY-----
|
||
MIGkAgEBBDBP/t89wrC0RFVs0N+jiRuGPptoxI1Iyu42/PzzZWMKYnO7yCWFG/Qv
|
||
zC8cRa8PDqegBwYFK4EEACKhZANiAAQI9e8n9RD6gOd5YpWpDi5AoPbskxQSogxx
|
||
dYFzzHwS0RYIucmlcJ2CuJQNc+9E4dUCMsYr2cAnCgA4iUHzGaje3Fa4O667LVH1
|
||
imAyAj5nbfSd89iNzg4XNPkFjuVNBlE=
|
||
-----END EC PRIVATE KEY-----
|
||
`
|
||
|
||
ecCACert string = `-----BEGIN CERTIFICATE-----
|
||
MIIDHzCCAqSgAwIBAgIUEQ4L+8Xl9+/uxU3MMCrd3Bw0HMcwCgYIKoZIzj0EAwIw
|
||
XzEjMCEGA1UEAxMaVmF1bHQgRUMgdGVzdGluZyByb290IGNlcnQxODA2BgNVBAUT
|
||
Lzk3MzY2MDk3NDQ1ODU2MDI3MDY5MDQ0MTkxNjIxODI4NjI0NjM0NTI5MTkzMTU5
|
||
MB4XDTE1MTAwNTE2MzAwMFoXDTM1MDkzMDE2MzAwMFowXzEjMCEGA1UEAxMaVmF1
|
||
bHQgRUMgdGVzdGluZyByb290IGNlcnQxODA2BgNVBAUTLzk3MzY2MDk3NDQ1ODU2
|
||
MDI3MDY5MDQ0MTkxNjIxODI4NjI0NjM0NTI5MTkzMTU5MHYwEAYHKoZIzj0CAQYF
|
||
K4EEACIDYgAECPXvJ/UQ+oDneWKVqQ4uQKD27JMUEqIMcXWBc8x8EtEWCLnJpXCd
|
||
griUDXPvROHVAjLGK9nAJwoAOIlB8xmo3txWuDuuuy1R9YpgMgI+Z230nfPYjc4O
|
||
FzT5BY7lTQZRo4IBHzCCARswDgYDVR0PAQH/BAQDAgGuMBMGA1UdJQQMMAoGCCsG
|
||
AQUFBwMJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFCIBqs15CiKuj7vqmIW5
|
||
L07WSeLhMB8GA1UdIwQYMBaAFCIBqs15CiKuj7vqmIW5L07WSeLhMEIGCCsGAQUF
|
||
BwEBBDYwNDAyBggrBgEFBQcwAoYmaHR0cDovL3ZhdWx0LmV4YW1wbGUuY29tL3Yx
|
||
L3Jvb3Rwa2kvY2EwJQYDVR0RBB4wHIIaVmF1bHQgRUMgdGVzdGluZyByb290IGNl
|
||
cnQwOAYDVR0fBDEwLzAtoCugKYYnaHR0cDovL3ZhdWx0LmV4YW1wbGUuY29tL3Yx
|
||
L3Jvb3Rwa2kvY3JsMAoGCCqGSM49BAMCA2kAMGYCMQDRrxXskBtXjuZ1tUTk+qae
|
||
3bNVE1oeTDJhe0m3KN7qTykSGslxfEjlv83GYXziiv0CMQDsqu1U9uXPn3ezSbgG
|
||
O30prQ/sanDzNAeJhftoGtNPJDspwx0fzclHvKIhgl3JVUc=
|
||
-----END CERTIFICATE-----
|
||
`
|
||
)
|