mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	 86ba0dbdeb
			
		
	
	86ba0dbdeb
	
	
	
		
			
			* Use DRBG based RSA key generation everywhere * switch to the conditional generator * Use DRBG based RSA key generation everywhere * switch to the conditional generator * Add an ENV var to disable the DRBG in a pinch * update go.mod * Use DRBG based RSA key generation everywhere * switch to the conditional generator * Add an ENV var to disable the DRBG in a pinch * Use DRBG based RSA key generation everywhere * update go.mod * fix import * Remove rsa2 alias, remove test code * move cryptoutil/rsa.go to sdk * move imports too * remove makefile change * rsa2->rsa * more rsa2->rsa, remove test code * fix some overzelous search/replace * Update to a real tag * changelog * copyright * work around copyright check * work around copyright check pt2 * bunch of dupe imports * missing import * wrong license * fix go.mod conflict * missed a spot * dupe import
		
			
				
	
	
		
			713 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			713 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) HashiCorp, Inc.
 | |
| // SPDX-License-Identifier: BUSL-1.1
 | |
| 
 | |
| package pki
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"crypto/ecdsa"
 | |
| 	"crypto/ed25519"
 | |
| 	"crypto/elliptic"
 | |
| 	"crypto/rand"
 | |
| 	"crypto/x509"
 | |
| 	"crypto/x509/pkix"
 | |
| 	"encoding/json"
 | |
| 	"encoding/pem"
 | |
| 	"math/big"
 | |
| 	mathrand "math/rand"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/go-test/deep"
 | |
| 	"github.com/hashicorp/vault/api"
 | |
| 	vaulthttp "github.com/hashicorp/vault/http"
 | |
| 	"github.com/hashicorp/vault/sdk/helper/certutil"
 | |
| 	"github.com/hashicorp/vault/sdk/helper/cryptoutil"
 | |
| 	"github.com/hashicorp/vault/sdk/logical"
 | |
| 	"github.com/hashicorp/vault/vault"
 | |
| )
 | |
| 
 | |
| func TestBackend_CA_Steps(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	var b *backend
 | |
| 
 | |
| 	factory := func(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
 | |
| 		be, err := Factory(ctx, conf)
 | |
| 		if err == nil {
 | |
| 			b = be.(*backend)
 | |
| 		}
 | |
| 		return be, err
 | |
| 	}
 | |
| 
 | |
| 	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
 | |
| 
 | |
| 	// Set RSA/EC CA certificates
 | |
| 	var rsaCAKey, rsaCACert, ecCAKey, ecCACert, edCAKey, edCACert string
 | |
| 	{
 | |
| 		cak, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		marshaledKey, err := x509.MarshalECPrivateKey(cak)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		keyPEMBlock := &pem.Block{
 | |
| 			Type:  "EC PRIVATE KEY",
 | |
| 			Bytes: marshaledKey,
 | |
| 		}
 | |
| 		ecCAKey = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		subjKeyID, err := certutil.GetSubjKeyID(cak)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		caCertTemplate := &x509.Certificate{
 | |
| 			Subject: pkix.Name{
 | |
| 				CommonName: "root.localhost",
 | |
| 			},
 | |
| 			SubjectKeyId:          subjKeyID,
 | |
| 			DNSNames:              []string{"root.localhost"},
 | |
| 			KeyUsage:              x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign),
 | |
| 			SerialNumber:          big.NewInt(mathrand.Int63()),
 | |
| 			NotAfter:              time.Now().Add(262980 * time.Hour),
 | |
| 			BasicConstraintsValid: true,
 | |
| 			IsCA:                  true,
 | |
| 		}
 | |
| 		caBytes, err := x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, cak.Public(), cak)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		caCertPEMBlock := &pem.Block{
 | |
| 			Type:  "CERTIFICATE",
 | |
| 			Bytes: caBytes,
 | |
| 		}
 | |
| 		ecCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock)))
 | |
| 
 | |
| 		rak, err := cryptoutil.GenerateRSAKey(rand.Reader, 2048)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		marshaledKey = x509.MarshalPKCS1PrivateKey(rak)
 | |
| 		keyPEMBlock = &pem.Block{
 | |
| 			Type:  "RSA PRIVATE KEY",
 | |
| 			Bytes: marshaledKey,
 | |
| 		}
 | |
| 		rsaCAKey = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		_, err = certutil.GetSubjKeyID(rak)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		caBytes, err = x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, rak.Public(), rak)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		caCertPEMBlock = &pem.Block{
 | |
| 			Type:  "CERTIFICATE",
 | |
| 			Bytes: caBytes,
 | |
| 		}
 | |
| 		rsaCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock)))
 | |
| 
 | |
| 		_, edk, err := ed25519.GenerateKey(rand.Reader)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		marshaledKey, err = x509.MarshalPKCS8PrivateKey(edk)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		keyPEMBlock = &pem.Block{
 | |
| 			Type:  "PRIVATE KEY",
 | |
| 			Bytes: marshaledKey,
 | |
| 		}
 | |
| 		edCAKey = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		_, err = certutil.GetSubjKeyID(edk)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		caBytes, err = x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, edk.Public(), edk)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		caCertPEMBlock = &pem.Block{
 | |
| 			Type:  "CERTIFICATE",
 | |
| 			Bytes: caBytes,
 | |
| 		}
 | |
| 		edCACert = strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock)))
 | |
| 	}
 | |
| 
 | |
| 	// Setup backends
 | |
| 	var rsaRoot, rsaInt, ecRoot, ecInt, edRoot, edInt *backend
 | |
| 	{
 | |
| 		if err := client.Sys().Mount("rsaroot", &api.MountInput{
 | |
| 			Type: "pki",
 | |
| 			Config: api.MountConfigInput{
 | |
| 				DefaultLeaseTTL: "16h",
 | |
| 				MaxLeaseTTL:     "60h",
 | |
| 			},
 | |
| 		}); err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		rsaRoot = b
 | |
| 
 | |
| 		if err := client.Sys().Mount("rsaint", &api.MountInput{
 | |
| 			Type: "pki",
 | |
| 			Config: api.MountConfigInput{
 | |
| 				DefaultLeaseTTL: "16h",
 | |
| 				MaxLeaseTTL:     "60h",
 | |
| 			},
 | |
| 		}); err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		rsaInt = b
 | |
| 
 | |
| 		if err := client.Sys().Mount("ecroot", &api.MountInput{
 | |
| 			Type: "pki",
 | |
| 			Config: api.MountConfigInput{
 | |
| 				DefaultLeaseTTL: "16h",
 | |
| 				MaxLeaseTTL:     "60h",
 | |
| 			},
 | |
| 		}); err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		ecRoot = b
 | |
| 
 | |
| 		if err := client.Sys().Mount("ecint", &api.MountInput{
 | |
| 			Type: "pki",
 | |
| 			Config: api.MountConfigInput{
 | |
| 				DefaultLeaseTTL: "16h",
 | |
| 				MaxLeaseTTL:     "60h",
 | |
| 			},
 | |
| 		}); err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		ecInt = b
 | |
| 
 | |
| 		if err := client.Sys().Mount("ed25519root", &api.MountInput{
 | |
| 			Type: "pki",
 | |
| 			Config: api.MountConfigInput{
 | |
| 				DefaultLeaseTTL: "16h",
 | |
| 				MaxLeaseTTL:     "60h",
 | |
| 			},
 | |
| 		}); err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		edRoot = b
 | |
| 
 | |
| 		if err := client.Sys().Mount("ed25519int", &api.MountInput{
 | |
| 			Type: "pki",
 | |
| 			Config: api.MountConfigInput{
 | |
| 				DefaultLeaseTTL: "16h",
 | |
| 				MaxLeaseTTL:     "60h",
 | |
| 			},
 | |
| 		}); err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		edInt = b
 | |
| 	}
 | |
| 
 | |
