forked from ops/terraform-provider-tor
Compare commits
12 commits
aarch64-da
...
main
Author | SHA1 | Date | |
---|---|---|---|
139f5f6d9c | |||
7f6d7cc0c4 | |||
43c442ad20 | |||
f1cccbe22b | |||
5bf771ac96 | |||
b406226f0f | |||
4eadc8416e | |||
62b243c8e4 | |||
83df31ec80 | |||
005634ff1c | |||
f60d0d655b | |||
53f019906e |
13 changed files with 316 additions and 44 deletions
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -1,6 +1,6 @@
|
|||
.*
|
||||
*.dll
|
||||
*.exe
|
||||
.DS_Store
|
||||
example.tf
|
||||
terraform.tfplan
|
||||
terraform.tfstate
|
||||
|
@ -15,12 +15,9 @@ website/node_modules
|
|||
.vagrant/
|
||||
*.backup
|
||||
./*.tfstate
|
||||
.terraform/
|
||||
*.log
|
||||
*.bak
|
||||
*~
|
||||
.*.swp
|
||||
.idea
|
||||
*.iml
|
||||
*.test
|
||||
*.iml
|
||||
|
@ -35,10 +32,11 @@ website/vendor
|
|||
*.winfile eol=crlf
|
||||
terraform-provider-tor
|
||||
dev/
|
||||
CLAUDE.md
|
||||
*.md
|
||||
!README.md
|
||||
!CHANGELOG.md
|
||||
!CONTRIBUTING.md
|
||||
extra
|
||||
.direnv
|
||||
.claude
|
||||
e2e-tests/**/*tfstate*
|
||||
e2e-tests/**/providers
|
||||
e2e-tests/**/.terraform.lock.hcl
|
||||
|
|
15
README.md
15
README.md
|
@ -58,6 +58,8 @@ resource "local_sensitive_file" "family_key" {
|
|||
resource "tor_relay_identity_rsa" "bridge" {}
|
||||
|
||||
resource "tor_relay_identity_ed25519" "bridge" {}
|
||||
# Note: Ed25519 keys are available in both PEM format (private_key_pem, public_key_pem)
|
||||
# and Tor's binary format (private_key_tor, public_key_tor)
|
||||
|
||||
resource "tor_obfs4_state" "bridge" {
|
||||
rsa_identity_private_key = tor_relay_identity_rsa.bridge.private_key_pem
|
||||
|
@ -80,12 +82,23 @@ output "rsa_identity_pem" {
|
|||
sensitive = true
|
||||
}
|
||||
|
||||
output "rsa_fingerprint_hashed" {
|
||||
description = "Hashed RSA fingerprint for privacy in monitoring systems"
|
||||
value = tor_relay_identity_rsa.bridge.public_key_fingerprint_sha1_hashed
|
||||
}
|
||||
|
||||
output "ed25519_identity_pem" {
|
||||
description = "Ed25519 identity private key for bridge configuration"
|
||||
description = "Ed25519 identity private key for bridge configuration (PEM format)"
|
||||
value = tor_relay_identity_ed25519.bridge.private_key_pem
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "ed25519_identity_tor" {
|
||||
description = "Ed25519 identity private key in Tor's binary format (base64 encoded)"
|
||||
value = tor_relay_identity_ed25519.bridge.private_key_tor
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "obfs4_state_json" {
|
||||
description = "Complete obfs4 state for bridge runtime"
|
||||
value = tor_obfs4_state.bridge.state_json
|
||||
|
|
|
@ -52,5 +52,7 @@ output "public_key_fingerprint_sha256" {
|
|||
- `algorithm` (String) Name of the algorithm used when generating the private key (always 'Ed25519')
|
||||
- `id` (String) Unique identifier based on public key fingerprint
|
||||
- `private_key_pem` (String, Sensitive) Private key data in PEM (RFC 1421) format
|
||||
- `public_key_fingerprint_sha256` (String) SHA256 fingerprint of the public key in hex format
|
||||
- `private_key_tor` (String, Sensitive) Private key data in Tor's binary format, base64 encoded
|
||||
- `public_key_fingerprint_sha256` (String) Base64 encoded public key bytes (32 bytes) without padding, used as the Tor Ed25519 fingerprint
|
||||
- `public_key_pem` (String) Public key data in PEM (RFC 1421) format
|
||||
- `public_key_tor` (String) Public key data in Tor's binary format, base64 encoded
|
||||
|
|
|
@ -49,6 +49,11 @@ output "public_key_fingerprint_sha256" {
|
|||
description = "SHA256 fingerprint of the RSA public key"
|
||||
value = tor_relay_identity_rsa.example.public_key_fingerprint_sha256
|
||||
}
|
||||
|
||||
output "public_key_fingerprint_sha1_hashed" {
|
||||
description = "Hashed SHA1 fingerprint of the RSA public key for privacy in monitoring systems"
|
||||
value = tor_relay_identity_rsa.example.public_key_fingerprint_sha1_hashed
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
|
@ -60,5 +65,6 @@ output "public_key_fingerprint_sha256" {
|
|||
- `id` (String) Unique identifier based on public key fingerprint
|
||||
- `private_key_pem` (String, Sensitive) Private key data in PEM (RFC 1421) format
|
||||
- `public_key_fingerprint_sha1` (String) SHA1 fingerprint of the public key in hex format
|
||||
- `public_key_fingerprint_sha1_hashed` (String) SHA1 hash of the binary form of the SHA1 fingerprint in hex format
|
||||
- `public_key_fingerprint_sha256` (String) SHA256 fingerprint of the public key in hex format
|
||||
- `public_key_pem` (String) Public key data in PEM (RFC 1421) format
|
||||
|
|
|
@ -26,6 +26,13 @@ resource "tor_obfs4_state" "bridge" {
|
|||
ed25519_identity_private_key = tor_relay_identity_ed25519.bridge.private_key_pem
|
||||
}
|
||||
|
||||
# Alternative: obfs4 state could also use Tor format keys (demonstration only)
|
||||
# resource "tor_obfs4_state" "bridge_alt" {
|
||||
# rsa_identity_private_key = tor_relay_identity_rsa.bridge.private_key_pem
|
||||
# ed25519_identity_private_key = tor_relay_identity_ed25519.bridge.private_key_pem
|
||||
# # Note: private_key_tor could be used here as well for ed25519 keys
|
||||
# }
|
||||
|
||||
# Generate bridge line for client distribution
|
||||
data "tor_obfs4_bridge_line" "bridge" {
|
||||
ip_address = "203.0.113.1"
|
||||
|
@ -46,11 +53,27 @@ output "rsa_fingerprint_sha256" {
|
|||
value = tor_relay_identity_rsa.bridge.public_key_fingerprint_sha256
|
||||
}
|
||||
|
||||
output "rsa_fingerprint_sha1_hashed" {
|
||||
description = "RSA identity fingerprint (SHA1) hashed for privacy"
|
||||
value = tor_relay_identity_rsa.bridge.public_key_fingerprint_sha1_hashed
|
||||
}
|
||||
|
||||
output "ed25519_fingerprint_sha256" {
|
||||
description = "Ed25519 identity fingerprint (SHA256)"
|
||||
value = tor_relay_identity_ed25519.bridge.public_key_fingerprint_sha256
|
||||
}
|
||||
|
||||
output "ed25519_private_key_tor" {
|
||||
description = "Ed25519 private key in Tor binary format (base64)"
|
||||
value = tor_relay_identity_ed25519.bridge.private_key_tor
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "ed25519_public_key_tor" {
|
||||
description = "Ed25519 public key in Tor binary format (base64)"
|
||||
value = tor_relay_identity_ed25519.bridge.public_key_tor
|
||||
}
|
||||
|
||||
output "obfs4_certificate" {
|
||||
description = "obfs4 certificate for bridge line"
|
||||
value = tor_obfs4_state.bridge.certificate
|
||||
|
|
|
@ -17,6 +17,10 @@ resource "tor_family_identity" "this" {
|
|||
family_name = "MyFamily"
|
||||
}
|
||||
|
||||
resource "tor_relay_identity_rsa" "this" {}
|
||||
|
||||
resource "tor_relay_identity_ed25519" "this" {}
|
||||
|
||||
resource "local_sensitive_file" "family_key" {
|
||||
content_base64 = tor_family_identity.this.secret_key
|
||||
filename = "./data/keys/MyKey.secret_family_key"
|
||||
|
@ -42,3 +46,34 @@ output "family_id" {
|
|||
description = "Family ID for the bridge"
|
||||
value = tor_family_identity.this.id
|
||||
}
|
||||
|
||||
output "rsa_fingerprint_sha1" {
|
||||
description = "RSA identity fingerprint (SHA1) - should be uppercase hex"
|
||||
value = tor_relay_identity_rsa.this.public_key_fingerprint_sha1
|
||||
}
|
||||
|
||||
output "rsa_fingerprint_sha1_hashed" {
|
||||
description = "RSA identity fingerprint (SHA1) hashed for privacy monitoring"
|
||||
value = tor_relay_identity_rsa.this.public_key_fingerprint_sha1_hashed
|
||||
}
|
||||
|
||||
output "rsa_fingerprint_sha256" {
|
||||
description = "RSA identity fingerprint (SHA256)"
|
||||
value = tor_relay_identity_rsa.this.public_key_fingerprint_sha256
|
||||
}
|
||||
|
||||
output "ed25519_fingerprint_sha256" {
|
||||
description = "ED25519 identity fingerprint (base64 encoded public key bytes)"
|
||||
value = tor_relay_identity_ed25519.this.public_key_fingerprint_sha256
|
||||
}
|
||||
|
||||
output "ed25519_private_key_tor" {
|
||||
description = "ED25519 private key in Tor binary format (base64 encoded)"
|
||||
value = tor_relay_identity_ed25519.this.private_key_tor
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "ed25519_public_key_tor" {
|
||||
description = "ED25519 public key in Tor binary format (base64 encoded)"
|
||||
value = tor_relay_identity_ed25519.this.public_key_tor
|
||||
}
|
||||
|
|
|
@ -33,4 +33,9 @@ output "public_key_fingerprint_sha1" {
|
|||
output "public_key_fingerprint_sha256" {
|
||||
description = "SHA256 fingerprint of the RSA public key"
|
||||
value = tor_relay_identity_rsa.example.public_key_fingerprint_sha256
|
||||
}
|
||||
|
||||
output "public_key_fingerprint_sha1_hashed" {
|
||||
description = "Hashed SHA1 fingerprint of the RSA public key for privacy in monitoring systems"
|
||||
value = tor_relay_identity_rsa.example.public_key_fingerprint_sha1_hashed
|
||||
}
|
|
@ -31,9 +31,8 @@ func TestTorObfs4BridgeLineDataSource(t *testing.T) {
|
|||
// Check computed values are generated
|
||||
resource.TestCheckResourceAttrSet("data.tor_obfs4_bridge_line.test", "bridge_line"),
|
||||
|
||||
// Check bridge line format
|
||||
resource.TestMatchResourceAttr("data.tor_obfs4_bridge_line.test", "bridge_line",
|
||||
regexp.MustCompile(`^Bridge obfs4 192\.0\.2\.1:443 [0-9a-f]{40} cert=[A-Za-z0-9+/]+ iat-mode=[0-2]$`)),
|
||||
regexp.MustCompile(`^Bridge obfs4 192\.0\.2\.1:443 [0-9A-F]{40} cert=[A-Za-z0-9+/]+ iat-mode=[0-2]$`)),
|
||||
|
||||
// Check that input values are used correctly
|
||||
resource.TestCheckResourceAttrPair("data.tor_obfs4_bridge_line.test", "identity_fingerprint_sha1", "tor_relay_identity_rsa.test", "public_key_fingerprint_sha1"),
|
||||
|
|
|
@ -406,11 +406,7 @@ func (r *TorObfs4StateResource) deriveNodeIdFromRsaKey(rsaPrivateKeyPem string)
|
|||
return nil, fmt.Errorf("failed to parse RSA private key: %w", err)
|
||||
}
|
||||
|
||||
// Extract the public key and encode it
|
||||
publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal public key: %w", err)
|
||||
}
|
||||
publicKeyBytes := x509.MarshalPKCS1PublicKey(&privateKey.PublicKey)
|
||||
|
||||
// Generate SHA1 hash of public key (this is the relay fingerprint/node ID)
|
||||
hash := sha1.Sum(publicKeyBytes)
|
||||
|
|
|
@ -7,10 +7,11 @@ import (
|
|||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
|
@ -33,6 +34,8 @@ type TorRelayIdentityEd25519ResourceModel struct {
|
|||
Algorithm types.String `tfsdk:"algorithm"`
|
||||
PrivateKeyPem types.String `tfsdk:"private_key_pem"`
|
||||
PublicKeyPem types.String `tfsdk:"public_key_pem"`
|
||||
PrivateKeyTor types.String `tfsdk:"private_key_tor"`
|
||||
PublicKeyTor types.String `tfsdk:"public_key_tor"`
|
||||
PublicKeyFingerprintSha256 types.String `tfsdk:"public_key_fingerprint_sha256"`
|
||||
}
|
||||
|
||||
|
@ -74,9 +77,24 @@ func (r *TorRelayIdentityEd25519Resource) Schema(ctx context.Context, req resour
|
|||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"private_key_tor": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Sensitive: true,
|
||||
MarkdownDescription: "Private key data in Tor's binary format, base64 encoded",
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"public_key_tor": schema.StringAttribute{
|
||||
Computed: true,
|
||||
MarkdownDescription: "Public key data in Tor's binary format, base64 encoded",
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"public_key_fingerprint_sha256": schema.StringAttribute{
|
||||
Computed: true,
|
||||
MarkdownDescription: "SHA256 fingerprint of the public key in hex format",
|
||||
MarkdownDescription: "Base64 encoded public key bytes (32 bytes) without padding, used as the Tor Ed25519 fingerprint",
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
|
@ -125,12 +143,19 @@ func (r *TorRelayIdentityEd25519Resource) Create(ctx context.Context, req resour
|
|||
}
|
||||
data.PublicKeyPem = types.StringValue(publicKeyPem)
|
||||
|
||||
// Generate SHA256 fingerprint
|
||||
sha256Fingerprint := r.generateSha256Fingerprint(publicKey)
|
||||
data.PublicKeyFingerprintSha256 = types.StringValue(sha256Fingerprint)
|
||||
// Encode keys in Tor format
|
||||
privateKeyTor := r.encodeTorPrivateKey(privateKey)
|
||||
data.PrivateKeyTor = types.StringValue(privateKeyTor)
|
||||
|
||||
// Generate ID from SHA256 fingerprint
|
||||
data.Id = types.StringValue(fmt.Sprintf("ed25519-%s", sha256Fingerprint[:16]))
|
||||
publicKeyTor := r.encodeTorPublicKey(publicKey)
|
||||
data.PublicKeyTor = types.StringValue(publicKeyTor)
|
||||
|
||||
// Generate Tor Ed25519 fingerprint (base64 encoded public key bytes without padding)
|
||||
ed25519Fingerprint := r.generateEd25519Fingerprint(publicKey)
|
||||
data.PublicKeyFingerprintSha256 = types.StringValue(ed25519Fingerprint)
|
||||
|
||||
// Generate ID from Ed25519 fingerprint
|
||||
data.Id = types.StringValue(fmt.Sprintf("ed25519-%s", ed25519Fingerprint[:16]))
|
||||
|
||||
tflog.Trace(ctx, "created tor relay identity Ed25519 resource")
|
||||
|
||||
|
@ -181,8 +206,37 @@ func (r *TorRelayIdentityEd25519Resource) encodePublicKeyPEM(publicKey ed25519.P
|
|||
return string(publicKeyPem), nil
|
||||
}
|
||||
|
||||
func (r *TorRelayIdentityEd25519Resource) generateSha256Fingerprint(publicKey ed25519.PublicKey) string {
|
||||
publicKeyBytes, _ := x509.MarshalPKIXPublicKey(publicKey)
|
||||
sha256Sum := sha256.Sum256(publicKeyBytes)
|
||||
return fmt.Sprintf("%x", sha256Sum)
|
||||
func (r *TorRelayIdentityEd25519Resource) generateEd25519Fingerprint(publicKey ed25519.PublicKey) string {
|
||||
fingerprint := base64.StdEncoding.EncodeToString(publicKey)
|
||||
return strings.TrimRight(fingerprint, "=")
|
||||
}
|
||||
|
||||
func (r *TorRelayIdentityEd25519Resource) encodeTorPrivateKey(privateKey ed25519.PrivateKey) string {
|
||||
// Tor Ed25519 private key format:
|
||||
// "== ed25519v1-secret: type0 ==" + null bytes + 64 bytes key data
|
||||
header := "== ed25519v1-secret: type0 =="
|
||||
|
||||
// Create 96-byte buffer: 32 bytes header + null padding + 64 bytes key
|
||||
torKey := make([]byte, 96)
|
||||
copy(torKey, header)
|
||||
|
||||
// Copy the private key (64 bytes) starting at offset 32
|
||||
copy(torKey[32:], privateKey)
|
||||
|
||||
return base64.StdEncoding.EncodeToString(torKey)
|
||||
}
|
||||
|
||||
func (r *TorRelayIdentityEd25519Resource) encodeTorPublicKey(publicKey ed25519.PublicKey) string {
|
||||
// Tor Ed25519 public key format:
|
||||
// "== ed25519v1-public: type0 ==" + null bytes + 32 bytes key data
|
||||
header := "== ed25519v1-public: type0 =="
|
||||
|
||||
// Create 64-byte buffer: 32 bytes header + null padding + 32 bytes key
|
||||
torKey := make([]byte, 64)
|
||||
copy(torKey, header)
|
||||
|
||||
// Copy the public key (32 bytes) starting at offset 32
|
||||
copy(torKey[32:], publicKey)
|
||||
|
||||
return base64.StdEncoding.EncodeToString(torKey)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
|
@ -27,14 +28,67 @@ func TestAccTorRelayIdentityEd25519Resource(t *testing.T) {
|
|||
// Verify PEM format
|
||||
resource.TestMatchResourceAttr("tor_relay_identity_ed25519.test", "private_key_pem", regexp.MustCompile(`^-----BEGIN PRIVATE KEY-----`)),
|
||||
resource.TestMatchResourceAttr("tor_relay_identity_ed25519.test", "public_key_pem", regexp.MustCompile(`^-----BEGIN PUBLIC KEY-----`)),
|
||||
// Verify fingerprint format (64 hex characters for SHA256)
|
||||
resource.TestMatchResourceAttr("tor_relay_identity_ed25519.test", "public_key_fingerprint_sha256", regexp.MustCompile(`^[0-9a-f]{64}$`)),
|
||||
resource.TestMatchResourceAttr("tor_relay_identity_ed25519.test", "public_key_fingerprint_sha256", regexp.MustCompile(`^[A-Za-z0-9+/]{43}$`)),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestTorEd25519FingerprintGeneration(t *testing.T) {
|
||||
testPubKeyBase64 := "PT0gZWQyNTUxOXYxLXB1YmxpYzogdHlwZTAgPT0AAAB6UEqfT3OvqdpNfw/rbOucsc5AXRUw4lcy/SaWxruoYA=="
|
||||
expectedFingerprint := "elBKn09zr6naTX8P62zrnLHOQF0VMOJXMv0mlsa7qGA"
|
||||
|
||||
pubKeyBytes, err := base64.StdEncoding.DecodeString(testPubKeyBase64)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to decode test public key: %v", err)
|
||||
}
|
||||
|
||||
actualFingerprint := base64.StdEncoding.EncodeToString(pubKeyBytes[32:])
|
||||
actualFingerprint = regexp.MustCompile(`=*$`).ReplaceAllString(actualFingerprint, "")
|
||||
|
||||
if actualFingerprint != expectedFingerprint {
|
||||
t.Errorf("Expected fingerprint %s, got %s", expectedFingerprint, actualFingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccTorRelayIdentityEd25519TorFormat(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccTorRelayIdentityEd25519ResourceConfig,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttrSet("tor_relay_identity_ed25519.test", "private_key_tor"),
|
||||
resource.TestCheckResourceAttrSet("tor_relay_identity_ed25519.test", "public_key_tor"),
|
||||
resource.TestMatchResourceAttr("tor_relay_identity_ed25519.test", "private_key_tor", regexp.MustCompile(`^[A-Za-z0-9+/]+=*$`)),
|
||||
resource.TestMatchResourceAttr("tor_relay_identity_ed25519.test", "public_key_tor", regexp.MustCompile(`^[A-Za-z0-9+/]+=*$`)),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestTorEd25519FormatEncoding(t *testing.T) {
|
||||
testPubKeyBase64 := "PT0gZWQyNTUxOXYxLXB1YmxpYzogdHlwZTAgPT0AAAB6UEqfT3OvqdpNfw/rbOucsc5AXRUw4lcy/SaWxruoYA=="
|
||||
|
||||
pubKeyBytes, err := base64.StdEncoding.DecodeString(testPubKeyBase64)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to decode test public key: %v", err)
|
||||
}
|
||||
|
||||
if len(pubKeyBytes) != 64 {
|
||||
t.Errorf("Expected public key to be 64 bytes, got %d", len(pubKeyBytes))
|
||||
}
|
||||
|
||||
expectedHeader := "== ed25519v1-public: type0 =="
|
||||
actualHeader := string(pubKeyBytes[:len(expectedHeader)])
|
||||
if actualHeader != expectedHeader {
|
||||
t.Errorf("Expected header %q, got %q", expectedHeader, actualHeader)
|
||||
}
|
||||
}
|
||||
|
||||
const testAccTorRelayIdentityEd25519ResourceConfig = `
|
||||
resource "tor_relay_identity_ed25519" "test" {}
|
||||
`
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
|
@ -30,12 +31,13 @@ func NewTorRelayIdentityRsaResource() resource.Resource {
|
|||
type TorRelayIdentityRsaResource struct{}
|
||||
|
||||
type TorRelayIdentityRsaResourceModel struct {
|
||||
Id types.String `tfsdk:"id"`
|
||||
Algorithm types.String `tfsdk:"algorithm"`
|
||||
PrivateKeyPem types.String `tfsdk:"private_key_pem"`
|
||||
PublicKeyPem types.String `tfsdk:"public_key_pem"`
|
||||
PublicKeyFingerprintSha1 types.String `tfsdk:"public_key_fingerprint_sha1"`
|
||||
PublicKeyFingerprintSha256 types.String `tfsdk:"public_key_fingerprint_sha256"`
|
||||
Id types.String `tfsdk:"id"`
|
||||
Algorithm types.String `tfsdk:"algorithm"`
|
||||
PrivateKeyPem types.String `tfsdk:"private_key_pem"`
|
||||
PublicKeyPem types.String `tfsdk:"public_key_pem"`
|
||||
PublicKeyFingerprintSha1 types.String `tfsdk:"public_key_fingerprint_sha1"`
|
||||
PublicKeyFingerprintSha256 types.String `tfsdk:"public_key_fingerprint_sha256"`
|
||||
PublicKeyFingerprintSha1Hashed types.String `tfsdk:"public_key_fingerprint_sha1_hashed"`
|
||||
}
|
||||
|
||||
func (r *TorRelayIdentityRsaResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
|
@ -90,6 +92,13 @@ func (r *TorRelayIdentityRsaResource) Schema(ctx context.Context, req resource.S
|
|||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"public_key_fingerprint_sha1_hashed": schema.StringAttribute{
|
||||
Computed: true,
|
||||
MarkdownDescription: "SHA1 hash of the binary form of the SHA1 fingerprint in hex format",
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -146,6 +155,15 @@ func (r *TorRelayIdentityRsaResource) Create(ctx context.Context, req resource.C
|
|||
data.PublicKeyFingerprintSha1 = types.StringValue(sha1Fingerprint)
|
||||
data.PublicKeyFingerprintSha256 = types.StringValue(sha256Fingerprint)
|
||||
|
||||
// Generate hashed fingerprint
|
||||
hashedFingerprint, err := r.generateHashedFingerprint(sha1Fingerprint)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Hashed Fingerprint Generation Error",
|
||||
fmt.Sprintf("Unable to generate hashed fingerprint: %s", err))
|
||||
return
|
||||
}
|
||||
data.PublicKeyFingerprintSha1Hashed = types.StringValue(hashedFingerprint)
|
||||
|
||||
// Generate ID from SHA1 fingerprint
|
||||
data.Id = types.StringValue(fmt.Sprintf("rsa-%s", sha1Fingerprint[:16]))
|
||||
|
||||
|
@ -196,16 +214,25 @@ func (r *TorRelayIdentityRsaResource) encodePublicKeyPEM(publicKey *rsa.PublicKe
|
|||
}
|
||||
|
||||
func (r *TorRelayIdentityRsaResource) generateFingerprints(publicKey *rsa.PublicKey) (string, string, error) {
|
||||
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
publicKeyBytes := x509.MarshalPKCS1PublicKey(publicKey)
|
||||
|
||||
sha1Sum := sha1.Sum(publicKeyBytes)
|
||||
sha256Sum := sha256.Sum256(publicKeyBytes)
|
||||
|
||||
sha1Fingerprint := fmt.Sprintf("%x", sha1Sum)
|
||||
sha256Fingerprint := fmt.Sprintf("%x", sha256Sum)
|
||||
sha1Fingerprint := fmt.Sprintf("%X", sha1Sum)
|
||||
sha256Fingerprint := fmt.Sprintf("%X", sha256Sum)
|
||||
|
||||
return sha1Fingerprint, sha256Fingerprint, nil
|
||||
}
|
||||
|
||||
func (r *TorRelayIdentityRsaResource) generateHashedFingerprint(fingerprint string) (string, error) {
|
||||
fingerprintBytes, err := hex.DecodeString(fingerprint)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decode fingerprint hex: %w", err)
|
||||
}
|
||||
|
||||
hashedSum := sha1.Sum(fingerprintBytes)
|
||||
hashedFingerprint := fmt.Sprintf("%X", hashedSum)
|
||||
|
||||
return hashedFingerprint, nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
|
@ -25,18 +27,76 @@ func TestAccTorRelayIdentityRsaResource(t *testing.T) {
|
|||
resource.TestCheckResourceAttrSet("tor_relay_identity_rsa.test", "public_key_pem"),
|
||||
resource.TestCheckResourceAttrSet("tor_relay_identity_rsa.test", "public_key_fingerprint_sha1"),
|
||||
resource.TestCheckResourceAttrSet("tor_relay_identity_rsa.test", "public_key_fingerprint_sha256"),
|
||||
resource.TestCheckResourceAttrSet("tor_relay_identity_rsa.test", "public_key_fingerprint_sha1_hashed"),
|
||||
// Verify PEM format
|
||||
resource.TestMatchResourceAttr("tor_relay_identity_rsa.test", "private_key_pem", regexp.MustCompile(`^-----BEGIN RSA PRIVATE KEY-----`)),
|
||||
resource.TestMatchResourceAttr("tor_relay_identity_rsa.test", "public_key_pem", regexp.MustCompile(`^-----BEGIN PUBLIC KEY-----`)),
|
||||
// Verify fingerprint formats (hex strings)
|
||||
resource.TestMatchResourceAttr("tor_relay_identity_rsa.test", "public_key_fingerprint_sha1", regexp.MustCompile(`^[0-9a-f]{40}$`)),
|
||||
resource.TestMatchResourceAttr("tor_relay_identity_rsa.test", "public_key_fingerprint_sha256", regexp.MustCompile(`^[0-9a-f]{64}$`)),
|
||||
resource.TestMatchResourceAttr("tor_relay_identity_rsa.test", "public_key_fingerprint_sha1", regexp.MustCompile(`^[0-9A-F]{40}$`)),
|
||||
resource.TestMatchResourceAttr("tor_relay_identity_rsa.test", "public_key_fingerprint_sha256", regexp.MustCompile(`^[0-9A-F]{64}$`)),
|
||||
resource.TestMatchResourceAttr("tor_relay_identity_rsa.test", "public_key_fingerprint_sha1_hashed", regexp.MustCompile(`^[0-9A-F]{40}$`)),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestRsaFingerprintGeneration(t *testing.T) {
|
||||
testRsaPrivateKeyPem := `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQDxZrR1gsB00rE5Rift0y35j/F5Jt03ExlW0H7fUI+4W2MyZ+pE
|
||||
0CM512o1tshfpDVxtVrnJuI8dFAVgO9Ct7aEKYTNk++g9llF+Q0M4nIrKftv/4Fo
|
||||
MlFHwa+C41LGiucauaagiatiESNWNK3FhOLx1Who9duUN5SVVFyXpApPrQIDAQAB
|
||||
AoGAJya2G9zh49CMB7L2JN88NJ6A1lpURGtnj6nu+b7yID9KHlG2MATlwarLQfzs
|
||||
EH7sYA2+uYCX7qAaoPIxW8u54PLKbgVD3Kh7eXBO0isIx4FPMHD4khv5CLiNVopI
|
||||
VwStg9Fv3FZ6h2+2FTVfLK4+xuwfyoShUwfEp3eV7c8YRSECQQD1ntdJrokM2zWR
|
||||
hf+Cl/L62tl58hSYVZoSrR+b0cTQlbN1rnYTvY+1jXbBP8fKFBoAHKZ1xMCY/m67
|
||||
H7qt+nalAkEA+5o4GQb6YCHYJZ/lhGQFaSGnWnKE16MsW1xLimQY8gOMbg3AYcXk
|
||||
B8fylmp/XpNG1/PC+M6m5C0DjI85eKYuaQJBAJ2eOPmHj1s4sL+aBcWATOS93CFt
|
||||
P9oh1KV3g3kyu+I+rtMuCYfRdY9EIJkSnNsI20aHHCsm/5EudVCPo/RRbiECQEQu
|
||||
psUhfvhOM6T+j9QwxsaWuCNqpVVKgtq/SDlYpunuzD+GunvEhOcW6Eaa1alrf+dF
|
||||
x7BlUBTFnhCZP5nSbwECQGgUr7jW/xrwbkDAP3+ql6o0yyhLMtvIqAKk3fUWxPXO
|
||||
OhEqFiIYW5mI//JWsqSZZxy4nMqgejKkrRgOOQbL0NE=
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
|
||||
expectedSha1Fingerprint := "DA5CEC632A9A544394403BD533E1A7BDE2F26EDD"
|
||||
|
||||
block, _ := pem.Decode([]byte(testRsaPrivateKeyPem))
|
||||
if block == nil {
|
||||
t.Fatal("failed to decode PEM block")
|
||||
}
|
||||
|
||||
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse RSA private key: %v", err)
|
||||
}
|
||||
|
||||
resource := &TorRelayIdentityRsaResource{}
|
||||
sha1Fingerprint, _, err := resource.generateFingerprints(&privateKey.PublicKey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate fingerprint: %v", err)
|
||||
}
|
||||
|
||||
if sha1Fingerprint != expectedSha1Fingerprint {
|
||||
t.Errorf("SHA1 fingerprint mismatch:\nExpected: %s\nActual: %s",
|
||||
expectedSha1Fingerprint, sha1Fingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashedFingerprintGeneration(t *testing.T) {
|
||||
testFingerprint := "DA5CEC632A9A544394403BD533E1A7BDE2F26EDD"
|
||||
expectedHashedFingerprint := "922650D27357BE307B3B322A5ABC3E9585AF776F"
|
||||
|
||||
resource := &TorRelayIdentityRsaResource{}
|
||||
hashedFingerprint, err := resource.generateHashedFingerprint(testFingerprint)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate hashed fingerprint: %v", err)
|
||||
}
|
||||
|
||||
if hashedFingerprint != expectedHashedFingerprint {
|
||||
t.Errorf("Hashed fingerprint mismatch:\nExpected: %s\nActual: %s",
|
||||
expectedHashedFingerprint, hashedFingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
const testAccTorRelayIdentityRsaResourceConfig = `
|
||||
resource "tor_relay_identity_rsa" "test" {}
|
||||
`
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue