connect: Support RSA keys in addition to ECDSA (#6055)
Support RSA keys in addition to ECDSA
This commit is contained in:
parent
c691b75b75
commit
1b14d6595e
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -6,7 +6,9 @@
|
|||
*.test
|
||||
.DS_Store
|
||||
.fseventsd
|
||||
.envrc
|
||||
.vagrant/
|
||||
.idea/
|
||||
/pkg
|
||||
Thumbs.db
|
||||
bin/
|
||||
|
|
|
@ -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",
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
150
agent/connect/generate_test.go
Normal file
150
agent/connect/generate_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue