mirror of
https://dev.narayana.im/narayana/telegabber.git
synced 2026-08-05 04:07:07 +00:00
Go bindings for libomemo-c and libsignal-protocol-c
This commit is contained in:
parent
b9bb367287
commit
ca0dd4361a
25 changed files with 1807 additions and 5 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
FROM golang:1.19-bookworm AS base
|
FROM golang:1.19-bookworm AS base
|
||||||
|
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
run apt-get install -y libssl-dev cmake build-essential gperf libz-dev make git
|
run apt-get install -y libssl-dev cmake build-essential gperf libz-dev make git libomemo-c-dev
|
||||||
|
|
||||||
FROM base AS tdlib
|
FROM base AS tdlib
|
||||||
|
|
||||||
|
|
|
||||||
9
Makefile
9
Makefile
|
|
@ -5,12 +5,17 @@ TD_COMMIT := "5bbfc1cf5dab94f82e02f3430ded7241d4653551"
|
||||||
VERSION := "v2.0.0-dev"
|
VERSION := "v2.0.0-dev"
|
||||||
MAKEOPTS := "-j4"
|
MAKEOPTS := "-j4"
|
||||||
|
|
||||||
|
# GOTAGS=signal_legacy builds the OMEMO cgo bindings against the pre-fork
|
||||||
|
# libsignal-protocol-c instead of libomemo-c (e.g. on Debian bullseye, which
|
||||||
|
# only packages the former) - legacy/siacs OMEMO only, no modern OMEMO 1/2.
|
||||||
|
GOTAGS :=
|
||||||
|
|
||||||
all:
|
all:
|
||||||
mkdir -p release
|
mkdir -p release
|
||||||
go build -ldflags "-X main.commit=${COMMIT}" -o release/telegabber
|
go build -ldflags "-X main.commit=${COMMIT}" -tags "${GOTAGS}" -o release/telegabber
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -v ./config ./ ./telegram ./xmpp ./xmpp/gateway ./persistence ./telegram/formatter ./badger
|
go test -tags "${GOTAGS}" -v ./config ./ ./telegram ./xmpp ./xmpp/gateway ./persistence ./telegram/formatter ./badger
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
$(GOPATH)/bin/golint ./...
|
$(GOPATH)/bin/golint ./...
|
||||||
|
|
|
||||||
13
README.md
13
README.md
|
|
@ -10,6 +10,19 @@ Prerequisites: Go links the binary against the `glibc` version present in the sy
|
||||||
|
|
||||||
* Build TDLib according to [TDLib build instructions](https://tdlib.github.io/td/build.html). Roll back to commit 8d7bda00a535d1eda684c3c8802e85d69c89a14a if the compatibility got broken.
|
* Build TDLib according to [TDLib build instructions](https://tdlib.github.io/td/build.html). Roll back to commit 8d7bda00a535d1eda684c3c8802e85d69c89a14a if the compatibility got broken.
|
||||||
|
|
||||||
|
* Install a Signal-protocol C library for OMEMO support - one of:
|
||||||
|
* [libomemo-c](https://github.com/dino/libomemo-c), the Dino project's fork that supports both legacy/siacs OMEMO and modern OMEMO (1/2). Packaged on Debian bookworm and later (and correspondingly recent Ubuntu/derivatives) as `libomemo-c-dev` - `apt install libomemo-c-dev` is all you need. This is the default Telegabber builds against.
|
||||||
|
* [libsignal-protocol-c](https://github.com/signalapp/libsignal-protocol-c), the pre-fork original, on distros that only package that one (e.g. Debian bullseye: `apt install libsignal-protocol-c-dev`). It only supports legacy/siacs OMEMO, not modern OMEMO 1/2. Build with `make GOTAGS=signal_legacy` (or `go build -tags signal_legacy ...`) to link against it instead.
|
||||||
|
|
||||||
|
Neither package available for your distro? Build either one from source the same way:
|
||||||
|
```
|
||||||
|
git clone https://github.com/dino/libomemo-c # or .../signalapp/libsignal-protocol-c
|
||||||
|
cd libomemo-c && mkdir build && cd build
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||||
|
make && sudo make install
|
||||||
|
```
|
||||||
|
libomemo-c additionally requires `libprotobuf-c` (dev headers + library) to build. Either way, Telegabber's cgo bindings locate whichever one you chose via `pkg-config` (`libomemo-c` or `libsignal-protocol-c`, matching the build tag), so make sure that succeeds afterward - install to a prefix `pkg-config` already searches (the distro packages do this automatically; a manual `make install` defaults to `/usr/local`, or point `PKG_CONFIG_PATH` at wherever the `.pc` file ended up, typically `<prefix>/lib/pkgconfig`).
|
||||||
|
|
||||||
* Install Go (tested with 1.13, but may work with earlier versions too).
|
* Install Go (tested with 1.13, but may work with earlier versions too).
|
||||||
* Open the source dir in a new shell (to make sure that `$GOPATH` works) and run `make`. Dependencies will be installed automatically.
|
* Open the source dir in a new shell (to make sure that `$GOPATH` works) and run `make`. Dependencies will be installed automatically.
|
||||||
|
|
||||||
|
|
|
||||||
45
e2ee/omemo/libsignal/address.go
Normal file
45
e2ee/omemo/libsignal/address.go
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <signal_protocol.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Address identifies a Signal Protocol session peer: a logical recipient
|
||||||
|
// name (for OMEMO, a bare JID) plus a numeric device ID.
|
||||||
|
type Address struct {
|
||||||
|
Name string
|
||||||
|
DeviceID uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// withCAddress converts addr to a C signal_protocol_address and invokes fn
|
||||||
|
// with a pointer to it. The pointer - and the memory its name field borrows
|
||||||
|
// from addr.Name - is only valid for the duration of fn; it must not be
|
||||||
|
// retained past fn's return.
|
||||||
|
func withCAddress(addr Address, fn func(*C.signal_protocol_address)) {
|
||||||
|
nameBytes := []byte(addr.Name)
|
||||||
|
var namePtr *C.char
|
||||||
|
if len(nameBytes) > 0 {
|
||||||
|
namePtr = (*C.char)(unsafe.Pointer(&nameBytes[0]))
|
||||||
|
}
|
||||||
|
cAddr := C.signal_protocol_address{
|
||||||
|
name: namePtr,
|
||||||
|
name_len: C.size_t(len(nameBytes)),
|
||||||
|
device_id: C.int32_t(addr.DeviceID),
|
||||||
|
}
|
||||||
|
fn(&cAddr)
|
||||||
|
runtime.KeepAlive(nameBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addressFromC copies a C signal_protocol_address into a Go Address.
|
||||||
|
func addressFromC(addr *C.signal_protocol_address) Address {
|
||||||
|
return Address{
|
||||||
|
Name: C.GoStringN(addr.name, C.int(addr.name_len)),
|
||||||
|
DeviceID: uint32(addr.device_id),
|
||||||
|
}
|
||||||
|
}
|
||||||
50
e2ee/omemo/libsignal/buffer.go
Normal file
50
e2ee/omemo/libsignal/buffer.go
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <signal_protocol.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
// bytesToBuffer copies a Go byte slice into a newly allocated signal_buffer.
|
||||||
|
// Ownership of the returned buffer transfers to whichever C API consumes it
|
||||||
|
// (matching the library's own buffer-ownership convention documented in
|
||||||
|
// signal_protocol.h).
|
||||||
|
func bytesToBuffer(b []byte) *C.signal_buffer {
|
||||||
|
var ptr *C.uint8_t
|
||||||
|
if len(b) > 0 {
|
||||||
|
ptr = (*C.uint8_t)(unsafe.Pointer(&b[0]))
|
||||||
|
}
|
||||||
|
return C.signal_buffer_create(ptr, C.size_t(len(b)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufferToBytes copies the contents of a signal_buffer into a new Go byte
|
||||||
|
// slice. It does not free or otherwise take ownership of buf.
|
||||||
|
func bufferToBytes(buf *C.signal_buffer) []byte {
|
||||||
|
if buf == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
n := C.signal_buffer_len(buf)
|
||||||
|
if n == 0 {
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
|
return C.GoBytes(unsafe.Pointer(C.signal_buffer_const_data(buf)), C.int(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// freeBuffer frees a signal_buffer allocated by the library.
|
||||||
|
func freeBuffer(buf *C.signal_buffer) {
|
||||||
|
if buf != nil {
|
||||||
|
C.signal_buffer_free(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cBytesToGo copies a raw C byte pointer + length into a new Go byte slice.
|
||||||
|
// Used for reading uint8_t*/size_t pairs (as opposed to signal_buffer*) out
|
||||||
|
// of C structures, e.g. session_signed_pre_key signatures.
|
||||||
|
func cBytesToGo(data *C.uint8_t, length C.size_t) []byte {
|
||||||
|
if data == nil || length == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return C.GoBytes(unsafe.Pointer(data), C.int(length))
|
||||||
|
}
|
||||||
293
e2ee/omemo/libsignal/callbacks.go
Normal file
293
e2ee/omemo/libsignal/callbacks.go
Normal file
|
|
@ -0,0 +1,293 @@
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <signal_protocol.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime/cgo"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// storeFromUserData recovers the Store bound to a StoreContext from the
|
||||||
|
// cgo.Handle round-tripped through the vtable's void *user_data (see
|
||||||
|
// store.go's NewStoreContext, which is the only place that creates such a
|
||||||
|
// handle).
|
||||||
|
func storeFromUserData(userData unsafe.Pointer) (Store, bool) {
|
||||||
|
if userData == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
s, ok := cgo.Handle(uintptr(userData)).Value().(Store)
|
||||||
|
return s, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- identity key store ----
|
||||||
|
|
||||||
|
//export go_get_identity_key_pair
|
||||||
|
func go_get_identity_key_pair(publicData, privateData **C.signal_buffer, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
pub, priv, err := store.GetIdentityKeyPair()
|
||||||
|
if err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
*publicData = bytesToBuffer(pub)
|
||||||
|
*privateData = bytesToBuffer(priv)
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_get_local_registration_id
|
||||||
|
func go_get_local_registration_id(userData unsafe.Pointer, registrationID *C.uint32_t) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
id, err := store.GetLocalRegistrationID()
|
||||||
|
if err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
*registrationID = C.uint32_t(id)
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_save_identity
|
||||||
|
func go_save_identity(address *C.signal_protocol_address, keyData *C.uint8_t, keyLen C.size_t, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
if err := store.SaveIdentity(addressFromC(address), cBytesToGo(keyData, keyLen)); err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_is_trusted_identity
|
||||||
|
func go_is_trusted_identity(address *C.signal_protocol_address, keyData *C.uint8_t, keyLen C.size_t, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
trusted, err := store.IsTrustedIdentity(addressFromC(address), cBytesToGo(keyData, keyLen))
|
||||||
|
if err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
if trusted {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_identity_destroy
|
||||||
|
func go_identity_destroy(userData unsafe.Pointer) {}
|
||||||
|
|
||||||
|
// ---- pre key store ----
|
||||||
|
|
||||||
|
//export go_load_pre_key
|
||||||
|
func go_load_pre_key(record **C.signal_buffer, preKeyID C.uint32_t, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
data, found, err := store.LoadPreKey(uint32(preKeyID))
|
||||||
|
if err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return C.int(ErrInvalidKeyID)
|
||||||
|
}
|
||||||
|
*record = bytesToBuffer(data)
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_store_pre_key
|
||||||
|
func go_store_pre_key(preKeyID C.uint32_t, record *C.uint8_t, recordLen C.size_t, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
if err := store.StorePreKey(uint32(preKeyID), cBytesToGo(record, recordLen)); err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_contains_pre_key
|
||||||
|
func go_contains_pre_key(preKeyID C.uint32_t, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if store.ContainsPreKey(uint32(preKeyID)) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_remove_pre_key
|
||||||
|
func go_remove_pre_key(preKeyID C.uint32_t, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
if err := store.RemovePreKey(uint32(preKeyID)); err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_pre_key_destroy
|
||||||
|
func go_pre_key_destroy(userData unsafe.Pointer) {}
|
||||||
|
|
||||||
|
// ---- signed pre key store ----
|
||||||
|
|
||||||
|
//export go_load_signed_pre_key
|
||||||
|
func go_load_signed_pre_key(record **C.signal_buffer, signedPreKeyID C.uint32_t, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
data, found, err := store.LoadSignedPreKey(uint32(signedPreKeyID))
|
||||||
|
if err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return C.int(ErrInvalidKeyID)
|
||||||
|
}
|
||||||
|
*record = bytesToBuffer(data)
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_store_signed_pre_key
|
||||||
|
func go_store_signed_pre_key(signedPreKeyID C.uint32_t, record *C.uint8_t, recordLen C.size_t, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
if err := store.StoreSignedPreKey(uint32(signedPreKeyID), cBytesToGo(record, recordLen)); err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_contains_signed_pre_key
|
||||||
|
func go_contains_signed_pre_key(signedPreKeyID C.uint32_t, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if store.ContainsSignedPreKey(uint32(signedPreKeyID)) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_remove_signed_pre_key
|
||||||
|
func go_remove_signed_pre_key(signedPreKeyID C.uint32_t, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
if err := store.RemoveSignedPreKey(uint32(signedPreKeyID)); err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_signed_pre_key_destroy
|
||||||
|
func go_signed_pre_key_destroy(userData unsafe.Pointer) {}
|
||||||
|
|
||||||
|
// ---- session store ----
|
||||||
|
|
||||||
|
//export go_load_session
|
||||||
|
func go_load_session(record, userRecord **C.signal_buffer, address *C.signal_protocol_address, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
data, found, err := store.LoadSession(addressFromC(address))
|
||||||
|
if err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
*record = bytesToBuffer(data)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_get_sub_device_sessions
|
||||||
|
func go_get_sub_device_sessions(sessions **C.signal_int_list, name *C.char, nameLen C.size_t, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
deviceIDs, err := store.GetSubDeviceSessions(C.GoStringN(name, C.int(nameLen)))
|
||||||
|
if err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
list := C.signal_int_list_alloc()
|
||||||
|
if list == nil {
|
||||||
|
return C.int(ErrNoMemory)
|
||||||
|
}
|
||||||
|
for _, id := range deviceIDs {
|
||||||
|
C.signal_int_list_push_back(list, C.int(id))
|
||||||
|
}
|
||||||
|
*sessions = list
|
||||||
|
return C.int(len(deviceIDs))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_store_session
|
||||||
|
func go_store_session(address *C.signal_protocol_address, record *C.uint8_t, recordLen C.size_t, userRecord *C.uint8_t, userRecordLen C.size_t, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
if err := store.StoreSession(addressFromC(address), cBytesToGo(record, recordLen)); err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_contains_session
|
||||||
|
func go_contains_session(address *C.signal_protocol_address, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if store.ContainsSession(addressFromC(address)) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_delete_session
|
||||||
|
func go_delete_session(address *C.signal_protocol_address, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
if err := store.DeleteSession(addressFromC(address)); err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_delete_all_sessions
|
||||||
|
func go_delete_all_sessions(name *C.char, nameLen C.size_t, userData unsafe.Pointer) C.int {
|
||||||
|
store, ok := storeFromUserData(userData)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
count, err := store.DeleteAllSessions(C.GoStringN(name, C.int(nameLen)))
|
||||||
|
if err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
return C.int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_session_destroy
|
||||||
|
func go_session_destroy(userData unsafe.Pointer) {}
|
||||||
8
e2ee/omemo/libsignal/capability_legacy.go
Normal file
8
e2ee/omemo/libsignal/capability_legacy.go
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
//go:build signal_legacy
|
||||||
|
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
// ModernOMEMOSupported is false under the "signal_legacy" build tag: this
|
||||||
|
// build links vanilla libsignal-protocol-c, which has no modern OMEMO
|
||||||
|
// (protocol v4) support at all - only legacy/siacs OMEMO (protocol v3).
|
||||||
|
const ModernOMEMOSupported = false
|
||||||
10
e2ee/omemo/libsignal/capability_modern.go
Normal file
10
e2ee/omemo/libsignal/capability_modern.go
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
//go:build !signal_legacy
|
||||||
|
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
// ModernOMEMOSupported reports whether this build links a library capable
|
||||||
|
// of modern OMEMO (protocol v4, shared by OMEMO 1/urn:xmpp:omemo:1 and
|
||||||
|
// OMEMO 2/urn:xmpp:omemo:2), as opposed to only legacy/siacs OMEMO
|
||||||
|
// (protocol v3). Callers should check this before advertising or
|
||||||
|
// negotiating anything beyond the legacy namespace.
|
||||||
|
const ModernOMEMOSupported = true
|
||||||
17
e2ee/omemo/libsignal/cgo_legacy.go
Normal file
17
e2ee/omemo/libsignal/cgo_legacy.go
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
//go:build signal_legacy
|
||||||
|
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo pkg-config: libsignal-protocol-c
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// This file (selected by the "signal_legacy" build tag) links against the
|
||||||
|
// pre-fork libsignal-protocol-c instead of libomemo-c - e.g. on Debian
|
||||||
|
// bullseye, which packages the former (libsignal-protocol-c-dev) but not
|
||||||
|
// the latter (libomemo-c-dev only arrived in bookworm). Vanilla
|
||||||
|
// libsignal-protocol-c only ever produces/consumes protocol v3 (legacy/
|
||||||
|
// siacs OMEMO) messages; see capability_legacy.go, version_legacy.go,
|
||||||
|
// deserialize_legacy.go, and signedprekey_legacy.go for the resulting
|
||||||
|
// reduced-capability shims.
|
||||||
19
e2ee/omemo/libsignal/cgo_modern.go
Normal file
19
e2ee/omemo/libsignal/cgo_modern.go
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
//go:build !signal_legacy
|
||||||
|
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo pkg-config: libomemo-c
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// This file (selected by the absence of the "signal_legacy" build tag,
|
||||||
|
// the default) links against libomemo-c, the Dino project's fork of
|
||||||
|
// libsignal-protocol-c that additionally supports modern OMEMO (protocol
|
||||||
|
// v4, shared by OMEMO 1 and OMEMO 2) alongside legacy/siacs OMEMO
|
||||||
|
// (protocol v3). See cgo_legacy.go for the alternative.
|
||||||
|
//
|
||||||
|
// The #cgo pkg-config directive above supplies the CFLAGS/LDFLAGS for
|
||||||
|
// every file in this package, per Go's cgo rules (such directives are
|
||||||
|
// package-wide, not per-file) - every other file just #includes the
|
||||||
|
// headers it needs without repeating this directive.
|
||||||
144
e2ee/omemo/libsignal/cipher.go
Normal file
144
e2ee/omemo/libsignal/cipher.go
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <signal_protocol.h>
|
||||||
|
#include <session_cipher.h>
|
||||||
|
#include <protocol.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ciphertext message types (protocol.h), discriminating what
|
||||||
|
// CiphertextMessage.Serialized holds.
|
||||||
|
const (
|
||||||
|
// CiphertextSignalType is an ordinary ratchet message; a session must
|
||||||
|
// already exist to decrypt one.
|
||||||
|
CiphertextSignalType = C.CIPHERTEXT_SIGNAL_TYPE
|
||||||
|
// CiphertextPreKeyType is a prekey message that also establishes the
|
||||||
|
// session on the receiving end, via X3DH.
|
||||||
|
CiphertextPreKeyType = C.CIPHERTEXT_PREKEY_TYPE
|
||||||
|
)
|
||||||
|
|
||||||
|
// CiphertextMessage is an encrypted message ready to be embedded in a
|
||||||
|
// stanza. Type distinguishes an ordinary ratchet message from a prekey
|
||||||
|
// message - the caller needs this to choose the right XEP-0384 wire
|
||||||
|
// representation (a plain <key> vs a <key prekey="true">).
|
||||||
|
type CiphertextMessage struct {
|
||||||
|
Type int
|
||||||
|
Serialized []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionCipher performs encrypt/decrypt for an already-established session.
|
||||||
|
type SessionCipher struct {
|
||||||
|
raw *C.session_cipher
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSessionCipher creates a session cipher for storeCtx's local identity
|
||||||
|
// talking to remote. version selects the wire format for messages this
|
||||||
|
// cipher *produces* (ProtocolVersionLegacy or ProtocolVersionModern);
|
||||||
|
// decrypting adapts to whatever version the incoming message declares.
|
||||||
|
func NewSessionCipher(ctx *Context, storeCtx *StoreContext, remote Address, version int) (*SessionCipher, error) {
|
||||||
|
var raw *C.session_cipher
|
||||||
|
var code C.int
|
||||||
|
withCAddress(remote, func(cAddr *C.signal_protocol_address) {
|
||||||
|
code = C.session_cipher_create(&raw, storeCtx.raw, cAddr, ctx.raw)
|
||||||
|
})
|
||||||
|
if code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("session_cipher_create", int(code))
|
||||||
|
}
|
||||||
|
if err := setCipherVersion(raw, version); err != nil {
|
||||||
|
C.session_cipher_free(raw)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &SessionCipher{raw: raw}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close frees the session cipher. Do not use it afterward.
|
||||||
|
func (c *SessionCipher) Close() {
|
||||||
|
if c.raw != nil {
|
||||||
|
C.session_cipher_free(c.raw)
|
||||||
|
c.raw = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt encrypts plaintext for this cipher's session, in whichever
|
||||||
|
// protocol version the cipher was created with.
|
||||||
|
func (c *SessionCipher) Encrypt(plaintext []byte) (*CiphertextMessage, error) {
|
||||||
|
var ptr *C.uint8_t
|
||||||
|
if len(plaintext) > 0 {
|
||||||
|
ptr = (*C.uint8_t)(unsafe.Pointer(&plaintext[0]))
|
||||||
|
}
|
||||||
|
var msg *C.ciphertext_message
|
||||||
|
code := C.session_cipher_encrypt(c.raw, ptr, C.size_t(len(plaintext)), &msg)
|
||||||
|
runtime.KeepAlive(plaintext)
|
||||||
|
if code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("session_cipher_encrypt", int(code))
|
||||||
|
}
|
||||||
|
defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(msg)))
|
||||||
|
|
||||||
|
return &CiphertextMessage{
|
||||||
|
Type: int(C.ciphertext_message_get_type(msg)),
|
||||||
|
Serialized: bufferToBytes(C.ciphertext_message_get_serialized(msg)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt decrypts a message received from this cipher's remote peer.
|
||||||
|
//
|
||||||
|
// useOmemoFraming selects between the legacy wire encoding and the OMEMO
|
||||||
|
// (protocol v4) one for parsing msg.Serialized - it must match whichever
|
||||||
|
// namespace/version the sender actually used, not necessarily this
|
||||||
|
// cipher's own outgoing version (a peer's incoming and outgoing wire
|
||||||
|
// format need not match while a conversation is transitioning between
|
||||||
|
// legacy and modern OMEMO). ownRegistrationID is this identity's own
|
||||||
|
// registration id (IdentityStore.GetLocalRegistrationID) - only consulted
|
||||||
|
// for CiphertextPreKeyType messages under OMEMO framing, which the C API
|
||||||
|
// requires it for.
|
||||||
|
func (c *SessionCipher) Decrypt(ctx *Context, msg CiphertextMessage, useOmemoFraming bool, ownRegistrationID uint32) ([]byte, error) {
|
||||||
|
var ptr *C.uint8_t
|
||||||
|
if len(msg.Serialized) > 0 {
|
||||||
|
ptr = (*C.uint8_t)(unsafe.Pointer(&msg.Serialized[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
var plaintextBuf *C.signal_buffer
|
||||||
|
var code C.int
|
||||||
|
|
||||||
|
switch msg.Type {
|
||||||
|
case CiphertextPreKeyType:
|
||||||
|
var preKeyMsg *C.pre_key_signal_message
|
||||||
|
if useOmemoFraming {
|
||||||
|
preKeyMsg, code = deserializePreKeySignalMessageOmemo(ctx, ptr, C.size_t(len(msg.Serialized)), ownRegistrationID)
|
||||||
|
} else {
|
||||||
|
code = C.pre_key_signal_message_deserialize(&preKeyMsg, ptr, C.size_t(len(msg.Serialized)), ctx.raw)
|
||||||
|
}
|
||||||
|
if code != C.SG_SUCCESS {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(preKeyMsg)))
|
||||||
|
code = C.session_cipher_decrypt_pre_key_signal_message(c.raw, preKeyMsg, nil, &plaintextBuf)
|
||||||
|
case CiphertextSignalType:
|
||||||
|
var signalMsg *C.signal_message
|
||||||
|
if useOmemoFraming {
|
||||||
|
signalMsg, code = deserializeSignalMessageOmemo(ctx, ptr, C.size_t(len(msg.Serialized)))
|
||||||
|
} else {
|
||||||
|
code = C.signal_message_deserialize(&signalMsg, ptr, C.size_t(len(msg.Serialized)), ctx.raw)
|
||||||
|
}
|
||||||
|
if code != C.SG_SUCCESS {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(signalMsg)))
|
||||||
|
code = C.session_cipher_decrypt_signal_message(c.raw, signalMsg, nil, &plaintextBuf)
|
||||||
|
default:
|
||||||
|
code = C.int(ErrInvalidMessage)
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(msg.Serialized)
|
||||||
|
|
||||||
|
if code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("session_cipher_decrypt", int(code))
|
||||||
|
}
|
||||||
|
defer freeBuffer(plaintextBuf)
|
||||||
|
return bufferToBytes(plaintextBuf), nil
|
||||||
|
}
|
||||||
89
e2ee/omemo/libsignal/context.go
Normal file
89
e2ee/omemo/libsignal/context.go
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <signal_protocol.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Forward declarations of the Go-exported crypto callbacks implemented in
|
||||||
|
// crypto.go. Declared here (rather than relying on a generated
|
||||||
|
// _cgo_export.h) with signatures matching signal_protocol.h's
|
||||||
|
// signal_crypto_provider field types exactly, so the struct-literal
|
||||||
|
// assignment below type-checks; the actual symbols are resolved at link
|
||||||
|
// time regardless of the const-qualifier differences cgo's own generated
|
||||||
|
// prototypes would have (const has no effect on calling convention).
|
||||||
|
extern int go_crypto_random(uint8_t *data, size_t len, void *user_data);
|
||||||
|
extern int go_crypto_hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *user_data);
|
||||||
|
extern int go_crypto_hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *user_data);
|
||||||
|
extern int go_crypto_hmac_sha256_final(void *hmac_context, signal_buffer **output, void *user_data);
|
||||||
|
extern void go_crypto_hmac_sha256_cleanup(void *hmac_context, void *user_data);
|
||||||
|
extern int go_crypto_sha512_init(void **digest_context, void *user_data);
|
||||||
|
extern int go_crypto_sha512_update(void *digest_context, const uint8_t *data, size_t data_len, void *user_data);
|
||||||
|
extern int go_crypto_sha512_final(void *digest_context, signal_buffer **output, void *user_data);
|
||||||
|
extern void go_crypto_sha512_cleanup(void *digest_context, void *user_data);
|
||||||
|
extern int go_crypto_encrypt(signal_buffer **output, int cipher,
|
||||||
|
const uint8_t *key, size_t key_len,
|
||||||
|
const uint8_t *iv, size_t iv_len,
|
||||||
|
const uint8_t *plaintext, size_t plaintext_len, void *user_data);
|
||||||
|
extern int go_crypto_decrypt(signal_buffer **output, int cipher,
|
||||||
|
const uint8_t *key, size_t key_len,
|
||||||
|
const uint8_t *iv, size_t iv_len,
|
||||||
|
const uint8_t *ciphertext, size_t ciphertext_len, void *user_data);
|
||||||
|
|
||||||
|
static int telegabber_setup_crypto_provider(signal_context *ctx) {
|
||||||
|
signal_crypto_provider provider;
|
||||||
|
provider.random_func = go_crypto_random;
|
||||||
|
provider.hmac_sha256_init_func = go_crypto_hmac_sha256_init;
|
||||||
|
provider.hmac_sha256_update_func = go_crypto_hmac_sha256_update;
|
||||||
|
provider.hmac_sha256_final_func = go_crypto_hmac_sha256_final;
|
||||||
|
provider.hmac_sha256_cleanup_func = go_crypto_hmac_sha256_cleanup;
|
||||||
|
provider.sha512_digest_init_func = go_crypto_sha512_init;
|
||||||
|
provider.sha512_digest_update_func = go_crypto_sha512_update;
|
||||||
|
provider.sha512_digest_final_func = go_crypto_sha512_final;
|
||||||
|
provider.sha512_digest_cleanup_func = go_crypto_sha512_cleanup;
|
||||||
|
provider.encrypt_func = go_crypto_encrypt;
|
||||||
|
provider.decrypt_func = go_crypto_decrypt;
|
||||||
|
provider.user_data = 0;
|
||||||
|
return signal_context_set_crypto_provider(ctx, &provider);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// Context wraps the global signal_context, wiring up a crypto provider
|
||||||
|
// backed by Go's standard library (libomemo-c ships no default one).
|
||||||
|
//
|
||||||
|
// This binding does not register locking functions with the library (see
|
||||||
|
// signal_context_set_locking_functions), because the C API requires them to
|
||||||
|
// support recursive locking, which a plain sync.Mutex cannot safely
|
||||||
|
// provide. Instead, callers MUST serialize all operations against a given
|
||||||
|
// Context - and anything built from it (StoreContext, SessionBuilder,
|
||||||
|
// SessionCipher) - themselves; libomemo-c does not spawn threads of its
|
||||||
|
// own, so a single mutex held by the caller around each call is sufficient.
|
||||||
|
type Context struct {
|
||||||
|
raw *C.signal_context
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext creates and configures a new global library context.
|
||||||
|
func NewContext() (*Context, error) {
|
||||||
|
ctx := &Context{}
|
||||||
|
if code := C.signal_context_create(&ctx.raw, nil); code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("signal_context_create", int(code))
|
||||||
|
}
|
||||||
|
if code := C.telegabber_setup_crypto_provider(ctx.raw); code != C.SG_SUCCESS {
|
||||||
|
C.signal_context_destroy(ctx.raw)
|
||||||
|
return nil, newError("signal_context_set_crypto_provider", int(code))
|
||||||
|
}
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close destroys the underlying signal_context. Do not use the Context (or
|
||||||
|
// anything built from it) afterward.
|
||||||
|
func (c *Context) Close() {
|
||||||
|
if c.raw != nil {
|
||||||
|
C.signal_context_destroy(c.raw)
|
||||||
|
c.raw = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errContextClosed = errors.New("libsignal: context is closed")
|
||||||
205
e2ee/omemo/libsignal/crypto.go
Normal file
205
e2ee/omemo/libsignal/crypto.go
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <signal_protocol.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/hmac"
|
||||||
|
rand2 "crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"errors"
|
||||||
|
"hash"
|
||||||
|
"runtime/cgo"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// libomemo-c ships no default signal_crypto_provider (confirmed by reading
|
||||||
|
// signal_protocol.h - only test-only OpenSSL/CommonCrypto implementations
|
||||||
|
// exist, never installed). This file implements the five callback groups
|
||||||
|
// documented there - random, HMAC-SHA256, SHA-512, and AES encrypt/decrypt
|
||||||
|
// in the two modes the library actually asks for (CTR-nopadding and
|
||||||
|
// CBC-PKCS5; no GCM) - entirely against Go's standard library.
|
||||||
|
//
|
||||||
|
// hmac_context/digest_context are opaque per-operation state the library
|
||||||
|
// holds between init/update/final/cleanup calls. Since C can't safely hold
|
||||||
|
// a long-lived Go pointer, each is a runtime/cgo.Handle round-tripped
|
||||||
|
// through a uintptr, exactly like the store callbacks in callbacks.go.
|
||||||
|
|
||||||
|
//export go_crypto_random
|
||||||
|
func go_crypto_random(data *C.uint8_t, length C.size_t, userData unsafe.Pointer) C.int {
|
||||||
|
buf := unsafe.Slice((*byte)(unsafe.Pointer(data)), int(length))
|
||||||
|
if _, err := rand2.Read(buf); err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
type hmacState struct {
|
||||||
|
h hash.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_crypto_hmac_sha256_init
|
||||||
|
func go_crypto_hmac_sha256_init(hmacContext *unsafe.Pointer, key *C.uint8_t, keyLen C.size_t, userData unsafe.Pointer) C.int {
|
||||||
|
keyBytes := cBytesToGo(key, keyLen)
|
||||||
|
handle := cgo.NewHandle(&hmacState{h: hmac.New(sha256.New, keyBytes)})
|
||||||
|
// go vet flags this as "possible misuse of unsafe.Pointer" - expected: a
|
||||||
|
// cgo.Handle is an opaque uintptr token, never dereferenced as a real
|
||||||
|
// address, and signal_protocol.h's callback shape requires void* here.
|
||||||
|
*hmacContext = unsafe.Pointer(uintptr(handle))
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_crypto_hmac_sha256_update
|
||||||
|
func go_crypto_hmac_sha256_update(hmacContext unsafe.Pointer, data *C.uint8_t, dataLen C.size_t, userData unsafe.Pointer) C.int {
|
||||||
|
st, ok := cgo.Handle(uintptr(hmacContext)).Value().(*hmacState)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
st.h.Write(cBytesToGo(data, dataLen))
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_crypto_hmac_sha256_final
|
||||||
|
func go_crypto_hmac_sha256_final(hmacContext unsafe.Pointer, output **C.signal_buffer, userData unsafe.Pointer) C.int {
|
||||||
|
st, ok := cgo.Handle(uintptr(hmacContext)).Value().(*hmacState)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
*output = bytesToBuffer(st.h.Sum(nil))
|
||||||
|
st.h.Reset()
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_crypto_hmac_sha256_cleanup
|
||||||
|
func go_crypto_hmac_sha256_cleanup(hmacContext unsafe.Pointer, userData unsafe.Pointer) {
|
||||||
|
cgo.Handle(uintptr(hmacContext)).Delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
type digestState struct {
|
||||||
|
h hash.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_crypto_sha512_init
|
||||||
|
func go_crypto_sha512_init(digestContext *unsafe.Pointer, userData unsafe.Pointer) C.int {
|
||||||
|
handle := cgo.NewHandle(&digestState{h: sha512.New()})
|
||||||
|
// See the identical vet note in go_crypto_hmac_sha256_init above.
|
||||||
|
*digestContext = unsafe.Pointer(uintptr(handle))
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_crypto_sha512_update
|
||||||
|
func go_crypto_sha512_update(digestContext unsafe.Pointer, data *C.uint8_t, dataLen C.size_t, userData unsafe.Pointer) C.int {
|
||||||
|
st, ok := cgo.Handle(uintptr(digestContext)).Value().(*digestState)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
st.h.Write(cBytesToGo(data, dataLen))
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_crypto_sha512_final
|
||||||
|
func go_crypto_sha512_final(digestContext unsafe.Pointer, output **C.signal_buffer, userData unsafe.Pointer) C.int {
|
||||||
|
st, ok := cgo.Handle(uintptr(digestContext)).Value().(*digestState)
|
||||||
|
if !ok {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
*output = bytesToBuffer(st.h.Sum(nil))
|
||||||
|
st.h.Reset()
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_crypto_sha512_cleanup
|
||||||
|
func go_crypto_sha512_cleanup(digestContext unsafe.Pointer, userData unsafe.Pointer) {
|
||||||
|
cgo.Handle(uintptr(digestContext)).Delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_crypto_encrypt
|
||||||
|
func go_crypto_encrypt(output **C.signal_buffer, cipherMode C.int, key *C.uint8_t, keyLen C.size_t, iv *C.uint8_t, ivLen C.size_t, plaintext *C.uint8_t, plaintextLen C.size_t, userData unsafe.Pointer) C.int {
|
||||||
|
block, err := aes.NewCipher(cBytesToGo(key, keyLen))
|
||||||
|
if err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
ivBytes := cBytesToGo(iv, ivLen)
|
||||||
|
plaintextBytes := cBytesToGo(plaintext, plaintextLen)
|
||||||
|
|
||||||
|
var out []byte
|
||||||
|
switch cipherMode {
|
||||||
|
case C.SG_CIPHER_AES_CTR_NOPADDING:
|
||||||
|
out = make([]byte, len(plaintextBytes))
|
||||||
|
cipher.NewCTR(block, ivBytes).XORKeyStream(out, plaintextBytes)
|
||||||
|
case C.SG_CIPHER_AES_CBC_PKCS5:
|
||||||
|
padded := pkcs7Pad(plaintextBytes, aes.BlockSize)
|
||||||
|
out = make([]byte, len(padded))
|
||||||
|
cipher.NewCBCEncrypter(block, ivBytes).CryptBlocks(out, padded)
|
||||||
|
default:
|
||||||
|
return C.int(ErrInvalidArgument)
|
||||||
|
}
|
||||||
|
|
||||||
|
*output = bytesToBuffer(out)
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
//export go_crypto_decrypt
|
||||||
|
func go_crypto_decrypt(output **C.signal_buffer, cipherMode C.int, key *C.uint8_t, keyLen C.size_t, iv *C.uint8_t, ivLen C.size_t, ciphertext *C.uint8_t, ciphertextLen C.size_t, userData unsafe.Pointer) C.int {
|
||||||
|
block, err := aes.NewCipher(cBytesToGo(key, keyLen))
|
||||||
|
if err != nil {
|
||||||
|
return C.int(ErrUnknown)
|
||||||
|
}
|
||||||
|
ivBytes := cBytesToGo(iv, ivLen)
|
||||||
|
ciphertextBytes := cBytesToGo(ciphertext, ciphertextLen)
|
||||||
|
|
||||||
|
var out []byte
|
||||||
|
switch cipherMode {
|
||||||
|
case C.SG_CIPHER_AES_CTR_NOPADDING:
|
||||||
|
out = make([]byte, len(ciphertextBytes))
|
||||||
|
cipher.NewCTR(block, ivBytes).XORKeyStream(out, ciphertextBytes)
|
||||||
|
case C.SG_CIPHER_AES_CBC_PKCS5:
|
||||||
|
if len(ciphertextBytes) == 0 || len(ciphertextBytes)%aes.BlockSize != 0 {
|
||||||
|
return C.int(ErrInvalidMessage)
|
||||||
|
}
|
||||||
|
decrypted := make([]byte, len(ciphertextBytes))
|
||||||
|
cipher.NewCBCDecrypter(block, ivBytes).CryptBlocks(decrypted, ciphertextBytes)
|
||||||
|
unpadded, err := pkcs7Unpad(decrypted)
|
||||||
|
if err != nil {
|
||||||
|
return C.int(ErrInvalidMessage)
|
||||||
|
}
|
||||||
|
out = unpadded
|
||||||
|
default:
|
||||||
|
return C.int(ErrInvalidArgument)
|
||||||
|
}
|
||||||
|
|
||||||
|
*output = bytesToBuffer(out)
|
||||||
|
return C.SG_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
func pkcs7Pad(data []byte, blockSize int) []byte {
|
||||||
|
padLen := blockSize - (len(data) % blockSize)
|
||||||
|
padded := make([]byte, len(data)+padLen)
|
||||||
|
copy(padded, data)
|
||||||
|
for i := len(data); i < len(padded); i++ {
|
||||||
|
padded[i] = byte(padLen)
|
||||||
|
}
|
||||||
|
return padded
|
||||||
|
}
|
||||||
|
|
||||||
|
func pkcs7Unpad(data []byte) ([]byte, error) {
|
||||||
|
n := len(data)
|
||||||
|
if n == 0 {
|
||||||
|
return nil, errors.New("libsignal: cannot unpad empty data")
|
||||||
|
}
|
||||||
|
padLen := int(data[n-1])
|
||||||
|
if padLen == 0 || padLen > n || padLen > aes.BlockSize {
|
||||||
|
return nil, errors.New("libsignal: invalid PKCS7 padding")
|
||||||
|
}
|
||||||
|
for _, b := range data[n-padLen:] {
|
||||||
|
if int(b) != padLen {
|
||||||
|
return nil, errors.New("libsignal: invalid PKCS7 padding")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data[:n-padLen], nil
|
||||||
|
}
|
||||||
23
e2ee/omemo/libsignal/deserialize_legacy.go
Normal file
23
e2ee/omemo/libsignal/deserialize_legacy.go
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
//go:build signal_legacy
|
||||||
|
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <signal_protocol.h>
|
||||||
|
#include <protocol.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// Vanilla libsignal-protocol-c has no OMEMO (protocol v4) wire format
|
||||||
|
// support at all, so these always fail with ErrInvalidVersion - callers
|
||||||
|
// (SessionCipher.Decrypt) should never reach here with useOmemoFraming set
|
||||||
|
// under this build, since ModernOMEMOSupported is false and the caller is
|
||||||
|
// expected to check it first.
|
||||||
|
|
||||||
|
func deserializeSignalMessageOmemo(ctx *Context, data *C.uint8_t, length C.size_t) (*C.signal_message, C.int) {
|
||||||
|
return nil, C.int(ErrInvalidVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deserializePreKeySignalMessageOmemo(ctx *Context, data *C.uint8_t, length C.size_t, registrationID uint32) (*C.pre_key_signal_message, C.int) {
|
||||||
|
return nil, C.int(ErrInvalidVersion)
|
||||||
|
}
|
||||||
27
e2ee/omemo/libsignal/deserialize_modern.go
Normal file
27
e2ee/omemo/libsignal/deserialize_modern.go
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
//go:build !signal_legacy
|
||||||
|
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <signal_protocol.h>
|
||||||
|
#include <protocol.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// deserializeSignalMessageOmemo and deserializePreKeySignalMessageOmemo
|
||||||
|
// parse the OMEMO (protocol v4) wire encoding, which only libomemo-c
|
||||||
|
// understands - signal_message_deserialize_omemo and
|
||||||
|
// pre_key_signal_message_deserialize_omemo don't exist in vanilla
|
||||||
|
// libsignal-protocol-c (see deserialize_legacy.go).
|
||||||
|
|
||||||
|
func deserializeSignalMessageOmemo(ctx *Context, data *C.uint8_t, length C.size_t) (*C.signal_message, C.int) {
|
||||||
|
var msg *C.signal_message
|
||||||
|
code := C.signal_message_deserialize_omemo(&msg, data, length, ctx.raw)
|
||||||
|
return msg, code
|
||||||
|
}
|
||||||
|
|
||||||
|
func deserializePreKeySignalMessageOmemo(ctx *Context, data *C.uint8_t, length C.size_t, registrationID uint32) (*C.pre_key_signal_message, C.int) {
|
||||||
|
var msg *C.pre_key_signal_message
|
||||||
|
code := C.pre_key_signal_message_deserialize_omemo(&msg, data, length, C.uint32_t(registrationID), ctx.raw)
|
||||||
|
return msg, code
|
||||||
|
}
|
||||||
29
e2ee/omemo/libsignal/doc.go
Normal file
29
e2ee/omemo/libsignal/doc.go
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
// Package libsignal is a cgo binding around libomemo-c (github.com/dino/libomemo-c),
|
||||||
|
// a fork of libsignal-protocol-c that implements both legacy "siacs" OMEMO
|
||||||
|
// (protocol version 3) and modern OMEMO 1/OMEMO 2 (protocol version 4) on a
|
||||||
|
// shared Double Ratchet / X3DH crypto core.
|
||||||
|
//
|
||||||
|
// This package deliberately knows nothing about XMPP or OMEMO's XML wire
|
||||||
|
// format (device lists, bundles, the <encrypted> stanza shape) - it only
|
||||||
|
// exposes the underlying session/cipher primitives and a pluggable Store
|
||||||
|
// interface for persistence. The XEP-0384 specific layer lives above this
|
||||||
|
// package, in dev.narayana.im/narayana/telegabber/e2ee/omemo.
|
||||||
|
//
|
||||||
|
// # Build modes
|
||||||
|
//
|
||||||
|
// By default this package links libomemo-c and supports both legacy and
|
||||||
|
// modern OMEMO. Passing the "signal_legacy" build tag (e.g. `go build
|
||||||
|
// -tags signal_legacy`) instead links the pre-fork libsignal-protocol-c -
|
||||||
|
// useful on distros that only package that one (e.g. Debian bullseye ships
|
||||||
|
// libsignal-protocol-c-dev but not libomemo-c-dev, which arrived in
|
||||||
|
// bookworm). That build only supports legacy/siacs OMEMO; check
|
||||||
|
// ModernOMEMOSupported before relying on anything else. See cgo_modern.go/
|
||||||
|
// cgo_legacy.go and the version_*/deserialize_*/signedprekey_* files for
|
||||||
|
// the resulting API-surface differences between the two libraries.
|
||||||
|
//
|
||||||
|
// Concurrency: neither library is internally thread-safe (this binding
|
||||||
|
// does not wire up the optional locking-function hook - see Context).
|
||||||
|
// Callers must serialize all operations against a given Context/
|
||||||
|
// StoreContext pair themselves, e.g. with a single mutex per bridged chat
|
||||||
|
// identity.
|
||||||
|
package libsignal
|
||||||
113
e2ee/omemo/libsignal/errors.go
Normal file
113
e2ee/omemo/libsignal/errors.go
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Error codes as defined in signal_protocol.h. Duplicated here as plain Go
|
||||||
|
// constants (rather than referenced via cgo) so this file has no cgo
|
||||||
|
// dependency and callers outside this package can compare against them.
|
||||||
|
const (
|
||||||
|
ErrNoMemory = -12
|
||||||
|
ErrInvalidArgument = -22
|
||||||
|
ErrUnknown = -1000
|
||||||
|
ErrDuplicateMessage = -1001
|
||||||
|
ErrInvalidKey = -1002
|
||||||
|
ErrInvalidKeyID = -1003
|
||||||
|
ErrInvalidMAC = -1004
|
||||||
|
ErrInvalidMessage = -1005
|
||||||
|
ErrInvalidVersion = -1006
|
||||||
|
ErrLegacyMessage = -1007
|
||||||
|
ErrNoSession = -1008
|
||||||
|
ErrStaleKeyExchange = -1009
|
||||||
|
ErrUntrustedIdentity = -1010
|
||||||
|
ErrVRFSigVerifyFailed = -1011
|
||||||
|
ErrInvalidProtobuf = -1100
|
||||||
|
ErrFPVersionMismatch = -1200
|
||||||
|
ErrFPIdentMismatch = -1201
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error wraps a libsignal-c return code.
|
||||||
|
type Error struct {
|
||||||
|
Code int
|
||||||
|
Op string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
return fmt.Sprintf("libsignal: %s: %s (%d)", e.Op, errName(e.Code), e.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errName(code int) string {
|
||||||
|
switch code {
|
||||||
|
case ErrNoMemory:
|
||||||
|
return "out of memory"
|
||||||
|
case ErrInvalidArgument:
|
||||||
|
return "invalid argument"
|
||||||
|
case ErrUnknown:
|
||||||
|
return "unknown error"
|
||||||
|
case ErrDuplicateMessage:
|
||||||
|
return "duplicate message"
|
||||||
|
case ErrInvalidKey:
|
||||||
|
return "invalid key"
|
||||||
|
case ErrInvalidKeyID:
|
||||||
|
return "invalid key id"
|
||||||
|
case ErrInvalidMAC:
|
||||||
|
return "invalid MAC"
|
||||||
|
case ErrInvalidMessage:
|
||||||
|
return "invalid message"
|
||||||
|
case ErrInvalidVersion:
|
||||||
|
return "invalid version"
|
||||||
|
case ErrLegacyMessage:
|
||||||
|
return "message uses a protocol version no longer supported"
|
||||||
|
case ErrNoSession:
|
||||||
|
return "no session"
|
||||||
|
case ErrStaleKeyExchange:
|
||||||
|
return "stale key exchange"
|
||||||
|
case ErrUntrustedIdentity:
|
||||||
|
return "untrusted identity"
|
||||||
|
case ErrVRFSigVerifyFailed:
|
||||||
|
return "VRF signature verification failed"
|
||||||
|
case ErrInvalidProtobuf:
|
||||||
|
return "invalid protobuf"
|
||||||
|
case ErrFPVersionMismatch:
|
||||||
|
return "fingerprint version mismatch"
|
||||||
|
case ErrFPIdentMismatch:
|
||||||
|
return "fingerprint identity mismatch"
|
||||||
|
default:
|
||||||
|
return "library error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newError returns nil for a non-negative (success) return code, or an
|
||||||
|
// *Error wrapping it otherwise. op names the operation that produced code,
|
||||||
|
// for diagnostics.
|
||||||
|
func newError(op string, code int) error {
|
||||||
|
if code >= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Error{Code: code, Op: op}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsUntrustedIdentity reports whether err is (or wraps) SG_ERR_UNTRUSTED_IDENTITY -
|
||||||
|
// the signal that a remote device's identity key changed since it was last
|
||||||
|
// trusted (the case a TOFU trust policy must not silently paper over).
|
||||||
|
func IsUntrustedIdentity(err error) bool {
|
||||||
|
e, ok := err.(*Error)
|
||||||
|
return ok && e.Code == ErrUntrustedIdentity
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNoSession reports whether err is (or wraps) SG_ERR_NO_SESSION.
|
||||||
|
func IsNoSession(err error) bool {
|
||||||
|
e, ok := err.(*Error)
|
||||||
|
return ok && e.Code == ErrNoSession
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDuplicateMessage reports whether err is (or wraps) SG_ERR_DUPLICATE_MESSAGE.
|
||||||
|
func IsDuplicateMessage(err error) bool {
|
||||||
|
e, ok := err.(*Error)
|
||||||
|
return ok && e.Code == ErrDuplicateMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLegacyMessage reports whether err is (or wraps) SG_ERR_LEGACY_MESSAGE.
|
||||||
|
func IsLegacyMessage(err error) bool {
|
||||||
|
e, ok := err.(*Error)
|
||||||
|
return ok && e.Code == ErrLegacyMessage
|
||||||
|
}
|
||||||
268
e2ee/omemo/libsignal/keys.go
Normal file
268
e2ee/omemo/libsignal/keys.go
Normal file
|
|
@ -0,0 +1,268 @@
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <signal_protocol.h>
|
||||||
|
#include <key_helper.h>
|
||||||
|
#include <session_pre_key.h>
|
||||||
|
#include <ratchet.h>
|
||||||
|
#include <curve.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IdentityKeyPair is the serialized (protobuf) form of a Curve25519
|
||||||
|
// identity key pair, as produced by ratchet_identity_key_pair_serialize.
|
||||||
|
// This is the form to persist; use SplitIdentityKeyPair/DecodeIdentityPublicKey
|
||||||
|
// to recover the individual keys when needed.
|
||||||
|
type IdentityKeyPair struct {
|
||||||
|
Record []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateIdentityKeyPair creates a new identity key pair. Do this once per
|
||||||
|
// bridged-chat identity, at first use, and persist the result.
|
||||||
|
func GenerateIdentityKeyPair(ctx *Context) (*IdentityKeyPair, error) {
|
||||||
|
var kp *C.ratchet_identity_key_pair
|
||||||
|
if code := C.signal_protocol_key_helper_generate_identity_key_pair(&kp, ctx.raw); code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("signal_protocol_key_helper_generate_identity_key_pair", int(code))
|
||||||
|
}
|
||||||
|
defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(kp)))
|
||||||
|
|
||||||
|
var buf *C.signal_buffer
|
||||||
|
if code := C.ratchet_identity_key_pair_serialize(&buf, kp); code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("ratchet_identity_key_pair_serialize", int(code))
|
||||||
|
}
|
||||||
|
defer freeBuffer(buf)
|
||||||
|
|
||||||
|
return &IdentityKeyPair{Record: bufferToBytes(buf)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deserializeIdentityKeyPair(ctx *Context, identity *IdentityKeyPair) (*C.ratchet_identity_key_pair, error) {
|
||||||
|
var kp *C.ratchet_identity_key_pair
|
||||||
|
data := identity.Record
|
||||||
|
var ptr *C.uint8_t
|
||||||
|
if len(data) > 0 {
|
||||||
|
ptr = (*C.uint8_t)(unsafe.Pointer(&data[0]))
|
||||||
|
}
|
||||||
|
code := C.ratchet_identity_key_pair_deserialize(&kp, ptr, C.size_t(len(data)), ctx.raw)
|
||||||
|
runtime.KeepAlive(data)
|
||||||
|
if code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("ratchet_identity_key_pair_deserialize", int(code))
|
||||||
|
}
|
||||||
|
return kp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitIdentityKeyPair deserializes a combined identity key pair record (as
|
||||||
|
// produced by GenerateIdentityKeyPair) into the separate public/private key
|
||||||
|
// buffers the library's identity-key-store callback (IdentityStore.
|
||||||
|
// GetIdentityKeyPair) is expected to return.
|
||||||
|
func SplitIdentityKeyPair(ctx *Context, record []byte) (public, private []byte, err error) {
|
||||||
|
kp, err := deserializeIdentityKeyPair(ctx, &IdentityKeyPair{Record: record})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(kp)))
|
||||||
|
|
||||||
|
var pubBuf *C.signal_buffer
|
||||||
|
if code := C.ec_public_key_serialize(&pubBuf, C.ratchet_identity_key_pair_get_public(kp)); code != C.SG_SUCCESS {
|
||||||
|
return nil, nil, newError("ec_public_key_serialize", int(code))
|
||||||
|
}
|
||||||
|
defer freeBuffer(pubBuf)
|
||||||
|
|
||||||
|
var privBuf *C.signal_buffer
|
||||||
|
if code := C.ec_private_key_serialize(&privBuf, C.ratchet_identity_key_pair_get_private(kp)); code != C.SG_SUCCESS {
|
||||||
|
return nil, nil, newError("ec_private_key_serialize", int(code))
|
||||||
|
}
|
||||||
|
defer freeBuffer(privBuf)
|
||||||
|
|
||||||
|
return bufferToBytes(pubBuf), bufferToBytes(privBuf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeIdentityPublicKey extracts the raw public key bytes from a combined
|
||||||
|
// identity key pair record, for publishing in an XEP-0384 bundle.
|
||||||
|
func DecodeIdentityPublicKey(ctx *Context, record []byte) ([]byte, error) {
|
||||||
|
kp, err := deserializeIdentityKeyPair(ctx, &IdentityKeyPair{Record: record})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(kp)))
|
||||||
|
|
||||||
|
var pubBuf *C.signal_buffer
|
||||||
|
if code := C.ec_public_key_serialize(&pubBuf, C.ratchet_identity_key_pair_get_public(kp)); code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("ec_public_key_serialize", int(code))
|
||||||
|
}
|
||||||
|
defer freeBuffer(pubBuf)
|
||||||
|
return bufferToBytes(pubBuf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateRegistrationID creates a new registration id - a random number
|
||||||
|
// each identity picks once, at "install" time.
|
||||||
|
func GenerateRegistrationID(ctx *Context) (uint32, error) {
|
||||||
|
var id C.uint32_t
|
||||||
|
if code := C.signal_protocol_key_helper_generate_registration_id(&id, 0, ctx.raw); code != C.SG_SUCCESS {
|
||||||
|
return 0, newError("signal_protocol_key_helper_generate_registration_id", int(code))
|
||||||
|
}
|
||||||
|
return uint32(id), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreKey is a serialized one-time prekey record plus its numeric id.
|
||||||
|
type PreKey struct {
|
||||||
|
ID uint32
|
||||||
|
Record []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneratePreKeys creates count one-time prekeys with sequential ids
|
||||||
|
// starting at start. Store every one; each is consumed (and should then be
|
||||||
|
// deleted) the first time a remote peer's session is built from it.
|
||||||
|
func GeneratePreKeys(ctx *Context, start, count uint32) ([]PreKey, error) {
|
||||||
|
var head *C.signal_protocol_key_helper_pre_key_list_node
|
||||||
|
if code := C.signal_protocol_key_helper_generate_pre_keys(&head, C.uint(start), C.uint(count), ctx.raw); code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("signal_protocol_key_helper_generate_pre_keys", int(code))
|
||||||
|
}
|
||||||
|
defer C.signal_protocol_key_helper_key_list_free(head)
|
||||||
|
|
||||||
|
var preKeys []PreKey
|
||||||
|
for node := head; node != nil; node = C.signal_protocol_key_helper_key_list_next(node) {
|
||||||
|
pk := C.signal_protocol_key_helper_key_list_element(node)
|
||||||
|
var buf *C.signal_buffer
|
||||||
|
if code := C.session_pre_key_serialize(&buf, pk); code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("session_pre_key_serialize", int(code))
|
||||||
|
}
|
||||||
|
preKeys = append(preKeys, PreKey{
|
||||||
|
ID: uint32(C.session_pre_key_get_id(pk)),
|
||||||
|
Record: bufferToBytes(buf),
|
||||||
|
})
|
||||||
|
freeBuffer(buf)
|
||||||
|
}
|
||||||
|
return preKeys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deserializePreKey(ctx *Context, record []byte) (*C.session_pre_key, error) {
|
||||||
|
var pk *C.session_pre_key
|
||||||
|
var ptr *C.uint8_t
|
||||||
|
if len(record) > 0 {
|
||||||
|
ptr = (*C.uint8_t)(unsafe.Pointer(&record[0]))
|
||||||
|
}
|
||||||
|
code := C.session_pre_key_deserialize(&pk, ptr, C.size_t(len(record)), ctx.raw)
|
||||||
|
runtime.KeepAlive(record)
|
||||||
|
if code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("session_pre_key_deserialize", int(code))
|
||||||
|
}
|
||||||
|
return pk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreKeyInfo is the decoded, ready-to-publish material for a one-time prekey.
|
||||||
|
type PreKeyInfo struct {
|
||||||
|
ID uint32
|
||||||
|
PublicKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodePreKey deserializes a stored one-time prekey record (as produced by
|
||||||
|
// GeneratePreKeys) and extracts its id and raw public key, for publishing in
|
||||||
|
// an XEP-0384 bundle.
|
||||||
|
func DecodePreKey(ctx *Context, record []byte) (*PreKeyInfo, error) {
|
||||||
|
pk, err := deserializePreKey(ctx, record)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(pk)))
|
||||||
|
|
||||||
|
var pubBuf *C.signal_buffer
|
||||||
|
if code := C.ec_public_key_serialize(&pubBuf, C.ec_key_pair_get_public(C.session_pre_key_get_key_pair(pk))); code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("ec_public_key_serialize", int(code))
|
||||||
|
}
|
||||||
|
defer freeBuffer(pubBuf)
|
||||||
|
|
||||||
|
return &PreKeyInfo{
|
||||||
|
ID: uint32(C.session_pre_key_get_id(pk)),
|
||||||
|
PublicKey: bufferToBytes(pubBuf),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedPreKey is a serialized signed prekey record plus its numeric id.
|
||||||
|
type SignedPreKey struct {
|
||||||
|
ID uint32
|
||||||
|
Record []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSignedPreKey creates a new signed prekey, signed by identity (in
|
||||||
|
// its combined serialized form, as produced by GenerateIdentityKeyPair),
|
||||||
|
// with the given id.
|
||||||
|
func GenerateSignedPreKey(ctx *Context, identity *IdentityKeyPair, id uint32) (*SignedPreKey, error) {
|
||||||
|
idKeyPair, err := deserializeIdentityKeyPair(ctx, identity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(idKeyPair)))
|
||||||
|
|
||||||
|
var signedPreKey *C.session_signed_pre_key
|
||||||
|
timestamp := C.uint64_t(time.Now().UnixMilli())
|
||||||
|
if code := C.signal_protocol_key_helper_generate_signed_pre_key(&signedPreKey, idKeyPair, C.uint32_t(id), timestamp, ctx.raw); code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("signal_protocol_key_helper_generate_signed_pre_key", int(code))
|
||||||
|
}
|
||||||
|
defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(signedPreKey)))
|
||||||
|
|
||||||
|
var buf *C.signal_buffer
|
||||||
|
if code := C.session_signed_pre_key_serialize(&buf, signedPreKey); code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("session_signed_pre_key_serialize", int(code))
|
||||||
|
}
|
||||||
|
defer freeBuffer(buf)
|
||||||
|
|
||||||
|
return &SignedPreKey{
|
||||||
|
ID: uint32(C.session_signed_pre_key_get_id(signedPreKey)),
|
||||||
|
Record: bufferToBytes(buf),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deserializeSignedPreKey(ctx *Context, record []byte) (*C.session_signed_pre_key, error) {
|
||||||
|
var spk *C.session_signed_pre_key
|
||||||
|
var ptr *C.uint8_t
|
||||||
|
if len(record) > 0 {
|
||||||
|
ptr = (*C.uint8_t)(unsafe.Pointer(&record[0]))
|
||||||
|
}
|
||||||
|
code := C.session_signed_pre_key_deserialize(&spk, ptr, C.size_t(len(record)), ctx.raw)
|
||||||
|
runtime.KeepAlive(record)
|
||||||
|
if code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("session_signed_pre_key_deserialize", int(code))
|
||||||
|
}
|
||||||
|
return spk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedPreKeyInfo is the decoded, ready-to-publish material for a signed
|
||||||
|
// prekey: its numeric id, raw public key, and (both) signature forms the
|
||||||
|
// library maintains - a legacy signature and an OMEMO signature (libomemo-c
|
||||||
|
// computes both regardless of which protocol version ends up using it).
|
||||||
|
type SignedPreKeyInfo struct {
|
||||||
|
ID uint32
|
||||||
|
PublicKey []byte
|
||||||
|
Signature []byte
|
||||||
|
SignatureOMEMO []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeSignedPreKey deserializes a stored signed prekey record (as produced
|
||||||
|
// by GenerateSignedPreKey) and extracts the fields needed to publish it in
|
||||||
|
// an XEP-0384 bundle.
|
||||||
|
func DecodeSignedPreKey(ctx *Context, record []byte) (*SignedPreKeyInfo, error) {
|
||||||
|
spk, err := deserializeSignedPreKey(ctx, record)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(spk)))
|
||||||
|
|
||||||
|
var pubBuf *C.signal_buffer
|
||||||
|
if code := C.ec_public_key_serialize(&pubBuf, C.ec_key_pair_get_public(C.session_signed_pre_key_get_key_pair(spk))); code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("ec_public_key_serialize", int(code))
|
||||||
|
}
|
||||||
|
defer freeBuffer(pubBuf)
|
||||||
|
|
||||||
|
return &SignedPreKeyInfo{
|
||||||
|
ID: uint32(C.session_signed_pre_key_get_id(spk)),
|
||||||
|
PublicKey: bufferToBytes(pubBuf),
|
||||||
|
Signature: cBytesToGo(C.session_signed_pre_key_get_signature(spk), C.session_signed_pre_key_get_signature_len(spk)),
|
||||||
|
SignatureOMEMO: signedPreKeyOmemoSignature(spk),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
142
e2ee/omemo/libsignal/session.go
Normal file
142
e2ee/omemo/libsignal/session.go
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
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))
|
||||||
|
}
|
||||||
15
e2ee/omemo/libsignal/signedprekey_legacy.go
Normal file
15
e2ee/omemo/libsignal/signedprekey_legacy.go
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
//go:build signal_legacy
|
||||||
|
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <signal_protocol.h>
|
||||||
|
#include <session_pre_key.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// Vanilla libsignal-protocol-c's signed prekeys carry only the one
|
||||||
|
// (legacy) signature - there is no second OMEMO signature to extract.
|
||||||
|
func signedPreKeyOmemoSignature(spk *C.session_signed_pre_key) []byte {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
17
e2ee/omemo/libsignal/signedprekey_modern.go
Normal file
17
e2ee/omemo/libsignal/signedprekey_modern.go
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
//go:build !signal_legacy
|
||||||
|
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <signal_protocol.h>
|
||||||
|
#include <session_pre_key.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// signedPreKeyOmemoSignature extracts the OMEMO (XEdDSA/Ed25519) signature
|
||||||
|
// libomemo-c computes on every signed prekey alongside the legacy one -
|
||||||
|
// vanilla libsignal-protocol-c has no such second signature (see
|
||||||
|
// signedprekey_legacy.go).
|
||||||
|
func signedPreKeyOmemoSignature(spk *C.session_signed_pre_key) []byte {
|
||||||
|
return cBytesToGo(C.session_signed_pre_key_get_signature_omemo(spk), C.session_signed_pre_key_get_signature_omemo_len(spk))
|
||||||
|
}
|
||||||
204
e2ee/omemo/libsignal/store.go
Normal file
204
e2ee/omemo/libsignal/store.go
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <signal_protocol.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Forward declarations of the Go-exported store callbacks implemented in
|
||||||
|
// callbacks.go. See context.go for why these are hand-written rather than
|
||||||
|
// relying on a generated header, and why const-qualifier differences from
|
||||||
|
// the "true" cgo-generated prototypes are harmless here.
|
||||||
|
extern int go_get_identity_key_pair(signal_buffer **public_data, signal_buffer **private_data, void *user_data);
|
||||||
|
extern int go_get_local_registration_id(void *user_data, uint32_t *registration_id);
|
||||||
|
extern int go_save_identity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *user_data);
|
||||||
|
extern int go_is_trusted_identity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *user_data);
|
||||||
|
extern void go_identity_destroy(void *user_data);
|
||||||
|
|
||||||
|
extern int go_load_pre_key(signal_buffer **record, uint32_t pre_key_id, void *user_data);
|
||||||
|
extern int go_store_pre_key(uint32_t pre_key_id, uint8_t *record, size_t record_len, void *user_data);
|
||||||
|
extern int go_contains_pre_key(uint32_t pre_key_id, void *user_data);
|
||||||
|
extern int go_remove_pre_key(uint32_t pre_key_id, void *user_data);
|
||||||
|
extern void go_pre_key_destroy(void *user_data);
|
||||||
|
|
||||||
|
extern int go_load_signed_pre_key(signal_buffer **record, uint32_t signed_pre_key_id, void *user_data);
|
||||||
|
extern int go_store_signed_pre_key(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *user_data);
|
||||||
|
extern int go_contains_signed_pre_key(uint32_t signed_pre_key_id, void *user_data);
|
||||||
|
extern int go_remove_signed_pre_key(uint32_t signed_pre_key_id, void *user_data);
|
||||||
|
extern void go_signed_pre_key_destroy(void *user_data);
|
||||||
|
|
||||||
|
extern int go_load_session(signal_buffer **record, signal_buffer **user_record, const signal_protocol_address *address, void *user_data);
|
||||||
|
extern int go_get_sub_device_sessions(signal_int_list **sessions, const char *name, size_t name_len, void *user_data);
|
||||||
|
extern int go_store_session(const signal_protocol_address *address, uint8_t *record, size_t record_len, uint8_t *user_record, size_t user_record_len, void *user_data);
|
||||||
|
extern int go_contains_session(const signal_protocol_address *address, void *user_data);
|
||||||
|
extern int go_delete_session(const signal_protocol_address *address, void *user_data);
|
||||||
|
extern int go_delete_all_sessions(const char *name, size_t name_len, void *user_data);
|
||||||
|
extern void go_session_destroy(void *user_data);
|
||||||
|
|
||||||
|
static void telegabber_fill_identity_key_store(signal_protocol_identity_key_store *s, void *user_data) {
|
||||||
|
s->get_identity_key_pair = go_get_identity_key_pair;
|
||||||
|
s->get_local_registration_id = go_get_local_registration_id;
|
||||||
|
s->save_identity = go_save_identity;
|
||||||
|
s->is_trusted_identity = go_is_trusted_identity;
|
||||||
|
s->destroy_func = go_identity_destroy;
|
||||||
|
s->user_data = user_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void telegabber_fill_pre_key_store(signal_protocol_pre_key_store *s, void *user_data) {
|
||||||
|
s->load_pre_key = go_load_pre_key;
|
||||||
|
s->store_pre_key = go_store_pre_key;
|
||||||
|
s->contains_pre_key = go_contains_pre_key;
|
||||||
|
s->remove_pre_key = go_remove_pre_key;
|
||||||
|
s->destroy_func = go_pre_key_destroy;
|
||||||
|
s->user_data = user_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void telegabber_fill_signed_pre_key_store(signal_protocol_signed_pre_key_store *s, void *user_data) {
|
||||||
|
s->load_signed_pre_key = go_load_signed_pre_key;
|
||||||
|
s->store_signed_pre_key = go_store_signed_pre_key;
|
||||||
|
s->contains_signed_pre_key = go_contains_signed_pre_key;
|
||||||
|
s->remove_signed_pre_key = go_remove_signed_pre_key;
|
||||||
|
s->destroy_func = go_signed_pre_key_destroy;
|
||||||
|
s->user_data = user_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void telegabber_fill_session_store(signal_protocol_session_store *s, void *user_data) {
|
||||||
|
s->load_session_func = go_load_session;
|
||||||
|
s->get_sub_device_sessions_func = go_get_sub_device_sessions;
|
||||||
|
s->store_session_func = go_store_session;
|
||||||
|
s->contains_session_func = go_contains_session;
|
||||||
|
s->delete_session_func = go_delete_session;
|
||||||
|
s->delete_all_sessions_func = go_delete_all_sessions;
|
||||||
|
s->destroy_func = go_session_destroy;
|
||||||
|
s->user_data = user_data;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime/cgo"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IdentityStore persists the local identity key pair/registration id and
|
||||||
|
// tracks trust for remote identities.
|
||||||
|
type IdentityStore interface {
|
||||||
|
// GetIdentityKeyPair returns this store's own identity key pair
|
||||||
|
// (public and private key, in the library's serialized form).
|
||||||
|
GetIdentityKeyPair() (public, private []byte, err error)
|
||||||
|
// GetLocalRegistrationID returns this store's own registration id.
|
||||||
|
GetLocalRegistrationID() (uint32, error)
|
||||||
|
// SaveIdentity records addr's identity key as trusted. A nil key
|
||||||
|
// means: forget the key material but keep any other bookkeeping.
|
||||||
|
SaveIdentity(addr Address, key []byte) error
|
||||||
|
// IsTrustedIdentity reports whether key is the trusted identity key
|
||||||
|
// for addr. Per the library's TOFU convention, an address with no
|
||||||
|
// stored identity at all is trusted implicitly (return true, nil) -
|
||||||
|
// only a *mismatching* stored key should be rejected.
|
||||||
|
IsTrustedIdentity(addr Address, key []byte) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreKeyStore persists this identity's own one-time prekeys.
|
||||||
|
type PreKeyStore interface {
|
||||||
|
LoadPreKey(id uint32) (record []byte, found bool, err error)
|
||||||
|
StorePreKey(id uint32, record []byte) error
|
||||||
|
ContainsPreKey(id uint32) bool
|
||||||
|
RemovePreKey(id uint32) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedPreKeyStore persists this identity's own signed prekeys.
|
||||||
|
type SignedPreKeyStore interface {
|
||||||
|
LoadSignedPreKey(id uint32) (record []byte, found bool, err error)
|
||||||
|
StoreSignedPreKey(id uint32, record []byte) error
|
||||||
|
ContainsSignedPreKey(id uint32) bool
|
||||||
|
RemoveSignedPreKey(id uint32) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionStore persists per-device Double Ratchet session state.
|
||||||
|
type SessionStore interface {
|
||||||
|
LoadSession(addr Address) (record []byte, found bool, err error)
|
||||||
|
// GetSubDeviceSessions returns the device IDs of all known sessions
|
||||||
|
// for name, excluding the sentinel device ID 1 (per library
|
||||||
|
// convention - see get_sub_device_sessions_func in signal_protocol.h).
|
||||||
|
GetSubDeviceSessions(name string) ([]uint32, error)
|
||||||
|
StoreSession(addr Address, record []byte) error
|
||||||
|
ContainsSession(addr Address) bool
|
||||||
|
DeleteSession(addr Address) error
|
||||||
|
// DeleteAllSessions removes every session for name and returns how
|
||||||
|
// many were deleted.
|
||||||
|
DeleteAllSessions(name string) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store is the full persistence contract libomemo-c requires: one local
|
||||||
|
// identity's keys, prekeys, and remote sessions/trust state.
|
||||||
|
type Store interface {
|
||||||
|
IdentityStore
|
||||||
|
PreKeyStore
|
||||||
|
SignedPreKeyStore
|
||||||
|
SessionStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreContext wraps a signal_protocol_store_context bound to a Go Store
|
||||||
|
// implementation via a runtime/cgo.Handle (C void* cannot safely hold a Go
|
||||||
|
// interface value directly - see callbacks.go for the recovery side).
|
||||||
|
type StoreContext struct {
|
||||||
|
raw *C.signal_protocol_store_context
|
||||||
|
ctx *Context
|
||||||
|
handle cgo.Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStoreContext creates a signal_protocol_store_context that dispatches
|
||||||
|
// every callback to store. ctx must outlive the returned StoreContext.
|
||||||
|
func NewStoreContext(ctx *Context, store Store) (*StoreContext, error) {
|
||||||
|
var raw *C.signal_protocol_store_context
|
||||||
|
if code := C.signal_protocol_store_context_create(&raw, ctx.raw); code != C.SG_SUCCESS {
|
||||||
|
return nil, newError("signal_protocol_store_context_create", int(code))
|
||||||
|
}
|
||||||
|
|
||||||
|
handle := cgo.NewHandle(store)
|
||||||
|
// go vet flags this as "possible misuse of unsafe.Pointer" - expected,
|
||||||
|
// see the note in crypto.go's go_crypto_hmac_sha256_init: a cgo.Handle
|
||||||
|
// is an opaque uintptr token, never dereferenced as a real address, and
|
||||||
|
// the vtable structs' user_data fields require void* here.
|
||||||
|
userData := unsafe.Pointer(uintptr(handle))
|
||||||
|
|
||||||
|
var idStore C.signal_protocol_identity_key_store
|
||||||
|
C.telegabber_fill_identity_key_store(&idStore, userData)
|
||||||
|
if code := C.signal_protocol_store_context_set_identity_key_store(raw, &idStore); code != C.SG_SUCCESS {
|
||||||
|
handle.Delete()
|
||||||
|
return nil, newError("signal_protocol_store_context_set_identity_key_store", int(code))
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkStore C.signal_protocol_pre_key_store
|
||||||
|
C.telegabber_fill_pre_key_store(&pkStore, userData)
|
||||||
|
if code := C.signal_protocol_store_context_set_pre_key_store(raw, &pkStore); code != C.SG_SUCCESS {
|
||||||
|
handle.Delete()
|
||||||
|
return nil, newError("signal_protocol_store_context_set_pre_key_store", int(code))
|
||||||
|
}
|
||||||
|
|
||||||
|
var spkStore C.signal_protocol_signed_pre_key_store
|
||||||
|
C.telegabber_fill_signed_pre_key_store(&spkStore, userData)
|
||||||
|
if code := C.signal_protocol_store_context_set_signed_pre_key_store(raw, &spkStore); code != C.SG_SUCCESS {
|
||||||
|
handle.Delete()
|
||||||
|
return nil, newError("signal_protocol_store_context_set_signed_pre_key_store", int(code))
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessStore C.signal_protocol_session_store
|
||||||
|
C.telegabber_fill_session_store(&sessStore, userData)
|
||||||
|
if code := C.signal_protocol_store_context_set_session_store(raw, &sessStore); code != C.SG_SUCCESS {
|
||||||
|
handle.Delete()
|
||||||
|
return nil, newError("signal_protocol_store_context_set_session_store", int(code))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &StoreContext{raw: raw, ctx: ctx, handle: handle}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close destroys the underlying signal_protocol_store_context and releases
|
||||||
|
// the Go-side handle keeping the Store alive. Do not use the StoreContext
|
||||||
|
// (or anything built from it) afterward.
|
||||||
|
func (sc *StoreContext) Close() {
|
||||||
|
if sc.raw != nil {
|
||||||
|
C.signal_protocol_store_context_destroy(sc.raw)
|
||||||
|
sc.raw = nil
|
||||||
|
}
|
||||||
|
sc.handle.Delete()
|
||||||
|
}
|
||||||
37
e2ee/omemo/libsignal/version_legacy.go
Normal file
37
e2ee/omemo/libsignal/version_legacy.go
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
//go:build signal_legacy
|
||||||
|
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <signal_protocol.h>
|
||||||
|
#include <session_builder.h>
|
||||||
|
#include <session_cipher.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// errModernOMEMOUnavailable is returned whenever the caller asks this
|
||||||
|
// legacy build to do something only libomemo-c can do.
|
||||||
|
var errModernOMEMOUnavailable = errors.New("libsignal: modern OMEMO (protocol v4) requires linking libomemo-c; this build links vanilla libsignal-protocol-c (build tag \"signal_legacy\")")
|
||||||
|
|
||||||
|
// Vanilla libsignal-protocol-c has no session_builder_set_version /
|
||||||
|
// session_cipher_set_version - it only ever produces/consumes
|
||||||
|
// CIPHERTEXT_CURRENT_VERSION (protocol v3, legacy/siacs OMEMO) messages.
|
||||||
|
// Requesting ProtocolVersionLegacy is a no-op (that's the library's only
|
||||||
|
// behavior); requesting anything else is rejected rather than silently
|
||||||
|
// ignored.
|
||||||
|
|
||||||
|
func setBuilderVersion(b *C.session_builder, version int) error {
|
||||||
|
if version != ProtocolVersionLegacy {
|
||||||
|
return errModernOMEMOUnavailable
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCipherVersion(c *C.session_cipher, version int) error {
|
||||||
|
if version != ProtocolVersionLegacy {
|
||||||
|
return errModernOMEMOUnavailable
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
26
e2ee/omemo/libsignal/version_modern.go
Normal file
26
e2ee/omemo/libsignal/version_modern.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
//go:build !signal_legacy
|
||||||
|
|
||||||
|
package libsignal
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <signal_protocol.h>
|
||||||
|
#include <session_builder.h>
|
||||||
|
#include <session_cipher.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// setBuilderVersion and setCipherVersion select the wire protocol version
|
||||||
|
// (ProtocolVersionLegacy or ProtocolVersionModern) libomemo-c's
|
||||||
|
// session_builder_set_version/session_cipher_set_version support. See
|
||||||
|
// version_legacy.go for the vanilla-libsignal-protocol-c build, which has
|
||||||
|
// no such functions and only ever produces protocol v3.
|
||||||
|
|
||||||
|
func setBuilderVersion(b *C.session_builder, version int) error {
|
||||||
|
C.session_builder_set_version(b, C.uint32_t(version))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCipherVersion(c *C.session_cipher, version int) error {
|
||||||
|
C.session_cipher_set_version(c, C.uint32_t(version))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
FROM golang:1.19-bullseye AS base
|
FROM golang:1.19-bullseye AS base
|
||||||
|
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install -y libssl-dev cmake build-essential gperf libz-dev make git php
|
RUN apt-get install -y libssl-dev cmake build-essential gperf libz-dev make git php libsignal-protocol-c-dev
|
||||||
|
|
||||||
FROM base AS tdlib
|
FROM base AS tdlib
|
||||||
|
|
||||||
|
|
@ -32,11 +32,14 @@ RUN --mount=type=cache,target=/gomod-cache \
|
||||||
FROM cache AS build
|
FROM cache AS build
|
||||||
ARG MAKEOPTS
|
ARG MAKEOPTS
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
# Bullseye only has the pre-fork libsignal-protocol-c packaged (libomemo-c
|
||||||
|
# is bookworm+) - GOTAGS=signal_legacy builds telegabber's OMEMO bindings
|
||||||
|
# against it, which only supports legacy/siacs OMEMO, not modern OMEMO 1/2.
|
||||||
RUN --mount=type=bind,source=./,target=/src,rw \
|
RUN --mount=type=bind,source=./,target=/src,rw \
|
||||||
--mount=type=cache,target=/go-cache \
|
--mount=type=cache,target=/go-cache \
|
||||||
--mount=type=cache,target=/gomod-cache \
|
--mount=type=cache,target=/gomod-cache \
|
||||||
--mount=type=cache,destination=/src/release \
|
--mount=type=cache,destination=/src/release \
|
||||||
make ${MAKEOPTS}
|
make ${MAKEOPTS} GOTAGS=signal_legacy
|
||||||
|
|
||||||
FROM build AS release
|
FROM build AS release
|
||||||
RUN --mount=type=cache,destination=/src/release \
|
RUN --mount=type=cache,destination=/src/release \
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue