kamune

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 8, 2025 License: MIT Imports: 31 Imported by: 0

README

Kamune

Communication over untrusted networks.

Kamune provides Ed25519_ML-KEM-768_HKDF_SHA512_ChaCha20-Poly1305X security suite. Optionally, ML-DSA can be used for full quantum safety.

demo

[!NOTE] This is an experimental project. All suggestions and feedbacks are welcome and greatly appreciated.

Features

  • Message signing and verification using Ed25519, with support for quantum safe ML-DSA-65
  • Ephemeral, quantum-resistant key encapsulation with ML-KEM-768
  • Key derivation via HKDF-SHA512 (HMAC-based extract-and-expand)
  • End-to-End, bidirectional symmetric encryption using ChaCha20-Poly1305X
  • Forward secrecy with Double Ratchet and ECDH exchange
  • Lightweight, custom protocol implemented in both TCP and UDP for minimal overhead and latency
  • Real-time, instant messaging over socket-based connection
  • Direct peer-to-peer communication, no intermediary server required
  • Protobuf for fast, compact binary message encoding

Roadmap

The following is a list of features that are currently planned or have been conceived of. It is by no means exhaustive, and does not imply a commitment to fully implement any or all of them. It will be updated as the project progresses.

Items marked with * are subject to edits, changes, and even partial or total removal.

  • Settle on the cipher suite
  • Write the core functionality
  • Implement a minimal TUI
  • Stabilize the package API
  • Bind ciphers to session-specific info
  • Network protocols support
    • TCP
    • UDP
    • QUIC, WebRTC, or others? *
  • Better timeout and deadline management
  • Double Ratchet
  • Routes and session reconnection
  • Relay server
    • IP discovery
    • Message conveying *
    • Queue persistence *
  • Handling remotes, connection retries, and session management
    • QR code generation
    • Peer name
    • Remote's public key expiration
    • Key rotation *
  • Messaging Layer Security (MLS) and group chats *
  • Provide NAT traversal and/or hole punching strategies *
  • Saving and restoring chat history *
  • Considering FFI or an intermediary server for non-Go clients *
  • Native and/or web clients *
  • Replace Protobuf with a custom encoding\decoding protocol *

How does it work?

There are three stages. In the following terminology, server is the party who is accepting connections, and the client is the party who is trying to establish a connection to the server.

Introduction

Client sends its public key (think of it like an ID card) to the server and server, in return, responds with its own public key (ID card). If both parties verify the other one's identity, handshake process gets started.

Handshake

Client creates a new, ephemeral (one-time use) ml-kem key. Its public key, alongside randomly generated salt and ID prefix are sent to the server.

Server uses the received public key to derive a secret (also called shared secret; as well as a ciphertext that we'll get to in a minute). With that secret, a decryption cipher is created to decrypt incoming messages. By deriving another key from the shared secret, an encryption cipher is also created to encrypt outgoing messages. The ciphertext plus newly generated ID suffix and salt are sent back to the client.

Client uses the received ciphertext and their private key (that was previously generated), to derive the same exact shared secret. Then, encryption and decryption ciphers are created likewise.

To make sure everyone are on the same page, each party performs a challenge to verify that the other party (them) can decipher our messages, and if we can decipher their messages as well.
A random text is created by driving a new key from the shared secret and the agreed upon session ID (which was created by concatenating the ID prefix and suffix). It is encrypted and sent to the other party. They should decrypt the message, encrypt it again with their own encryption cipher, and send it back.
If each side receive and successfully verify their text, the handshake is deemed successful!

Communication

Imagine a post office. When a cargo is accepted, A unique signature is generated based on its content and the sender's identity. Everyone can verify the signature, but only the sender can issue a new one.
The cargo, the signature, and some other info such as timestamp and a number (sequence) are placed inside a box. Then, the box will be locked and sealed. Shipment will be done via a custom gateway specifically designed for this, and it will deliver the package straight to the recipient.

At destination, the parcel will be checked for any kind of temperaments or changes. Using pre-established keys from the handshake phase, smallest modifications will be detected and the package is rejected. If all checks pass successfully, the cargo will be delivered and opened.

Documentation

Overview

Package kamune provides secure communication over untrusted networks.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrConnClosed         = errors.New("connection has been closed")
	ErrInvalidSignature   = errors.New("invalid signature")
	ErrVerificationFailed = errors.New("verification failed")
	ErrMessageTooLarge    = errors.New("message is too large")
	ErrOutOfSync          = errors.New("peers are out of sync")
)
View Source
var (
	ErrMissingChatBucket = errors.New("chat bucket not found")
)
View Source
var (
	ErrPeerExpired = errors.New("peer has been expired")
)

Functions

func Bytes

func Bytes(b []byte) *wrapperspb.BytesValue

Types

type ChatEntry

type ChatEntry struct {
	Timestamp time.Time
	Data      []byte
	Sender    Sender
}

ChatEntry represents a decrypted chat message stored in the DB.

type Conn

type Conn interface {
	net.Conn
	ReadBytes() ([]byte, error)
	WriteBytes(data []byte) error
}

type ConnOption

type ConnOption func(*conn) error

func ConnWithReadTimeout

func ConnWithReadTimeout(timeout time.Duration) ConnOption

func ConnWithWriteTimeout

func ConnWithWriteTimeout(timeout time.Duration) ConnOption

type DialOption

type DialOption func(*Dialer)

func DialWithAlgorithm

func DialWithAlgorithm(a attest.Algorithm) DialOption

func DialWithClientName

func DialWithClientName(name string) DialOption

func DialWithDialTimeout

func DialWithDialTimeout(timeout time.Duration) DialOption

func DialWithExistingConn

func DialWithExistingConn(conn Conn) DialOption

func DialWithReadTimeout

func DialWithReadTimeout(timeout time.Duration) DialOption

func DialWithRemoteVerifier

func DialWithRemoteVerifier(verifier RemoteVerifier) DialOption

func DialWithStorageOpts

func DialWithStorageOpts(opts ...StorageOption) DialOption

func DialWithTCPConn

func DialWithTCPConn(opts ...ConnOption) DialOption

func DialWithUDPConn

func DialWithUDPConn(opts ...ConnOption) DialOption

func DialWithWriteTimeout

func DialWithWriteTimeout(timeout time.Duration) DialOption

type Dialer

type Dialer struct {
	// contains filtered or unexported fields
}

func NewDialer

func NewDialer(addr string, opts ...DialOption) (*Dialer, error)

func (*Dialer) Dial

func (d *Dialer) Dial() (*Transport, error)

func (*Dialer) PublicKey

func (d *Dialer) PublicKey() PublicKey

type HandlerFunc

type HandlerFunc func(t *Transport) error

type Metadata

type Metadata struct {
	// contains filtered or unexported fields
}

func (Metadata) ID

func (m Metadata) ID() string

func (Metadata) SequenceNum

func (m Metadata) SequenceNum() uint64

func (Metadata) Timestamp

func (m Metadata) Timestamp() time.Time

type PassphraseHandler

type PassphraseHandler func() ([]byte, error)

type Peer

type Peer struct {
	FirstSeen time.Time
	PublicKey PublicKey
	Name      string
	Algorithm attest.Algorithm
}

type PublicKey

type PublicKey = attest.PublicKey

type RemoteVerifier

type RemoteVerifier func(store *Storage, peer *Peer) (err error)

type Sender

type Sender uint16
const (
	SenderLocal Sender = 0 + iota
	SenderPeer
)

type Server

type Server struct {
	// contains filtered or unexported fields
}

func NewServer

func NewServer(
	addr string, handler HandlerFunc, opts ...ServerOptions,
) (*Server, error)

func (*Server) ListenAndServe

func (s *Server) ListenAndServe() error

func (*Server) PublicKey

func (s *Server) PublicKey() PublicKey

type ServerOptions

type ServerOptions func(*Server)

func ServeWithAlgorithm

func ServeWithAlgorithm(a attest.Algorithm) ServerOptions

func ServeWithName

func ServeWithName(name string) ServerOptions

func ServeWithRemoteVerifier

func ServeWithRemoteVerifier(remote RemoteVerifier) ServerOptions

func ServeWithStorageOpts

func ServeWithStorageOpts(opts ...StorageOption) ServerOptions

func ServeWithTCP

func ServeWithTCP(opts ...ConnOption) ServerOptions

func ServeWithUDP

func ServeWithUDP(opts ...ConnOption) ServerOptions

type Storage

type Storage struct {
	// contains filtered or unexported fields
}

func OpenStorage

func OpenStorage(opts ...StorageOption) (*Storage, error)

func (*Storage) AddChatEntry

func (s *Storage) AddChatEntry(
	sessionID string, payload []byte, ts time.Time, sender Sender,
) error

AddChatEntry stores a chat message for the given session ID. The message is stored in a bucket named "chat_<sessionID>" and the key begins with an 8-byte big-endian uint64 representation of the timestamp's UnixNano value. 2 bytes are used for the sender identity. Currently, 0 means local user, 1 means remote user. To avoid collisions when two messages have the same timestamp, a 4-byte random suffix is appended to the key to avoid collision. The session ID is used as the bucket name, which scopes entries per session. If the provided timestamp is zero, the current time is used.

func (*Storage) Close

func (s *Storage) Close() error

func (*Storage) FindPeer

func (s *Storage) FindPeer(claim []byte) (*Peer, error)

func (*Storage) GetChatHistory

func (s *Storage) GetChatHistory(sessionID string) ([]ChatEntry, error)

GetChatHistory returns decrypted chat entries stored under a bucket specific to the session ID. The bucket name used is "chat_<sessionID>" and keys are expected to be 14 bytes total, composed of:

  • 8 bytes: UnixNano timestamp (big-endian)
  • 2 bytes: sender ID (big-endian; 0 means local user, 1 means remote user)
  • 4 bytes: random suffix to avoid collision

func (*Storage) StorePeer

func (s *Storage) StorePeer(peer *Peer) error

type StorageOption

type StorageOption func(*Storage)

func StorageWithAlgorithm

func StorageWithAlgorithm(algorithm attest.Algorithm) StorageOption

func StorageWithDBPath

func StorageWithDBPath(path string) StorageOption

func StorageWithExpiryDuration

func StorageWithExpiryDuration(duration time.Duration) StorageOption

func StorageWithNoPassphrase

func StorageWithNoPassphrase() StorageOption

func StorageWithPassphraseHandler

func StorageWithPassphraseHandler(fn PassphraseHandler) StorageOption

type Transferable

type Transferable interface {
	proto.Message
}

type Transport

type Transport struct {
	// contains filtered or unexported fields
}

func (*Transport) Close

func (t *Transport) Close() error

func (*Transport) Receive

func (t *Transport) Receive(dst Transferable) (*Metadata, error)

func (*Transport) Send

func (t *Transport) Send(message Transferable) (*Metadata, error)

func (*Transport) SessionID

func (t *Transport) SessionID() string

func (*Transport) Store

func (t *Transport) Store() *Storage

Directories

Path Synopsis
internal
pkg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL