diff --git a/.gitignore b/.gitignore index f0e5eea..e4c5140 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md index 3ff504d..a8fefa5 100644 --- a/README.md +++ b/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 diff --git a/docs/resources/relay_identity_ed25519.md b/docs/resources/relay_identity_ed25519.md index b1662d9..79b32df 100644 --- a/docs/resources/relay_identity_ed25519.md +++ b/docs/resources/relay_identity_ed25519.md @@ -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 diff --git a/docs/resources/relay_identity_rsa.md b/docs/resources/relay_identity_rsa.md index b67920d..6b1599b 100644 --- a/docs/resources/relay_identity_rsa.md +++ b/docs/resources/relay_identity_rsa.md @@ -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 +} ``` @@ -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 diff --git a/e2e-tests/obfs4/main.tf b/e2e-tests/obfs4/main.tf index 08a3d97..01e21b5 100644 --- a/e2e-tests/obfs4/main.tf +++ b/e2e-tests/obfs4/main.tf @@ -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 diff --git a/e2e-tests/tor-family/main.tf b/e2e-tests/tor-family/main.tf index e851a18..f2417a4 100644 --- a/e2e-tests/tor-family/main.tf +++ b/e2e-tests/tor-family/main.tf @@ -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 +} diff --git a/examples/resources/tor_relay_identity_rsa/resource.tf b/examples/resources/tor_relay_identity_rsa/resource.tf index d88113f..76c6203 100644 --- a/examples/resources/tor_relay_identity_rsa/resource.tf +++ b/examples/resources/tor_relay_identity_rsa/resource.tf @@ -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 } \ No newline at end of file diff --git a/flake.nix b/flake.nix index 363a18f..46453b4 100644 --- a/flake.nix +++ b/flake.nix @@ -8,6 +8,7 @@ let supportedSystems = [ "x86_64-linux" + "aarch64-darwin" ]; forEachSupportedSystem = f: diff --git a/internal/provider/tor_obfs4_bridge_line_data_source_test.go b/internal/provider/tor_obfs4_bridge_line_data_source_test.go index d600fe0..3f30230 100644 --- a/internal/provider/tor_obfs4_bridge_line_data_source_test.go +++ b/internal/provider/tor_obfs4_bridge_line_data_source_test.go @@ -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"), diff --git a/internal/provider/tor_obfs4_state_resource.go b/internal/provider/tor_obfs4_state_resource.go index 97e8dd5..7fe87b3 100644 --- a/internal/provider/tor_obfs4_state_resource.go +++ b/internal/provider/tor_obfs4_state_resource.go @@ -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) diff --git a/internal/provider/tor_relay_identity_ed25519_resource.go b/internal/provider/tor_relay_identity_ed25519_resource.go index 4a3f29d..fdec904 100644 --- a/internal/provider/tor_relay_identity_ed25519_resource.go +++ b/internal/provider/tor_relay_identity_ed25519_resource.go @@ -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) } diff --git a/internal/provider/tor_relay_identity_ed25519_resource_test.go b/internal/provider/tor_relay_identity_ed25519_resource_test.go index 7b0873e..ea2638e 100644 --- a/internal/provider/tor_relay_identity_ed25519_resource_test.go +++ b/internal/provider/tor_relay_identity_ed25519_resource_test.go @@ -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" {} ` diff --git a/internal/provider/tor_relay_identity_rsa_resource.go b/internal/provider/tor_relay_identity_rsa_resource.go index 2a6e453..bf8ccb6 100644 --- a/internal/provider/tor_relay_identity_rsa_resource.go +++ b/internal/provider/tor_relay_identity_rsa_resource.go @@ -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 +} diff --git a/internal/provider/tor_relay_identity_rsa_resource_test.go b/internal/provider/tor_relay_identity_rsa_resource_test.go index f670f89..58da2b9 100644 --- a/internal/provider/tor_relay_identity_rsa_resource_test.go +++ b/internal/provider/tor_relay_identity_rsa_resource_test.go @@ -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" {} `