Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue preventing the generation of subject key identifier for private keys using ED2551 #182

Merged
merged 8 commits into from
Apr 5, 2022
37 changes: 26 additions & 11 deletions internal/provider/common_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
Expand All @@ -12,7 +13,6 @@ import (
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
"math/big"
"sort"
Expand Down Expand Up @@ -68,24 +68,39 @@ func supportedKeyUsages() []string {
}

// generateSubjectKeyID generates a SHA-1 hash of the subject public key.
func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) {
var publicKeyBytes []byte
func generateSubjectKeyID(pubKey crypto.PublicKey) ([]byte, error) {
var pubKeyBytes []byte
var err error

switch pub := pub.(type) {
// Marshal public key to bytes or set an error
switch pub := pubKey.(type) {
case *rsa.PublicKey:
publicKeyBytes, err = asn1.Marshal(*pub)
if err != nil {
return nil, err
if pub != nil {
pubKeyBytes, err = asn1.Marshal(*pub)
} else {
err = fmt.Errorf("received 'nil' pointer instead of public key")
}
case *ecdsa.PublicKey:
publicKeyBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
pubKeyBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
case ed25519.PublicKey:
pubKeyBytes, err = asn1.Marshal(pub)
case *ed25519.PublicKey:
if pub != nil {
pubKeyBytes, err = asn1.Marshal(*pub)
} else {
err = fmt.Errorf("received 'nil' pointer instead of public key")
}
default:
return nil, errors.New("only RSA and ECDSA public keys supported")
err = fmt.Errorf("unsupported public key type %T", pub)
}

// If any of the cases above failed, an error would have been set
if err != nil {
return nil, fmt.Errorf("failed to marshal public key of type %T: %w", pubKey, err)
}

hash := sha1.Sum(publicKeyBytes)
return hash[:], nil
pubKeyHash := sha1.Sum(pubKeyBytes)
return pubKeyHash[:], nil
}

// setCertificateSubjectSchema sets on the given reference to map of schema.Schema
Expand Down
43 changes: 21 additions & 22 deletions internal/provider/common_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ var keyGenerators = map[Algorithm]keyGenerator{
if err != nil {
return nil, fmt.Errorf("failed to generate ED25519 key: %s", err)
}
return &key, err
return key, err
},
}

Expand Down Expand Up @@ -97,7 +97,7 @@ func parsePrivateKeyPEM(keyPEMBytes []byte) (crypto.PrivateKey, Algorithm, error
// Identify the Algorithm of the crypto.PrivateKey
algorithm, err := privateKeyToAlgorithm(prvKey)
if err != nil {
return nil, "", err
return nil, "", fmt.Errorf("failed to determine key algorithm for private key of type %T: %w", prvKey, err)
}

return prvKey, algorithm, nil
Expand All @@ -109,53 +109,52 @@ func parsePrivateKeyPEM(keyPEMBytes []byte) (crypto.PrivateKey, Algorithm, error
func parsePrivateKeyOpenSSHPEM(keyOpenSSHPEMBytes []byte) (crypto.PrivateKey, Algorithm, error) {
prvKey, err := ssh.ParseRawPrivateKey(keyOpenSSHPEMBytes)
if err != nil {
return nil, "", err
return nil, "", fmt.Errorf("failed to parse openssh private key: %w", err)
}

algorithm, err := privateKeyToAlgorithm(prvKey)
if err != nil {
return nil, "", err
return nil, "", fmt.Errorf("failed to determine key algorithm for private key of type %T: %w", prvKey, err)
}

return prvKey, algorithm, nil
}

// privateKeyToPublicKey takes a crypto.PrivateKey and extracts the corresponding crypto.PublicKey,
// after having figured out its type.
func privateKeyToPublicKey(prvKey crypto.PrivateKey) crypto.PublicKey {
switch k := prvKey.(type) {
case *rsa.PrivateKey:
return &k.PublicKey
case *ecdsa.PrivateKey:
return &k.PublicKey
case *ed25519.PrivateKey:
return k.Public()
default:
return nil
func privateKeyToPublicKey(prvKey crypto.PrivateKey) (crypto.PublicKey, error) {
signer, ok := prvKey.(crypto.Signer)
if !ok {
return nil, fmt.Errorf("unsupported private key type: %T", prvKey)
}

return signer.Public(), nil
}

// privateKeyToAlgorithm identifies the Algorithm used by a given crypto.PrivateKey.
func privateKeyToAlgorithm(prvKey crypto.PrivateKey) (Algorithm, error) {
switch k := prvKey.(type) {
case *rsa.PrivateKey:
switch prvKey.(type) {
case rsa.PrivateKey, *rsa.PrivateKey:
return RSA, nil
case *ecdsa.PrivateKey:
case ecdsa.PrivateKey, *ecdsa.PrivateKey:
return ECDSA, nil
case *ed25519.PrivateKey:
case ed25519.PrivateKey, *ed25519.PrivateKey:
return ED25519, nil
default:
return "", fmt.Errorf("failed to identify key algorithm for unsupported private key: %#v", k)
return "", fmt.Errorf("unsupported private key type: %T", prvKey)
}
}

// setPublicKeyAttributes takes a crypto.PrivateKey, extracts the corresponding crypto.PublicKey and then
// encodes related attributes on the given schema.ResourceData.
func setPublicKeyAttributes(d *schema.ResourceData, prvKey crypto.PrivateKey) error {
pubKey := privateKeyToPublicKey(prvKey)
pubKey, err := privateKeyToPublicKey(prvKey)
if err != nil {
return fmt.Errorf("failed to get public key from private key: %w", err)
}
pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
return fmt.Errorf("failed to marshal public key error: %s", err)
return fmt.Errorf("failed to marshal public key: %w", err)
}
pubKeyPemBlock := &pem.Block{
Type: PreamblePublicKey.String(),
Expand All @@ -170,7 +169,7 @@ func setPublicKeyAttributes(d *schema.ResourceData, prvKey crypto.PrivateKey) er

// NOTE: ECDSA keys with elliptic curve P-224 are not supported by `x/crypto/ssh`,
// so this will return an error: in that case, we set the below fields to emptry strings
sshPubKey, err := ssh.NewPublicKey(privateKeyToPublicKey(prvKey))
sshPubKey, err := ssh.NewPublicKey(pubKey)
var pubKeySSH, pubKeySSHFingerprintMD5, pubKeySSHFingerprintSHA256 string
if err == nil {
sshPubKeyBytes := ssh.MarshalAuthorizedKey(sshPubKey)
Expand Down
162 changes: 159 additions & 3 deletions internal/provider/resource_locally_signed_cert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package provider
import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"regexp"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -127,7 +127,7 @@ func TestLocallySignedCert(t *testing.T) {
// Verify certificate
_, err = cert.Verify(x509.VerifyOptions{Roots: certPool})
if err == nil {
return errors.New("incorrectly verified certificate")
return fmt.Errorf("incorrectly verified certificate")
} else if _, ok := err.(x509.UnknownAuthorityError); !ok {
return fmt.Errorf("incorrect verify error: expected UnknownAuthorityError, got %v", err)
}
Expand Down Expand Up @@ -412,7 +412,7 @@ func TestAccLocallySignedCert_HandleKeyAlgorithmDeprecation(t *testing.T) {
// Verify certificate
_, err = cert.Verify(x509.VerifyOptions{Roots: certPool})
if err == nil {
return errors.New("incorrectly verified certificate")
return fmt.Errorf("incorrectly verified certificate")
} else if _, ok := err.(x509.UnknownAuthorityError); !ok {
return fmt.Errorf("incorrect verify error: expected UnknownAuthorityError, got %v", err)
}
Expand Down Expand Up @@ -463,3 +463,159 @@ EOT
}
`, testCertRequest, validity, earlyRenewal, testCACert, caKeyAlgorithmCfg, testCAPrivateKey)
}

func TestAccResourceLocallySignedCert_FromED25519PrivateKeyResource(t *testing.T) {
r.UnitTest(t, r.TestCase{
Providers: testProviders,
Steps: []r.TestStep{
{
Config: `
resource "tls_private_key" "ca_prv_test" {
algorithm = "ED25519"
}
resource "tls_self_signed_cert" "ca_cert_test" {
private_key_pem = tls_private_key.ca_prv_test.private_key_pem
subject {
organization = "test-organization"
}
is_ca_certificate = true
validity_period_hours = 8760
allowed_uses = [
"cert_signing",
]
}

resource "tls_private_key" "test" {
algorithm = "ED25519"
}
resource "tls_cert_request" "test" {
private_key_pem = tls_private_key.test.private_key_pem
subject {
common_name = "test.com"
}
}

resource "tls_locally_signed_cert" "test" {
validity_period_hours = 1
early_renewal_hours = 0
allowed_uses = [
"server_auth",
"client_auth",
]
cert_request_pem = tls_cert_request.test.cert_request_pem
ca_cert_pem = tls_self_signed_cert.ca_cert_test.cert_pem
ca_private_key_pem = tls_private_key.ca_prv_test.private_key_pem
}
`,
Check: r.ComposeTestCheckFunc(
r.TestCheckResourceAttr("tls_locally_signed_cert.test", "ca_key_algorithm", "ED25519"),
r.TestMatchResourceAttr("tls_locally_signed_cert.test", "cert_pem", regexp.MustCompile(`-----BEGIN CERTIFICATE-----((.|\n)+?)-----END CERTIFICATE-----`)),
),
},
},
})
}

func TestAccResourceLocallySignedCert_FromECDSAPrivateKeyResource(t *testing.T) {
r.UnitTest(t, r.TestCase{
Providers: testProviders,
Steps: []r.TestStep{
{
Config: `
resource "tls_private_key" "ca_prv_test" {
algorithm = "ECDSA"
}
resource "tls_self_signed_cert" "ca_cert_test" {
private_key_pem = tls_private_key.ca_prv_test.private_key_pem
subject {
organization = "test-organization"
}
is_ca_certificate = true
validity_period_hours = 8760
allowed_uses = [
"cert_signing",
]
}

resource "tls_private_key" "test" {
algorithm = "ECDSA"
}
resource "tls_cert_request" "test" {
private_key_pem = tls_private_key.test.private_key_pem
subject {
common_name = "test.com"
}
}

resource "tls_locally_signed_cert" "test" {
validity_period_hours = 1
early_renewal_hours = 0
allowed_uses = [
"server_auth",
"client_auth",
]
cert_request_pem = tls_cert_request.test.cert_request_pem
ca_cert_pem = tls_self_signed_cert.ca_cert_test.cert_pem
ca_private_key_pem = tls_private_key.ca_prv_test.private_key_pem
}
`,
Check: r.ComposeTestCheckFunc(
r.TestCheckResourceAttr("tls_locally_signed_cert.test", "ca_key_algorithm", "ECDSA"),
r.TestMatchResourceAttr("tls_locally_signed_cert.test", "cert_pem", regexp.MustCompile(`-----BEGIN CERTIFICATE-----((.|\n)+?)-----END CERTIFICATE-----`)),
),
},
},
})
}

func TestAccResourceLocallySignedCert_FromRSAPrivateKeyResource(t *testing.T) {
r.UnitTest(t, r.TestCase{
Providers: testProviders,
Steps: []r.TestStep{
{
Config: `
resource "tls_private_key" "ca_prv_test" {
algorithm = "RSA"
}
resource "tls_self_signed_cert" "ca_cert_test" {
private_key_pem = tls_private_key.ca_prv_test.private_key_pem
subject {
organization = "test-organization"
}
is_ca_certificate = true
validity_period_hours = 8760
allowed_uses = [
"cert_signing",
]
}

resource "tls_private_key" "test" {
algorithm = "RSA"
}
resource "tls_cert_request" "test" {
private_key_pem = tls_private_key.test.private_key_pem
subject {
common_name = "test.com"
}
}

resource "tls_locally_signed_cert" "test" {
validity_period_hours = 1
early_renewal_hours = 0
allowed_uses = [
"server_auth",
"client_auth",
]
cert_request_pem = tls_cert_request.test.cert_request_pem
ca_cert_pem = tls_self_signed_cert.ca_cert_test.cert_pem
ca_private_key_pem = tls_private_key.ca_prv_test.private_key_pem
}
`,
Check: r.ComposeTestCheckFunc(
r.TestCheckResourceAttr("tls_locally_signed_cert.test", "ca_key_algorithm", "RSA"),
r.TestMatchResourceAttr("tls_locally_signed_cert.test", "cert_pem", regexp.MustCompile(`-----BEGIN CERTIFICATE-----((.|\n)+?)-----END CERTIFICATE-----`)),
),
},
},
})
}
4 changes: 2 additions & 2 deletions internal/provider/resource_private_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ func createResourcePrivateKey(d *schema.ResourceData, _ interface{}) error {
if k.Curve.Params().Name == "P-224" {
doMarshalOpenSSHKeyPemBlock = false
}
case *ed25519.PrivateKey:
keyBytes, err := x509.MarshalPKCS8PrivateKey(*k)
case ed25519.PrivateKey:
keyBytes, err := x509.MarshalPKCS8PrivateKey(k)
if err != nil {
return fmt.Errorf("error encoding key to PEM: %s", err)
}
Expand Down
8 changes: 6 additions & 2 deletions internal/provider/resource_self_signed_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func resourceSelfSignedCert() *schema.Resource {
}
}

func createSelfSignedCert(d *schema.ResourceData, meta interface{}) error {
func createSelfSignedCert(d *schema.ResourceData, _ interface{}) error {
key, algorithm, err := parsePrivateKeyPEM([]byte(d.Get("private_key_pem").(string)))
if err != nil {
return err
Expand Down Expand Up @@ -73,5 +73,9 @@ func createSelfSignedCert(d *schema.ResourceData, meta interface{}) error {
cert.URIs = append(cert.URIs, uri)
}

return createCertificate(d, &cert, &cert, privateKeyToPublicKey(key), key)
publicKey, err := privateKeyToPublicKey(key)
if err != nil {
return fmt.Errorf("failed to get public key from private key: %w", err)
}
return createCertificate(d, &cert, &cert, publicKey, key)
}
Loading