Compare commits

..

3 commits
main ... main

Author SHA1 Message Date
e1dc9e1ffa Update README
All checks were successful
CI / ci (push) Successful in 4m18s
2025-06-06 13:04:33 +02:00
ba5761e973 Update documentation
Some checks failed
CI / ci (push) Has been cancelled
2025-06-06 13:02:05 +02:00
ec57a47ba2 Implement the tor_family_identity resource 2025-06-06 12:57:37 +02:00
14 changed files with 44 additions and 317 deletions

12
.gitignore vendored
View file

@ -1,6 +1,6 @@
.*
*.dll *.dll
*.exe *.exe
.DS_Store
example.tf example.tf
terraform.tfplan terraform.tfplan
terraform.tfstate terraform.tfstate
@ -15,9 +15,12 @@ website/node_modules
.vagrant/ .vagrant/
*.backup *.backup
./*.tfstate ./*.tfstate
.terraform/
*.log *.log
*.bak *.bak
*~ *~
.*.swp
.idea
*.iml *.iml
*.test *.test
*.iml *.iml
@ -32,11 +35,10 @@ website/vendor
*.winfile eol=crlf *.winfile eol=crlf
terraform-provider-tor terraform-provider-tor
dev/ dev/
*.md CLAUDE.md
!README.md
!CHANGELOG.md
!CONTRIBUTING.md
extra extra
.direnv
.claude
e2e-tests/**/*tfstate* e2e-tests/**/*tfstate*
e2e-tests/**/providers e2e-tests/**/providers
e2e-tests/**/.terraform.lock.hcl e2e-tests/**/.terraform.lock.hcl

View file

@ -58,8 +58,6 @@ resource "local_sensitive_file" "family_key" {
resource "tor_relay_identity_rsa" "bridge" {} resource "tor_relay_identity_rsa" "bridge" {}
resource "tor_relay_identity_ed25519" "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" { resource "tor_obfs4_state" "bridge" {
rsa_identity_private_key = tor_relay_identity_rsa.bridge.private_key_pem rsa_identity_private_key = tor_relay_identity_rsa.bridge.private_key_pem
@ -82,23 +80,12 @@ output "rsa_identity_pem" {
sensitive = true 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" { output "ed25519_identity_pem" {
description = "Ed25519 identity private key for bridge configuration (PEM format)" description = "Ed25519 identity private key for bridge configuration"
value = tor_relay_identity_ed25519.bridge.private_key_pem value = tor_relay_identity_ed25519.bridge.private_key_pem
sensitive = true 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" { output "obfs4_state_json" {
description = "Complete obfs4 state for bridge runtime" description = "Complete obfs4 state for bridge runtime"
value = tor_obfs4_state.bridge.state_json value = tor_obfs4_state.bridge.state_json

View file

@ -52,7 +52,5 @@ output "public_key_fingerprint_sha256" {
- `algorithm` (String) Name of the algorithm used when generating the private key (always 'Ed25519') - `algorithm` (String) Name of the algorithm used when generating the private key (always 'Ed25519')
- `id` (String) Unique identifier based on public key fingerprint - `id` (String) Unique identifier based on public key fingerprint
- `private_key_pem` (String, Sensitive) Private key data in PEM (RFC 1421) format - `private_key_pem` (String, Sensitive) Private key data in PEM (RFC 1421) format
- `private_key_tor` (String, Sensitive) Private key data in Tor's binary format, base64 encoded - `public_key_fingerprint_sha256` (String) SHA256 fingerprint of the public key in hex format
- `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_pem` (String) Public key data in PEM (RFC 1421) format
- `public_key_tor` (String) Public key data in Tor's binary format, base64 encoded

View file

@ -49,11 +49,6 @@ output "public_key_fingerprint_sha256" {
description = "SHA256 fingerprint of the RSA public key" description = "SHA256 fingerprint of the RSA public key"
value = tor_relay_identity_rsa.example.public_key_fingerprint_sha256 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 --> <!-- schema generated by tfplugindocs -->
@ -65,6 +60,5 @@ output "public_key_fingerprint_sha1_hashed" {
- `id` (String) Unique identifier based on public key fingerprint - `id` (String) Unique identifier based on public key fingerprint
- `private_key_pem` (String, Sensitive) Private key data in PEM (RFC 1421) format - `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` (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_fingerprint_sha256` (String) SHA256 fingerprint of the public key in hex format
- `public_key_pem` (String) Public key data in PEM (RFC 1421) format - `public_key_pem` (String) Public key data in PEM (RFC 1421) format

View file

@ -26,13 +26,6 @@ resource "tor_obfs4_state" "bridge" {
ed25519_identity_private_key = tor_relay_identity_ed25519.bridge.private_key_pem 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 # Generate bridge line for client distribution
data "tor_obfs4_bridge_line" "bridge" { data "tor_obfs4_bridge_line" "bridge" {
ip_address = "203.0.113.1" ip_address = "203.0.113.1"
@ -53,27 +46,11 @@ output "rsa_fingerprint_sha256" {
value = tor_relay_identity_rsa.bridge.public_key_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" { output "ed25519_fingerprint_sha256" {
description = "Ed25519 identity fingerprint (SHA256)" description = "Ed25519 identity fingerprint (SHA256)"
value = tor_relay_identity_ed25519.bridge.public_key_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" { output "obfs4_certificate" {
description = "obfs4 certificate for bridge line" description = "obfs4 certificate for bridge line"
value = tor_obfs4_state.bridge.certificate value = tor_obfs4_state.bridge.certificate

View file

@ -17,10 +17,6 @@ resource "tor_family_identity" "this" {
family_name = "MyFamily" family_name = "MyFamily"
} }
resource "tor_relay_identity_rsa" "this" {}
resource "tor_relay_identity_ed25519" "this" {}
resource "local_sensitive_file" "family_key" { resource "local_sensitive_file" "family_key" {
content_base64 = tor_family_identity.this.secret_key content_base64 = tor_family_identity.this.secret_key
filename = "./data/keys/MyKey.secret_family_key" filename = "./data/keys/MyKey.secret_family_key"
@ -46,34 +42,3 @@ output "family_id" {
description = "Family ID for the bridge" description = "Family ID for the bridge"
value = tor_family_identity.this.id 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
}

View file

@ -33,9 +33,4 @@ output "public_key_fingerprint_sha1" {
output "public_key_fingerprint_sha256" { output "public_key_fingerprint_sha256" {
description = "SHA256 fingerprint of the RSA public key" description = "SHA256 fingerprint of the RSA public key"
value = tor_relay_identity_rsa.example.public_key_fingerprint_sha256 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
} }

View file

@ -8,7 +8,6 @@
let let
supportedSystems = [ supportedSystems = [
"x86_64-linux" "x86_64-linux"
"aarch64-darwin"
]; ];
forEachSupportedSystem = forEachSupportedSystem =
f: f:

View file

@ -31,8 +31,9 @@ func TestTorObfs4BridgeLineDataSource(t *testing.T) {
// Check computed values are generated // Check computed values are generated
resource.TestCheckResourceAttrSet("data.tor_obfs4_bridge_line.test", "bridge_line"), resource.TestCheckResourceAttrSet("data.tor_obfs4_bridge_line.test", "bridge_line"),
// Check bridge line format
resource.TestMatchResourceAttr("data.tor_obfs4_bridge_line.test", "bridge_line", 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 // 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"), resource.TestCheckResourceAttrPair("data.tor_obfs4_bridge_line.test", "identity_fingerprint_sha1", "tor_relay_identity_rsa.test", "public_key_fingerprint_sha1"),

View file

@ -406,7 +406,11 @@ func (r *TorObfs4StateResource) deriveNodeIdFromRsaKey(rsaPrivateKeyPem string)
return nil, fmt.Errorf("failed to parse RSA private key: %w", err) return nil, fmt.Errorf("failed to parse RSA private key: %w", err)
} }
publicKeyBytes := x509.MarshalPKCS1PublicKey(&privateKey.PublicKey) // 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)
}
// Generate SHA1 hash of public key (this is the relay fingerprint/node ID) // Generate SHA1 hash of public key (this is the relay fingerprint/node ID)
hash := sha1.Sum(publicKeyBytes) hash := sha1.Sum(publicKeyBytes)

View file

@ -7,11 +7,10 @@ import (
"context" "context"
"crypto/ed25519" "crypto/ed25519"
"crypto/rand" "crypto/rand"
"crypto/sha256"
"crypto/x509" "crypto/x509"
"encoding/base64"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"strings"
"github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema"
@ -34,8 +33,6 @@ type TorRelayIdentityEd25519ResourceModel struct {
Algorithm types.String `tfsdk:"algorithm"` Algorithm types.String `tfsdk:"algorithm"`
PrivateKeyPem types.String `tfsdk:"private_key_pem"` PrivateKeyPem types.String `tfsdk:"private_key_pem"`
PublicKeyPem types.String `tfsdk:"public_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"` PublicKeyFingerprintSha256 types.String `tfsdk:"public_key_fingerprint_sha256"`
} }
@ -77,24 +74,9 @@ func (r *TorRelayIdentityEd25519Resource) Schema(ctx context.Context, req resour
stringplanmodifier.UseStateForUnknown(), 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{ "public_key_fingerprint_sha256": schema.StringAttribute{
Computed: true, Computed: true,
MarkdownDescription: "Base64 encoded public key bytes (32 bytes) without padding, used as the Tor Ed25519 fingerprint", MarkdownDescription: "SHA256 fingerprint of the public key in hex format",
PlanModifiers: []planmodifier.String{ PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(), stringplanmodifier.UseStateForUnknown(),
}, },
@ -143,19 +125,12 @@ func (r *TorRelayIdentityEd25519Resource) Create(ctx context.Context, req resour
} }
data.PublicKeyPem = types.StringValue(publicKeyPem) data.PublicKeyPem = types.StringValue(publicKeyPem)
// Encode keys in Tor format // Generate SHA256 fingerprint
privateKeyTor := r.encodeTorPrivateKey(privateKey) sha256Fingerprint := r.generateSha256Fingerprint(publicKey)
data.PrivateKeyTor = types.StringValue(privateKeyTor) data.PublicKeyFingerprintSha256 = types.StringValue(sha256Fingerprint)
publicKeyTor := r.encodeTorPublicKey(publicKey) // Generate ID from SHA256 fingerprint
data.PublicKeyTor = types.StringValue(publicKeyTor) data.Id = types.StringValue(fmt.Sprintf("ed25519-%s", sha256Fingerprint[:16]))
// 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") tflog.Trace(ctx, "created tor relay identity Ed25519 resource")
@ -206,37 +181,8 @@ func (r *TorRelayIdentityEd25519Resource) encodePublicKeyPEM(publicKey ed25519.P
return string(publicKeyPem), nil return string(publicKeyPem), nil
} }
func (r *TorRelayIdentityEd25519Resource) generateEd25519Fingerprint(publicKey ed25519.PublicKey) string { func (r *TorRelayIdentityEd25519Resource) generateSha256Fingerprint(publicKey ed25519.PublicKey) string {
fingerprint := base64.StdEncoding.EncodeToString(publicKey) publicKeyBytes, _ := x509.MarshalPKIXPublicKey(publicKey)
return strings.TrimRight(fingerprint, "=") sha256Sum := sha256.Sum256(publicKeyBytes)
} return fmt.Sprintf("%x", sha256Sum)
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)
} }

View file

@ -4,7 +4,6 @@
package provider package provider
import ( import (
"encoding/base64"
"regexp" "regexp"
"testing" "testing"
@ -28,67 +27,14 @@ func TestAccTorRelayIdentityEd25519Resource(t *testing.T) {
// Verify PEM format // 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", "private_key_pem", regexp.MustCompile(`^-----BEGIN PRIVATE KEY-----`)),
resource.TestMatchResourceAttr("tor_relay_identity_ed25519.test", "public_key_pem", regexp.MustCompile(`^-----BEGIN PUBLIC KEY-----`)), resource.TestMatchResourceAttr("tor_relay_identity_ed25519.test", "public_key_pem", regexp.MustCompile(`^-----BEGIN PUBLIC KEY-----`)),
resource.TestMatchResourceAttr("tor_relay_identity_ed25519.test", "public_key_fingerprint_sha256", regexp.MustCompile(`^[A-Za-z0-9+/]{43}$`)), // 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}$`)),
), ),
}, },
}, },
}) })
} }
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 = ` const testAccTorRelayIdentityEd25519ResourceConfig = `
resource "tor_relay_identity_ed25519" "test" {} resource "tor_relay_identity_ed25519" "test" {}
` `

