diff options
| author | gabrix73 <gabriel1@frozenstar.info> | 2026-04-10 16:10:48 +0200 |
|---|---|---|
| committer | gabrix73 <gabriel1@frozenstar.info> | 2026-04-10 16:10:48 +0200 |
| commit | 88375da30ea667ec25c2000874557fcc4785a558 (patch) | |
| tree | 71364a43230977481c6d3280246aa8bb48562bdc /addressbook | |
| download | khimera-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 'addressbook')
| -rw-r--r-- | addressbook/addressbook.go | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/addressbook/addressbook.go b/addressbook/addressbook.go new file mode 100644 index 0000000..e6876c0 --- /dev/null +++ b/addressbook/addressbook.go @@ -0,0 +1,217 @@ +// Package addressbook manages contacts and their public keys +package addressbook + +import ( + "crypto/ed25519" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "sync" + "time" +) + +// Contact represents a contact in the address book +type Contact struct { + Name string `json:"name"` + PublicKey string `json:"public_key"` // hex-encoded Ed25519 public key + OnionAddress string `json:"onion_address"` // Tor hidden service address + Fingerprint string `json:"fingerprint"` // Short fingerprint for verification + AddedAt time.Time `json:"added_at"` + LastSeen time.Time `json:"last_seen"` + Verified bool `json:"verified"` // Manual verification flag + Notes string `json:"notes"` + Metadata map[string]string `json:"metadata,omitempty"` +} + +// AddressBook manages a collection of contacts +type AddressBook struct { + contacts map[string]*Contact // key = public key hex + mu sync.RWMutex + path string +} + +// NewAddressBook creates a new empty address book +func NewAddressBook() *AddressBook { + return &AddressBook{ + contacts: make(map[string]*Contact), + } +} + +// LoadAddressBook loads an address book from disk +func LoadAddressBook(path string) (*AddressBook, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read address book: %w", err) + } + + var contacts []*Contact + if err := json.Unmarshal(data, &contacts); err != nil { + return nil, fmt.Errorf("failed to parse address book: %w", err) + } + + ab := &AddressBook{ + contacts: make(map[string]*Contact), + path: path, + } + + for _, contact := range contacts { + ab.contacts[contact.PublicKey] = contact + } + + return ab, nil +} + +// Save saves the address book to disk +func (ab *AddressBook) Save(path string) error { + ab.mu.RLock() + defer ab.mu.RUnlock() + + // Convert map to slice + contacts := make([]*Contact, 0, len(ab.contacts)) + for _, contact := range ab.contacts { + contacts = append(contacts, contact) + } + + // Marshal to JSON + data, err := json.MarshalIndent(contacts, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal address book: %w", err) + } + + // Write to file + if err := os.WriteFile(path, data, 0600); err != nil { + return fmt.Errorf("failed to write address book: %w", err) + } + + ab.path = path + return nil +} + +// AddContact adds a new contact to the address book +func (ab *AddressBook) AddContact(contact *Contact) error { + ab.mu.Lock() + defer ab.mu.Unlock() + + // Validate public key + pubKeyBytes, err := hex.DecodeString(contact.PublicKey) + if err != nil { + return fmt.Errorf("invalid public key hex: %w", err) + } + if len(pubKeyBytes) != ed25519.PublicKeySize { + return fmt.Errorf("invalid public key size: expected %d, got %d", ed25519.PublicKeySize, len(pubKeyBytes)) + } + + // Generate fingerprint (first 8 bytes of public key) + contact.Fingerprint = hex.EncodeToString(pubKeyBytes[:8]) + contact.AddedAt = time.Now() + contact.LastSeen = time.Now() + + ab.contacts[contact.PublicKey] = contact + return nil +} + +// GetContact retrieves a contact by public key +func (ab *AddressBook) GetContact(publicKey string) (*Contact, bool) { + ab.mu.RLock() + defer ab.mu.RUnlock() + + contact, exists := ab.contacts[publicKey] + return contact, exists +} + +// GetContactByName retrieves a contact by name (first match) +func (ab *AddressBook) GetContactByName(name string) (*Contact, bool) { + ab.mu.RLock() + defer ab.mu.RUnlock() + + for _, contact := range ab.contacts { + if contact.Name == name { + return contact, true + } + } + return nil, false +} + +// RemoveContact removes a contact from the address book +func (ab *AddressBook) RemoveContact(publicKey string) bool { + ab.mu.Lock() + defer ab.mu.Unlock() + + if _, exists := ab.contacts[publicKey]; exists { + delete(ab.contacts, publicKey) + return true + } + return false +} + +// UpdateLastSeen updates the last seen timestamp for a contact +func (ab *AddressBook) UpdateLastSeen(publicKey string) { + ab.mu.Lock() + defer ab.mu.Unlock() + + if contact, exists := ab.contacts[publicKey]; exists { + contact.LastSeen = time.Now() + } +} + +// MarkVerified marks a contact as manually verified +func (ab *AddressBook) MarkVerified(publicKey string, verified bool) error { + ab.mu.Lock() + defer ab.mu.Unlock() + + contact, exists := ab.contacts[publicKey] + if !exists { + return fmt.Errorf("contact not found") + } + + contact.Verified = verified + return nil +} + +// ListContacts returns all contacts +func (ab *AddressBook) ListContacts() []*Contact { + ab.mu.RLock() + defer ab.mu.RUnlock() + + contacts := make([]*Contact, 0, len(ab.contacts)) + for _, contact := range ab.contacts { + contacts = append(contacts, contact) + } + return contacts +} + +// Count returns the number of contacts +func (ab *AddressBook) Count() int { + ab.mu.RLock() + defer ab.mu.RUnlock() + return len(ab.contacts) +} + +// Clear removes all contacts +func (ab *AddressBook) Clear() { + ab.mu.Lock() + defer ab.mu.Unlock() + ab.contacts = make(map[string]*Contact) +} + +// VerifyFingerprint checks if a fingerprint matches a public key +func VerifyFingerprint(publicKeyHex, fingerprint string) bool { + pubKeyBytes, err := hex.DecodeString(publicKeyHex) + if err != nil { + return false + } + if len(pubKeyBytes) < 8 { + return false + } + + expectedFingerprint := hex.EncodeToString(pubKeyBytes[:8]) + return expectedFingerprint == fingerprint +} + +// String returns a string representation +func (ab *AddressBook) String() string { + ab.mu.RLock() + defer ab.mu.RUnlock() + return fmt.Sprintf("AddressBook(contacts=%d)", len(ab.contacts)) +} |
