From 88375da30ea667ec25c2000874557fcc4785a558 Mon Sep 17 00:00:00 2001 From: gabrix73 Date: Fri, 10 Apr 2026 16:10:48 +0200 Subject: 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 --- identity/identity.go | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 identity/identity.go (limited to 'identity') 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]) +} -- cgit v1.2.3