connect: Support RSA keys in addition to ECDSA (#6055)

Support RSA keys in addition to ECDSA
This commit is contained in:
Todd Radel 2019-07-30 17:47:39 -04:00 committed by GitHub
parent c691b75b75
commit 1b14d6595e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 401 additions and 94 deletions

2
.gitignore vendored
View file

@ -6,7 +6,9 @@
*.test
.DS_Store
.fseventsd
.envrc
.vagrant/
.idea/
/pkg
Thumbs.db
bin/

View file

@ -588,6 +588,8 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
"leaf_cert_ttl": "LeafCertTTL",
"csr_max_per_second": "CSRMaxPerSecond",
"csr_max_concurrent": "CSRMaxConcurrent",
"private_key_type": "PrivateKeyType",
"private_key_bits": "PrivateKeyBits",
})
}

View file

@ -138,7 +138,7 @@ func (c *ConsulProvider) GenerateRoot() error {
// Generate a private key if needed
newState := *providerState
if c.config.PrivateKey == "" {
_, pk, err := connect.GeneratePrivateKey()
_, pk, err := connect.GeneratePrivateKeyWithConfig(c.config.PrivateKeyType, c.config.PrivateKeyBits)
if err != nil {
return err
}
@ -184,7 +184,7 @@ func (c *ConsulProvider) GenerateIntermediateCSR() (string, error) {
}
// Create a new private key and CSR.
signer, pk, err := connect.GeneratePrivateKey()
signer, pk, err := connect.GeneratePrivateKeyWithConfig(c.config.PrivateKeyType, c.config.PrivateKeyBits)
if err != nil {
return "", err
}

View file

@ -4,6 +4,7 @@ import (
"fmt"
"time"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/structs"
"github.com/mitchellh/mapstructure"
)
@ -41,6 +42,8 @@ func ParseConsulCAConfig(raw map[string]interface{}) (*structs.ConsulCAProviderC
func defaultCommonConfig() structs.CommonCAProviderConfig {
return structs.CommonCAProviderConfig{
LeafCertTTL: 3 * 24 * time.Hour,
LeafCertTTL: 3 * 24 * time.Hour,
PrivateKeyType: connect.DefaultPrivateKeyType,
PrivateKeyBits: connect.DefaultPrivateKeyBits,
}
}

View file

@ -104,6 +104,8 @@ func (v *VaultProvider) GenerateRoot() error {
_, err = v.client.Logical().Write(v.config.RootPKIPath+"root/generate/internal", map[string]interface{}{
"common_name": fmt.Sprintf("Vault CA Root Authority %s", uuid),
"uri_sans": spiffeID.URI().String(),
"key_type": v.config.PrivateKeyType,
"key_bits": v.config.PrivateKeyBits,
})
if err != nil {
return err
@ -174,8 +176,8 @@ func (v *VaultProvider) generateIntermediateCSR() (string, error) {
// Generate a new intermediate CSR for the root to sign.
data, err := v.client.Logical().Write(v.config.IntermediatePKIPath+"intermediate/generate/internal", map[string]interface{}{
"common_name": "Vault CA Intermediate Authority",
"key_bits": 224,
"key_type": "ec",
"key_type": v.config.PrivateKeyType,
"key_bits": v.config.PrivateKeyBits,
"uri_sans": spiffeID.URI().String(),
})
if err != nil {

View file

@ -6,30 +6,93 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"strings"
)
// GeneratePrivateKey generates a new Private key
func GeneratePrivateKey() (crypto.Signer, string, error) {
var pk *ecdsa.PrivateKey
const (
DefaultPrivateKeyType = "ec"
DefaultPrivateKeyBits = 256
)
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
func pemEncodeKey(key []byte, blockType string) (string, error) {
var buf bytes.Buffer
if err := pem.Encode(&buf, &pem.Block{Type: blockType, Bytes: key}); err != nil {
return "", fmt.Errorf("error encoding private key: %s", err)
}
return buf.String(), nil
}
func generateRSAKey(keyBits int) (crypto.Signer, string, error) {
var pk *rsa.PrivateKey
pk, err := rsa.GenerateKey(rand.Reader, keyBits)
if err != nil {
return nil, "", fmt.Errorf("error generating private key: %s", err)
return nil, "", fmt.Errorf("error generating RSA private key: %s", err)
}
bs := x509.MarshalPKCS1PrivateKey(pk)
pemBlock, err := pemEncodeKey(bs, "RSA PRIVATE KEY")
if err != nil {
return nil, "", err
}
return pk, pemBlock, nil
}
func generateECDSAKey(keyBits int) (crypto.Signer, string, error) {
var pk *ecdsa.PrivateKey
var curve elliptic.Curve
switch keyBits {
case 224:
curve = elliptic.P224()
case 256:
curve = elliptic.P256()
case 384:
curve = elliptic.P384()
case 521:
curve = elliptic.P521()
default:
return nil, "", fmt.Errorf("error generating ECDSA private key: unknown curve length %d", keyBits)
}
pk, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, "", fmt.Errorf("error generating ECDSA private key: %s", err)
}
bs, err := x509.MarshalECPrivateKey(pk)
if err != nil {
return nil, "", fmt.Errorf("error generating private key: %s", err)
return nil, "", fmt.Errorf("error marshaling ECDSA private key: %s", err)
}
var buf bytes.Buffer
err = pem.Encode(&buf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: bs})
pemBlock, err := pemEncodeKey(bs, "EC PRIVATE KEY")
if err != nil {
return nil, "", fmt.Errorf("error encoding private key: %s", err)
return nil, "", err
}
return pk, buf.String(), nil
return pk, pemBlock, nil
}
// GeneratePrivateKey generates a new Private key
func GeneratePrivateKeyWithConfig(keyType string, keyBits int) (crypto.Signer, string, error) {
switch strings.ToLower(keyType) {
case "rsa":
return generateRSAKey(keyBits)
case "ec":
return generateECDSAKey(keyBits)
default:
return nil, "", fmt.Errorf("unknown private key type requested: %s", keyType)
}
}
func GeneratePrivateKey() (crypto.Signer, string, error) {
// TODO: find any calls to this func, replace with calls to GeneratePrivateKeyWithConfig()
// using prefs `private_key_type` and `private_key_bits`
return GeneratePrivateKeyWithConfig(DefaultPrivateKeyType, DefaultPrivateKeyBits)
}

View file

@ -0,0 +1,150 @@
package connect
import (
"fmt"
"testing"
"time"
"crypto/x509"
"encoding/pem"
"github.com/hashicorp/consul/agent/structs"
"github.com/stretchr/testify/require"
)
type KeyConfig struct {
keyType string
keyBits int
}
var goodParams, badParams []KeyConfig
func init() {
goodParams = []KeyConfig{
{keyType: "rsa", keyBits: 2048},
{keyType: "rsa", keyBits: 4096},
{keyType: "ec", keyBits: 224},
{keyType: "ec", keyBits: 256},
{keyType: "ec", keyBits: 384},
{keyType: "ec", keyBits: 521},
}
badParams = []KeyConfig{
{keyType: "rsa", keyBits: 0},
{keyType: "rsa", keyBits: 1024},
{keyType: "rsa", keyBits: 24601},
{keyType: "ec", keyBits: 0},
{keyType: "ec", keyBits: 512},
{keyType: "ec", keyBits: 321},
{keyType: "ecdsa", keyBits: 256}, // test for "ecdsa" instead of "ec"
{keyType: "aes", keyBits: 128},
}
}
func makeConfig(kc KeyConfig) structs.CommonCAProviderConfig {
return structs.CommonCAProviderConfig{
LeafCertTTL: 3 * 24 * time.Hour,
PrivateKeyType: kc.keyType,
PrivateKeyBits: kc.keyBits,
}
}
func testGenerateRSAKey(t *testing.T, bits int) {
r := require.New(t)
_, rsaBlock, err := GeneratePrivateKeyWithConfig("rsa", bits)
r.NoError(err)
r.Contains(rsaBlock, "RSA PRIVATE KEY")
rsaBytes, _ := pem.Decode([]byte(rsaBlock))
r.NotNil(rsaBytes)
rsaKey, err := x509.ParsePKCS1PrivateKey(rsaBytes.Bytes)
r.NoError(err)
r.NoError(rsaKey.Validate())
r.Equal(bits/8, rsaKey.Size()) // note: returned size is in bytes. 2048/8==256
}
func testGenerateECDSAKey(t *testing.T, bits int) {
r := require.New(t)
_, pemBlock, err := GeneratePrivateKeyWithConfig("ec", bits)
r.NoError(err)
r.Contains(pemBlock, "EC PRIVATE KEY")
block, _ := pem.Decode([]byte(pemBlock))
r.NotNil(block)
pk, err := x509.ParseECPrivateKey(block.Bytes)
r.NoError(err)
r.Equal(bits, pk.Curve.Params().BitSize)
}
// Tests to make sure we are able to generate every type of private key supported by the x509 lib.
func TestGenerateKeys(t *testing.T) {
t.Parallel()
for _, params := range goodParams {
t.Run(fmt.Sprintf("TestGenerateKeys-%s-%d", params.keyType, params.keyBits),
func(t *testing.T) {
switch params.keyType {
case "rsa":
testGenerateRSAKey(t, params.keyBits)
case "ec":
testGenerateECDSAKey(t, params.keyBits)
default:
t.Fatalf("unkown key type: %s", params.keyType)
}
})
}
}
// Tests a variety of valid private key configs to make sure they're accepted.
func TestValidateGoodConfigs(t *testing.T) {
t.Parallel()
for _, params := range goodParams {
config := makeConfig(params)
t.Run(fmt.Sprintf("TestValidateGoodConfigs-%s-%d", params.keyType, params.keyBits),
func(t *testing.T) {
require.New(t).NoError(config.Validate(), "unexpected error: type=%s bits=%d",
params.keyType, params.keyBits)
})
}
}
// Tests a variety of invalid private key configs to make sure they're caught.
func TestValidateBadConfigs(t *testing.T) {
t.Parallel()
for _, params := range badParams {
config := makeConfig(params)
t.Run(fmt.Sprintf("TestValidateBadConfigs-%s-%d", params.keyType, params.keyBits), func(t *testing.T) {
require.New(t).Error(config.Validate(), "expected error: type=%s bits=%d",
params.keyType, params.keyBits)
})
}
}
// Tests the ability of a CA to sign a CSR using a different key type. If the key types differ, the test should fail.
func TestSignatureMismatches(t *testing.T) {
t.Parallel()
r := require.New(t)
for _, p1 := range goodParams {
for _, p2 := range goodParams {
if p1 == p2 {
continue
}
t.Run(fmt.Sprintf("TestMismatches-%s%d-%s%d", p1.keyType, p1.keyBits, p2.keyType, p2.keyBits), func(t *testing.T) {
ca := TestCAWithKeyType(t, nil, p1.keyType, p1.keyBits)
r.Equal(p1.keyType, ca.PrivateKeyType)
r.Equal(p1.keyBits, ca.PrivateKeyBits)
certPEM, keyPEM, err := testLeaf(t, "foobar.service.consul", ca, p2.keyType, p2.keyBits)
if p1.keyType == p2.keyType {
r.NoError(err)
_, err := ParseCert(certPEM)
r.NoError(err)
_, err = ParseSigner(keyPEM)
r.NoError(err)
} else {
r.Error(err)
}
})
}
}
}

View file

@ -3,6 +3,7 @@ package connect
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
@ -97,6 +98,7 @@ func ParseCSR(pemValue string) (*x509.CertificateRequest, error) {
func KeyId(raw interface{}) ([]byte, error) {
switch raw.(type) {
case *ecdsa.PublicKey:
case *rsa.PublicKey:
default:
return nil, fmt.Errorf("invalid key type: %T", raw)
}

View file

@ -3,8 +3,6 @@ package connect
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
@ -27,21 +25,17 @@ const TestClusterID = "11111111-2222-3333-4444-555555555555"
// unique names for the CA certs.
var testCACounter uint64
// TestCA creates a test CA certificate and signing key and returns it
// in the CARoot structure format. The returned CA will be set as Active = true.
//
// If xc is non-nil, then the returned certificate will have a signing cert
// that is cross-signed with the previous cert, and this will be set as
// SigningCert.
func TestCA(t testing.T, xc *structs.CARoot) *structs.CARoot {
func testCA(t testing.T, xc *structs.CARoot, keyType string, keyBits int) *structs.CARoot {
var result structs.CARoot
result.Active = true
result.Name = fmt.Sprintf("Test CA %d", atomic.AddUint64(&testCACounter, 1))
// Create the private key we'll use for this CA cert.
signer, keyPEM := testPrivateKey(t)
signer, keyPEM := testPrivateKey(t, keyType, keyBits)
result.SigningKey = keyPEM
result.SigningKeyID = HexString(testKeyID(t, signer.Public()))
result.PrivateKeyType = keyType
result.PrivateKeyBits = keyBits
// The serial number for the cert
sn, err := testSerialNumber()
@ -127,9 +121,23 @@ func TestCA(t testing.T, xc *structs.CARoot) *structs.CARoot {
return &result
}
// TestLeaf returns a valid leaf certificate and it's private key for the named
// service with the given CA Root.
func TestLeaf(t testing.T, service string, root *structs.CARoot) (string, string) {
// TestCA creates a test CA certificate and signing key and returns it
// in the CARoot structure format. The returned CA will be set as Active = true.
//
// If xc is non-nil, then the returned certificate will have a signing cert
// that is cross-signed with the previous cert, and this will be set as
// SigningCert.
func TestCA(t testing.T, xc *structs.CARoot) *structs.CARoot {
return testCA(t, xc, DefaultPrivateKeyType, DefaultPrivateKeyBits)
}
// TestCAWithKeyType is similar to TestCA, except that it
// takes two additional arguments to override the default private key type and size.
func TestCAWithKeyType(t testing.T, xc *structs.CARoot, keyType string, keyBits int) *structs.CARoot {
return testCA(t, xc, keyType, keyBits)
}
func testLeaf(t testing.T, service string, root *structs.CARoot, keyType string, keyBits int) (string, string, error) {
// Parse the CA cert and signing key from the root
cert := root.SigningCert
if cert == "" {
@ -137,11 +145,11 @@ func TestLeaf(t testing.T, service string, root *structs.CARoot) (string, string
}
caCert, err := ParseCert(cert)
if err != nil {
t.Fatalf("error parsing CA cert: %s", err)
return "", "", fmt.Errorf("error parsing CA cert: %s", err)
}
caSigner, err := ParseSigner(root.SigningKey)
if err != nil {
t.Fatalf("error parsing signing key: %s", err)
return "", "", fmt.Errorf("error parsing signing key: %s", err)
}
// Build the SPIFFE ID
@ -155,13 +163,18 @@ func TestLeaf(t testing.T, service string, root *structs.CARoot) (string, string
// The serial number for the cert
sn, err := testSerialNumber()
if err != nil {
t.Fatalf("error generating serial number: %s", err)
return "", "", fmt.Errorf("error generating serial number: %s", err)
}
// Generate fresh private key
pkSigner, pkPEM, err := GeneratePrivateKey()
pkSigner, pkPEM, err := GeneratePrivateKeyWithConfig(keyType, keyBits)
if err != nil {
t.Fatalf("failed to generate private key: %s", err)
return "", "", fmt.Errorf("failed to generate private key: %s", err)
}
sigAlgo := x509.ECDSAWithSHA256
if keyType == "rsa" {
sigAlgo = x509.SHA256WithRSA
}
// Cert template for generation
@ -169,7 +182,7 @@ func TestLeaf(t testing.T, service string, root *structs.CARoot) (string, string
SerialNumber: sn,
Subject: pkix.Name{CommonName: service},
URIs: []*url.URL{spiffeId.URI()},
SignatureAlgorithm: x509.ECDSAWithSHA256,
SignatureAlgorithm: sigAlgo,
BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageDataEncipherment |
x509.KeyUsageKeyAgreement |
@ -190,14 +203,24 @@ func TestLeaf(t testing.T, service string, root *structs.CARoot) (string, string
bs, err := x509.CreateCertificate(
rand.Reader, &template, caCert, pkSigner.Public(), caSigner)
if err != nil {
t.Fatalf("error generating certificate: %s", err)
return "", "", fmt.Errorf("error generating certificate: %s", err)
}
err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: bs})
if err != nil {
t.Fatalf("error encoding private key: %s", err)
return "", "", fmt.Errorf("error encoding private key: %s", err)
}
return buf.String(), pkPEM
return buf.String(), pkPEM, nil
}
// TestLeaf returns a valid leaf certificate and it's private key for the named
// service with the given CA Root.
func TestLeaf(t testing.T, service string, root *structs.CARoot) (string, string) {
certPEM, keyPEM, err := testLeaf(t, service, root, root.PrivateKeyType, root.PrivateKeyBits)
if err != nil {
t.Fatalf(err.Error())
}
return certPEM, keyPEM
}
// TestCSR returns a CSR to sign the given service along with the PEM-encoded
@ -209,7 +232,7 @@ func TestCSR(t testing.T, uri CertURI) (string, string) {
}
// Create the private key we'll use
signer, pkPEM := testPrivateKey(t)
signer, pkPEM := testPrivateKey(t, DefaultPrivateKeyType, DefaultPrivateKeyBits)
// Create the CSR itself
var csrBuf bytes.Buffer
@ -253,24 +276,13 @@ func testKeyID(t testing.T, raw interface{}) []byte {
// which will be the same for multiple CAs/Leafs. Also note that our UUID
// generator also reads from crypto rand and is called far more often during
// tests than this will be.
func testPrivateKey(t testing.T) (crypto.Signer, string) {
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
func testPrivateKey(t testing.T, keyType string, keyBits int) (crypto.Signer, string) {
pk, pkPEM, err := GeneratePrivateKeyWithConfig(keyType, keyBits)
if err != nil {
t.Fatalf("error generating private key: %s", err)
}
bs, err := x509.MarshalECPrivateKey(pk)
if err != nil {
t.Fatalf("error generating private key: %s", err)
}
var buf bytes.Buffer
err = pem.Encode(&buf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: bs})
if err != nil {
t.Fatalf("error encoding private key: %s", err)
}
return pk, buf.String()
return pk, pkPEM
}
// testSerialNumber generates a serial number suitable for a certificate. For
@ -298,21 +310,12 @@ type TestAgentRPC interface {
RPC(method string, args interface{}, reply interface{}) error
}
// TestCAConfigSet sets a CARoot returned by TestCA into the TestAgent state. It
// requires that TestAgent had connect enabled in it's config. If ca is nil, a
// new CA is created.
//
// It returns the CARoot passed or created.
//
// Note that we have to use an interface for the TestAgent.RPC method since we
// can't introduce an import cycle by importing `agent.TestAgent` here directly.
// It also means this will work in a few other places we mock that method.
func TestCAConfigSet(t testing.T, a TestAgentRPC,
ca *structs.CARoot) *structs.CARoot {
func testCAConfigSet(t testing.T, a TestAgentRPC,
ca *structs.CARoot, keyType string, keyBits int) *structs.CARoot {
t.Helper()
if ca == nil {
ca = TestCA(t, nil)
ca = TestCAWithKeyType(t, nil, keyType, keyBits)
}
newConfig := &structs.CAConfiguration{
Provider: "consul",
@ -320,6 +323,8 @@ func TestCAConfigSet(t testing.T, a TestAgentRPC,
"PrivateKey": ca.SigningKey,
"RootCert": ca.RootCert,
"RotationPeriod": 180 * 24 * time.Hour,
"PrivateKeyType": ca.PrivateKeyType,
"PrivateKeyBits": ca.PrivateKeyBits,
},
}
args := &structs.CARequest{
@ -334,3 +339,24 @@ func TestCAConfigSet(t testing.T, a TestAgentRPC,
}
return ca
}
// TestCAConfigSet sets a CARoot returned by TestCA into the TestAgent state. It
// requires that TestAgent had connect enabled in it's config. If ca is nil, a
// new CA is created.
//
// It returns the CARoot passed or created.
//
// Note that we have to use an interface for the TestAgent.RPC method since we
// can't introduce an import cycle by importing `agent.TestAgent` here directly.
// It also means this will work in a few other places we mock that method.
func TestCAConfigSet(t testing.T, a TestAgentRPC,
ca *structs.CARoot) *structs.CARoot {
return testCAConfigSet(t, a, ca, DefaultPrivateKeyType, DefaultPrivateKeyBits)
}
// TestCAConfigSetWithKeyType is similar to TestCAConfigSet, except that it
// takes two additional arguments to override the default private key type and size.
func TestCAConfigSetWithKeyType(t testing.T, a TestAgentRPC,
ca *structs.CARoot, keyType string, keyBits int) *structs.CARoot {
return testCAConfigSet(t, a, ca, keyType, keyBits)
}

View file

@ -1,6 +1,7 @@
package connect
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
@ -14,12 +15,21 @@ import (
var hasOpenSSL bool
func init() {
goodParams = []KeyConfig{
{keyType: "rsa", keyBits: 2048},
{keyType: "rsa", keyBits: 4096},
{keyType: "ec", keyBits: 224},
{keyType: "ec", keyBits: 256},
{keyType: "ec", keyBits: 384},
{keyType: "ec", keyBits: 521},
}
_, err := exec.LookPath("openssl")
hasOpenSSL = err == nil
}
// Test that the TestCA and TestLeaf functions generate valid certificates.
func TestTestCAAndLeaf(t *testing.T) {
func testCAAndLeaf(t *testing.T, keyType string, keyBits int) {
if !hasOpenSSL {
t.Skip("openssl not found")
return
@ -28,7 +38,7 @@ func TestTestCAAndLeaf(t *testing.T) {
assert := assert.New(t)
// Create the certs
ca := TestCA(t, nil)
ca := TestCAWithKeyType(t, nil, keyType, keyBits)
leaf, _ := TestLeaf(t, "web", ca)
// Create a temporary directory for storing the certs
@ -51,7 +61,7 @@ func TestTestCAAndLeaf(t *testing.T) {
}
// Test cross-signing.
func TestTestCAAndLeaf_xc(t *testing.T) {
func testCAAndLeaf_xc(t *testing.T, keyType string, keyBits int) {
if !hasOpenSSL {
t.Skip("openssl not found")
return
@ -60,8 +70,8 @@ func TestTestCAAndLeaf_xc(t *testing.T) {
assert := assert.New(t)
// Create the certs
ca1 := TestCA(t, nil)
ca2 := TestCA(t, ca1)
ca1 := TestCAWithKeyType(t, nil, keyType, keyBits)
ca2 := TestCAWithKeyType(t, ca1, keyType, keyBits)
leaf1, _ := TestLeaf(t, "web", ca1)
leaf2, _ := TestLeaf(t, "web", ca2)
@ -98,3 +108,23 @@ func TestTestCAAndLeaf_xc(t *testing.T) {
assert.Nil(err)
}
}
func TestTestCAAndLeaf(t *testing.T) {
t.Parallel()
for _, params := range goodParams {
t.Run(fmt.Sprintf("TestTestCAAndLeaf-%s-%d", params.keyType, params.keyBits),
func(t *testing.T) {
testCAAndLeaf(t, params.keyType, params.keyBits)
})
}
}
func TestTestCAAndLeaf_xc(t *testing.T) {
t.Parallel()
for _, params := range goodParams {
t.Run(fmt.Sprintf("TestTestCAAndLeaf_xc-%s-%d", params.keyType, params.keyBits),
func(t *testing.T) {
testCAAndLeaf_xc(t, params.keyType, params.keyBits)
})
}
}

View file

@ -73,6 +73,8 @@ func TestConnectCAConfig(t *testing.T) {
RotationPeriod: 90 * 24 * time.Hour,
}
expected.LeafCertTTL = 72 * time.Hour
expected.PrivateKeyType = connect.DefaultPrivateKeyType
expected.PrivateKeyBits = connect.DefaultPrivateKeyBits
// Get the initial config.
{
@ -107,6 +109,7 @@ func TestConnectCAConfig(t *testing.T) {
// The config should be updated now.
{
expected.RotationPeriod = time.Hour
req, _ := http.NewRequest("GET", "/v1/connect/ca/configuration", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.ConnectCAConfiguration(resp, req)

View file

@ -47,8 +47,13 @@ func (c *Client) RequestAutoEncryptCerts(servers []string, defaultPort int, toke
Agent: string(c.config.NodeName),
}
conf, err := c.config.CAConfig.GetCommonConfig()
if err != nil {
return errFn(err)
}
// Create a new private key
pk, pkPEM, err := connect.GeneratePrivateKey()
pk, pkPEM, err := connect.GeneratePrivateKeyWithConfig(conf.PrivateKeyType, conf.PrivateKeyBits)
if err != nil {
return errFn(err)
}

View file

@ -359,6 +359,8 @@ func (s *ConnectCA) Roots(
IntermediateCerts: r.IntermediateCerts,
RaftIndex: r.RaftIndex,
Active: r.Active,
PrivateKeyType: r.PrivateKeyType,
PrivateKeyBits: r.PrivateKeyBits,
}
if r.Active {

View file

@ -9,7 +9,7 @@ import (
"sync/atomic"
"time"
metrics "github.com/armon/go-metrics"
"github.com/armon/go-metrics"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/consul/autopilot"
"github.com/hashicorp/consul/agent/metadata"
@ -17,9 +17,9 @@ import (
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/lib"
"github.com/hashicorp/consul/types"
memdb "github.com/hashicorp/go-memdb"
uuid "github.com/hashicorp/go-uuid"
version "github.com/hashicorp/go-version"
"github.com/hashicorp/go-memdb"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/go-version"
"github.com/hashicorp/raft"
"github.com/hashicorp/serf/serf"
"golang.org/x/time/rate"

View file

@ -215,6 +215,13 @@ func (s *Server) initializeRootCA(provider ca.Provider, conf *structs.CAConfigur
return err
}
commonConfig, err := conf.GetCommonConfig()
if err != nil {
return err
}
rootCA.PrivateKeyType = commonConfig.PrivateKeyType
rootCA.PrivateKeyBits = commonConfig.PrivateKeyBits
// Check if the CA root is already initialized and exit if it is,
// adding on any existing intermediate certs since they aren't directly
// tied to the provider.

View file

@ -102,6 +102,10 @@ type CARoot struct {
// active root.
RotatedOutAt time.Time `json:"-"`
// Type of private key used to create the CA cert.
PrivateKeyType string
PrivateKeyBits int
RaftIndex
}
@ -277,6 +281,9 @@ type CommonCAProviderConfig struct {
// immediately in the RPC goroutine. This is 0 by default and CSRMaxPerSecond
// is used. This is ignored if CSRMaxPerSecond is non-zero.
CSRMaxConcurrent int
PrivateKeyType string
PrivateKeyBits int
}
func (c CommonCAProviderConfig) Validate() error {
@ -292,6 +299,19 @@ func (c CommonCAProviderConfig) Validate() error {
return fmt.Errorf("leaf cert TTL must be less than 1 year")
}
switch c.PrivateKeyType {
case "ec":
if c.PrivateKeyBits != 224 && c.PrivateKeyBits != 256 && c.PrivateKeyBits != 384 && c.PrivateKeyBits != 521 {
return fmt.Errorf("ECDSA key length must be one of (224, 256, 384, 521) bits")
}
case "rsa":
if c.PrivateKeyBits != 2048 && c.PrivateKeyBits != 4096 {
return fmt.Errorf("RSA key length must be 2048 or 4096 bits")
}
default:
return fmt.Errorf("private key type must be either 'ecdsa' or 'rsa'")
}
return nil
}

View file

@ -44,9 +44,15 @@ func main() {
var numCAs = 2
var services = []string{"web", "db", "cache"}
var outDir string
var keyType string = "ec"
var keyBits int = 256
flag.StringVar(&outDir, "out-dir", "",
"REQUIRED: the dir to write certificates to")
flag.StringVar(&keyType, "key-type", "ec",
"Type of private key to create (ec, rsa)")
flag.IntVar(&keyBits, "key-bits", 256,
"Size of private key to create, in bits")
flag.Parse()
if outDir == "" {
@ -57,7 +63,7 @@ func main() {
// Create CA certs
var prevCA *structs.CARoot
for i := 1; i <= numCAs; i++ {
ca := connect.TestCA(&testing.RuntimeT{}, prevCA)
ca := connect.TestCAWithKeyType(&testing.RuntimeT{}, prevCA, keyType, keyBits)
prefix := fmt.Sprintf("%s/ca%d-ca", outDir, i)
writeFile(prefix+".cert.pem", ca.RootCert)
writeFile(prefix+".key.pem", ca.SigningKey)

View file

@ -4,6 +4,7 @@ import (
"bytes"
"context"
"fmt"
"github.com/hashicorp/consul/connect"
"log"
"net"
"os"
@ -15,7 +16,6 @@ import (
"github.com/stretchr/testify/require"
agConnect "github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/connect"
"github.com/hashicorp/consul/ipaddr"
"github.com/hashicorp/consul/sdk/freeport"
)

View file

@ -4,13 +4,13 @@ import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"github.com/hashicorp/consul/agent/connect"
"math/big"
"net"
"strings"
@ -29,23 +29,7 @@ func GenerateSerialNumber() (*big.Int, error) {
// GeneratePrivateKey generates a new ecdsa private key
func GeneratePrivateKey() (crypto.Signer, string, error) {
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, "", fmt.Errorf("error generating private key: %s", err)
}
bs, err := x509.MarshalECPrivateKey(pk)
if err != nil {
return nil, "", fmt.Errorf("error generating private key: %s", err)
}
var buf bytes.Buffer
err = pem.Encode(&buf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: bs})
if err != nil {
return nil, "", fmt.Errorf("error encoding private key: %s", err)
}
return pk, buf.String(), nil
return connect.GeneratePrivateKey()
}
// GenerateCA generates a new CA for agent TLS (not to be confused with Connect TLS)