View file

@ -10,7 +10,6 @@ import (
"crypto/sha1" "crypto/sha1"
"crypto/sha256" "crypto/sha256"
"crypto/x509" "crypto/x509"
"encoding/hex"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
@ -31,13 +30,12 @@ func NewTorRelayIdentityRsaResource() resource.Resource {
type TorRelayIdentityRsaResource struct{} type TorRelayIdentityRsaResource struct{}
type TorRelayIdentityRsaResourceModel struct { type TorRelayIdentityRsaResourceModel struct {
Id types.String `tfsdk:"id"` Id types.String `tfsdk:"id"`
Algorithm types.String `tfsdk:"algorithm"` Algorithm types.String `tfsdk:"algorithm"`
PrivateKeyPem types.String `tfsdk:"private_key_pem"` PrivateKeyPem types.String `tfsdk:"private_key_pem"`
PublicKeyPem types.String `tfsdk:"public_key_pem"` PublicKeyPem types.String `tfsdk:"public_key_pem"`
PublicKeyFingerprintSha1 types.String `tfsdk:"public_key_fingerprint_sha1"` PublicKeyFingerprintSha1 types.String `tfsdk:"public_key_fingerprint_sha1"`
PublicKeyFingerprintSha256 types.String `tfsdk:"public_key_fingerprint_sha256"` 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) { func (r *TorRelayIdentityRsaResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
@ -92,13 +90,6 @@ func (r *TorRelayIdentityRsaResource) Schema(ctx context.Context, req resource.S
stringplanmodifier.UseStateForUnknown(), 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(),
},
},
}, },
} }
} }
@ -155,15 +146,6 @@ func (r *TorRelayIdentityRsaResource) Create(ctx context.Context, req resource.C
data.PublicKeyFingerprintSha1 = types.StringValue(sha1Fingerprint) data.PublicKeyFingerprintSha1 = types.StringValue(sha1Fingerprint)
data.PublicKeyFingerprintSha256 = types.StringValue(sha256Fingerprint) 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 // Generate ID from SHA1 fingerprint
data.Id = types.StringValue(fmt.Sprintf("rsa-%s", sha1Fingerprint[:16])) data.Id = types.StringValue(fmt.Sprintf("rsa-%s", sha1Fingerprint[:16]))
@ -214,25 +196,16 @@ func (r *TorRelayIdentityRsaResource) encodePublicKeyPEM(publicKey *rsa.PublicKe
} }
func (r *TorRelayIdentityRsaResource) generateFingerprints(publicKey *rsa.PublicKey) (string, string, error) { func (r *TorRelayIdentityRsaResource) generateFingerprints(publicKey *rsa.PublicKey) (string, string, error) {
publicKeyBytes := x509.MarshalPKCS1PublicKey(publicKey) publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return "", "", err
}
sha1Sum := sha1.Sum(publicKeyBytes) sha1Sum := sha1.Sum(publicKeyBytes)
sha256Sum := sha256.Sum256(publicKeyBytes) sha256Sum := sha256.Sum256(publicKeyBytes)
sha1Fingerprint := fmt.Sprintf("%X", sha1Sum) sha1Fingerprint := fmt.Sprintf("%x", sha1Sum)
sha256Fingerprint := fmt.Sprintf("%X", sha256Sum) sha256Fingerprint := fmt.Sprintf("%x", sha256Sum)
return sha1Fingerprint, sha256Fingerprint, nil 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
}

