Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion tls/resource_private_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"

"github.com/hashicorp/terraform/helper/schema"
"golang.org/x/crypto/ed25519"
)

type keyAlgo func(d *schema.ResourceData) (interface{}, error)
Expand All @@ -35,6 +36,12 @@ var keyAlgos map[string]keyAlgo = map[string]keyAlgo{
return nil, fmt.Errorf("invalid ecdsa_curve; must be P224, P256, P384 or P521")
}
},
"ED25519": func(d *schema.ResourceData) (interface{}, error) {
return func() (interface{}, error) {
_, priv, err := ed25519.GenerateKey(rand.Reader)
return priv, err
}()
},
}

var keyParsers map[string]keyParser = map[string]keyParser{
Expand All @@ -44,6 +51,9 @@ var keyParsers map[string]keyParser = map[string]keyParser{
"ECDSA": func(der []byte) (interface{}, error) {
return x509.ParseECPrivateKey(der)
},
"ED25519": func(der []byte) (interface{}, error) {
return ed25519.NewKeyFromSeed(der), nil
},
}

func resourcePrivateKey() *schema.Resource {
Expand Down Expand Up @@ -128,13 +138,17 @@ func CreatePrivateKey(d *schema.ResourceData, meta interface{}) error {
Type: "EC PRIVATE KEY",
Bytes: keyBytes,
}
case ed25519.PrivateKey:
keyPemBlock = &pem.Block{
Type: "ED25519 PRIVATE KEY",
Bytes: k.Seed(),
}
default:
return fmt.Errorf("unsupported private key type")
}
keyPem := string(pem.EncodeToMemory(keyPemBlock))

d.Set("private_key_pem", keyPem)

return readPublicKey(d, key)
}

Expand All @@ -153,7 +167,26 @@ func publicKey(priv interface{}) interface{} {
return &k.PublicKey
case *ecdsa.PrivateKey:
return &k.PublicKey
case ed25519.PrivateKey:
return k.Public()
default:
return nil
}
}

func publicKeyBytes(priv interface{}) ([]byte, error) {
switch k := priv.(type) {
case *rsa.PrivateKey:
return x509.MarshalPKIXPublicKey(&k.PublicKey)
case *ecdsa.PrivateKey:
return x509.MarshalPKIXPublicKey(&k.PublicKey)
case ed25519.PrivateKey:
pubKey, ok := k.Public().(ed25519.PublicKey)
if !ok {
return nil, fmt.Errorf("failed to get ed25519 public key")
}
return []byte(pubKey), nil
default:
return nil, fmt.Errorf("unsupported private key type")
}
}
70 changes: 70 additions & 0 deletions tls/resource_private_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func TestPrivateKeyRSA(t *testing.T) {
if !strings.HasPrefix(gotPrivate, "-----BEGIN RSA PRIVATE KEY----") {
return fmt.Errorf("private key is missing RSA key PEM preamble")
}

if len(gotPrivate) > 1700 {
return fmt.Errorf("private key PEM looks too long for a 2048-bit key (got %v characters)", len(gotPrivate))
}
Expand Down Expand Up @@ -222,3 +223,72 @@ func TestPrivateKeyECDSA(t *testing.T) {
},
})
}

func TestPrivateKeyEd25519(t *testing.T) {
r.Test(t, r.TestCase{
Providers: testProviders,
Steps: []r.TestStep{
r.TestStep{
Config: `
resource "tls_private_key" "test" {
algorithm = "ED25519"
}
output "private_key_pem" {
value = "${tls_private_key.test.private_key_pem}"
}
output "public_key_pem" {
value = "${tls_private_key.test.public_key_pem}"
}
output "public_key_openssh" {
value = "${tls_private_key.test.public_key_openssh}"
}
output "public_key_fingerprint_md5" {
value = "${tls_private_key.test.public_key_fingerprint_md5}"
}
`,
Check: func(s *terraform.State) error {
gotPrivateUntyped := s.RootModule().Outputs["private_key_pem"].Value
gotPrivate, ok := gotPrivateUntyped.(string)
if !ok {
return fmt.Errorf("output for \"private_key_pem\" is not a string")
}
if !strings.HasPrefix(gotPrivate, "-----BEGIN ED25519 PRIVATE KEY-----") {
return fmt.Errorf("prviate key is missing ED25519 key PEM preamble")
}
if len(gotPrivate) > 115 {
return fmt.Errorf("private key PEM looks too long for a 32-bit key (got %v characters)", len(gotPrivate))
}

gotPublicUntyped := s.RootModule().Outputs["public_key_pem"].Value
gotPublic, ok := gotPublicUntyped.(string)
if !ok {
return fmt.Errorf("output for \"public_key_pem\" is not a string")
}
if !strings.HasPrefix(gotPublic, "-----BEGIN PUBLIC KEY----") {
return fmt.Errorf("public key is missing public key PEM preamble")
}

gotPublicSSHUntyped := s.RootModule().Outputs["public_key_openssh"].Value
gotPublicSSH, ok := gotPublicSSHUntyped.(string)
if !ok {
return fmt.Errorf("output for \"public_key_openssh\" is not a string")
}
if !strings.HasPrefix(gotPublicSSH, "ssh-ed25519") {
return fmt.Errorf("SSH public key is missing ssh-ed25519 prefix")
}

gotPublicFingerprintUntyped := s.RootModule().Outputs["public_key_fingerprint_md5"].Value
gotPublicFingerprint, ok := gotPublicFingerprintUntyped.(string)
if !ok {
return fmt.Errorf("output for \"public_key_fingerprint_md5\" is not a string")
}
if !(gotPublicFingerprint[2] == ':') {
return fmt.Errorf("MD5 public key fingerprint is missing : in the correct place")
}

return nil
},
},
},
})
}
11 changes: 4 additions & 7 deletions tls/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,18 @@ func parseCertificateRequest(d *schema.ResourceData, pemKey string) (*x509.Certi
return certReq, nil
}

func readPublicKey(d *schema.ResourceData, rsaKey interface{}) error {
pubKey := publicKey(rsaKey)
pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey)
func readPublicKey(d *schema.ResourceData, privKey interface{}) error {
pubKeyBytes, err := publicKeyBytes(privKey)
if err != nil {
return fmt.Errorf("failed to marshal public key error: %s", err)
return err
}
pubKeyPemBlock := &pem.Block{
Type: "PUBLIC KEY",
Bytes: pubKeyBytes,
}

d.SetId(hashForState(string((pubKeyBytes))))
d.Set("public_key_pem", string(pem.EncodeToMemory(pubKeyPemBlock)))

sshPubKey, err := ssh.NewPublicKey(publicKey(rsaKey))
sshPubKey, err := ssh.NewPublicKey(publicKey(privKey))
if err == nil {
// Not all EC types can be SSH keys, so we'll produce this only
// if an appropriate type was selected.
Expand Down
11 changes: 6 additions & 5 deletions vendor/golang.org/x/crypto/bcrypt/bcrypt.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vendor/golang.org/x/crypto/blowfish/cipher.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vendor/golang.org/x/crypto/blowfish/const.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vendor/golang.org/x/crypto/curve25519/const_amd64.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vendor/golang.org/x/crypto/curve25519/const_amd64.s

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading