mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			2842 lines
		
	
	
		
			81 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			2842 lines
		
	
	
		
			81 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package pki
 | ||
| 
 | ||
| import (
 | ||
| 	"bytes"
 | ||
| 	"crypto"
 | ||
| 	"crypto/ecdsa"
 | ||
| 	"crypto/elliptic"
 | ||
| 	"crypto/rand"
 | ||
| 	"crypto/rsa"
 | ||
| 	"crypto/x509"
 | ||
| 	"crypto/x509/pkix"
 | ||
| 	"encoding/base64"
 | ||
| 	"encoding/pem"
 | ||
| 	"fmt"
 | ||
| 	"math"
 | ||
| 	"math/big"
 | ||
| 	mathrand "math/rand"
 | ||
| 	"net"
 | ||
| 	"os"
 | ||
| 	"reflect"
 | ||
| 	"strconv"
 | ||
| 	"strings"
 | ||
| 	"testing"
 | ||
| 	"time"
 | ||
| 
 | ||
| 	"github.com/fatih/structs"
 | ||
| 	"github.com/hashicorp/vault/api"
 | ||
| 	"github.com/hashicorp/vault/helper/certutil"
 | ||
| 	"github.com/hashicorp/vault/helper/strutil"
 | ||
| 	vaulthttp "github.com/hashicorp/vault/http"
 | ||
| 	"github.com/hashicorp/vault/logical"
 | ||
| 	logicaltest "github.com/hashicorp/vault/logical/testing"
 | ||
| 	"github.com/hashicorp/vault/vault"
 | ||
| 	"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, 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)
 | ||
| }
 | ||
| 
 | ||
| // 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())) > 20 {
 | ||
| 		return nil, fmt.Errorf("Certificate validity end: %s; expected within 20 seconds of %s", cert.NotAfter.Format(time.RFC3339), time.Now().Add(validity).Format(time.RFC3339))
 | ||
| 	}
 | ||
| 
 | ||
| 	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.DeleteOperation,
 | ||
| 			Path:      "root",
 | ||
| 		},
 | ||
| 
 | ||
| 		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.DeleteOperation,
 | ||
| 			Path:      "root",
 | ||
| 		},
 | ||
| 
 | ||
| 		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.DeleteOperation,
 | ||
| 			Path:      "root",
 | ||
| 		},
 | ||
| 
 | ||
| 		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 != nil && 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 != nil && 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 != nil && 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 != nil && 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 != nil {
 | ||
| 					return fmt.Errorf("expected no response")
 | ||
| 				}
 | ||
| 
 | ||
| 				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 != nil {
 | ||
| 					return fmt.Errorf("expected no response")
 | ||
| 				}
 | ||
| 
 | ||
| 				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 = roleVals.ToResponseData()
 | ||
| 		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.ParseDedupLowercaseAndSortStrings(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.ParseDedupLowercaseAndSortStrings(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"`
 | ||
| 		GlobDomain           bool `structs:"fooexample.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 = append(usage, "DigitalSignature")
 | ||
| 			}
 | ||
| 			if mathRand.Int()%2 == 1 {
 | ||
| 				usage = append(usage, "ContentCoMmitment")
 | ||
| 			}
 | ||
| 			if mathRand.Int()%2 == 1 {
 | ||
| 				usage = append(usage, "KeyEncipherment")
 | ||
| 			}
 | ||
| 			if mathRand.Int()%2 == 1 {
 | ||
| 				usage = append(usage, "DataEncipherment")
 | ||
| 			}
 | ||
| 			if mathRand.Int()%2 == 1 {
 | ||
| 				usage = append(usage, "KeyAgreemEnt")
 | ||
| 			}
 | ||
| 			if mathRand.Int()%2 == 1 {
 | ||
| 				usage = append(usage, "CertSign")
 | ||
| 			}
 | ||
| 			if mathRand.Int()%2 == 1 {
 | ||
| 				usage = append(usage, "CRLSign")
 | ||
| 			}
 | ||
| 			if mathRand.Int()%2 == 1 {
 | ||
| 				usage = append(usage, "EncipherOnly")
 | ||
| 			}
 | ||
| 			if mathRand.Int()%2 == 1 {
 | ||
| 				usage = append(usage, "DecipherOnly")
 | ||
| 			}
 | ||
| 
 | ||
| 			roleVals.KeyUsage = usage
 | ||
| 			parsedKeyUsage := parseKeyUsages(roleVals.KeyUsage)
 | ||
| 			if parsedKeyUsage == 0 && len(usage) != 0 {
 | ||
| 				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 = []string{"foobar.com"}
 | ||
| 		addCnTests()
 | ||
| 
 | ||
| 		roleVals.AllowedDomains = []string{"example.com"}
 | ||
| 		roleVals.AllowSubdomains = true
 | ||
| 		commonNames.SubDomain = true
 | ||
| 		commonNames.Wildcard = true
 | ||
| 		commonNames.SubSubdomain = true
 | ||
| 		commonNames.SubSubdomainWildcard = true
 | ||
| 		addCnTests()
 | ||
| 
 | ||
| 		roleVals.AllowedDomains = []string{"foobar.com", "example.com"}
 | ||
| 		commonNames.SecondDomain = true
 | ||
| 		roleVals.AllowBareDomains = true
 | ||
| 		commonNames.BareDomain = true
 | ||
| 		addCnTests()
 | ||
| 
 | ||
| 		roleVals.AllowedDomains = []string{"foobar.com", "*example.com"}
 | ||
| 		roleVals.AllowGlobDomains = true
 | ||
| 		commonNames.GlobDomain = 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
 | ||
| 	{
 | ||
| 		roleVals.UseCSRSANs = true
 | ||
| 		roleVals.AllowIPSANs = false
 | ||
| 		issueTestStep.ErrorOk = false
 | ||
| 		addTests(nil)
 | ||
| 
 | ||
| 		roleVals.UseCSRSANs = false
 | ||
| 		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
 | ||
| 		roleVals.TTL = ""
 | ||
| 		roleVals.MaxTTL = "12h"
 | ||
| 	}
 | ||
| 
 | ||
| 	// 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")
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestBackend_SignVerbatim(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":         "172800",
 | ||
| 	}
 | ||
| 
 | ||
| 	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)
 | ||
| 	}
 | ||
| 
 | ||
| 	// create a CSR and key
 | ||
| 	key, err := rsa.GenerateKey(rand.Reader, 2048)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	csrReq := &x509.CertificateRequest{
 | ||
| 		Subject: pkix.Name{
 | ||
| 			CommonName: "foo.bar.com",
 | ||
| 		},
 | ||
| 	}
 | ||
| 	csr, err := x509.CreateCertificateRequest(rand.Reader, csrReq, key)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if len(csr) == 0 {
 | ||
| 		t.Fatal("generated csr is empty")
 | ||
| 	}
 | ||
| 	pemCSR := pem.EncodeToMemory(&pem.Block{
 | ||
| 		Type:  "CERTIFICATE REQUEST",
 | ||
| 		Bytes: csr,
 | ||
| 	})
 | ||
| 	if len(pemCSR) == 0 {
 | ||
| 		t.Fatal("pem csr is empty")
 | ||
| 	}
 | ||
| 
 | ||
| 	resp, err = b.HandleRequest(&logical.Request{
 | ||
| 		Operation: logical.UpdateOperation,
 | ||
| 		Path:      "sign-verbatim",
 | ||
| 		Storage:   storage,
 | ||
| 		Data: map[string]interface{}{
 | ||
| 			"csr": string(pemCSR),
 | ||
| 		},
 | ||
| 	})
 | ||
| 	if resp != nil && resp.IsError() {
 | ||
| 		t.Fatalf("failed to sign-verbatim basic CSR: %#v", *resp)
 | ||
| 	}
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if resp.Secret != nil {
 | ||
| 		t.Fatal("secret is not nil")
 | ||
| 	}
 | ||
| 
 | ||
| 	// create a role entry; we use this to check that sign-verbatim when used with a role is still honoring TTLs
 | ||
| 	roleData := map[string]interface{}{
 | ||
| 		"ttl":     "4h",
 | ||
| 		"max_ttl": "8h",
 | ||
| 	}
 | ||
| 	resp, err = b.HandleRequest(&logical.Request{
 | ||
| 		Operation: logical.UpdateOperation,
 | ||
| 		Path:      "roles/test",
 | ||
| 		Storage:   storage,
 | ||
| 		Data:      roleData,
 | ||
| 	})
 | ||
| 	if resp != nil && resp.IsError() {
 | ||
| 		t.Fatalf("failed to create a role, %#v", *resp)
 | ||
| 	}
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	resp, err = b.HandleRequest(&logical.Request{
 | ||
| 		Operation: logical.UpdateOperation,
 | ||
| 		Path:      "sign-verbatim/test",
 | ||
| 		Storage:   storage,
 | ||
| 		Data: map[string]interface{}{
 | ||
| 			"csr": string(pemCSR),
 | ||
| 			"ttl": "5h",
 | ||
| 		},
 | ||
| 	})
 | ||
| 	if resp != nil && resp.IsError() {
 | ||
| 		t.Fatalf("failed to sign-verbatim ttl'd CSR: %#v", *resp)
 | ||
| 	}
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if resp.Secret != nil {
 | ||
| 		t.Fatal("got a lease when we should not have")
 | ||
| 	}
 | ||
| 	resp, err = b.HandleRequest(&logical.Request{
 | ||
| 		Operation: logical.UpdateOperation,
 | ||
| 		Path:      "sign-verbatim/test",
 | ||
| 		Storage:   storage,
 | ||
| 		Data: map[string]interface{}{
 | ||
| 			"csr": string(pemCSR),
 | ||
| 			"ttl": "12h",
 | ||
| 		},
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if resp != nil && resp.IsError() {
 | ||
| 		t.Fatalf(resp.Error().Error())
 | ||
| 	}
 | ||
| 	if resp.Data == nil || resp.Data["certificate"] == nil {
 | ||
| 		t.Fatal("did not get expected data")
 | ||
| 	}
 | ||
| 	certString := resp.Data["certificate"].(string)
 | ||
| 	block, _ := pem.Decode([]byte(certString))
 | ||
| 	if block == nil {
 | ||
| 		t.Fatal("nil pem block")
 | ||
| 	}
 | ||
| 	certs, err := x509.ParseCertificates(block.Bytes)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if len(certs) != 1 {
 | ||
| 		t.Fatalf("expected a single cert, got %d", len(certs))
 | ||
| 	}
 | ||
| 	cert := certs[0]
 | ||
| 	if math.Abs(float64(time.Now().Add(12*time.Hour).Unix()-cert.NotAfter.Unix())) < 10 {
 | ||
| 		t.Fatalf("sign-verbatim did not properly cap validiaty period on signed CSR")
 | ||
| 	}
 | ||
| 
 | ||
| 	// now check that if we set generate-lease it takes it from the role and the TTLs match
 | ||
| 	roleData = map[string]interface{}{
 | ||
| 		"ttl":            "4h",
 | ||
| 		"max_ttl":        "8h",
 | ||
| 		"generate_lease": true,
 | ||
| 	}
 | ||
| 	resp, err = b.HandleRequest(&logical.Request{
 | ||
| 		Operation: logical.UpdateOperation,
 | ||
| 		Path:      "roles/test",
 | ||
| 		Storage:   storage,
 | ||
| 		Data:      roleData,
 | ||
| 	})
 | ||
| 	if resp != nil && resp.IsError() {
 | ||
| 		t.Fatalf("failed to create a role, %#v", *resp)
 | ||
| 	}
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	resp, err = b.HandleRequest(&logical.Request{
 | ||
| 		Operation: logical.UpdateOperation,
 | ||
| 		Path:      "sign-verbatim/test",
 | ||
| 		Storage:   storage,
 | ||
| 		Data: map[string]interface{}{
 | ||
| 			"csr": string(pemCSR),
 | ||
| 			"ttl": "5h",
 | ||
| 		},
 | ||
| 	})
 | ||
| 	if resp != nil && resp.IsError() {
 | ||
| 		t.Fatalf("failed to sign-verbatim role-leased CSR: %#v", *resp)
 | ||
| 	}
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if resp.Secret == nil {
 | ||
| 		t.Fatalf("secret is nil, response is %#v", *resp)
 | ||
| 	}
 | ||
| 	if math.Abs(float64(resp.Secret.TTL-(5*time.Hour))) > float64(5*time.Hour) {
 | ||
| 		t.Fatalf("ttl not default; wanted %v, got %v", b.System().DefaultLeaseTTL(), resp.Secret.TTL)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestBackend_Root_Idempotentcy(t *testing.T) {
 | ||
| 	coreConfig := &vault.CoreConfig{
 | ||
| 		LogicalBackends: map[string]logical.Factory{
 | ||
| 			"pki": Factory,
 | ||
| 		},
 | ||
| 	}
 | ||
| 	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
 | ||
| 		HandlerFunc: vaulthttp.Handler,
 | ||
| 	})
 | ||
| 	cluster.Start()
 | ||
| 	defer cluster.Cleanup()
 | ||
| 
 | ||
| 	client := cluster.Cores[0].Client
 | ||
| 	var err error
 | ||
| 	err = client.Sys().Mount("pki", &api.MountInput{
 | ||
| 		Type: "pki",
 | ||
| 		Config: api.MountConfigInput{
 | ||
| 			DefaultLeaseTTL: "16h",
 | ||
| 			MaxLeaseTTL:     "32h",
 | ||
| 		},
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	resp, err := client.Logical().Write("pki/root/generate/internal", map[string]interface{}{
 | ||
| 		"common_name": "myvault.com",
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if resp == nil {
 | ||
| 		t.Fatal("expected ca info")
 | ||
| 	}
 | ||
| 	resp, err = client.Logical().Read("pki/cert/ca_chain")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatalf("error reading ca_chain: %v", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	r1Data := resp.Data
 | ||
| 
 | ||
| 	// Try again, make sure it's a 204 and same CA
 | ||
| 	resp, err = client.Logical().Write("pki/root/generate/internal", map[string]interface{}{
 | ||
| 		"common_name": "myvault.com",
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if resp != nil {
 | ||
| 		t.Fatal("expected no ca info")
 | ||
| 	}
 | ||
| 	resp, err = client.Logical().Read("pki/cert/ca_chain")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatalf("error reading ca_chain: %v", err)
 | ||
| 	}
 | ||
| 	r2Data := resp.Data
 | ||
| 	if !reflect.DeepEqual(r1Data, r2Data) {
 | ||
| 		t.Fatal("got different ca certs")
 | ||
| 	}
 | ||
| 
 | ||
| 	resp, err = client.Logical().Delete("pki/root")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if resp != nil {
 | ||
| 		t.Fatal("expected nil response")
 | ||
| 	}
 | ||
| 	// Make sure it behaves the same
 | ||
| 	resp, err = client.Logical().Delete("pki/root")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if resp != nil {
 | ||
| 		t.Fatal("expected nil response")
 | ||
| 	}
 | ||
| 
 | ||
| 	_, err = client.Logical().Read("pki/cert/ca_chain")
 | ||
| 	if err == nil {
 | ||
| 		t.Fatal("expected error")
 | ||
| 	}
 | ||
| 
 | ||
| 	resp, err = client.Logical().Write("pki/root/generate/internal", map[string]interface{}{
 | ||
| 		"common_name": "myvault.com",
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if resp == nil {
 | ||
| 		t.Fatal("expected ca info")
 | ||
| 	}
 | ||
| 
 | ||
| 	_, err = client.Logical().Read("pki/cert/ca_chain")
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestBackend_Permitted_DNS_Domains(t *testing.T) {
 | ||
| 	coreConfig := &vault.CoreConfig{
 | ||
| 		LogicalBackends: map[string]logical.Factory{
 | ||
| 			"pki": Factory,
 | ||
| 		},
 | ||
| 	}
 | ||
| 	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
 | ||
| 		HandlerFunc: vaulthttp.Handler,
 | ||
| 	})
 | ||
| 	cluster.Start()
 | ||
| 	defer cluster.Cleanup()
 | ||
| 
 | ||
| 	client := cluster.Cores[0].Client
 | ||
| 	var err error
 | ||
| 	err = client.Sys().Mount("root", &api.MountInput{
 | ||
| 		Type: "pki",
 | ||
| 		Config: api.MountConfigInput{
 | ||
| 			DefaultLeaseTTL: "16h",
 | ||
| 			MaxLeaseTTL:     "32h",
 | ||
| 		},
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	err = client.Sys().Mount("int", &api.MountInput{
 | ||
| 		Type: "pki",
 | ||
| 		Config: api.MountConfigInput{
 | ||
| 			DefaultLeaseTTL: "4h",
 | ||
| 			MaxLeaseTTL:     "20h",
 | ||
| 		},
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	_, err = client.Logical().Write("root/roles/example", map[string]interface{}{
 | ||
| 		"allowed_domains":    "foobar.com,zipzap.com,abc.com,xyz.com",
 | ||
| 		"allow_bare_domains": true,
 | ||
| 		"allow_subdomains":   true,
 | ||
| 		"max_ttl":            "2h",
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	_, err = client.Logical().Write("int/roles/example", map[string]interface{}{
 | ||
| 		"allowed_domains":    "foobar.com,zipzap.com,abc.com,xyz.com",
 | ||
| 		"allow_subdomains":   true,
 | ||
| 		"allow_bare_domains": true,
 | ||
| 		"max_ttl":            "2h",
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	// Direct issuing from root
 | ||
| 	_, err = client.Logical().Write("root/root/generate/internal", map[string]interface{}{
 | ||
| 		"ttl":                   "40h",
 | ||
| 		"common_name":           "myvault.com",
 | ||
| 		"permitted_dns_domains": []string{"foobar.com", ".zipzap.com"},
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	clientKey, err := rsa.GenerateKey(rand.Reader, 2048)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	path := "root/"
 | ||
| 	checkIssue := func(valid bool, args ...interface{}) {
 | ||
| 		argMap := map[string]interface{}{}
 | ||
| 		var currString string
 | ||
| 		for i, arg := range args {
 | ||
| 			if i%2 == 0 {
 | ||
| 				currString = arg.(string)
 | ||
| 			} else {
 | ||
| 				argMap[currString] = arg
 | ||
| 			}
 | ||
| 		}
 | ||
| 		_, err = client.Logical().Write(path+"issue/example", argMap)
 | ||
| 		switch {
 | ||
| 		case valid && err != nil:
 | ||
| 			t.Fatal(err)
 | ||
| 		case !valid && err == nil:
 | ||
| 			t.Fatal("expected error")
 | ||
| 		}
 | ||
| 
 | ||
| 		csr, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{
 | ||
| 			Subject: pkix.Name{
 | ||
| 				CommonName: argMap["common_name"].(string),
 | ||
| 			},
 | ||
| 		}, clientKey)
 | ||
| 		if err != nil {
 | ||
| 			t.Fatal(err)
 | ||
| 		}
 | ||
| 		delete(argMap, "common_name")
 | ||
| 		argMap["csr"] = string(pem.EncodeToMemory(&pem.Block{
 | ||
| 			Type:  "CERTIFICATE REQUEST",
 | ||
| 			Bytes: csr,
 | ||
| 		}))
 | ||
| 
 | ||
| 		_, err = client.Logical().Write(path+"sign/example", argMap)
 | ||
| 		switch {
 | ||
| 		case valid && err != nil:
 | ||
| 			t.Fatal(err)
 | ||
| 		case !valid && err == nil:
 | ||
| 			t.Fatal("expected error")
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	// Check issuing and signing against root's permitted domains
 | ||
| 	checkIssue(false, "common_name", "zipzap.com")
 | ||
| 	checkIssue(false, "common_name", "host.foobar.com")
 | ||
| 	checkIssue(true, "common_name", "host.zipzap.com")
 | ||
| 	checkIssue(true, "common_name", "foobar.com")
 | ||
| 
 | ||
| 	// Verify that root also won't issue an intermediate outside of its permitted domains
 | ||
| 	resp, err := client.Logical().Write("int/intermediate/generate/internal", map[string]interface{}{
 | ||
| 		"common_name": "issuer.abc.com",
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	_, err = client.Logical().Write("root/root/sign-intermediate", map[string]interface{}{
 | ||
| 		"common_name": "issuer.abc.com",
 | ||
| 		"csr":         resp.Data["csr"],
 | ||
| 		"permitted_dns_domains": []string{"abc.com", ".xyz.com"},
 | ||
| 		"ttl": "5h",
 | ||
| 	})
 | ||
| 	if err == nil {
 | ||
| 		t.Fatal("expected error")
 | ||
| 	}
 | ||
| 	_, err = client.Logical().Write("root/root/sign-intermediate", map[string]interface{}{
 | ||
| 		"use_csr_values": true,
 | ||
| 		"csr":            resp.Data["csr"],
 | ||
| 		"permitted_dns_domains": []string{"abc.com", ".xyz.com"},
 | ||
| 		"ttl": "5h",
 | ||
| 	})
 | ||
| 	if err == nil {
 | ||
| 		t.Fatal("expected error")
 | ||
| 	}
 | ||
| 
 | ||
| 	// Sign a valid intermediate
 | ||
| 	resp, err = client.Logical().Write("root/root/sign-intermediate", map[string]interface{}{
 | ||
| 		"common_name": "issuer.zipzap.com",
 | ||
| 		"csr":         resp.Data["csr"],
 | ||
| 		"permitted_dns_domains": []string{"abc.com", ".xyz.com"},
 | ||
| 		"ttl": "5h",
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	resp, err = client.Logical().Write("int/intermediate/set-signed", map[string]interface{}{
 | ||
| 		"certificate": resp.Data["certificate"],
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	// Check enforcement with the intermediate's set values
 | ||
| 	path = "int/"
 | ||
| 	checkIssue(false, "common_name", "host.abc.com")
 | ||
| 	checkIssue(false, "common_name", "xyz.com")
 | ||
| 	checkIssue(true, "common_name", "abc.com")
 | ||
| 	checkIssue(true, "common_name", "host.xyz.com")
 | ||
| }
 | ||
| 
 | ||
| func TestBackend_SignIntermediate_AllowedPastCA(t *testing.T) {
 | ||
| 	coreConfig := &vault.CoreConfig{
 | ||
| 		LogicalBackends: map[string]logical.Factory{
 | ||
| 			"pki": Factory,
 | ||
| 		},
 | ||
| 	}
 | ||
| 	cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
 | ||
| 		HandlerFunc: vaulthttp.Handler,
 | ||
| 	})
 | ||
| 	cluster.Start()
 | ||
| 	defer cluster.Cleanup()
 | ||
| 
 | ||
| 	client := cluster.Cores[0].Client
 | ||
| 	var err error
 | ||
| 	err = client.Sys().Mount("root", &api.MountInput{
 | ||
| 		Type: "pki",
 | ||
| 		Config: api.MountConfigInput{
 | ||
| 			DefaultLeaseTTL: "16h",
 | ||
| 			MaxLeaseTTL:     "60h",
 | ||
| 		},
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	err = client.Sys().Mount("int", &api.MountInput{
 | ||
| 		Type: "pki",
 | ||
| 		Config: api.MountConfigInput{
 | ||
| 			DefaultLeaseTTL: "4h",
 | ||
| 			MaxLeaseTTL:     "20h",
 | ||
| 		},
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	// Direct issuing from root
 | ||
| 	_, err = client.Logical().Write("root/root/generate/internal", map[string]interface{}{
 | ||
| 		"ttl":         "40h",
 | ||
| 		"common_name": "myvault.com",
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	_, err = client.Logical().Write("root/roles/test", map[string]interface{}{
 | ||
| 		"allow_bare_domains": true,
 | ||
| 		"allow_subdomains":   true,
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	resp, err := client.Logical().Write("int/intermediate/generate/internal", map[string]interface{}{
 | ||
| 		"common_name": "myint.com",
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	csr := resp.Data["csr"]
 | ||
| 
 | ||
| 	_, err = client.Logical().Write("root/sign/test", map[string]interface{}{
 | ||
| 		"common_name": "myint.com",
 | ||
| 		"csr":         csr,
 | ||
| 		"ttl":         "60h",
 | ||
| 	})
 | ||
| 	if err == nil {
 | ||
| 		t.Fatal("expected error")
 | ||
| 	}
 | ||
| 
 | ||
| 	_, err = client.Logical().Write("root/sign-verbatim/test", map[string]interface{}{
 | ||
| 		"common_name": "myint.com",
 | ||
| 		"csr":         csr,
 | ||
| 		"ttl":         "60h",
 | ||
| 	})
 | ||
| 	if err == nil {
 | ||
| 		t.Fatal("expected error")
 | ||
| 	}
 | ||
| 
 | ||
| 	resp, err = client.Logical().Write("root/root/sign-intermediate", map[string]interface{}{
 | ||
| 		"common_name": "myint.com",
 | ||
| 		"csr":         csr,
 | ||
| 		"ttl":         "60h",
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatalf("got error: %v", err)
 | ||
| 	}
 | ||
| 	if resp == nil {
 | ||
| 		t.Fatal("got nil response")
 | ||
| 	}
 | ||
| 	if len(resp.Warnings) == 0 {
 | ||
| 		t.Fatalf("expected warnings, got %#v", *resp)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func TestBackend_SignSelfIssued(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":         "172800",
 | ||
| 	}
 | ||
| 
 | ||
| 	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)
 | ||
| 	}
 | ||
| 
 | ||
| 	key, err := rsa.GenerateKey(rand.Reader, 2048)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	getSelfSigned := func(subject, issuer *x509.Certificate) (string, *x509.Certificate) {
 | ||
| 		selfSigned, err := x509.CreateCertificate(rand.Reader, subject, issuer, key.Public(), key)
 | ||
| 		if err != nil {
 | ||
| 			t.Fatal(err)
 | ||
| 		}
 | ||
| 		cert, err := x509.ParseCertificate(selfSigned)
 | ||
| 		if err != nil {
 | ||
| 			t.Fatal(err)
 | ||
| 		}
 | ||
| 		pemSS := pem.EncodeToMemory(&pem.Block{
 | ||
| 			Type:  "CERTIFICATE",
 | ||
| 			Bytes: selfSigned,
 | ||
| 		})
 | ||
| 		return string(pemSS), cert
 | ||
| 	}
 | ||
| 
 | ||
| 	template := &x509.Certificate{
 | ||
| 		Subject: pkix.Name{
 | ||
| 			CommonName: "foo.bar.com",
 | ||
| 		},
 | ||
| 		SerialNumber: big.NewInt(1234),
 | ||
| 		IsCA:         false,
 | ||
| 		BasicConstraintsValid: true,
 | ||
| 	}
 | ||
| 
 | ||
| 	ss, _ := getSelfSigned(template, template)
 | ||
| 	resp, err = b.HandleRequest(&logical.Request{
 | ||
| 		Operation: logical.UpdateOperation,
 | ||
| 		Path:      "root/sign-self-issued",
 | ||
| 		Storage:   storage,
 | ||
| 		Data: map[string]interface{}{
 | ||
| 			"certificate": ss,
 | ||
| 		},
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if resp == nil {
 | ||
| 		t.Fatal("got nil response")
 | ||
| 	}
 | ||
| 	if !resp.IsError() {
 | ||
| 		t.Fatalf("expected error due to non-CA; got: %#v", *resp)
 | ||
| 	}
 | ||
| 
 | ||
| 	// Set CA to true, but leave issuer alone
 | ||
| 	template.IsCA = true
 | ||
| 
 | ||
| 	issuer := &x509.Certificate{
 | ||
| 		Subject: pkix.Name{
 | ||
| 			CommonName: "bar.foo.com",
 | ||
| 		},
 | ||
| 		SerialNumber: big.NewInt(2345),
 | ||
| 		IsCA:         true,
 | ||
| 		BasicConstraintsValid: true,
 | ||
| 	}
 | ||
| 	ss, ssCert := getSelfSigned(template, issuer)
 | ||
| 	resp, err = b.HandleRequest(&logical.Request{
 | ||
| 		Operation: logical.UpdateOperation,
 | ||
| 		Path:      "root/sign-self-issued",
 | ||
| 		Storage:   storage,
 | ||
| 		Data: map[string]interface{}{
 | ||
| 			"certificate": ss,
 | ||
| 		},
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if resp == nil {
 | ||
| 		t.Fatal("got nil response")
 | ||
| 	}
 | ||
| 	if !resp.IsError() {
 | ||
| 		t.Fatalf("expected error due to different issuer; cert info is\nIssuer\n%#v\nSubject\n%#v\n", ssCert.Issuer, ssCert.Subject)
 | ||
| 	}
 | ||
| 
 | ||
| 	ss, ssCert = getSelfSigned(template, template)
 | ||
| 	resp, err = b.HandleRequest(&logical.Request{
 | ||
| 		Operation: logical.UpdateOperation,
 | ||
| 		Path:      "root/sign-self-issued",
 | ||
| 		Storage:   storage,
 | ||
| 		Data: map[string]interface{}{
 | ||
| 			"certificate": ss,
 | ||
| 		},
 | ||
| 	})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if resp == nil {
 | ||
| 		t.Fatal("got nil response")
 | ||
| 	}
 | ||
| 	if resp.IsError() {
 | ||
| 		t.Fatalf("error in response: %s", resp.Error().Error())
 | ||
| 	}
 | ||
| 
 | ||
| 	newCertString := resp.Data["certificate"].(string)
 | ||
| 	block, _ := pem.Decode([]byte(newCertString))
 | ||
| 	newCert, err := x509.ParseCertificate(block.Bytes)
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	signingBundle, err := fetchCAInfo(&logical.Request{Storage: storage})
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 	if reflect.DeepEqual(newCert.Subject, newCert.Issuer) {
 | ||
| 		t.Fatal("expected different subject/issuer")
 | ||
| 	}
 | ||
| 	if !reflect.DeepEqual(newCert.Issuer, signingBundle.Certificate.Subject) {
 | ||
| 		t.Fatalf("expected matching issuer/CA subject\n\nIssuer:\n%#v\nSubject:\n%#v\n", newCert.Issuer, signingBundle.Certificate.Subject)
 | ||
| 	}
 | ||
| 	if bytes.Equal(newCert.AuthorityKeyId, newCert.SubjectKeyId) {
 | ||
| 		t.Fatal("expected different authority/subject")
 | ||
| 	}
 | ||
| 	if !bytes.Equal(newCert.AuthorityKeyId, signingBundle.Certificate.SubjectKeyId) {
 | ||
| 		t.Fatal("expected authority on new cert to be same as signing subject")
 | ||
| 	}
 | ||
| 	if newCert.Subject.CommonName != "foo.bar.com" {
 | ||
| 		t.Fatalf("unexpected common name on new cert: %s", newCert.Subject.CommonName)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| 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-----
 | ||
| `
 | ||
| )
 | 