View file

@ -4,8 +4,6 @@
package provider package provider
import ( import (
"crypto/x509"
"encoding/pem"
"regexp" "regexp"
"testing" "testing"
@ -27,76 +25,18 @@ func TestAccTorRelayIdentityRsaResource(t *testing.T) {
resource.TestCheckResourceAttrSet("tor_relay_identity_rsa.test", "public_key_pem"), 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_sha1"),
resource.TestCheckResourceAttrSet("tor_relay_identity_rsa.test", "public_key_fingerprint_sha256"), 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 // 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", "private_key_pem", regexp.MustCompile(`^-----BEGIN RSA PRIVATE KEY-----`)),
resource.TestMatchResourceAttr("tor_relay_identity_rsa.test", "public_key_pem", regexp.MustCompile(`^-----BEGIN PUBLIC KEY-----`)), resource.TestMatchResourceAttr("tor_relay_identity_rsa.test", "public_key_pem", regexp.MustCompile(`^-----BEGIN PUBLIC KEY-----`)),
resource.TestMatchResourceAttr("tor_relay_identity_rsa.test", "public_key_fingerprint_sha1", regexp.MustCompile(`^[0-9A-F]{40}$`)), // Verify fingerprint formats (hex strings)
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_sha1_hashed", regexp.MustCompile(`^[0-9A-F]{40}$`)), resource.TestMatchResourceAttr("tor_relay_identity_rsa.test", "public_key_fingerprint_sha256", regexp.MustCompile(`^[0-9a-f]{64}$`)),
), ),
}, },
}, },
}) })
} }
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 = ` const testAccTorRelayIdentityRsaResourceConfig = `
resource "tor_relay_identity_rsa" "test" {} resource "tor_relay_identity_rsa" "test" {}
` `