Compare commits

..

12 commits

Author SHA1 Message Date
139f5f6d9c run nix fmt 2025-09-10 14:14:13 +02:00
7f6d7cc0c4 Enhance tor-family e2e test to validate RSA and ED25519 relay identity fixes 2025-09-10 14:13:18 +02:00
43c442ad20 Update obfs4 e2e test to include new ED25519 Tor format and RSA hashed fingerprint outputs 2025-09-10 14:06:28 +02:00
f1cccbe22b Add Tor-specific binary format for ED25519 keys
fixes #5
2025-09-10 14:00:12 +02:00
5bf771ac96 Fix Ed25519 fingerprint to use base64 encoded public key bytes
fixes #4
2025-09-10 13:49:21 +02:00
b406226f0f add aarch64-darwin to supported systems for dev shell 2025-09-10 13:49:21 +02:00
4eadc8416e Add hashed fingerprint functionality to RSA relay identity keys
fixes #3
2025-09-10 13:45:39 +02:00
62b243c8e4 Fix RSA fingerprint generation to use PKCS1 encoding and uppercase format
related #2
2025-09-10 13:45:39 +02:00
83df31ec80 Fix incorrect RSA identity key fingerprint generation
The fingerprint calculation was using PKIX encoding instead of the
required PKCS1 DER encoding for RSA public keys. This affected both
the relay identity resource and obfs4 node ID derivation.

- Use x509.MarshalPKCS1PublicKey instead of x509.MarshalPKIXPublicKey
- Add test case with known fingerprint vector to prevent regression
- Update both generateFingerprints and deriveNodeIdFromRsaKey functions

fixes #2
2025-09-10 13:45:39 +02:00
005634ff1c Update README 2025-09-10 13:45:39 +02:00
f60d0d655b Update documentation 2025-09-10 13:45:39 +02:00
53f019906e Implement the tor_family_identity resource 2025-09-10 13:45:39 +02:00
13 changed files with 316 additions and 44 deletions

12
.gitignore vendored
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -34,3 +34,8 @@ 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
}

View file

@ -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"),

View file

@ -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)

View file

@ -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)
}

View file

@ -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" {}
`

View file

@ -10,6 +10,7 @@ import (
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"fmt"
@ -36,6 +37,7 @@ type TorRelayIdentityRsaResourceModel struct {
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
}

View file

@ -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" {}
`