summaryrefslogtreecommitdiffstats
path: root/identity/identity.go
diff options
context:
space:
mode:
authorgabrix73 <gabriel1@frozenstar.info>2026-04-10 16:10:48 +0200
committergabrix73 <gabriel1@frozenstar.info>2026-04-10 16:10:48 +0200
commit88375da30ea667ec25c2000874557fcc4785a558 (patch)
tree71364a43230977481c6d3280246aa8bb48562bdc /identity/identity.go
downloadkhimera-88375da30ea667ec25c2000874557fcc4785a558.tar.gz
khimera-88375da30ea667ec25c2000874557fcc4785a558.tar.xz
khimera-88375da30ea667ec25c2000874557fcc4785a558.zip
Initial commit: Veilith v0.9 - P2P Encrypted Messenger
Military-grade secure messenger with: - End-to-End Encryption (Noise Protocol XX) - Forward Secrecy (automatic session rotation) - Anti-Traffic Analysis (padding + cover traffic) - Secure Deletion (DOD 5220.22-M) - Tor Integration (embedded with bridge support) - Portable Mode (no installation required) Generated with Claude Code
Diffstat (limited to 'identity/identity.go')
-rw-r--r--identity/identity.go167
1 files changed, 167 insertions, 0 deletions
diff --git a/identity/identity.go b/identity/identity.go
new file mode 100644
index 0000000..407e524
--- /dev/null
+++ b/identity/identity.go
@@ -0,0 +1,167 @@
+// Package identity manages Ed25519 cryptographic identities
+package identity
+
+import (
+ "crypto/ed25519"
+ "crypto/rand"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "os"
+
+ "golang.org/x/crypto/scrypt"
+ "golang.org/x/crypto/nacl/secretbox"
+)
+
+// Identity represents a cryptographic identity
+type Identity struct {
+ PublicKey ed25519.PublicKey
+ PrivateKey ed25519.PrivateKey
+}
+
+// encryptedIdentity is the encrypted on-disk format
+type encryptedIdentity struct {
+ Nonce [24]byte `json:"nonce"`
+ Ciphertext []byte `json:"ciphertext"`
+ Salt []byte `json:"salt"`
+}
+
+// NewIdentity generates a new Ed25519 identity
+func NewIdentity() (*Identity, error) {
+ pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ return nil, fmt.Errorf("failed to generate keypair: %w", err)
+ }
+
+ return &Identity{
+ PublicKey: pubKey,
+ PrivateKey: privKey,
+ }, nil
+}
+
+// LoadIdentity loads an encrypted identity from disk
+func LoadIdentity(path string, passphrase []byte) (*Identity, error) {
+ // Read encrypted file
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read identity file: %w", err)
+ }
+
+ // Parse JSON
+ var encrypted encryptedIdentity
+ if err := json.Unmarshal(data, &encrypted); err != nil {
+ return nil, fmt.Errorf("failed to parse identity file: %w", err)
+ }
+
+ // Derive encryption key from passphrase
+ key, err := scrypt.Key(passphrase, encrypted.Salt, 32768, 8, 1, 32)
+ if err != nil {
+ return nil, fmt.Errorf("failed to derive key: %w", err)
+ }
+
+ // Decrypt private key
+ var keyArray [32]byte
+ copy(keyArray[:], key)
+
+ plaintext, ok := secretbox.Open(nil, encrypted.Ciphertext, &encrypted.Nonce, &keyArray)
+ if !ok {
+ return nil, fmt.Errorf("decryption failed: wrong passphrase or corrupted file")
+ }
+
+ // Parse private key
+ if len(plaintext) != ed25519.PrivateKeySize {
+ return nil, fmt.Errorf("invalid private key size")
+ }
+
+ privKey := ed25519.PrivateKey(plaintext)
+ pubKey := privKey.Public().(ed25519.PublicKey)
+
+ return &Identity{
+ PublicKey: pubKey,
+ PrivateKey: privKey,
+ }, nil
+}
+
+// Save encrypts and saves the identity to disk
+func (id *Identity) Save(path string, passphrase []byte) error {
+ // Generate random salt
+ salt := make([]byte, 32)
+ if _, err := rand.Read(salt); err != nil {
+ return fmt.Errorf("failed to generate salt: %w", err)
+ }
+
+ // Derive encryption key
+ key, err := scrypt.Key(passphrase, salt, 32768, 8, 1, 32)
+ if err != nil {
+ return fmt.Errorf("failed to derive key: %w", err)
+ }
+
+ // Generate random nonce
+ var nonce [24]byte
+ if _, err := rand.Read(nonce[:]); err != nil {
+ return fmt.Errorf("failed to generate nonce: %w", err)
+ }
+
+ // Encrypt private key
+ var keyArray [32]byte
+ copy(keyArray[:], key)
+
+ ciphertext := secretbox.Seal(nil, id.PrivateKey, &nonce, &keyArray)
+
+ // Create encrypted structure
+ encrypted := encryptedIdentity{
+ Nonce: nonce,
+ Ciphertext: ciphertext,
+ Salt: salt,
+ }
+
+ // Marshal to JSON
+ data, err := json.MarshalIndent(encrypted, "", " ")
+ if err != nil {
+ return fmt.Errorf("failed to marshal identity: %w", err)
+ }
+
+ // Write to file with secure permissions
+ if err := os.WriteFile(path, data, 0600); err != nil {
+ return fmt.Errorf("failed to write identity file: %w", err)
+ }
+
+ return nil
+}
+
+// GetPublicKeyHex returns the public key as hex string
+func (id *Identity) GetPublicKeyHex() string {
+ return hex.EncodeToString(id.PublicKey)
+}
+
+// GetPrivateKeyHex returns the private key as hex string (use with caution!)
+func (id *Identity) GetPrivateKeyHex() string {
+ return hex.EncodeToString(id.PrivateKey)
+}
+
+// Sign signs a message with the private key
+func (id *Identity) Sign(message []byte) []byte {
+ return ed25519.Sign(id.PrivateKey, message)
+}
+
+// Verify verifies a signature
+func (id *Identity) Verify(message, signature []byte) bool {
+ return ed25519.Verify(id.PublicKey, message, signature)
+}
+
+// SecureWipe zeros out sensitive key material from memory
+func (id *Identity) SecureWipe() {
+ // Zero out private key
+ for i := range id.PrivateKey {
+ id.PrivateKey[i] = 0
+ }
+ // Public key is not sensitive, but wipe it anyway
+ for i := range id.PublicKey {
+ id.PublicKey[i] = 0
+ }
+}
+
+// String returns a safe string representation
+func (id *Identity) String() string {
+ return fmt.Sprintf("Identity(pubkey=%s...)", id.GetPublicKeyHex()[:16])
+}