omemo

package module
v0.0.0-...-8908de6 Latest Latest
Warning

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

Go to latest
Published: Feb 28, 2026 License: MIT Imports: 16 Imported by: 0

README

crypto/omemo -- OMEMO v2 Signal Protocol Implementation

This package implements the cryptographic layer of OMEMO v2 (XEP-0384) for end-to-end encrypted messaging over XMPP. It provides X3DH key agreement, Double Ratchet message encryption, and a high-level Manager API.

This is a standalone Go module with no dependency on the main xmpp-go library. You wire the two together on the client side.

For the full integration guide covering both server and client setup, see docs/omemo.md.

Install

go get github.com/meszmate/xmpp-go/crypto/omemo

Where This Fits

This package is client-side only. The server stores public data (device lists, bundles with public keys) via storage.PubSubStore. This package stores private data (private keys, sessions, trust) locally. The server never sees private keys or session state -- that's what makes it end-to-end encrypted.

Quick Start

import "github.com/meszmate/xmpp-go/crypto/omemo"

// 1. Create a client-side store (use a real DB for production)
store := omemo.NewMemoryStore(myDeviceID)
manager := omemo.NewManager(store)

// 2. Generate bundle (private keys stay local)
bundle, _ := manager.GenerateBundle(25)
// Publish bundle.IdentityKey, bundle.SignedPreKey, bundle.PreKeys
// to the server via PEP (see docs/omemo.md)

// 3. Process a remote bundle fetched from the server
manager.ProcessBundle(addr, remoteBundleParsedFromXML)

// 4. Encrypt
encMsg, _ := manager.Encrypt([]byte("Hello!"), recipientAddr1, recipientAddr2)

// 5. Decrypt (existing session)
plaintext, _ := manager.Decrypt(senderAddr, encMsg)

// 6. Decrypt first message (pre-key message, creates session as Bob)
plaintext, _ := manager.DecryptPreKeyMessage(
    senderAddr, identityKey, ephemeralKey, preKeyID, signedPreKeyID, encMsg,
)

API

Manager
manager := omemo.NewManager(store)

manager.GenerateBundle(preKeyCount int) (*Bundle, error)
manager.ProcessBundle(addr Address, bundle *Bundle)
manager.Encrypt(plaintext []byte, recipients ...Address) (*EncryptedMessage, error)
manager.Decrypt(sender Address, msg *EncryptedMessage) ([]byte, error)
manager.DecryptPreKeyMessage(sender Address, identityKey ed25519.PublicKey,
    ephemeralPubKey []byte, preKeyID *uint32, signedPreKeyID uint32,
    msg *EncryptedMessage) ([]byte, error)
Types
// Address uniquely identifies an OMEMO device
type Address struct {
    JID      string
    DeviceID uint32
}

// Bundle holds public key material for X3DH
type Bundle struct {
    IdentityKey           ed25519.PublicKey
    SignedPreKey          []byte   // X25519 public
    SignedPreKeyID        uint32
    SignedPreKeySignature []byte   // Ed25519 signature
    PreKeys               []BundlePreKey
}

// EncryptedMessage is the output of Encrypt / input to Decrypt
type EncryptedMessage struct {
    SenderDeviceID uint32
    Keys           []MessageKey  // one per recipient device
    IV             []byte        // 12 bytes
    Payload        []byte        // AES-GCM ciphertext (without auth tag)
}
Store Interface

The Store is client-side storage for private cryptographic state. Implement it backed by a local database for production. MemoryStore is for testing only.

type Store interface {
    GetIdentityKeyPair() (*IdentityKeyPair, error)
    SaveIdentityKeyPair(ikp *IdentityKeyPair) error
    GetLocalDeviceID() (uint32, error)

    GetRemoteIdentity(addr Address) (ed25519.PublicKey, error)
    SaveRemoteIdentity(addr Address, key ed25519.PublicKey) error
    IsTrusted(addr Address, key ed25519.PublicKey) (bool, error)

    GetPreKey(id uint32) (*PreKeyRecord, error)
    SavePreKey(record *PreKeyRecord) error
    RemovePreKey(id uint32) error

    GetSignedPreKey(id uint32) (*SignedPreKeyRecord, error)
    SaveSignedPreKey(record *SignedPreKeyRecord) error

    GetSession(addr Address) ([]byte, error)
    SaveSession(addr Address, data []byte) error
    ContainsSession(addr Address) (bool, error)
}

