telegabber/e2ee/omemo/libsignal/session.go
2026-07-28 00:38:46 -04:00

142 lines
4.6 KiB
Go

package libsignal
/*
#include <signal_protocol.h>
#include <session_builder.h>
#include <session_pre_key.h>
#include <curve.h>
*/
import "C"
import (
"runtime"
"unsafe"
)
// Protocol version constants (protocol.h's CIPHERTEXT_CURRENT_VERSION /
// CIPHERTEXT_OMEMO_VERSION), selecting legacy siacs OMEMO vs modern OMEMO.
// Modern is shared by OMEMO 1 (urn:xmpp:omemo:1) and OMEMO 2
// (urn:xmpp:omemo:2) - those two differ only in outer SCE stanza framing,
// not in this underlying Double Ratchet/X3DH wire format.
const (
ProtocolVersionLegacy = 3
ProtocolVersionModern = 4
)
// RemoteBundle is the decoded material from a peer's XEP-0384 device
// bundle, as needed to establish a session with one of their devices via
// X3DH.
type RemoteBundle struct {
RegistrationID uint32
DeviceID uint32
PreKeyID uint32 // 0 if the bundle carried no one-time prekey
PreKeyPublic []byte // nil if PreKeyID == 0
SignedPreKeyID uint32
SignedPreKeyPublic []byte
SignedPreKeySignature []byte
IdentityKeyPublic []byte
}
// SessionBuilder establishes (or refreshes) a Double Ratchet session with
// one remote device.
type SessionBuilder struct {
raw *C.session_builder
}
// NewSessionBuilder creates a session builder for storeCtx's local identity
// talking to remote. version controls the protocol version new sessions
// built by ProcessPreKeyBundle will use for encryption going forward
// (ProtocolVersionLegacy or ProtocolVersionModern).
func NewSessionBuilder(ctx *Context, storeCtx *StoreContext, remote Address, version int) (*SessionBuilder, error) {
var raw *C.session_builder
var code C.int
withCAddress(remote, func(cAddr *C.signal_protocol_address) {
code = C.session_builder_create(&raw, storeCtx.raw, cAddr, ctx.raw)
})
if code != C.SG_SUCCESS {
return nil, newError("session_builder_create", int(code))
}
if err := setBuilderVersion(raw, version); err != nil {
C.session_builder_free(raw)
return nil, err
}
return &SessionBuilder{raw: raw}, nil
}
// Close frees the session builder. Do not use it afterward.
func (b *SessionBuilder) Close() {
if b.raw != nil {
C.session_builder_free(b.raw)
b.raw = nil
}
}
// ProcessPreKeyBundle establishes a session from a remote peer's fetched
// device bundle (X3DH).
//
// IsUntrustedIdentity(err) reports the case where the remote's identity key
// does not match a previously trusted one for this device id - under a
// TOFU trust policy this is the one case that must NOT be silently
// re-trusted (it is what TOFU exists to catch: either genuine key rotation
// or an impersonation attempt), unlike a first-ever sighting of a device,
// which the identity store's IsTrustedIdentity should accept.
func (b *SessionBuilder) ProcessPreKeyBundle(ctx *Context, bundle RemoteBundle) error {
identityKey, err := decodePoint(ctx, bundle.IdentityKeyPublic)
if err != nil {
return err
}
defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(identityKey)))
signedPreKeyPublic, err := decodePoint(ctx, bundle.SignedPreKeyPublic)
if err != nil {
return err
}
defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(signedPreKeyPublic)))
var preKeyPublic *C.ec_public_key
if len(bundle.PreKeyPublic) > 0 {
preKeyPublic, err = decodePoint(ctx, bundle.PreKeyPublic)
if err != nil {
return err
}
defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(preKeyPublic)))
}
sigPtr, sigLen := cBytesPtrLen(bundle.SignedPreKeySignature)
var cBundle *C.session_pre_key_bundle
code := C.session_pre_key_bundle_create(&cBundle,
C.uint32_t(bundle.RegistrationID), C.int(bundle.DeviceID),
C.uint32_t(bundle.PreKeyID), preKeyPublic,
C.uint32_t(bundle.SignedPreKeyID), signedPreKeyPublic,
sigPtr, sigLen,
identityKey)
runtime.KeepAlive(bundle.SignedPreKeySignature)
if code != C.SG_SUCCESS {
return newError("session_pre_key_bundle_create", int(code))
}
defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(cBundle)))
if code := C.session_builder_process_pre_key_bundle(b.raw, cBundle); code != C.SG_SUCCESS {
return newError("session_builder_process_pre_key_bundle", int(code))
}
return nil
}
func decodePoint(ctx *Context, data []byte) (*C.ec_public_key, error) {
var key *C.ec_public_key
ptr, length := cBytesPtrLen(data)
code := C.curve_decode_point(&key, ptr, length, ctx.raw)
runtime.KeepAlive(data)
if code != C.SG_SUCCESS {
return nil, newError("curve_decode_point", int(code))
}
return key, nil
}
func cBytesPtrLen(b []byte) (*C.uint8_t, C.size_t) {
if len(b) == 0 {
return nil, 0
}
return (*C.uint8_t)(unsafe.Pointer(&b[0])), C.size_t(len(b))
}