diff --git a/docs/data-sources/public_key.md b/docs/data-sources/public_key.md index 93315783..9e25ef20 100644 --- a/docs/data-sources/public_key.md +++ b/docs/data-sources/public_key.md @@ -45,6 +45,7 @@ data "tls_public_key" "private_key_openssh-example" { - `id` (String) Unique identifier for this data source: hexadecimal representation of the SHA1 checksum of the data source. - `public_key_fingerprint_md5` (String) The fingerprint of the public key data in OpenSSH MD5 hash format, e.g. `aa:bb:cc:...`. Only available if the selected private key format is compatible, as per the rules for `public_key_openssh` and [ECDSA P224 limitations](../../docs#limitations). - `public_key_fingerprint_sha256` (String) The fingerprint of the public key data in OpenSSH SHA256 hash format, e.g. `SHA256:...`. Only available if the selected private key format is compatible, as per the rules for `public_key_openssh` and [ECDSA P224 limitations](../../docs#limitations). +- `public_key_fingerprint_x509_sha256` (String) The SHA256 hash of the binary key data, encoded as a base64 string - `public_key_openssh` (String) The public key, in [OpenSSH PEM (RFC 4716)](https://datatracker.ietf.org/doc/html/rfc4716) format. This is also known as ['Authorized Keys'](https://www.ssh.com/academy/ssh/authorized_keys/openssh#format-of-the-authorized-keys-file) format. This is populated only if the configured private key is supported: this includes all `RSA` and `ED25519` keys, as well as `ECDSA` keys with curves `P256`, `P384` and `P521`; `ECDSA` with curve `P224` [is not supported](../../docs#limitations). **NOTE**: the [underlying](https://pkg.go.dev/encoding/pem#Encode) [libraries](https://pkg.go.dev/golang.org/x/crypto/ssh#MarshalAuthorizedKey) that generate this value append a `\n` at the end of the PEM. In case this disrupts your use case, we recommend using [`trimspace()`](https://www.terraform.io/language/functions/trimspace). - `public_key_pem` (String) The public key, in [PEM (RFC 1421)](https://datatracker.ietf.org/doc/html/rfc1421) format. **NOTE**: the [underlying](https://pkg.go.dev/encoding/pem#Encode) [libraries](https://pkg.go.dev/golang.org/x/crypto/ssh#MarshalAuthorizedKey) that generate this value append a `\n` at the end of the PEM. In case this disrupts your use case, we recommend using [`trimspace()`](https://www.terraform.io/language/functions/trimspace). diff --git a/docs/resources/private_key.md b/docs/resources/private_key.md index 82d428f3..354ae1ba 100644 --- a/docs/resources/private_key.md +++ b/docs/resources/private_key.md @@ -62,6 +62,7 @@ resource "tls_private_key" "ed25519-example" { - `private_key_pem` (String, Sensitive) Private key data in [PEM (RFC 1421)](https://datatracker.ietf.org/doc/html/rfc1421) format. - `public_key_fingerprint_md5` (String) The fingerprint of the public key data in OpenSSH MD5 hash format, e.g. `aa:bb:cc:...`. Only available if the selected private key format is compatible, similarly to `public_key_openssh` and the [ECDSA P224 limitations](../../docs#limitations). - `public_key_fingerprint_sha256` (String) The fingerprint of the public key data in OpenSSH SHA256 hash format, e.g. `SHA256:...`. Only available if the selected private key format is compatible, similarly to `public_key_openssh` and the [ECDSA P224 limitations](../../docs#limitations). +- `public_key_fingerprint_x509_sha256` (String) The SHA256 hash of the binary key data, encoded as a base64 string - `public_key_openssh` (String) The public key data in ["Authorized Keys"](https://www.ssh.com/academy/ssh/authorized_keys/openssh#format-of-the-authorized-keys-file) format. This is populated only if the configured private key is supported: this includes all `RSA` and `ED25519` keys, as well as `ECDSA` keys with curves `P256`, `P384` and `P521`. `ECDSA` with curve `P224` [is not supported](../../docs#limitations). **NOTE**: the [underlying](https://pkg.go.dev/encoding/pem#Encode) [libraries](https://pkg.go.dev/golang.org/x/crypto/ssh#MarshalAuthorizedKey) that generate this value append a `\n` at the end of the PEM. In case this disrupts your use case, we recommend using [`trimspace()`](https://www.terraform.io/language/functions/trimspace). - `public_key_pem` (String) Public key data in [PEM (RFC 1421)](https://datatracker.ietf.org/doc/html/rfc1421) format. **NOTE**: the [underlying](https://pkg.go.dev/encoding/pem#Encode) [libraries](https://pkg.go.dev/golang.org/x/crypto/ssh#MarshalAuthorizedKey) that generate this value append a `\n` at the end of the PEM. In case this disrupts your use case, we recommend using [`trimspace()`](https://www.terraform.io/language/functions/trimspace). diff --git a/internal/provider/common_key.go b/internal/provider/common_key.go index 54048863..64e0ca8b 100644 --- a/internal/provider/common_key.go +++ b/internal/provider/common_key.go @@ -8,6 +8,7 @@ import ( "crypto/rand" "crypto/rsa" "crypto/x509" + "encoding/base64" "encoding/pem" "fmt" @@ -164,6 +165,13 @@ func setPublicKeyAttributes(d *schema.ResourceData, prvKey crypto.PrivateKey) di d.SetId(hashForState(string(pubKeyBytes))) + hasher := crypto.SHA256.New() + hasher.Write(pubKeyBytes) + pubKeyDERFingerprintSHA256 := hasher.Sum(nil) + if err := d.Set("public_key_fingerprint_x509_sha256", base64.StdEncoding.EncodeToString(pubKeyDERFingerprintSHA256)); err != nil { + return diag.Errorf("error setting value on key 'public_key_fingerprint_x509_sha256': %s", err) + } + if err := d.Set("public_key_pem", string(pem.EncodeToMemory(pubKeyPemBlock))); err != nil { return diag.Errorf("error setting value on key 'public_key_pem': %s", err) } diff --git a/internal/provider/data_source_public_key.go b/internal/provider/data_source_public_key.go index 93a3da74..d16f441a 100644 --- a/internal/provider/data_source_public_key.go +++ b/internal/provider/data_source_public_key.go @@ -85,6 +85,12 @@ func dataSourcePublicKey() *schema.Resource { "Only available if the selected private key format is compatible, as per the rules for " + "`public_key_openssh` and [ECDSA P224 limitations](../../docs#limitations).", }, + + "public_key_fingerprint_x509_sha256": { + Type: schema.TypeString, + Computed: true, + Description: "The SHA256 hash of the binary key data, encoded as a base64 string", + }, "id": { Type: schema.TypeString, diff --git a/internal/provider/data_source_public_key_test.go b/internal/provider/data_source_public_key_test.go index 04a699fe..1cc2920d 100644 --- a/internal/provider/data_source_public_key_test.go +++ b/internal/provider/data_source_public_key_test.go @@ -36,6 +36,7 @@ func TestAccPublicKey_dataSource_PEM(t *testing.T) { resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_pem", strings.TrimSpace(testPublicKeyPEM)+"\n"), resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_openssh", strings.TrimSpace(testPublicKeyOpenSSH)+"\n"), resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_fingerprint_md5", strings.TrimSpace(testPublicKeyOpenSSHFingerprintMD5)), + resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_fingerprint_x509_sha256", strings.TrimSpace((testPublicKeyX509FingerprintSHA256))), resource.TestCheckResourceAttr("data.tls_public_key.test", "algorithm", "RSA"), ), }, @@ -89,6 +90,7 @@ func TestAccPublicKey_dataSource_OpenSSHPEM(t *testing.T) { resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_pem", strings.TrimSpace(testPublicKeyPEM)+"\n"), resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_openssh", strings.TrimSpace(testPublicKeyOpenSSH)+"\n"), resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_fingerprint_md5", strings.TrimSpace(testPublicKeyOpenSSHFingerprintMD5)), + resource.TestCheckResourceAttr("data.tls_public_key.test", "public_key_fingerprint_x509_sha256", strings.TrimSpace(testPublicKeyX509FingerprintSHA256)), resource.TestCheckResourceAttr("data.tls_public_key.test", "algorithm", "RSA"), ), }, diff --git a/internal/provider/fixtures_test.go b/internal/provider/fixtures_test.go index 1232f830..6bb2a3e7 100644 --- a/internal/provider/fixtures_test.go +++ b/internal/provider/fixtures_test.go @@ -67,6 +67,7 @@ D9Hk2MajZuFnJiqj1QIDAQAB -----END PUBLIC KEY-----` testPublicKeyOpenSSH = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDPLaq43D9C596ko9yQipWUf2FbRhFs18D3wBDBqXLIoP7W3rm5S292/JiNPa+mX76IYFF416zTBGG9J5w4d4VFrROn8IuMWqHgdXsCUf2szN7EnJcVBsBzTxxWqz4DjX315vbm/PFOLlKzC0Ngs4h1iDiCD9Hk2MajZuFnJiqj1Q==` testPublicKeyOpenSSHFingerprintMD5 = `62:c2:c6:7a:d0:27:72:e7:0d:bc:4e:97:42:0e:9e:e6` + testPublicKeyX509FingerprintSHA256 = "Tjv0oV3kNJRjpeIadBtzbRYjG7IG/jnSyOEJnr7FL1s=" // NOTE: See ../scripts/make-test-ca.tf for a Terraform script to create the following CA Private Key and Certificate. testCAPrivateKey = ` diff --git a/internal/provider/resource_private_key.go b/internal/provider/resource_private_key.go index eab7a629..fd7c5dae 100644 --- a/internal/provider/resource_private_key.go +++ b/internal/provider/resource_private_key.go @@ -110,6 +110,12 @@ func resourcePrivateKey() *schema.Resource { "Only available if the selected private key format is compatible, similarly to " + "`public_key_openssh` and the [ECDSA P224 limitations](../../docs#limitations).", }, + + "public_key_fingerprint_x509_sha256": { + Type: schema.TypeString, + Computed: true, + Description: "The SHA256 hash of the binary key data, encoded as a base64 string", + }, "id": { Type: schema.TypeString, diff --git a/internal/provider/resource_private_key_test.go b/internal/provider/resource_private_key_test.go index ff23a441..ceddc7ed 100644 --- a/internal/provider/resource_private_key_test.go +++ b/internal/provider/resource_private_key_test.go @@ -38,6 +38,9 @@ func TestPrivateKeyRSA(t *testing.T) { output "public_key_fingerprint_sha256" { value = "${tls_private_key.test.public_key_fingerprint_sha256}" } + output "public_key_fingerprint_x509_sha256" { + value = "${tls_private_key.test.public_key_fingerprint_x509_sha256}" + } `, Check: func(s *terraform.State) error { // Check `.private_key_pem` @@ -100,7 +103,17 @@ func TestPrivateKeyRSA(t *testing.T) { return fmt.Errorf("output for \"public_key_fingerprint_sha256\" is not a string") } if !(strings.HasPrefix(gotPublicFingerprintSHA256, "SHA256:")) { - return fmt.Errorf("SHA256 public key fingerprint is is missing the expected preamble") + return fmt.Errorf("SHA256 public key fingerprint is missing the expected preamble") + } + + // Check `.public_key_fingerprint_x509_sha256` + gotPublicFingerprintX509SHA256Untyped := s.RootModule().Outputs["public_key_fingerprint_x509_sha256"].Value + gotPublicFingerprintX509SHA256, ok := gotPublicFingerprintX509SHA256Untyped.(string) + if !ok { + return fmt.Errorf("output for \"public_key_fingerprint_x509_sha256\" is not a string") + } + if len(gotPublicFingerprintX509SHA256) != 44 { + return fmt.Errorf("x509 public key fingerprint is not the expected length") } return nil @@ -165,6 +178,9 @@ func TestPrivateKeyECDSA(t *testing.T) { output "public_key_fingerprint_sha256" { value = "${tls_private_key.test.public_key_fingerprint_sha256}" } + output "public_key_fingerprint_x509_sha256" { + value = "${tls_private_key.test.public_key_fingerprint_x509_sha256}" + } `, Check: func(s *terraform.State) error { // Check `.private_key_pem` @@ -224,7 +240,17 @@ func TestPrivateKeyECDSA(t *testing.T) { return fmt.Errorf("output for \"public_key_fingerprint_sha256\" is not a string") } if gotPublicFingerprintSHA256 != "" { - return fmt.Errorf("SHA256 public key fingerprint should not be st for ECDSA P-224 key") + return fmt.Errorf("SHA256 public key fingerprint should not be set for ECDSA P-224 key") + } + + // Check `.public_key_fingerprint_x509_sha256` + gotPublicFingerprintX509SHA256Untyped := s.RootModule().Outputs["public_key_fingerprint_sha256"].Value + gotPublicFingerprintX509SHA256, ok := gotPublicFingerprintX509SHA256Untyped.(string) + if !ok { + return fmt.Errorf("output for \"public_key_fingerprint_x509_sha256\" is not a string") + } + if gotPublicFingerprintX509SHA256 != "" { + return fmt.Errorf("SHA256 public key fingerprint should not be set for ECDSA P-224 key") } return nil @@ -256,6 +282,9 @@ func TestPrivateKeyECDSA(t *testing.T) { output "public_key_fingerprint_sha256" { value = "${tls_private_key.test.public_key_fingerprint_sha256}" } + output "public_key_fingerprint_x509_sha256" { + value = "${tls_private_key.test.public_key_fingerprint_x509_sha256}" + } `, Check: func(s *terraform.State) error { // Check `.private_key_pem` @@ -318,6 +347,16 @@ func TestPrivateKeyECDSA(t *testing.T) { return fmt.Errorf("SHA256 public key fingerprint is is missing the expected preamble") } + // Check `.public_key_fingerprint_x509_sha256` + gotPublicFingerprintX509SHA256Untyped := s.RootModule().Outputs["public_key_fingerprint_x509_sha256"].Value + gotPublicFingerprintX509SHA256, ok := gotPublicFingerprintX509SHA256Untyped.(string) + if !ok { + return fmt.Errorf("output for \"public_key_fingerprint_x509_sha256\" is not a string") + } + if len(gotPublicFingerprintX509SHA256) != 44 { + return fmt.Errorf("x509 public key fingerprint is not the expected length") + } + return nil }, }, @@ -354,6 +393,9 @@ func TestPrivateKeyED25519(t *testing.T) { output "public_key_fingerprint_sha256" { value = "${tls_private_key.test.public_key_fingerprint_sha256}" } + output "public_key_fingerprint_x509_sha256" { + value = "${tls_private_key.test.public_key_fingerprint_x509_sha256}" + } `, Check: func(s *terraform.State) error { // Check `.private_key_pem` @@ -416,6 +458,16 @@ func TestPrivateKeyED25519(t *testing.T) { return fmt.Errorf("SHA256 public key fingerprint is is missing the expected preamble") } + // Check `.public_key_fingerprint_x509_sha256` + gotPublicFingerprintX509SHA256Untyped := s.RootModule().Outputs["public_key_fingerprint_x509_sha256"].Value + gotPublicFingerprintX509SHA256, ok := gotPublicFingerprintX509SHA256Untyped.(string) + if !ok { + return fmt.Errorf("output for \"public_key_fingerprint_x509_sha256\" is not a string") + } + if len(gotPublicFingerprintX509SHA256) != 44 { + return fmt.Errorf("x509 public key fingerprint is not the expected length") + } + return nil }, },