Cryptographic Primitives

Component Algorithm Purpose
Identity keys Ed25519 Long-term signing, converted to X25519 for DH
Key agreement X3DH (X25519) Establish shared secret between strangers
Message encryption Double Ratchet Per-message forward secrecy
Payload encryption AES-256-GCM Authenticated encryption of message content
Key derivation HKDF-SHA-256 Derive keys from shared secrets

Dependencies

Only golang.org/x/crypto (for HKDF). Everything else uses Go stdlib.

Documentation

Overview

Package omemo implements the Signal protocol cryptographic primitives for OMEMO v2 (XEP-0384) encryption in XMPP.

This package provides X3DH key agreement, Double Ratchet message encryption, and a high-level Manager API for encrypting and decrypting OMEMO messages. It is a standalone cryptographic module with no dependency on the main xmpp-go module -- users wire the two together on the client side.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNoSession         = errors.New("omemo: no session exists for address")
	ErrInvalidSignature  = errors.New("omemo: invalid signature")
	ErrInvalidMessage    = errors.New("omemo: invalid message")
	ErrDuplicateMessage  = errors.New("omemo: duplicate message")
	ErrUntrustedIdentity = errors.New("omemo: untrusted identity key")
	ErrNoPreKey          = errors.New("omemo: no pre-key available")
	ErrInvalidKeyLength  = errors.New("omemo: invalid key length")
	ErrSkippedKeyLimit   = errors.New("omemo: too many skipped message keys")
)

Functions

func Ed25519PrivateKeyToX25519

func Ed25519PrivateKeyToX25519(edPriv ed25519.PrivateKey) (*ecdh.PrivateKey, error)

Ed25519PrivateKeyToX25519 converts an Ed25519 private key to an X25519 private key. It hashes the 32-byte seed with SHA-512, clamps the first 32 bytes, and uses the result as the X25519 scalar.

func Ed25519PublicKeyToX25519

func Ed25519PublicKeyToX25519(edPub ed25519.PublicKey) ([]byte, error)

Ed25519PublicKeyToX25519 converts an Ed25519 public key to an X25519 public key using the birational map u = (1+y)/(1-y) mod p.

func GenerateX25519KeyPair

func GenerateX25519KeyPair() (*ecdh.PrivateKey, error)

GenerateX25519KeyPair generates a new X25519 key pair.

func X3DHRespond

func X3DHRespond(
	localIdentity *IdentityKeyPair,
	localSPK *ecdh.PrivateKey,
	localOPK *ecdh.PrivateKey,
	remoteIdentityKey ed25519.PublicKey,
	ephemeralPubKey []byte,
) ([]byte, error)

X3DHRespond performs the X3DH key agreement as the responder (Bob).

Types

type Address

type Address struct {
	JID      string
	DeviceID uint32
}

Address uniquely identifies an OMEMO device.

func (Address) String

func (a Address) String() string

type Bundle

type Bundle struct {
	IdentityKey           ed25519.PublicKey
	SignedPreKey          []byte // 32 bytes, X25519 public key
	SignedPreKeyID        uint32
	SignedPreKeySignature []byte // Ed25519 signature over SignedPreKey
	PreKeys               []BundlePreKey
}

Bundle holds the public key material needed for X3DH key agreement.

func GenerateBundle

func GenerateBundle(store Store, preKeyCount int) (*Bundle, error)

GenerateBundle generates a new OMEMO bundle, storing keys in the provided store.

type BundlePreKey

type BundlePreKey struct {
	ID        uint32
	PublicKey []byte // 32 bytes, X25519
}

BundlePreKey is a one-time pre-key in a bundle.

type EncryptedMessage

type EncryptedMessage struct {
	SenderDeviceID uint32
	Keys           []MessageKey
	IV             []byte // 12 bytes
	Payload        []byte // AES-GCM ciphertext without auth tag
}

EncryptedMessage represents an OMEMO encrypted message ready for XML serialization.

type IdentityKeyPair

type IdentityKeyPair struct {
	PrivateKey ed25519.PrivateKey
	PublicKey  ed25519.PublicKey
}

IdentityKeyPair holds an Ed25519 identity key pair.

func GenerateIdentityKeyPair

func GenerateIdentityKeyPair() (*IdentityKeyPair, error)

GenerateIdentityKeyPair generates a new Ed25519 identity key pair.

type Manager

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

Manager provides the high-level API for OMEMO encryption and decryption.

func NewManager

func NewManager(store Store) *Manager

NewManager creates a new OMEMO Manager.

func (*Manager) Decrypt

func (m *Manager) Decrypt(sender Address, msg *EncryptedMessage) ([]byte, error)

Decrypt decrypts an OMEMO encrypted message.

func (*Manager) DecryptPreKeyMessage

func (m *Manager) DecryptPreKeyMessage(
	sender Address,
	senderIdentityKey ed25519.PublicKey,
	ephemeralPubKey []byte,
	usedPreKeyID *uint32,
	signedPreKeyID uint32,
	msg *EncryptedMessage,
) ([]byte, error)

DecryptPreKeyMessage handles the full pre-key message decryption flow. It takes the sender's identity key, ephemeral key, and optionally the used pre-key ID to establish a new session as Bob and decrypt the message.

func (*Manager) Encrypt

func (m *Manager) Encrypt(plaintext []byte, recipients ...Address) (*EncryptedMessage, error)

Encrypt encrypts plaintext for multiple recipients.

func (*Manager) GenerateBundle

func (m *Manager) GenerateBundle(preKeyCount int) (*Bundle, error)

GenerateBundle generates a new OMEMO bundle for the local device.

func (*Manager) ProcessBundle

func (m *Manager) ProcessBundle(addr Address, bundle *Bundle)

ProcessBundle stores a remote bundle for later X3DH initiation.

type MemoryStore

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

MemoryStore is an in-memory Store implementation for testing. It uses a Trust On First Use (TOFU) model for identity trust.

func NewMemoryStore

func NewMemoryStore(deviceID uint32) *MemoryStore

NewMemoryStore creates a new in-memory store with the given device ID.

func (*MemoryStore) ContainsSession

func (s *MemoryStore) ContainsSession(addr Address) (bool, error)

func (*MemoryStore) GetIdentityKeyPair

func (s *MemoryStore) GetIdentityKeyPair() (*IdentityKeyPair, error)

func (*MemoryStore) GetLocalDeviceID

func (s *MemoryStore) GetLocalDeviceID() (uint32, error)

func (*MemoryStore) GetPreKey

func (s *MemoryStore) GetPreKey(id uint32) (*PreKeyRecord, error)

func (*MemoryStore) GetRemoteIdentity

func (s *MemoryStore) GetRemoteIdentity(addr Address) (ed25519.PublicKey, error)

func (*MemoryStore) GetSession

func (s *MemoryStore) GetSession(addr Address) ([]byte, error)

func (*MemoryStore) GetSignedPreKey

func (s *MemoryStore) GetSignedPreKey(id uint32) (*SignedPreKeyRecord, error)

func (*MemoryStore) IsTrusted

func (s *MemoryStore) IsTrusted(addr Address, key ed25519.PublicKey) (bool, error)

IsTrusted implements TOFU: trust on first use, reject on change.

func (*MemoryStore) RemovePreKey

func (s *MemoryStore) RemovePreKey(id uint32) error

func (*MemoryStore) SaveIdentityKeyPair

func (s *MemoryStore) SaveIdentityKeyPair(ikp *IdentityKeyPair) error

func (*MemoryStore) SavePreKey

func (s *MemoryStore) SavePreKey(record *PreKeyRecord) error

func (*MemoryStore) SaveRemoteIdentity

func (s *MemoryStore) SaveRemoteIdentity(addr Address, key ed25519.PublicKey) error

func (*MemoryStore) SaveSession

func (s *MemoryStore) SaveSession(addr Address, data []byte) error

func (*MemoryStore) SaveSignedPreKey

func (s *MemoryStore) SaveSignedPreKey(record *SignedPreKeyRecord) error

type MessageKey

type MessageKey struct {
	DeviceID uint32
	Data     []byte // ratchet-encrypted key material (header + ciphertext)
	IsPreKey bool   // true if this is a pre-key message (first message in a session)
}

MessageKey holds the encrypted key material for a single recipient device.

type PendingPreKey

type PendingPreKey struct {
	PreKeyID        *uint32
	SignedPreKeyID  uint32
	EphemeralPubKey []byte // 32 bytes, X25519
}

PendingPreKey tracks pre-key info for the initial message.

type PreKeyRecord

type PreKeyRecord struct {
	ID         uint32
	PrivateKey []byte // 32 bytes, X25519
	PublicKey  []byte // 32 bytes, X25519
}

PreKeyRecord holds a one-time pre-key pair.

type RatchetHeader

type RatchetHeader struct {
	DHPub []byte // 32 bytes, X25519 public ratchet key
	N     uint32 // message number in sending chain
	PN    uint32 // previous chain length
}

RatchetHeader contains the public information sent with each ratchet message.

func (*RatchetHeader) MarshalBinary

func (h *RatchetHeader) MarshalBinary() ([]byte, error)

MarshalBinary encodes a RatchetHeader to bytes.

func (*RatchetHeader) UnmarshalBinary

func (h *RatchetHeader) UnmarshalBinary(data []byte) error

UnmarshalBinary decodes a RatchetHeader from bytes.

type RatchetState

type RatchetState struct {
	DHs *ecdh.PrivateKey // our current ratchet key pair
	DHr []byte           // their current ratchet public key (32 bytes)

	RK  []byte // root key (32 bytes)
	CKs []byte // sending chain key (32 bytes)
	CKr []byte // receiving chain key (32 bytes)

	Ns uint32 // sending message number
	Nr uint32 // receiving message number
	PN uint32 // previous sending chain length

	MKSkipped map[skippedKey][]byte // skipped message keys
}

RatchetState holds the state of a Double Ratchet session.

func InitRatchetAsAlice

func InitRatchetAsAlice(sharedSecret, remoteSPK []byte) (*RatchetState, error)

InitRatchetAsAlice initializes a Double Ratchet as Alice (initiator). Alice generates a new DH pair and derives the first sending chain from DH with Bob's SPK.

func InitRatchetAsBob

func InitRatchetAsBob(sharedSecret []byte, localSPK *ecdh.PrivateKey) *RatchetState

InitRatchetAsBob initializes a Double Ratchet as Bob (responder). Bob uses SPK as initial ratchet key, waits for Alice's first message to complete DH ratchet.

func (*RatchetState) MarshalBinary

func (s *RatchetState) MarshalBinary() ([]byte, error)

MarshalBinary serializes the RatchetState to bytes.

func (*RatchetState) RatchetDecrypt

func (s *RatchetState) RatchetDecrypt(header *RatchetHeader, ciphertext []byte) ([]byte, error)

RatchetDecrypt decrypts a message using the Double Ratchet.

func (*RatchetState) RatchetEncrypt

func (s *RatchetState) RatchetEncrypt(plaintext []byte) (*RatchetHeader, []byte, error)

RatchetEncrypt encrypts plaintext using the Double Ratchet.

func (*RatchetState) UnmarshalBinary

func (s *RatchetState) UnmarshalBinary(data []byte) error

UnmarshalBinary deserializes a RatchetState from bytes.

type Session

type Session struct {
	Ratchet        *RatchetState
	RemoteIdentity ed25519.PublicKey
	PendingPreKey  *PendingPreKey // set until the first reply is received
}

Session wraps a Double Ratchet state with session metadata.

func InitSessionAsAlice

func InitSessionAsAlice(
	localIdentity *IdentityKeyPair,
	remoteBundle *Bundle,
) (*Session, error)

InitSessionAsAlice creates a new session as the initiator using X3DH.

func InitSessionAsBob

func InitSessionAsBob(
	localIdentity *IdentityKeyPair,
	localSPK *ecdh.PrivateKey,
	localOPK *ecdh.PrivateKey,
	remoteIdentityKey ed25519.PublicKey,
	ephemeralPubKey []byte,
) (*Session, error)

InitSessionAsBob creates a new session as the responder using X3DH.

func (*Session) Decrypt

func (s *Session) Decrypt(header *RatchetHeader, ciphertext []byte) ([]byte, error)

Decrypt decrypts a message using this session's ratchet.

func (*Session) Encrypt

func (s *Session) Encrypt(plaintext []byte) (*RatchetHeader, []byte, bool, error)

Encrypt encrypts plaintext using this session's ratchet.

func (*Session) MarshalBinary

func (s *Session) MarshalBinary() ([]byte, error)

MarshalBinary serializes the session state.

func (*Session) UnmarshalBinary

func (s *Session) UnmarshalBinary(data []byte) error

UnmarshalBinary deserializes a session from bytes.

type SignedPreKeyRecord

type SignedPreKeyRecord struct {
	ID         uint32
	PrivateKey []byte // 32 bytes, X25519
	PublicKey  []byte // 32 bytes, X25519
	Signature  []byte // Ed25519 signature over PublicKey
}

SignedPreKeyRecord holds a signed pre-key pair with its signature.

type Store

type Store interface {
	// GetIdentityKeyPair returns the local identity key pair.
	GetIdentityKeyPair() (*IdentityKeyPair, error)

	// SaveIdentityKeyPair stores the local identity key pair.
	SaveIdentityKeyPair(ikp *IdentityKeyPair) error

	// GetLocalDeviceID returns the local device ID.
	GetLocalDeviceID() (uint32, error)

	// GetRemoteIdentity returns the stored identity public key for an address.
	GetRemoteIdentity(addr Address) (ed25519.PublicKey, error)

	// SaveRemoteIdentity stores a remote identity public key.
	SaveRemoteIdentity(addr Address, key ed25519.PublicKey) error

	// IsTrusted returns whether the identity key for an address is trusted.
	IsTrusted(addr Address, key ed25519.PublicKey) (bool, error)

	// GetPreKey returns a pre-key by ID.
	GetPreKey(id uint32) (*PreKeyRecord, error)

	// SavePreKey stores a pre-key.
	SavePreKey(record *PreKeyRecord) error

	// RemovePreKey removes a pre-key by ID.
	RemovePreKey(id uint32) error

	// GetSignedPreKey returns a signed pre-key by ID.
	GetSignedPreKey(id uint32) (*SignedPreKeyRecord, error)

	// SaveSignedPreKey stores a signed pre-key.
	SaveSignedPreKey(record *SignedPreKeyRecord) error

	// GetSession returns the serialized session state for an address.
	GetSession(addr Address) ([]byte, error)

	// SaveSession stores the serialized session state for an address.
	SaveSession(addr Address, data []byte) error

	// ContainsSession returns whether a session exists for an address.
	ContainsSession(addr Address) (bool, error)
}

Store defines the persistence interface for OMEMO state.

type X3DHResult

type X3DHResult struct {
	SharedSecret    []byte
	EphemeralPubKey []byte // X25519 public key used by initiator
	UsedPreKeyID    *uint32
}

X3DHResult holds the result of an X3DH key agreement.

func X3DHInitiate

func X3DHInitiate(localIdentity *IdentityKeyPair, remoteBundle *Bundle) (*X3DHResult, error)

X3DHInitiate performs the X3DH key agreement as the initiator (Alice).

Jump to

Keyboard shortcuts

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