github.com/outbrain/consul@v1.4.5/agent/connect/testing_ca.go (about)

     1  package connect
     2  
     3  import (
     4  	"bytes"
     5  	"crypto"
     6  	"crypto/ecdsa"
     7  	"crypto/elliptic"
     8  	"crypto/rand"
     9  	"crypto/x509"
    10  	"crypto/x509/pkix"
    11  	"encoding/pem"
    12  	"fmt"
    13  	"math/big"
    14  	"net/url"
    15  	"sync/atomic"
    16  	"time"
    17  
    18  	"github.com/hashicorp/consul/agent/structs"
    19  	"github.com/hashicorp/go-uuid"
    20  	"github.com/mitchellh/go-testing-interface"
    21  )
    22  
    23  // TestClusterID is the Consul cluster ID for testing.
    24  const TestClusterID = "11111111-2222-3333-4444-555555555555"
    25  
    26  // testCACounter is just an atomically incremented counter for creating
    27  // unique names for the CA certs.
    28  var testCACounter uint64
    29  
    30  // TestCA creates a test CA certificate and signing key and returns it
    31  // in the CARoot structure format. The returned CA will be set as Active = true.
    32  //
    33  // If xc is non-nil, then the returned certificate will have a signing cert
    34  // that is cross-signed with the previous cert, and this will be set as
    35  // SigningCert.
    36  func TestCA(t testing.T, xc *structs.CARoot) *structs.CARoot {
    37  	var result structs.CARoot
    38  	result.Active = true
    39  	result.Name = fmt.Sprintf("Test CA %d", atomic.AddUint64(&testCACounter, 1))
    40  
    41  	// Create the private key we'll use for this CA cert.
    42  	signer, keyPEM := testPrivateKey(t)
    43  	result.SigningKey = keyPEM
    44  	result.SigningKeyID = HexString(testKeyID(t, signer.Public()))
    45  
    46  	// The serial number for the cert
    47  	sn, err := testSerialNumber()
    48  	if err != nil {
    49  		t.Fatalf("error generating serial number: %s", err)
    50  	}
    51  
    52  	// The URI (SPIFFE compatible) for the cert
    53  	id := &SpiffeIDSigning{ClusterID: TestClusterID, Domain: "consul"}
    54  
    55  	// Create the CA cert
    56  	template := x509.Certificate{
    57  		SerialNumber:          sn,
    58  		Subject:               pkix.Name{CommonName: result.Name},
    59  		URIs:                  []*url.URL{id.URI()},
    60  		BasicConstraintsValid: true,
    61  		KeyUsage: x509.KeyUsageCertSign |
    62  			x509.KeyUsageCRLSign |
    63  			x509.KeyUsageDigitalSignature,
    64  		IsCA:           true,
    65  		NotAfter:       time.Now().AddDate(10, 0, 0),
    66  		NotBefore:      time.Now(),
    67  		AuthorityKeyId: testKeyID(t, signer.Public()),
    68  		SubjectKeyId:   testKeyID(t, signer.Public()),
    69  	}
    70  
    71  	bs, err := x509.CreateCertificate(
    72  		rand.Reader, &template, &template, signer.Public(), signer)
    73  	if err != nil {
    74  		t.Fatalf("error generating CA certificate: %s", err)
    75  	}
    76  
    77  	var buf bytes.Buffer
    78  	err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
    79  	if err != nil {
    80  		t.Fatalf("error encoding private key: %s", err)
    81  	}
    82  	result.RootCert = buf.String()
    83  	result.ID, err = CalculateCertFingerprint(result.RootCert)
    84  	if err != nil {
    85  		t.Fatalf("error generating CA ID fingerprint: %s", err)
    86  	}
    87  	result.SerialNumber = uint64(sn.Int64())
    88  	result.NotBefore = template.NotBefore.UTC()
    89  	result.NotAfter = template.NotAfter.UTC()
    90  
    91  	// If there is a prior CA to cross-sign with, then we need to create that
    92  	// and set it as the signing cert.
    93  	if xc != nil {
    94  		xccert, err := ParseCert(xc.RootCert)
    95  		if err != nil {
    96  			t.Fatalf("error parsing CA cert: %s", err)
    97  		}
    98  		xcsigner, err := ParseSigner(xc.SigningKey)
    99  		if err != nil {
   100  			t.Fatalf("error parsing signing key: %s", err)
   101  		}
   102  
   103  		// Set the authority key to be the previous one.
   104  		// NOTE(mitchellh): From Paul Banks:  if we have to cross-sign a cert
   105  		// that came from outside (e.g. vault) we can't rely on them using the
   106  		// same KeyID hashing algo we do so we'd need to actually copy this
   107  		// from the xc cert's subjectKeyIdentifier extension.
   108  		template.AuthorityKeyId = testKeyID(t, xcsigner.Public())
   109  
   110  		// Create the new certificate where the parent is the previous
   111  		// CA, the public key is the new public key, and the signing private
   112  		// key is the old private key.
   113  		bs, err := x509.CreateCertificate(
   114  			rand.Reader, &template, xccert, signer.Public(), xcsigner)
   115  		if err != nil {
   116  			t.Fatalf("error generating CA certificate: %s", err)
   117  		}
   118  
   119  		var buf bytes.Buffer
   120  		err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
   121  		if err != nil {
   122  			t.Fatalf("error encoding private key: %s", err)
   123  		}
   124  		result.SigningCert = buf.String()
   125  	}
   126  
   127  	return &result
   128  }
   129  
   130  // TestLeaf returns a valid leaf certificate and it's private key for the named
   131  // service with the given CA Root.
   132  func TestLeaf(t testing.T, service string, root *structs.CARoot) (string, string) {
   133  	// Parse the CA cert and signing key from the root
   134  	cert := root.SigningCert
   135  	if cert == "" {
   136  		cert = root.RootCert
   137  	}
   138  	caCert, err := ParseCert(cert)
   139  	if err != nil {
   140  		t.Fatalf("error parsing CA cert: %s", err)
   141  	}
   142  	caSigner, err := ParseSigner(root.SigningKey)
   143  	if err != nil {
   144  		t.Fatalf("error parsing signing key: %s", err)
   145  	}
   146  
   147  	// Build the SPIFFE ID
   148  	spiffeId := &SpiffeIDService{
   149  		Host:       fmt.Sprintf("%s.consul", TestClusterID),
   150  		Namespace:  "default",
   151  		Datacenter: "dc1",
   152  		Service:    service,
   153  	}
   154  
   155  	// The serial number for the cert
   156  	sn, err := testSerialNumber()
   157  	if err != nil {
   158  		t.Fatalf("error generating serial number: %s", err)
   159  	}
   160  
   161  	// Generate fresh private key
   162  	pkSigner, pkPEM, err := GeneratePrivateKey()
   163  	if err != nil {
   164  		t.Fatalf("failed to generate private key: %s", err)
   165  	}
   166  
   167  	// Cert template for generation
   168  	template := x509.Certificate{
   169  		SerialNumber:          sn,
   170  		Subject:               pkix.Name{CommonName: service},
   171  		URIs:                  []*url.URL{spiffeId.URI()},
   172  		SignatureAlgorithm:    x509.ECDSAWithSHA256,
   173  		BasicConstraintsValid: true,
   174  		KeyUsage: x509.KeyUsageDataEncipherment |
   175  			x509.KeyUsageKeyAgreement |
   176  			x509.KeyUsageDigitalSignature |
   177  			x509.KeyUsageKeyEncipherment,
   178  		ExtKeyUsage: []x509.ExtKeyUsage{
   179  			x509.ExtKeyUsageClientAuth,
   180  			x509.ExtKeyUsageServerAuth,
   181  		},
   182  		NotAfter:       time.Now().AddDate(10, 0, 0),
   183  		NotBefore:      time.Now(),
   184  		AuthorityKeyId: testKeyID(t, caSigner.Public()),
   185  		SubjectKeyId:   testKeyID(t, pkSigner.Public()),
   186  	}
   187  
   188  	// Create the certificate, PEM encode it and return that value.
   189  	var buf bytes.Buffer
   190  	bs, err := x509.CreateCertificate(
   191  		rand.Reader, &template, caCert, pkSigner.Public(), caSigner)
   192  	if err != nil {
   193  		t.Fatalf("error generating certificate: %s", err)
   194  	}
   195  	err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
   196  	if err != nil {
   197  		t.Fatalf("error encoding private key: %s", err)
   198  	}
   199  
   200  	return buf.String(), pkPEM
   201  }
   202  
   203  // TestCSR returns a CSR to sign the given service along with the PEM-encoded
   204  // private key for this certificate.
   205  func TestCSR(t testing.T, uri CertURI) (string, string) {
   206  	template := &x509.CertificateRequest{
   207  		URIs:               []*url.URL{uri.URI()},
   208  		SignatureAlgorithm: x509.ECDSAWithSHA256,
   209  	}
   210  
   211  	// Create the private key we'll use
   212  	signer, pkPEM := testPrivateKey(t)
   213  
   214  	// Create the CSR itself
   215  	var csrBuf bytes.Buffer
   216  	bs, err := x509.CreateCertificateRequest(rand.Reader, template, signer)
   217  	if err != nil {
   218  		t.Fatalf("error creating CSR: %s", err)
   219  	}
   220  
   221  	err = pem.Encode(&csrBuf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: bs})
   222  	if err != nil {
   223  		t.Fatalf("error encoding CSR: %s", err)
   224  	}
   225  
   226  	return csrBuf.String(), pkPEM
   227  }
   228  
   229  // testKeyID returns a KeyID from the given public key. This just calls
   230  // KeyId but handles errors for tests.
   231  func testKeyID(t testing.T, raw interface{}) []byte {
   232  	result, err := KeyId(raw)
   233  	if err != nil {
   234  		t.Fatalf("KeyId error: %s", err)
   235  	}
   236  
   237  	return result
   238  }
   239  
   240  // testPrivateKey creates an ECDSA based private key. Both a crypto.Signer and
   241  // the key in PEM form are returned.
   242  //
   243  // NOTE(banks): this was memoized to save entropy during tests but it turns out
   244  // crypto/rand will never block and always reads from /dev/urandom on unix OSes
   245  // which does not consume entropy.
   246  //
   247  // If we find by profiling it's taking a lot of cycles we could optimize/cache
   248  // again but we at least need to use different keys for each distinct CA (when
   249  // multiple CAs are generated at once e.g. to test cross-signing) and a
   250  // different one again for the leafs otherwise we risk tests that have false
   251  // positives since signatures from different logical cert's keys are
   252  // indistinguishable, but worse we build validation chains using AuthorityKeyID
   253  // which will be the same for multiple CAs/Leafs. Also note that our UUID
   254  // generator also reads from crypto rand and is called far more often during
   255  // tests than this will be.
   256  func testPrivateKey(t testing.T) (crypto.Signer, string) {
   257  	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   258  	if err != nil {
   259  		t.Fatalf("error generating private key: %s", err)
   260  	}
   261  
   262  	bs, err := x509.MarshalECPrivateKey(pk)
   263  	if err != nil {
   264  		t.Fatalf("error generating private key: %s", err)
   265  	}
   266  
   267  	var buf bytes.Buffer
   268  	err = pem.Encode(&buf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: bs})
   269  	if err != nil {
   270  		t.Fatalf("error encoding private key: %s", err)
   271  	}
   272  
   273  	return pk, buf.String()
   274  }
   275  
   276  // testSerialNumber generates a serial number suitable for a certificate. For
   277  // testing, this just sets it to a random number, but one that can fit in a
   278  // uint64 since we use that in our datastructures and assume cert serials will
   279  // fit in that for now.
   280  func testSerialNumber() (*big.Int, error) {
   281  	return rand.Int(rand.Reader, (&big.Int{}).Exp(big.NewInt(2), big.NewInt(63), nil))
   282  }
   283  
   284  // testUUID generates a UUID for testing.
   285  func testUUID(t testing.T) string {
   286  	ret, err := uuid.GenerateUUID()
   287  	if err != nil {
   288  		t.Fatalf("Unable to generate a UUID, %s", err)
   289  	}
   290  
   291  	return ret
   292  }
   293  
   294  // TestAgentRPC is an interface that an RPC client must implement. This is a
   295  // helper interface that is implemented by the agent delegate so that test
   296  // helpers can make RPCs without introducing an import cycle on `agent`.
   297  type TestAgentRPC interface {
   298  	RPC(method string, args interface{}, reply interface{}) error
   299  }
   300  
   301  // TestCAConfigSet sets a CARoot returned by TestCA into the TestAgent state. It
   302  // requires that TestAgent had connect enabled in it's config. If ca is nil, a
   303  // new CA is created.
   304  //
   305  // It returns the CARoot passed or created.
   306  //
   307  // Note that we have to use an interface for the TestAgent.RPC method since we
   308  // can't introduce an import cycle by importing `agent.TestAgent` here directly.
   309  // It also means this will work in a few other places we mock that method.
   310  func TestCAConfigSet(t testing.T, a TestAgentRPC,
   311  	ca *structs.CARoot) *structs.CARoot {
   312  	t.Helper()
   313  
   314  	if ca == nil {
   315  		ca = TestCA(t, nil)
   316  	}
   317  	newConfig := &structs.CAConfiguration{
   318  		Provider: "consul",
   319  		Config: map[string]interface{}{
   320  			"PrivateKey":     ca.SigningKey,
   321  			"RootCert":       ca.RootCert,
   322  			"RotationPeriod": 180 * 24 * time.Hour,
   323  		},
   324  	}
   325  	args := &structs.CARequest{
   326  		Datacenter: "dc1",
   327  		Config:     newConfig,
   328  	}
   329  	var reply interface{}
   330  
   331  	err := a.RPC("ConnectCA.ConfigurationSet", args, &reply)
   332  	if err != nil {
   333  		t.Fatalf("failed to set test CA config: %s", err)
   334  	}
   335  	return ca
   336  }