| 	t.Run("teststeps", func(t *testing.T) {
 | |
| 		t.Run("rsa", func(t *testing.T) {
 | |
| 			t.Parallel()
 | |
| 			subClient, err := client.Clone()
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			subClient.SetToken(client.Token())
 | |
| 			runSteps(t, rsaRoot, rsaInt, subClient, "rsaroot/", "rsaint/", rsaCACert, rsaCAKey)
 | |
| 		})
 | |
| 		t.Run("ec", func(t *testing.T) {
 | |
| 			t.Parallel()
 | |
| 			subClient, err := client.Clone()
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			subClient.SetToken(client.Token())
 | |
| 			runSteps(t, ecRoot, ecInt, subClient, "ecroot/", "ecint/", ecCACert, ecCAKey)
 | |
| 		})
 | |
| 		t.Run("ed25519", func(t *testing.T) {
 | |
| 			t.Parallel()
 | |
| 			subClient, err := client.Clone()
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			subClient.SetToken(client.Token())
 | |
| 			runSteps(t, edRoot, edInt, subClient, "ed25519root/", "ed25519int/", edCACert, edCAKey)
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func runSteps(t *testing.T, rootB, intB *backend, client *api.Client, rootName, intName, caCert, caKey string) {
 | |
| 	//  Load CA cert/key in and ensure we can fetch it back in various formats,
 | |
| 	//  unauthenticated
 | |
| 	{
 | |
| 		// Attempt import but only provide one the cert; this should work.
 | |
| 		{
 | |
| 			_, err := client.Logical().Write(rootName+"config/ca", map[string]interface{}{
 | |
| 				"pem_bundle": caCert,
 | |
| 			})
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("unexpected error: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Same but with only the key
 | |
| 		{
 | |
| 			_, err := client.Logical().Write(rootName+"config/ca", map[string]interface{}{
 | |
| 				"pem_bundle": caKey,
 | |
| 			})
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("unexpected error: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Import entire CA bundle; this should work as well
 | |
| 		{
 | |
| 			_, err := client.Logical().Write(rootName+"config/ca", map[string]interface{}{
 | |
| 				"pem_bundle": strings.Join([]string{caKey, caCert}, "\n"),
 | |
| 			})
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("unexpected error: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		prevToken := client.Token()
 | |
| 		client.SetToken("")
 | |
| 
 | |
| 		// cert/ca and issuer/default/json path
 | |
| 		for _, path := range []string{"cert/ca", "issuer/default/json"} {
 | |
| 			resp, err := client.Logical().Read(rootName + path)
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			if resp == nil {
 | |
| 				t.Fatal("nil response")
 | |
| 			}
 | |
| 			expected := caCert
 | |
| 			if path == "issuer/default/json" {
 | |
| 				// Preserves the new line.
 | |
| 				expected += "\n"
 | |
| 				_, present := resp.Data["issuer_id"]
 | |
| 				if !present {
 | |
| 					t.Fatalf("expected issuer/default/json to include issuer_id")
 | |
| 				}
 | |
| 				_, present = resp.Data["issuer_name"]
 | |
| 				if !present {
 | |
| 					t.Fatalf("expected issuer/default/json to include issuer_name")
 | |
| 				}
 | |
| 			}
 | |
| 			if diff := deep.Equal(resp.Data["certificate"].(string), expected); diff != nil {
 | |
| 				t.Fatal(diff)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// ca/pem and issuer/default/pem path (raw string)
 | |
| 		for _, path := range []string{"ca/pem", "issuer/default/pem"} {
 | |
| 			req := &logical.Request{
 | |
| 				Path:      path,
 | |
| 				Operation: logical.ReadOperation,
 | |
| 				Storage:   rootB.storage,
 | |
| 			}
 | |
| 			resp, err := rootB.HandleRequest(context.Background(), req)
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			if resp == nil {
 | |
| 				t.Fatal("nil response")
 | |
| 			}
 | |
| 			expected := []byte(caCert)
 | |
| 			if path == "issuer/default/pem" {
 | |
| 				// Preserves the new line.
 | |
| 				expected = []byte(caCert + "\n")
 | |
| 			}
 | |
| 			if diff := deep.Equal(resp.Data["http_raw_body"].([]byte), expected); diff != nil {
 | |
| 				t.Fatal(diff)
 | |
| 			}
 | |
| 			if resp.Data["http_content_type"].(string) != "application/pem-certificate-chain" {
 | |
| 				t.Fatal("wrong content type")
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// ca and issuer/default/der (raw DER bytes)
 | |
| 		for _, path := range []string{"ca", "issuer/default/der"} {
 | |
| 			req := &logical.Request{
 | |
| 				Path:      path,
 | |
| 				Operation: logical.ReadOperation,
 | |
| 				Storage:   rootB.storage,
 | |
| 			}
 | |
| 			resp, err := rootB.HandleRequest(context.Background(), req)
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			if resp == nil {
 | |
| 				t.Fatal("nil response")
 | |
| 			}
 | |
| 			rawBytes := resp.Data["http_raw_body"].([]byte)
 | |
| 			pemBytes := strings.TrimSpace(string(pem.EncodeToMemory(&pem.Block{
 | |
| 				Type:  "CERTIFICATE",
 | |
| 				Bytes: rawBytes,
 | |
| 			})))
 | |
| 			if diff := deep.Equal(pemBytes, caCert); diff != nil {
 | |
| 				t.Fatal(diff)
 | |
| 			}
 | |
| 			if resp.Data["http_content_type"].(string) != "application/pkix-cert" {
 | |
| 				t.Fatal("wrong content type")
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		client.SetToken(prevToken)
 | |
| 	}
 | |
| 
 | |
| 	// Configure an expiry on the CRL and verify what comes back
 | |
| 	{
 | |
| 		// Set CRL config
 | |
| 		{
 | |
| 			_, err := client.Logical().Write(rootName+"config/crl", map[string]interface{}{
 | |
| 				"expiry": "16h",
 | |
| 			})
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Verify it
 | |
| 		{
 | |
| 			resp, err := client.Logical().Read(rootName + "config/crl")
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			if resp == nil {
 | |
| 				t.Fatal("nil response")
 | |
| 			}
 | |
| 			if resp.Data["expiry"].(string) != "16h" {
 | |
| 				t.Fatal("expected a 16 hour expiry")
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Test generating a root, an intermediate, signing it, setting signed, and
 | |
| 	// revoking it
 | |
| 
 | |
| 	// We'll need this later
 | |
| 	var intSerialNumber string
 | |
| 	{
 | |
| 		// First, delete the existing CA info
 | |
| 		{
 | |
| 			_, err := client.Logical().Delete(rootName + "root")
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		var rootPEM, rootKey, rootPEMBundle string
 | |
| 		// Test exported root generation
 | |
| 		{
 | |
| 			resp, err := client.Logical().Write(rootName+"root/generate/exported", map[string]interface{}{
 | |
| 				"common_name": "Root Cert",
 | |
| 				"ttl":         "180h",
 | |
| 			})
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			if resp == nil {
 | |
| 				t.Fatal("nil response")
 | |
| 			}
 | |
| 			rootPEM = resp.Data["certificate"].(string)
 | |
| 			rootKey = resp.Data["private_key"].(string)
 | |
| 			rootPEMBundle = strings.Join([]string{rootPEM, rootKey}, "\n")
 | |
| 			// This is really here to keep the use checker happy
 | |
| 			if rootPEMBundle == "" {
 | |
| 				t.Fatal("bad root pem bundle")
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		var intPEM, intCSR, intKey string
 | |
| 		// Test exported intermediate CSR generation
 | |
| 		{
 | |
| 			resp, err := client.Logical().Write(intName+"intermediate/generate/exported", map[string]interface{}{
 | |
| 				"common_name": "intermediate.cert.com",
 | |
| 				"ttl":         "180h",
 | |
| 			})
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			if resp == nil {
 | |
| 				t.Fatal("nil response")
 | |
| 			}
 | |
| 			intCSR = resp.Data["csr"].(string)
 | |
| 			intKey = resp.Data["private_key"].(string)
 | |
| 			// This is really here to keep the use checker happy
 | |
| 			if intCSR == "" || intKey == "" {
 | |
| 				t.Fatal("int csr or key empty")
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Test signing
 | |
| 		{
 | |
| 			resp, err := client.Logical().Write(rootName+"root/sign-intermediate", map[string]interface{}{
 | |
| 				"common_name": "intermediate.cert.com",
 | |
| 				"ttl":         "10s",
 | |
| 				"csr":         intCSR,
 | |
| 			})
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			if resp == nil {
 | |
| 				t.Fatal("nil response")
 | |
| 			}
 | |
| 			intPEM = resp.Data["certificate"].(string)
 | |
| 			intSerialNumber = resp.Data["serial_number"].(string)
 | |
| 		}
 | |
| 
 | |
| 		// Test setting signed
 | |
| 		{
 | |
| 			resp, err := client.Logical().Write(intName+"intermediate/set-signed", map[string]interface{}{
 | |
| 				"certificate": intPEM,
 | |
| 			})
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			if resp == nil {
 | |
| 				t.Fatal("nil response")
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Verify we can find it via the root
 | |
| 		{
 | |
| 			resp, err := client.Logical().Read(rootName + "cert/" + intSerialNumber)
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			if resp == nil {
 | |
| 				t.Fatal("nil response")
 | |
| 			}
 | |
| 			if resp.Data["revocation_time"].(json.Number).String() != "0" {
 | |
| 				t.Fatal("expected a zero revocation time")
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Revoke the intermediate
 | |
| 		{
 | |
| 			resp, err := client.Logical().Write(rootName+"revoke", map[string]interface{}{
 | |
| 				"serial_number": intSerialNumber,
 | |
| 			})
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			if resp == nil {
 | |
| 				t.Fatal("nil response")
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	verifyRevocation := func(t *testing.T, serial string, shouldFind bool) {
 | |
| 		t.Helper()
 | |
| 		// Verify it is now revoked
 | |
| 		{
 | |
| 			resp, err := client.Logical().Read(rootName + "cert/" + intSerialNumber)
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			switch shouldFind {
 | |
| 			case true:
 | |
| 				if resp == nil {
 | |
| 					t.Fatal("nil response")
 | |
| 				}
 | |
| 				if resp.Data["revocation_time"].(json.Number).String() == "0" {
 | |
| 					t.Fatal("expected a non-zero revocation time")
 | |
| 				}
 | |
| 			default:
 | |
| 				if resp != nil {
 | |
| 					t.Fatalf("expected nil response, got %#v", *resp)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// Fetch the CRL and make sure it shows up
 | |
| 		for path, derPemOrJSON := range map[string]int{
 | |
| 			"crl":                    0,
 | |
| 			"issuer/default/crl/der": 0,
 | |
| 			"crl/pem":                1,
 | |
| 			"issuer/default/crl/pem": 1,
 | |
| 			"cert/crl":               2,
 | |
| 			"issuer/default/crl":     3,
 | |
| 		} {
 | |
| 			req := &logical.Request{
 | |
| 				Path:      path,
 | |
| 				Operation: logical.ReadOperation,
 | |
| 				Storage:   rootB.storage,
 | |
| 			}
 | |
| 			resp, err := rootB.HandleRequest(context.Background(), req)
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			if resp == nil {
 | |
| 				t.Fatal("nil response")
 | |
| 			}
 | |
| 
 | |
| 			var crlBytes []byte
 | |
| 			if derPemOrJSON == 2 {
 | |
| 				// Old endpoint
 | |
| 				crlBytes = []byte(resp.Data["certificate"].(string))
 | |
| 			} else if derPemOrJSON == 3 {
 | |
| 				// New endpoint
 | |
| 				crlBytes = []byte(resp.Data["crl"].(string))
 | |
| 			} else {
 | |
| 				// DER or PEM
 | |
| 				crlBytes = resp.Data["http_raw_body"].([]byte)
 | |
| 			}
 | |
| 
 | |
| 			if derPemOrJSON >= 1 {
 | |
| 				// Do for both PEM and JSON endpoints
 | |
| 				pemBlock, _ := pem.Decode(crlBytes)
 | |
| 				crlBytes = pemBlock.Bytes
 | |
| 			}
 | |
| 
 | |
| 			certList, err := x509.ParseCRL(crlBytes)
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			switch shouldFind {
 | |
| 			case true:
 | |
| 				revokedList := certList.TBSCertList.RevokedCertificates
 | |
| 				if len(revokedList) != 1 {
 | |
| 					t.Fatalf("bad length of revoked list: %d", len(revokedList))
 | |
| 				}
 | |
| 				revokedString := certutil.GetHexFormatted(revokedList[0].SerialNumber.Bytes(), ":")
 | |
| 				if revokedString != intSerialNumber {
 | |
| 					t.Fatalf("bad revoked serial: %s", revokedString)
 | |
| 				}
 | |
| 			default:
 | |
| 				revokedList := certList.TBSCertList.RevokedCertificates
 | |
| 				if len(revokedList) != 0 {
 | |
| 					t.Fatalf("bad length of revoked list: %d", len(revokedList))
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	verifyTidyStatus := func(expectedCertStoreDeleteCount int, expectedRevokedCertDeletedCount int) {
 | |
| 		tidyStatus, err := client.Logical().Read(rootName + "tidy-status")
 | |
| 		if err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 
 | |
| 		if tidyStatus.Data["state"] != "Finished" {
 | |
| 			t.Fatalf("Expected tidy operation to be finished, but tidy-status reports its state is %v", tidyStatus.Data)
 | |
| 		}
 | |
| 
 | |
| 		var count int64
 | |
| 		if count, err = tidyStatus.Data["cert_store_deleted_count"].(json.Number).Int64(); err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		if int64(expectedCertStoreDeleteCount) != count {
 | |
| 			t.Fatalf("Expected %d for cert_store_deleted_count, but got %d", expectedCertStoreDeleteCount, count)
 | |
| 		}
 | |
| 
 | |
| 		if count, err = tidyStatus.Data["revoked_cert_deleted_count"].(json.Number).Int64(); err != nil {
 | |
| 			t.Fatal(err)
 | |
| 		}
 | |
| 		if int64(expectedRevokedCertDeletedCount) != count {
 | |
| 			t.Fatalf("Expected %d for revoked_cert_deleted_count, but got %d", expectedRevokedCertDeletedCount, count)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Validate current state of revoked certificates
 | |
| 	verifyRevocation(t, intSerialNumber, true)
 | |
| 
 | |
| 	// Give time for the safety buffer to pass before tidying
 | |
| 	time.Sleep(10 * time.Second)
 | |
| 
 | |
| 	// Test tidying
 | |
| 	{
 | |
| 		// Run with a high safety buffer, nothing should happen
 | |
| 		{
 | |
| 			resp, err := client.Logical().Write(rootName+"tidy", map[string]interface{}{
 | |
| 				"safety_buffer":      "3h",
 | |
| 				"tidy_cert_store":    true,
 | |
| 				"tidy_revoked_certs": true,
 | |
| 			})
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			if resp == nil {
 | |
| 				t.Fatal("expected warnings")
 | |
| 			}
 | |
| 
 | |
| 			// Wait a few seconds as it runs in a goroutine
 | |
| 			time.Sleep(5 * time.Second)
 | |
| 
 | |
| 			// Check to make sure we still find the cert and see it on the CRL
 | |
| 			verifyRevocation(t, intSerialNumber, true)
 | |
| 
 | |
| 			verifyTidyStatus(0, 0)
 | |
| 		}
 | |
| 
 | |
| 		// Run with both values set false, nothing should happen
 | |
| 		{
 | |
| 			resp, err := client.Logical().Write(rootName+"tidy", map[string]interface{}{
 | |
| 				"safety_buffer":      "1s",
 | |
| 				"tidy_cert_store":    false,
 | |
| 				"tidy_revoked_certs": false,
 | |
| 			})
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			if resp == nil {
 | |
| 				t.Fatal("expected warnings")
 | |
| 			}
 | |
| 
 | |
| 			// Wait a few seconds as it runs in a goroutine
 | |
| 			time.Sleep(5 * time.Second)
 | |
| 
 | |
| 			// Check to make sure we still find the cert and see it on the CRL
 | |
| 			verifyRevocation(t, intSerialNumber, true)
 | |
| 
 | |
| 			verifyTidyStatus(0, 0)
 | |
| 		}
 | |
| 
 | |
| 		// Run with a short safety buffer and both set to true, both should be cleared
 | |
| 		{
 | |
| 			resp, err := client.Logical().Write(rootName+"tidy", map[string]interface{}{
 | |
| 				"safety_buffer":      "1s",
 | |
| 				"tidy_cert_store":    true,
 | |
| 				"tidy_revoked_certs": true,
 | |
| 			})
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 			if resp == nil {
 | |
| 				t.Fatal("expected warnings")
 | |
| 			}
 | |
| 
 | |
| 			// Wait a few seconds as it runs in a goroutine
 | |
| 			time.Sleep(5 * time.Second)
 | |
| 
 | |
| 			// Check to make sure we still find the cert and see it on the CRL
 | |
| 			verifyRevocation(t, intSerialNumber, false)
 | |
| 
 | |
| 			verifyTidyStatus(1, 1)
 | |
| 		}
 | |
| 	}
 | |
| }
 |