Compare commits
No commits in common. "7f6d7cc0c44ba70178c09faa556c493c4a0bd7a0" and "4eadc8416e79f0d4a1b90dbdc5b6f747db2a2719" have entirely different histories.
7f6d7cc0c4
...
4eadc8416e
7 changed files with 14 additions and 192 deletions
10
README.md
10
README.md
|
@ -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
|
||||||
|
@ -88,17 +86,11 @@ output "rsa_fingerprint_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
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
let
|
let
|
||||||
supportedSystems = [
|
supportedSystems = [
|
||||||
"x86_64-linux"
|
"x86_64-linux"
|
||||||
"aarch64-darwin"
|
|
||||||
];
|
];
|
||||||
forEachSupportedSystem =
|
forEachSupportedSystem =
|
||||||
f:
|
f:
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -28,67 +27,13 @@ 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}$`)),
|
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" {}
|
||||||
`
|
`
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue