├── go.work ├── .dockerignore ├── .gitmodules ├── pkg ├── libsignalgo │ ├── resources │ │ └── clienthandshakestart.data │ ├── serviceid_clang.go │ ├── serviceid_gcc.go │ ├── go.mod │ ├── update-ffi.sh │ ├── util.go │ ├── README.md │ ├── address_test.go │ ├── kdf.go │ ├── conversions.go │ ├── identitykey_test.go │ ├── protocol.go │ ├── devicetransfer_test.go │ ├── storeutil.go │ ├── devicetransfer.go │ ├── setup_test.go │ ├── aes256gcmsiv_test.go │ ├── groupcipher_test.go │ ├── groupcipher.go │ ├── serviceid.go │ ├── buffer.go │ ├── senderkeyrecord.go │ ├── address.go │ ├── go.sum │ ├── logging.go │ ├── aes256gcmsiv.go │ ├── kdf_test.go │ ├── ciphertextmessage.go │ ├── serializedeserializeroundtrip_test.go │ ├── signedprekeystore.go │ ├── sessionstore.go │ ├── plaintextcontent.go │ ├── hsmenclave.go │ ├── publickey.go │ ├── prekeystore.go │ ├── fingerprint.go │ ├── kyberprekeystore.go │ ├── hsmenclave_test.go │ ├── senderkeydistributionmessage.go │ ├── senderkeystore.go │ ├── privatekey.go │ ├── prekeybundle.go │ ├── error.go │ ├── sessionrecord.go │ ├── message.go │ ├── authcredential.go │ ├── servercertificate.go │ ├── privatekey_test.go │ └── decryptionerrormessage.go └── signalmeow │ ├── upgrades │ ├── 02-groups.sql │ ├── 05-postgres-profile-key.sql │ ├── 04-kyber-prekeys.sql │ ├── 03-contacts.sql │ ├── upgrades.go │ └── 00-latest.sql │ ├── protobuf │ ├── DeviceName.proto │ ├── build-protos.sh │ ├── StickerResources.proto │ ├── update-protos.sh │ ├── WebSocketResources.proto │ ├── Provisioning.proto │ └── UnidentifiedDelivery.proto │ ├── go.mod │ ├── message.go │ ├── types.go │ ├── wspb │ └── wspb.go │ ├── group_store.go │ ├── profile_key_store.go │ ├── go.sum │ ├── sender_key_store.go │ ├── device.go │ ├── misc.go │ └── contact_store.go ├── .github ├── ISSUE_TEMPLATE │ ├── enhancement.md │ ├── bug.md │ └── config.yml └── workflows │ └── go.yml ├── .envrc ├── .gitignore ├── database ├── upgrades │ ├── 15-remove-unused-puppet-columns.sql │ ├── 14-remove-notice-room.sql │ ├── 13-upgrade-mx-state-store.sql │ ├── upgrades.go │ └── 00-initial.sql ├── database.go ├── user.go └── reaction.go ├── .gitlab-ci.yml ├── shell.nix ├── .editorconfig ├── Dockerfile.ci ├── .pre-commit-config.yaml ├── Makefile ├── README.md ├── config └── config.go ├── docker-run.sh ├── Dockerfile ├── msgconv └── signalfmt │ ├── tags.go │ ├── tree.go │ ├── html.go │ └── convert.go ├── go.mod ├── ROADMAP.md └── custompuppet.go /go.work: -------------------------------------------------------------------------------- 1 | go 1.20 2 | 3 | use ( 4 | . 5 | ./pkg/libsignalgo 6 | ./pkg/signalmeow 7 | ) 8 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .editorconfig 2 | logs 3 | start 4 | config.yaml 5 | registration.yaml 6 | *.db 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pkg/libsignalgo/libsignal"] 2 | path = pkg/libsignalgo/libsignal 3 | url = https://github.com/signalapp/libsignal.git 4 | -------------------------------------------------------------------------------- /pkg/libsignalgo/resources/clienthandshakestart.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mautrix/signalgo/HEAD/pkg/libsignalgo/resources/clienthandshakestart.data -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement request 3 | about: Submit a feature request or other suggestion 4 | labels: enhancement 5 | 6 | --- 7 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | if [[ $(uname -s) == "Linux" && $(uname --kernel-version | grep "NixOS") ]]; then 2 | echo "The best OS (NixOS) has been detected. Using nice tools." 3 | use nix 4 | fi 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.yaml 2 | !example-config.yaml 3 | !.pre-commit-config.yaml 4 | 5 | *.db* 6 | *.log* 7 | 8 | /mautrix-signal 9 | /mautrix-signalgo 10 | /start 11 | /libsignal_ffi.a 12 | -------------------------------------------------------------------------------- /database/upgrades/15-remove-unused-puppet-columns.sql: -------------------------------------------------------------------------------- 1 | -- v15: Remove unused columns in puppet table 2 | ALTER TABLE puppet DROP COLUMN next_batch; 3 | ALTER TABLE puppet DROP COLUMN base_url; 4 | -------------------------------------------------------------------------------- /database/upgrades/14-remove-notice-room.sql: -------------------------------------------------------------------------------- 1 | -- v14: Remove redundant notice_room column from users 2 | UPDATE "user" SET management_room = COALESCE(management_room, notice_room); 3 | ALTER TABLE "user" DROP COLUMN notice_room; 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: If something is definitely wrong in the bridge (rather than just a setup issue), 4 | file a bug report. Remember to include relevant logs. 5 | labels: bug 6 | 7 | --- 8 | -------------------------------------------------------------------------------- /pkg/libsignalgo/serviceid_clang.go: -------------------------------------------------------------------------------- 1 | //go:build darwin 2 | 3 | package libsignalgo 4 | 5 | /* 6 | #cgo LDFLAGS: -lsignal_ffi -ldl 7 | #include "./libsignal-ffi.h" 8 | #include 9 | */ 10 | import "C" 11 | 12 | type cPNIType = *C.SignalServiceIdFixedWidthBinaryBytes 13 | -------------------------------------------------------------------------------- /pkg/signalmeow/upgrades/02-groups.sql: -------------------------------------------------------------------------------- 1 | -- v2: Add group master key table 2 | CREATE TABLE signalmeow_groups ( 3 | our_aci_uuid TEXT NOT NULL, 4 | group_identifier TEXT NOT NULL, 5 | master_key TEXT NOT NULL, 6 | 7 | PRIMARY KEY (our_aci_uuid, group_identifier) 8 | ); 9 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - project: 'mautrix/ci' 3 | file: '/go.yml' 4 | 5 | variables: 6 | BUILDER_IMAGE: dock.mau.dev/tulir/gomuks-build-docker/signal 7 | BINARY_NAME: mautrix-signal 8 | 9 | # 32-bit arm builds aren't supported 10 | build arm: 11 | rules: 12 | - when: never 13 | -------------------------------------------------------------------------------- /pkg/signalmeow/protobuf/DeviceName.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package signalservice; 5 | 6 | message DeviceName { 7 | optional bytes ephemeralPublic = 1; 8 | optional bytes syntheticIv = 2; 9 | optional bytes ciphertext = 3; 10 | } 11 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | with import { }; 2 | let 3 | in 4 | mkShell rec { 5 | buildInputs = [ 6 | clang 7 | cmake 8 | gnumake 9 | protobuf 10 | rust-cbindgen 11 | rustup 12 | olm 13 | 14 | go_1_20 15 | 16 | pre-commit 17 | ]; 18 | 19 | LIBCLANG_PATH = "${pkgs.llvmPackages_11.libclang.lib}/lib"; 20 | } 21 | -------------------------------------------------------------------------------- /pkg/signalmeow/upgrades/05-postgres-profile-key.sql: -------------------------------------------------------------------------------- 1 | -- v5: Fix profile key column type on postgres 2 | -- only: postgres 3 | ALTER TABLE signalmeow_contacts ALTER COLUMN profile_key TYPE bytea USING profile_key::bytea; 4 | UPDATE signalmeow_contacts SET profile_key=key FROM signalmeow_profile_keys WHERE signalmeow_contacts.aci_uuid=signalmeow_profile_keys.their_aci_uuid; 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yaml,yml,sql}] 15 | indent_style = space 16 | 17 | [{.gitlab-ci.yml,.github/workflows/*.yml}] 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /pkg/libsignalgo/serviceid_gcc.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin 2 | 3 | package libsignalgo 4 | 5 | /* 6 | #cgo LDFLAGS: -lsignal_ffi -ldl 7 | #include "./libsignal-ffi.h" 8 | #include 9 | */ 10 | import "C" 11 | 12 | // Hack for https://github.com/golang/go/issues/7270 13 | // The clang version is more correct, but doesn't work with gcc 14 | 15 | type cPNIType = *[17]C.uint8_t 16 | -------------------------------------------------------------------------------- /Dockerfile.ci: -------------------------------------------------------------------------------- 1 | FROM alpine:3.19 2 | 3 | ENV UID=1337 \ 4 | GID=1337 5 | 6 | RUN apk add --no-cache ffmpeg su-exec ca-certificates bash jq curl yq 7 | 8 | ARG EXECUTABLE=./mautrix-signal 9 | COPY $EXECUTABLE /usr/bin/mautrix-signal 10 | COPY ./example-config.yaml /opt/mautrix-signal/example-config.yaml 11 | COPY ./docker-run.sh /docker-run.sh 12 | VOLUME /data 13 | 14 | CMD ["/docker-run.sh"] 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Troubleshooting docs & FAQ 3 | url: https://docs.mau.fi/bridges/general/troubleshooting.html 4 | about: Check this first if you're having problems setting up the bridge. 5 | - name: Support room 6 | url: https://matrix.to/#/#signal:maunium.net 7 | about: For setup issues not answered by the troubleshooting docs, ask in the Matrix room. 8 | -------------------------------------------------------------------------------- /pkg/signalmeow/upgrades/04-kyber-prekeys.sql: -------------------------------------------------------------------------------- 1 | -- v4: Add kyber prekeys table 2 | CREATE TABLE signalmeow_kyber_pre_keys ( 3 | aci_uuid TEXT NOT NULL, 4 | key_id INTEGER NOT NULL, 5 | uuid_kind TEXT NOT NULL, 6 | key_pair bytea NOT NULL, 7 | is_last_resort BOOLEAN NOT NULL, 8 | 9 | PRIMARY KEY (aci_uuid, uuid_kind, key_id), 10 | FOREIGN KEY (aci_uuid) REFERENCES signalmeow_device (aci_uuid) ON DELETE CASCADE ON UPDATE CASCADE 11 | ); 12 | -------------------------------------------------------------------------------- /pkg/signalmeow/protobuf/build-protos.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script is used to generate the protobuf files for the project. 4 | # It is assumed that the protoc compiler is installed and available on the path. 5 | 6 | # The script will generate the protobuf files for the following languages: 7 | # - Go 8 | 9 | PKG_IMPORT_PATH="go.mau.fi/mautrix-signal/pkg/signalmeow/signalpb" 10 | 11 | for file in *.proto 12 | do 13 | protoc --go_out=. \ 14 | --go_opt=M${file}=$PKG_IMPORT_PATH \ 15 | --go_opt=paths=source_relative \ 16 | $file 17 | done 18 | -------------------------------------------------------------------------------- /pkg/libsignalgo/go.mod: -------------------------------------------------------------------------------- 1 | module go.mau.fi/mautrix-signal/pkg/libsignalgo 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/google/uuid v1.5.0 7 | github.com/mattn/go-pointer v0.0.1 8 | github.com/rs/zerolog v1.31.0 9 | github.com/stretchr/testify v1.8.4 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/mattn/go-colorable v0.1.13 // indirect 15 | github.com/mattn/go-isatty v0.0.19 // indirect 16 | github.com/pmezard/go-difflib v1.0.0 // indirect 17 | golang.org/x/sys v0.12.0 // indirect 18 | gopkg.in/yaml.v3 v3.0.1 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /pkg/signalmeow/go.mod: -------------------------------------------------------------------------------- 1 | module go.mau.fi/mautrix-signal/pkg/signalmeow 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/google/uuid v1.5.0 7 | github.com/rs/zerolog v1.31.0 8 | go.mau.fi/util v0.2.1 9 | golang.org/x/crypto v0.16.0 10 | google.golang.org/protobuf v1.31.0 11 | nhooyr.io/websocket v1.8.10 12 | ) 13 | 14 | require ( 15 | github.com/google/go-cmp v0.5.8 // indirect 16 | github.com/mattn/go-colorable v0.1.13 // indirect 17 | github.com/mattn/go-isatty v0.0.19 // indirect 18 | github.com/mattn/go-pointer v0.0.1 // indirect 19 | golang.org/x/sys v0.15.0 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.5.0 4 | hooks: 5 | - id: trailing-whitespace 6 | exclude_types: [markdown] 7 | - id: end-of-file-fixer 8 | - id: check-yaml 9 | - id: check-added-large-files 10 | 11 | - repo: https://github.com/tekwizely/pre-commit-golang 12 | rev: v1.0.0-rc.1 13 | hooks: 14 | - id: go-imports 15 | exclude: "pb\\.go$" 16 | - id: go-vet-mod 17 | #- id: go-staticcheck-repo-mod 18 | # TODO: reenable this and fix all the problems 19 | -------------------------------------------------------------------------------- /pkg/signalmeow/upgrades/03-contacts.sql: -------------------------------------------------------------------------------- 1 | -- v3: Add contacts table 2 | CREATE TABLE signalmeow_contacts ( 3 | our_aci_uuid TEXT NOT NULL, 4 | aci_uuid TEXT NOT NULL, 5 | e164_number TEXT, 6 | contact_name TEXT, 7 | contact_avatar_hash TEXT, 8 | profile_key TEXT, 9 | profile_name TEXT, 10 | profile_about TEXT, 11 | profile_about_emoji TEXT, 12 | profile_avatar_hash TEXT, 13 | 14 | PRIMARY KEY (our_aci_uuid, aci_uuid), 15 | FOREIGN KEY (our_aci_uuid) REFERENCES signalmeow_device (aci_uuid) ON DELETE CASCADE ON UPDATE CASCADE 16 | ); 17 | -------------------------------------------------------------------------------- /pkg/signalmeow/protobuf/StickerResources.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2019 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | syntax = "proto2"; 7 | 8 | package signalservice; 9 | 10 | option java_package = "org.whispersystems.signalservice.internal.sticker"; 11 | option java_outer_classname = "StickerProtos"; 12 | 13 | message Pack { 14 | message Sticker { 15 | optional uint32 id = 1; 16 | optional string emoji = 2; 17 | optional string contentType = 3; 18 | } 19 | 20 | optional string title = 1; 21 | optional string author = 2; 22 | optional Sticker cover = 3; 23 | repeated Sticker stickers = 4; 24 | } 25 | -------------------------------------------------------------------------------- /database/upgrades/13-upgrade-mx-state-store.sql: -------------------------------------------------------------------------------- 1 | -- v13: Switch mx_room_state from Python to Go format 2 | ALTER TABLE mx_room_state DROP COLUMN is_encrypted; 3 | ALTER TABLE mx_room_state DROP COLUMN has_full_member_list; 4 | 5 | -- only: postgres for next 2 lines 6 | ALTER TABLE mx_room_state ALTER COLUMN power_levels TYPE jsonb USING power_levels::jsonb; 7 | ALTER TABLE mx_room_state ALTER COLUMN encryption TYPE jsonb USING encryption::jsonb; 8 | 9 | ALTER TABLE "user" ADD COLUMN management_room TEXT; 10 | 11 | UPDATE mx_user_profile SET displayname='' WHERE displayname IS NULL; 12 | UPDATE mx_user_profile SET avatar_url='' WHERE avatar_url IS NULL; 13 | 14 | CREATE TABLE mx_registrations ( 15 | user_id TEXT PRIMARY KEY 16 | ); 17 | 18 | UPDATE mx_version SET version=5; 19 | -------------------------------------------------------------------------------- /pkg/signalmeow/message.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Scott Weber 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package signalmeow 18 | 19 | type Message struct { 20 | // Structure to represent a message 21 | } 22 | -------------------------------------------------------------------------------- /pkg/libsignalgo/update-ffi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit on error 4 | set -e 5 | 6 | # Store the libsignal directory path 7 | LIBSIGNAL_DIRECTORY="libsignal" 8 | 9 | # Check if libsignal directory exists 10 | if [ ! -d "$LIBSIGNAL_DIRECTORY" ]; then 11 | echo "Error: Libsignal directory '$LIBSIGNAL_DIRECTORY' does not exist." 12 | exit 1 13 | fi 14 | 15 | # Store the current working directory 16 | ORIGINAL_DIR="$(pwd)" 17 | 18 | # Navigate to libsignal directory 19 | cd "$LIBSIGNAL_DIRECTORY" 20 | 21 | # Build libsignal 22 | cargo build -p libsignal-ffi --release 23 | 24 | # Regenerate the header file 25 | cbindgen --profile release rust/bridge/ffi -o libsignal-ffi.h 26 | 27 | # Navigate back to the original directory 28 | cd "$ORIGINAL_DIR" 29 | 30 | # Copy files from the libsignal directory 31 | cp "${LIBSIGNAL_DIRECTORY}/target/release/libsignal_ffi.a" . 32 | cp "${LIBSIGNAL_DIRECTORY}/libsignal-ffi.h" . 33 | 34 | echo "Files copied successfully." 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all build_rust copy_library build_go clean 2 | 3 | all: build_rust copy_library build_go 4 | 5 | LIBRARY_NAME=libsignal-ffi 6 | LIBRARY_FILENAME=libsignal_ffi.a 7 | RUST_DIR=pkg/libsignalgo/libsignal 8 | GO_BINARY=mautrix-signal 9 | 10 | ifneq ($(DBG),1) 11 | RUST_PROFILE=release 12 | RUST_TARGET_SUBDIR=release 13 | GO_GCFLAGS= 14 | else 15 | RUST_PROFILE=dev 16 | RUST_TARGET_SUBDIR=debug 17 | GO_GCFLAGS=all=-N -l 18 | endif 19 | 20 | build_rust: 21 | cd $(RUST_DIR) && cargo build -p $(LIBRARY_NAME) --profile=$(RUST_PROFILE) 22 | 23 | copy_library: 24 | cp $(RUST_DIR)/target/$(RUST_TARGET_SUBDIR)/$(LIBRARY_FILENAME) . 25 | 26 | build_go: 27 | LIBRARY_PATH="$${LIBRARY_PATH}:." go build -gcflags "$(GO_GCFLAGS)" -ldflags "-X main.Tag=$$(git describe --exact-match --tags 2>/dev/null) -X main.Commit=$$(git rev-parse HEAD) -X 'main.BuildTime=`date '+%b %_d %Y, %H:%M:%S'`'" 28 | 29 | clean: 30 | rm -f ./$(LIBRARY_FILENAME) 31 | cd $(RUST_DIR) && cargo clean 32 | rm -f $(GO_BINARY) 33 | -------------------------------------------------------------------------------- /pkg/libsignalgo/util.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | func sizeMustMatch(a, b int) int { 20 | if a != b { 21 | panic("libsignal-ffi type size mismatch") 22 | } 23 | 24 | return a 25 | } 26 | -------------------------------------------------------------------------------- /pkg/signalmeow/protobuf/update-protos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | ANDROID_GIT_REVISION=${1:-7275b95b583b64144fc7f935144b0a17c45244e7} 5 | DESKTOP_GIT_REVISION=${1:-d198ceda4bb5dfbc7c13a64b8355df6477952210} 6 | 7 | update_proto() { 8 | case "$1" in 9 | Signal-Android) 10 | prefix="libsignal/service/src/main/proto/" 11 | GIT_REVISION=$ANDROID_GIT_REVISION 12 | ;; 13 | Signal-Desktop) 14 | prefix="protos/" 15 | GIT_REVISION=$DESKTOP_GIT_REVISION 16 | ;; 17 | esac 18 | curl -sLOf https://raw.githubusercontent.com/signalapp/${1}/${GIT_REVISION}/${prefix}${2} 19 | } 20 | 21 | 22 | update_proto Signal-Android Groups.proto 23 | update_proto Signal-Android Provisioning.proto 24 | update_proto Signal-Android SignalService.proto 25 | update_proto Signal-Android StickerResources.proto 26 | update_proto Signal-Android WebSocketResources.proto 27 | 28 | update_proto Signal-Desktop DeviceName.proto 29 | update_proto Signal-Desktop UnidentifiedDelivery.proto 30 | -------------------------------------------------------------------------------- /pkg/signalmeow/upgrades/upgrades.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-Signal puppeting bridge. 2 | // Copyright (C) 2023 Tulir Asokan 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package upgrades 18 | 19 | import ( 20 | "embed" 21 | 22 | "go.mau.fi/util/dbutil" 23 | ) 24 | 25 | var Table dbutil.UpgradeTable 26 | 27 | //go:embed *.sql 28 | var rawUpgrades embed.FS 29 | 30 | func init() { 31 | Table.RegisterFS(rawUpgrades) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/signalmeow/protobuf/WebSocketResources.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | syntax = "proto2"; 7 | 8 | package signalservice; 9 | 10 | option java_package = "org.whispersystems.signalservice.internal.websocket"; 11 | option java_outer_classname = "WebSocketProtos"; 12 | 13 | message WebSocketRequestMessage { 14 | optional string verb = 1; 15 | optional string path = 2; 16 | optional bytes body = 3; 17 | repeated string headers = 5; 18 | optional uint64 id = 4; 19 | } 20 | 21 | message WebSocketResponseMessage { 22 | optional uint64 id = 1; 23 | optional uint32 status = 2; 24 | optional string message = 3; 25 | repeated string headers = 5; 26 | optional bytes body = 4; 27 | } 28 | 29 | message WebSocketMessage { 30 | enum Type { 31 | UNKNOWN = 0; 32 | REQUEST = 1; 33 | RESPONSE = 2; 34 | } 35 | 36 | optional Type type = 1; 37 | optional WebSocketRequestMessage request = 2; 38 | optional WebSocketResponseMessage response = 3; 39 | } 40 | -------------------------------------------------------------------------------- /pkg/libsignalgo/README.md: -------------------------------------------------------------------------------- 1 | # libsignalgo 2 | Go bindings for [libsignal](https://github.com/signalapp/libsignal). 3 | 4 | ## Installation 5 | 0. Install Rust. You may also need to install libclang-dev and cbindgen manually. 6 | 1. Clone [libsignal](https://github.com/signalapp/libsignal) somewhere. 7 | 2. Run `./update-ffi.sh ` (this builds the library, regenerates the header, and copies them both here) 8 | 3. Copy `libsignal_ffi.a` to `/usr/lib/`. 9 | * Alternatively, set `LIBRARY_PATH` to the directory containing `libsignal_ffi.a`. 10 | Something like this: `LIBRARY_PATH="$LIBRARY_PATH:./pkg/libsignalgo" ./build.sh` 11 | 4. Use like a normal Go library. 12 | 13 | ## Precompiled 14 | You can find precompiled `libsignal_ffi.a`'s on 15 | [mau.dev/tulir/gomuks-build-docker](https://mau.dev/tulir/gomuks-build-docker). 16 | Direct links: 17 | * [Linux amd64](https://mau.dev/tulir/gomuks-build-docker/-/jobs/artifacts/master/raw/libsignal_ffi.a?job=libsignal%20linux%20amd64) 18 | * [Linux arm64](https://mau.dev/tulir/gomuks-build-docker/-/jobs/artifacts/master/raw/libsignal_ffi.a?job=libsignal%20linux%20arm64) 19 | * [macOS arm64](https://mau.dev/tulir/gomuks-build-docker/-/jobs/artifacts/master/raw/libsignal_ffi.a?job=libsignal%20macos%20arm64) 20 | -------------------------------------------------------------------------------- /pkg/signalmeow/types.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Scott Weber 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package signalmeow 18 | 19 | const ( 20 | UUID_KIND_ACI = "aci" 21 | UUID_KIND_PNI = "pni" 22 | ) 23 | 24 | type UUIDKind string 25 | 26 | type GroupCredentials struct { 27 | Credentials []GroupCredential `json:"credentials"` 28 | Pni string `json:"pni"` 29 | } 30 | type GroupCredential struct { 31 | Credential []byte 32 | RedemptionTime int64 33 | } 34 | type GroupExternalCredential struct { 35 | Token []byte `json:"token"` 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mautrix-signal 2 | ![Languages](https://img.shields.io/github/languages/top/mautrix/signal.svg) 3 | [![License](https://img.shields.io/github/license/mautrix/signal.svg)](LICENSE) 4 | [![GitLab CI](https://mau.dev/mautrix/signal/badges/main/pipeline.svg)](https://mau.dev/mautrix/signal/container_registry) 5 | [![Code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 6 | [![Imports](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) 7 | 8 | A Matrix-Signal puppeting bridge. 9 | 10 | ## Documentation 11 | All setup and usage instructions are located on 12 | [docs.mau.fi](https://docs.mau.fi/bridges/go/signal/index.html). 13 | Some quick links: 14 | 15 | * [Bridge setup](https://docs.mau.fi/bridges/go/setup.html?bridge=signal) 16 | (or [with Docker](https://docs.mau.fi/bridges/general/docker-setup.html?bridge=signal)) 17 | * Basic usage: [Authentication](https://docs.mau.fi/bridges/go/signal/authentication.html) 18 | 19 | ### Features & Roadmap 20 | [ROADMAP.md](https://github.com/mautrix/signal/blob/main/ROADMAP.md) 21 | contains a general overview of what is supported by the bridge. 22 | 23 | ## Discussion 24 | Matrix room: [`#signal:maunium.net`](https://matrix.to/#/#signal:maunium.net) 25 | -------------------------------------------------------------------------------- /pkg/signalmeow/protobuf/Provisioning.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | syntax = "proto2"; 7 | 8 | package signalservice; 9 | 10 | option java_package = "org.whispersystems.signalservice.internal.push"; 11 | option java_outer_classname = "ProvisioningProtos"; 12 | 13 | message ProvisioningUuid { 14 | optional string uuid = 1; 15 | } 16 | 17 | message ProvisionEnvelope { 18 | optional bytes publicKey = 1; 19 | optional bytes body = 2; // Encrypted ProvisionMessage 20 | } 21 | 22 | message ProvisionMessage { 23 | optional bytes aciIdentityKeyPublic = 1; 24 | optional bytes aciIdentityKeyPrivate = 2; 25 | optional bytes pniIdentityKeyPublic = 11; 26 | optional bytes pniIdentityKeyPrivate = 12; 27 | optional string aci = 8; 28 | optional string pni = 10; 29 | optional string number = 3; 30 | optional string provisioningCode = 4; 31 | optional string userAgent = 5; 32 | optional bytes profileKey = 6; 33 | optional bool readReceipts = 7; 34 | optional uint32 provisioningVersion = 9; 35 | // NEXT ID: 13 36 | } 37 | 38 | enum ProvisioningVersion { 39 | option allow_alias = true; 40 | 41 | INITIAL = 0; 42 | TABLET_SUPPORT = 1; 43 | CURRENT = 1; 44 | } 45 | -------------------------------------------------------------------------------- /pkg/libsignalgo/address_test.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "go.mau.fi/mautrix-signal/pkg/libsignalgo" 25 | ) 26 | 27 | // From PublicAPITests.swift:testAddress 28 | func TestAddress(t *testing.T) { 29 | setupLogging() 30 | 31 | addr, err := libsignalgo.NewAddress("addr1", 5) 32 | assert.NoError(t, err) 33 | 34 | name, err := addr.Name() 35 | assert.NoError(t, err) 36 | assert.Equal(t, "addr1", name) 37 | 38 | deviceID, err := addr.DeviceID() 39 | assert.NoError(t, err) 40 | assert.Equal(t, uint(5), deviceID) 41 | } 42 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-Signal puppeting bridge. 2 | // Copyright (C) 2022 Tulir Asokan 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package config 18 | 19 | import ( 20 | "maunium.net/go/mautrix/bridge/bridgeconfig" 21 | "maunium.net/go/mautrix/id" 22 | ) 23 | 24 | type Config struct { 25 | *bridgeconfig.BaseConfig `yaml:",inline"` 26 | 27 | Metrics struct { 28 | Enabled bool `yaml:"enabled"` 29 | Listen string `yaml:"listen"` 30 | } `yaml:"metrics"` 31 | 32 | Bridge BridgeConfig `yaml:"bridge"` 33 | } 34 | 35 | func (config *Config) CanAutoDoublePuppet(userID id.UserID) bool { 36 | _, homeserver, _ := userID.Parse() 37 | _, hasSecret := config.Bridge.DoublePuppetConfig.SharedSecretMap[homeserver] 38 | 39 | return hasSecret 40 | } 41 | -------------------------------------------------------------------------------- /database/upgrades/upgrades.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-Signal puppeting bridge. 2 | // Copyright (C) 2023 Tulir Asokan 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package upgrades 18 | 19 | import ( 20 | "embed" 21 | "errors" 22 | 23 | "go.mau.fi/util/dbutil" 24 | ) 25 | 26 | var Table dbutil.UpgradeTable 27 | 28 | //go:embed *.sql 29 | var rawUpgrades embed.FS 30 | 31 | func init() { 32 | Table.Register(-1, 12, 0, "Unsupported version", false, func(tx dbutil.Execable, database *dbutil.Database) error { 33 | return errors.New("please upgrade to mautrix-signal v0.4.3 before upgrading to a newer version") 34 | }) 35 | Table.Register(1, 13, 0, "Jump to version 13", false, func(tx dbutil.Execable, database *dbutil.Database) error { 36 | return nil 37 | }) 38 | Table.RegisterFS(rawUpgrades) 39 | } 40 | -------------------------------------------------------------------------------- /docker-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ -z "$GID" ]]; then 4 | GID="$UID" 5 | fi 6 | 7 | # Define functions. 8 | function fixperms { 9 | chown -R $UID:$GID /data 10 | 11 | # /opt/mautrix-signal is read-only, so disable file logging if it's pointing there. 12 | if [[ "$(yq e '.logging.writers[1].filename' /data/config.yaml)" == "./logs/mautrix-signal.log" ]]; then 13 | yq -I4 e -i 'del(.logging.writers[1])' /data/config.yaml 14 | fi 15 | } 16 | 17 | if [[ ! -f /data/config.yaml ]]; then 18 | cp /opt/mautrix-signal/example-config.yaml /data/config.yaml 19 | echo "Didn't find a config file." 20 | echo "Copied default config file to /data/config.yaml" 21 | echo "Modify that config file to your liking." 22 | echo "Start the container again after that to generate the registration file." 23 | exit 24 | fi 25 | 26 | if [[ ! -f /data/registration.yaml ]]; then 27 | /usr/bin/mautrix-signal -g -c /data/config.yaml -r /data/registration.yaml || exit $? 28 | echo "Didn't find a registration file." 29 | echo "Generated one for you." 30 | echo "See https://docs.mau.fi/bridges/general/registering-appservices.html on how to use it." 31 | exit 32 | fi 33 | 34 | cd /data 35 | fixperms 36 | 37 | EXE=/usr/bin/mautrix-signal 38 | DLV=/usr/bin/dlv 39 | if [[ -x $DLV ]]; then 40 | if [[ $DBGWAIT -ne 1 ]]; then 41 | NOWAIT=1 42 | fi 43 | EXE="${DLV} exec ${EXE} ${NOWAIT:+--continue --accept-multiclient} --api-version 2 --headless -l :4040" 44 | fi 45 | 46 | exec su-exec $UID:$GID $EXE 47 | -------------------------------------------------------------------------------- /pkg/libsignalgo/kdf.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import "unsafe" 25 | 26 | func HKDFDerive(outputLength int, inputKeyMaterial, salt, info []byte) ([]byte, error) { 27 | output := BorrowedMutableBuffer(outputLength) 28 | signalFfiError := C.signal_hkdf_derive(output, BytesToBuffer(inputKeyMaterial), BytesToBuffer(info), BytesToBuffer(salt)) 29 | if signalFfiError != nil { 30 | return nil, wrapError(signalFfiError) 31 | } 32 | // No need to wrap this in a CopyBufferToBytes since this is allocated by 33 | // Go and thus will be properly garbage collected. 34 | return C.GoBytes(unsafe.Pointer(output.base), C.int(output.length)), nil 35 | } 36 | -------------------------------------------------------------------------------- /pkg/libsignalgo/conversions.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import "unsafe" 25 | 26 | func CopyCStringToString(cString *C.char) (s string) { 27 | s = C.GoString(cString) 28 | C.signal_free_string(cString) 29 | return 30 | } 31 | 32 | func CopyBufferToBytes(buffer *C.uchar, length C.size_t) (b []byte) { 33 | b = C.GoBytes(unsafe.Pointer(buffer), C.int(length)) 34 | C.signal_free_buffer(buffer, length) 35 | return 36 | } 37 | 38 | func CopySignalOwnedBufferToBytes(buffer C.SignalOwnedBuffer) (b []byte) { 39 | b = C.GoBytes(unsafe.Pointer(buffer.base), C.int(buffer.length)) 40 | C.signal_free_buffer(buffer.base, buffer.length) 41 | return 42 | } 43 | -------------------------------------------------------------------------------- /pkg/libsignalgo/identitykey_test.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "go.mau.fi/mautrix-signal/pkg/libsignalgo" 25 | ) 26 | 27 | // From PublicAPITests.swift:testSignAlternateIdentity 28 | func TestSignAlternateIdentity(t *testing.T) { 29 | primary, err := libsignalgo.GenerateIdentityKeyPair() 30 | assert.NoError(t, err) 31 | secondary, err := libsignalgo.GenerateIdentityKeyPair() 32 | assert.NoError(t, err) 33 | 34 | signature, err := secondary.SignAlternateIdentity(primary.GetIdentityKey()) 35 | assert.NoError(t, err) 36 | 37 | verified, err := secondary.GetIdentityKey().VerifyAlternateIdentity(primary.GetIdentityKey(), signature) 38 | assert.NoError(t, err) 39 | assert.True(t, verified) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/libsignalgo/protocol.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import "time" 25 | 26 | func Encrypt(plaintext []byte, forAddress *Address, sessionStore SessionStore, identityKeyStore IdentityKeyStore, ctx *CallbackContext) (*CiphertextMessage, error) { 27 | var ciphertextMessage *C.SignalCiphertextMessage 28 | var now C.uint64_t = C.uint64_t(time.Now().Unix()) 29 | signalFfiError := C.signal_encrypt_message( 30 | &ciphertextMessage, 31 | BytesToBuffer(plaintext), 32 | forAddress.ptr, 33 | wrapSessionStore(sessionStore), 34 | wrapIdentityKeyStore(identityKeyStore), 35 | now, 36 | ) 37 | if signalFfiError != nil { 38 | return nil, wrapError(signalFfiError) 39 | } 40 | return wrapCiphertextMessage(ciphertextMessage), nil 41 | } 42 | -------------------------------------------------------------------------------- /pkg/libsignalgo/devicetransfer_test.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "go.mau.fi/mautrix-signal/pkg/libsignalgo" 25 | ) 26 | 27 | // From PublicAPITests.swift:testDeviceTransferKey 28 | func TestDeviceTransferKey(t *testing.T) { 29 | deviceKey, err := libsignalgo.GenerateDeviceTransferKey() 30 | assert.NoError(t, err) 31 | 32 | /* 33 | Anything encoded in an ASN.1 SEQUENCE starts with 0x30 when encoded 34 | as DER. (This test could be better.) 35 | */ 36 | key := deviceKey.PrivateKeyMaterial() 37 | assert.Greater(t, len(key), 0) 38 | assert.EqualValues(t, 0x30, key[0]) 39 | 40 | cert, err := deviceKey.GenerateCertificate("name", 30) 41 | assert.NoError(t, err) 42 | assert.Greater(t, len(cert), 0) 43 | assert.EqualValues(t, 0x30, cert[0]) 44 | } 45 | -------------------------------------------------------------------------------- /pkg/libsignalgo/storeutil.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import ( 25 | "context" 26 | "unsafe" 27 | 28 | gopointer "github.com/mattn/go-pointer" 29 | ) 30 | 31 | type CallbackContext struct { 32 | Error error 33 | Ctx context.Context 34 | } 35 | 36 | func NewEmptyCallbackContext() *CallbackContext { 37 | return NewCallbackContext(context.TODO()) 38 | } 39 | 40 | func NewCallbackContext(ctx context.Context) *CallbackContext { 41 | return &CallbackContext{Ctx: ctx} 42 | } 43 | 44 | func wrapStoreCallback[T any](storeCtx, ctxPtr unsafe.Pointer, callback func(store T, ctx context.Context) error) C.int { 45 | store := gopointer.Restore(storeCtx).(T) 46 | ctx := NewEmptyCallbackContext() 47 | if ctxPtr != nil { 48 | if restored := gopointer.Restore(ctxPtr); restored != nil { 49 | ctx = restored.(*CallbackContext) 50 | } 51 | } 52 | if err := callback(store, ctx.Ctx); err != nil { 53 | ctx.Error = err 54 | return -1 55 | } 56 | return 0 57 | } 58 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # -- Build libsignal (with Rust) -- 2 | FROM rust:1.70.0-slim as rust-builder 3 | RUN apt-get update && apt-get install -y --no-install-recommends make cmake clang llvm protobuf-compiler 4 | 5 | WORKDIR /build 6 | # Copy all files needed for Rust build, and no Go files 7 | COPY pkg/libsignalgo/libsignal/. pkg/libsignalgo/libsignal/. 8 | COPY Makefile . 9 | 10 | ARG DBG=0 11 | RUN make build_rust 12 | RUN make copy_library 13 | 14 | # -- Build mautrix-signal (with Go) -- 15 | FROM golang:1.20-bookworm AS go-builder 16 | RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates libolm-dev 17 | 18 | ARG DBG=0 19 | RUN /bin/bash -c 'if [[ $DBG -eq 1 ]]; then go install github.com/go-delve/delve/cmd/dlv@latest; else touch /go/bin/dlv; fi' 20 | 21 | WORKDIR /build 22 | # Copy all files needed for Go build, and no Rust files 23 | COPY *.go go.* *.yaml ./ 24 | COPY pkg/signalmeow/. pkg/signalmeow/. 25 | COPY pkg/libsignalgo/* pkg/libsignalgo/ 26 | COPY pkg/libsignalgo/resources/. pkg/libsignalgo/resources/. 27 | COPY config/. config/. 28 | COPY database/. database/. 29 | COPY .git .git 30 | COPY Makefile . 31 | COPY docker-run.sh . 32 | 33 | COPY --from=rust-builder /build/libsignal_ffi.a /build/libsignal_ffi.a 34 | RUN make build_go 35 | 36 | # -- Run mautrix-signal -- 37 | FROM debian:12-slim 38 | 39 | ENV UID=1337 \ 40 | GID=1337 41 | 42 | RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates libolm-dev bash jq yq curl gosu && rm -rf /var/li/apt/lists/* 43 | 44 | COPY --from=go-builder /build/mautrix-signal /usr/bin/mautrix-signal 45 | COPY --from=go-builder /build/example-config.yaml /opt/mautrix-signal/example-config.yaml 46 | COPY --from=go-builder /build/docker-run.sh /docker-run.sh 47 | COPY --from=go-builder /go/bin/dlv /usr/bin/dlv 48 | VOLUME /data 49 | 50 | ARG DBGWAIT=0 51 | ENV DBGWAIT=${DBGWAIT} 52 | CMD ["/docker-run.sh"] 53 | -------------------------------------------------------------------------------- /msgconv/signalfmt/tags.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-Signal puppeting bridge. 2 | // Copyright (C) 2023 Tulir Asokan 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package signalfmt 18 | 19 | import ( 20 | "fmt" 21 | 22 | signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf" 23 | ) 24 | 25 | type BodyRangeValue interface { 26 | String() string 27 | Format(message string) string 28 | } 29 | 30 | type Mention UserInfo 31 | 32 | func (m Mention) String() string { 33 | return fmt.Sprintf("Mention{MXID: id.UserID(%q), Name: %q}", m.MXID, m.Name) 34 | } 35 | 36 | type Style int 37 | 38 | const ( 39 | StyleNone Style = iota 40 | StyleBold 41 | StyleItalic 42 | StyleSpoiler 43 | StyleStrikethrough 44 | StyleMonospace 45 | ) 46 | 47 | func (s Style) Proto() signalpb.BodyRange_Style { 48 | return signalpb.BodyRange_Style(s) 49 | } 50 | 51 | func (s Style) String() string { 52 | switch s { 53 | case StyleNone: 54 | return "StyleNone" 55 | case StyleBold: 56 | return "StyleBold" 57 | case StyleItalic: 58 | return "StyleItalic" 59 | case StyleSpoiler: 60 | return "StyleSpoiler" 61 | case StyleStrikethrough: 62 | return "StyleStrikethrough" 63 | case StyleMonospace: 64 | return "StyleMonospace" 65 | default: 66 | return fmt.Sprintf("Style(%d)", s) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pkg/libsignalgo/devicetransfer.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | 25 | type DeviceTransferKey struct { 26 | privateKey []byte 27 | } 28 | 29 | func GenerateDeviceTransferKey() (*DeviceTransferKey, error) { 30 | var resp C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 31 | signalFfiError := C.signal_device_transfer_generate_private_key(&resp) 32 | if signalFfiError != nil { 33 | return nil, wrapError(signalFfiError) 34 | } 35 | return &DeviceTransferKey{privateKey: CopySignalOwnedBufferToBytes(resp)}, nil 36 | } 37 | 38 | func (dtk *DeviceTransferKey) PrivateKeyMaterial() []byte { 39 | return dtk.privateKey 40 | } 41 | 42 | func (dtk *DeviceTransferKey) GenerateCertificate(name string, days int) ([]byte, error) { 43 | var resp C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 44 | signalFfiError := C.signal_device_transfer_generate_certificate(&resp, BytesToBuffer(dtk.privateKey), C.CString(name), C.uint32_t(days)) 45 | if signalFfiError != nil { 46 | return nil, wrapError(signalFfiError) 47 | } 48 | return CopySignalOwnedBufferToBytes(resp), nil 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | LIBSIGNAL_REF: "v0.36.1" 7 | 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - name: Set up Go 15 | uses: actions/setup-go@v3 16 | with: 17 | go-version: "1.21" 18 | cache: true 19 | 20 | - name: Install libolm 21 | run: sudo apt-get install libolm-dev 22 | 23 | - name: Install dependencies 24 | run: | 25 | go install golang.org/x/tools/cmd/goimports@latest 26 | go install honnef.co/go/tools/cmd/staticcheck@latest 27 | export PATH="$HOME/go/bin:$PATH" 28 | 29 | - name: Run pre-commit 30 | uses: pre-commit/action@v3.0.0 31 | 32 | test: 33 | runs-on: ubuntu-latest 34 | strategy: 35 | matrix: 36 | go-version: ["1.20", "1.21"] 37 | 38 | steps: 39 | - uses: actions/checkout@v3 40 | 41 | - name: Set up Go 42 | uses: actions/setup-go@v3 43 | with: 44 | go-version: ${{ matrix.go-version }} 45 | cache: true 46 | 47 | - name: Set up gotestfmt 48 | uses: GoTestTools/gotestfmt-action@v2 49 | with: 50 | token: ${{ secrets.GITHUB_TOKEN }} 51 | 52 | - name: Install libolm 53 | run: sudo apt-get install libolm-dev 54 | 55 | - name: Download libsignal 56 | run: | 57 | curl -L https://mau.dev/tulir/gomuks-build-docker/-/jobs/artifacts/master/raw/libsignal_ffi.a?job=libsignal%20linux%20amd64 -o libsignal_ffi.a 58 | 59 | - name: Run libsignalgo tests 60 | run: | 61 | set -euo pipefail 62 | export LIBRARY_PATH=. 63 | go test -v -ldflags "-extldflags -lm" -json ./pkg/libsignalgo -cover | gotestfmt || true 64 | 65 | - name: Run bridge tests 66 | run: | 67 | set -euo pipefail 68 | export LIBRARY_PATH=. 69 | go test -v -json ./... -cover | gotestfmt 70 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module go.mau.fi/mautrix-signal 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/google/uuid v1.5.0 7 | github.com/gorilla/mux v1.8.0 8 | github.com/lib/pq v1.10.9 9 | github.com/mattn/go-sqlite3 v1.14.19 10 | github.com/prometheus/client_golang v1.17.0 11 | github.com/rs/zerolog v1.31.0 12 | github.com/samber/lo v1.39.0 13 | github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e 14 | go.mau.fi/util v0.2.1 15 | go.mau.fi/webp v0.1.0 16 | golang.org/x/image v0.14.0 17 | maunium.net/go/maulogger/v2 v2.4.1 18 | maunium.net/go/mautrix v0.16.2 19 | ) 20 | 21 | require ( 22 | github.com/beorn7/perks v1.0.1 // indirect 23 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 24 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 25 | github.com/golang/protobuf v1.5.3 // indirect 26 | github.com/gorilla/websocket v1.5.0 // indirect 27 | github.com/kr/text v0.2.0 // indirect 28 | github.com/mattn/go-colorable v0.1.13 // indirect 29 | github.com/mattn/go-isatty v0.0.19 // indirect 30 | github.com/mattn/go-pointer v0.0.1 // indirect 31 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 32 | github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect 33 | github.com/prometheus/common v0.44.0 // indirect 34 | github.com/prometheus/procfs v0.11.1 // indirect 35 | github.com/tidwall/gjson v1.17.0 // indirect 36 | github.com/tidwall/match v1.1.1 // indirect 37 | github.com/tidwall/pretty v1.2.0 // indirect 38 | github.com/tidwall/sjson v1.2.5 // indirect 39 | github.com/yuin/goldmark v1.6.0 // indirect 40 | go.mau.fi/zeroconfig v0.1.2 // indirect 41 | golang.org/x/crypto v0.16.0 // indirect 42 | golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect 43 | golang.org/x/net v0.18.0 // indirect 44 | golang.org/x/sys v0.15.0 // indirect 45 | google.golang.org/protobuf v1.31.0 // indirect 46 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect 47 | gopkg.in/yaml.v3 v3.0.1 // indirect 48 | maunium.net/go/mauflag v1.0.0 // indirect 49 | nhooyr.io/websocket v1.8.10 // indirect 50 | ) 51 | -------------------------------------------------------------------------------- /pkg/libsignalgo/setup_test.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo_test 18 | 19 | import ( 20 | "os" 21 | 22 | "github.com/rs/zerolog" 23 | "github.com/rs/zerolog/log" 24 | 25 | "go.mau.fi/mautrix-signal/pkg/libsignalgo" 26 | ) 27 | 28 | type FFILogger struct{} 29 | 30 | func (FFILogger) Enabled(target string, level libsignalgo.LogLevel) bool { return true } 31 | 32 | func (FFILogger) Log(target string, level libsignalgo.LogLevel, file string, line uint, message string) { 33 | var evt *zerolog.Event 34 | switch level { 35 | case libsignalgo.LogLevelError: 36 | evt = log.Error() 37 | case libsignalgo.LogLevelWarn: 38 | evt = log.Warn() 39 | case libsignalgo.LogLevelInfo: 40 | evt = log.Info() 41 | case libsignalgo.LogLevelDebug: 42 | evt = log.Debug() 43 | case libsignalgo.LogLevelTrace: 44 | evt = log.Trace() 45 | default: 46 | panic("invalid log level from libsignal") 47 | } 48 | 49 | evt.Str("component", "libsignal"). 50 | Str("target", target). 51 | Str("file", file). 52 | Uint("line", line). 53 | Msg(message) 54 | } 55 | 56 | func (FFILogger) Flush() {} 57 | 58 | var loggingSetup = false 59 | 60 | func setupLogging() { 61 | if !loggingSetup { 62 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) 63 | libsignalgo.InitLogger(libsignalgo.LogLevelTrace, FFILogger{}) 64 | loggingSetup = true 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Features & roadmap 2 | 3 | * Matrix → Signal 4 | * [ ] Message content 5 | * [x] Text 6 | * [ ] Formatting 7 | * [x] Mentions 8 | * [ ] Media 9 | * [x] Images 10 | * [x] Audio files 11 | * [x] Voice messages 12 | * [x] Files 13 | * [x] Gifs 14 | * [ ] Locations 15 | * [x] Stickers 16 | * [x] Message reactions 17 | * [x] Message redactions 18 | * [ ] Group info changes 19 | * [ ] Name 20 | * [ ] Avatar 21 | * [ ] Topic 22 | * [ ] Membership actions 23 | * [ ] Join (accepting invites) 24 | * [ ] Invite 25 | * [ ] Leave 26 | * [ ] Kick/Ban/Unban 27 | * [x] Typing notifications 28 | * [ ] Read receipts (currently partial support, only marks last message) 29 | * [x] Delivery receipts (sent after message is bridged) 30 | * Signal → Matrix 31 | * [x] Message content 32 | * [x] Text 33 | * [x] Formatting 34 | * [x] Mentions 35 | * [x] Media 36 | * [x] Images 37 | * [x] Voice notes 38 | * [x] Files 39 | * [x] Gifs 40 | * [x] Contacts 41 | * [x] Stickers 42 | * [x] Message reactions 43 | * [x] Remote deletions 44 | * [x] Initial profile/contact info 45 | * [ ] Profile/contact info changes 46 | * [x] When restarting bridge or syncing 47 | * [ ] Real time 48 | * [x] Group info 49 | * [x] Name 50 | * [x] Avatar 51 | * [x] Topic 52 | * [ ] Membership actions 53 | * [ ] Join 54 | * [ ] Invite 55 | * [ ] Request join (via invite link, requires a client that supports knocks) 56 | * [ ] Leave 57 | * [ ] Kick/Ban/Unban 58 | * [ ] Group permissions 59 | * [x] Typing notifications 60 | * [x] Read receipts 61 | * [ ] Delivery receipts (there's no good way to bridge these) 62 | * [x] Disappearing messages 63 | * Misc 64 | * [ ] Automatic portal creation 65 | * [ ] After login 66 | * [x] When receiving message 67 | * [x] Linking as secondary device 68 | * [ ] Registering as primary device 69 | * [ ] Private chat/group creation by inviting Matrix puppet of Signal user to new room 70 | * [x] Option to use own Matrix account for messages sent from other Signal clients 71 | -------------------------------------------------------------------------------- /database/database.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Scott Weber 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package database 18 | 19 | import ( 20 | _ "embed" 21 | 22 | _ "github.com/lib/pq" 23 | _ "github.com/mattn/go-sqlite3" 24 | "go.mau.fi/util/dbutil" 25 | "maunium.net/go/maulogger/v2" 26 | 27 | "go.mau.fi/mautrix-signal/database/upgrades" 28 | ) 29 | 30 | type Database struct { 31 | *dbutil.Database 32 | 33 | User *UserQuery 34 | Portal *PortalQuery 35 | Puppet *PuppetQuery 36 | Message *MessageQuery 37 | Reaction *ReactionQuery 38 | DisappearingMessage *DisappearingMessageQuery 39 | } 40 | 41 | func New(baseDB *dbutil.Database, log maulogger.Logger) *Database { 42 | db := &Database{Database: baseDB} 43 | db.UpgradeTable = upgrades.Table 44 | db.User = &UserQuery{ 45 | db: db, 46 | log: log.Sub("User"), 47 | } 48 | db.Portal = &PortalQuery{ 49 | db: db, 50 | log: log.Sub("Portal"), 51 | } 52 | db.Puppet = &PuppetQuery{ 53 | db: db, 54 | log: log.Sub("Puppet"), 55 | } 56 | db.Message = &MessageQuery{ 57 | db: db, 58 | log: log.Sub("Message"), 59 | } 60 | db.Reaction = &ReactionQuery{ 61 | db: db, 62 | log: log.Sub("Reaction"), 63 | } 64 | db.DisappearingMessage = &DisappearingMessageQuery{ 65 | db: db, 66 | log: log.Sub("DisappearingMessage"), 67 | } 68 | return db 69 | } 70 | 71 | func strPtr(val string) *string { 72 | if val == "" { 73 | return nil 74 | } 75 | return &val 76 | } 77 | -------------------------------------------------------------------------------- /pkg/libsignalgo/aes256gcmsiv_test.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "go.mau.fi/mautrix-signal/pkg/libsignalgo" 25 | ) 26 | 27 | // From PublicAPITests.swift:testAesGcmSiv 28 | func TestAES256_GCM_SIV(t *testing.T) { 29 | plaintext := []byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 30 | expectedCiphertext := []byte{0x1d, 0xe2, 0x29, 0x67, 0x23, 0x7a, 0x81, 0x32, 0x91, 0x21, 0x3f, 0x26, 0x7e, 0x3b, 0x45, 0x2f, 0x02, 0xd0, 0x1a, 0xe3, 0x3e, 0x4e, 0xc8, 0x54} 31 | associatedData := []byte{0x01} 32 | key := []byte{ 33 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 34 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 35 | } 36 | nonce := []byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 37 | 38 | gcmSiv, err := libsignalgo.NewAES256_GCM_SIV(key) 39 | assert.NoError(t, err) 40 | 41 | ciphertext, err := gcmSiv.Encrypt(plaintext, nonce, associatedData) 42 | assert.NoError(t, err) 43 | assert.Equal(t, expectedCiphertext, ciphertext) 44 | 45 | recovered, err := gcmSiv.Decrypt(ciphertext, nonce, associatedData) 46 | assert.NoError(t, err) 47 | assert.Equal(t, plaintext, recovered) 48 | 49 | _, err = gcmSiv.Decrypt(plaintext, nonce, associatedData) 50 | assert.Error(t, err) 51 | _, err = gcmSiv.Decrypt(ciphertext, associatedData, nonce) 52 | assert.Error(t, err) 53 | } 54 | -------------------------------------------------------------------------------- /pkg/libsignalgo/groupcipher_test.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/google/uuid" 23 | "github.com/stretchr/testify/assert" 24 | 25 | "go.mau.fi/mautrix-signal/pkg/libsignalgo" 26 | ) 27 | 28 | // From PublicAPITests.swift:testGroupCipher 29 | func TestGroupCipher(t *testing.T) { 30 | ctx := libsignalgo.NewEmptyCallbackContext() 31 | sender, err := libsignalgo.NewAddress("+14159999111", 4) 32 | assert.NoError(t, err) 33 | 34 | distributionID, err := uuid.Parse("d1d1d1d1-7000-11eb-b32a-33b8a8a487a6") 35 | assert.NoError(t, err) 36 | 37 | aliceStore := NewInMemorySignalProtocolStore() 38 | 39 | skdm, err := libsignalgo.NewSenderKeyDistributionMessage(sender, distributionID, aliceStore, ctx) 40 | assert.NoError(t, err) 41 | 42 | serialized, err := skdm.Serialize() 43 | assert.NoError(t, err) 44 | 45 | skdmReloaded, err := libsignalgo.DeserializeSenderKeyDistributionMessage(serialized) 46 | assert.NoError(t, err) 47 | 48 | aliceCiphertextMessage, err := libsignalgo.GroupEncrypt([]byte{1, 2, 3}, sender, distributionID, aliceStore, ctx) 49 | assert.NoError(t, err) 50 | 51 | aliceCiphertext, err := aliceCiphertextMessage.Serialize() 52 | assert.NoError(t, err) 53 | 54 | bobStore := NewInMemorySignalProtocolStore() 55 | err = skdmReloaded.Process(sender, bobStore, ctx) 56 | assert.NoError(t, err) 57 | 58 | bobPtext, err := libsignalgo.GroupDecrypt(aliceCiphertext, sender, bobStore, ctx) 59 | assert.NoError(t, err) 60 | assert.Equal(t, []byte{1, 2, 3}, bobPtext) 61 | } 62 | -------------------------------------------------------------------------------- /pkg/libsignalgo/groupcipher.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import ( 25 | "unsafe" 26 | 27 | "github.com/google/uuid" 28 | gopointer "github.com/mattn/go-pointer" 29 | ) 30 | 31 | var UUIDLen = sizeMustMatch(C.SignalUUID_LEN, 16) 32 | 33 | func GroupEncrypt(ptext []byte, sender *Address, distributionID uuid.UUID, store SenderKeyStore, ctx *CallbackContext) (*CiphertextMessage, error) { 34 | contextPointer := gopointer.Save(ctx) 35 | defer gopointer.Unref(contextPointer) 36 | 37 | var ciphertextMessage *C.SignalCiphertextMessage 38 | signalFfiError := C.signal_group_encrypt_message( 39 | &ciphertextMessage, 40 | sender.ptr, 41 | (*[C.SignalUUID_LEN]C.uchar)(unsafe.Pointer(&distributionID)), 42 | BytesToBuffer(ptext), 43 | wrapSenderKeyStore(store)) 44 | if signalFfiError != nil { 45 | return nil, wrapCallbackError(signalFfiError, ctx) 46 | } 47 | return wrapCiphertextMessage(ciphertextMessage), nil 48 | } 49 | 50 | func GroupDecrypt(ctext []byte, sender *Address, store SenderKeyStore, ctx *CallbackContext) ([]byte, error) { 51 | contextPointer := gopointer.Save(ctx) 52 | defer gopointer.Unref(contextPointer) 53 | 54 | var resp C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 55 | signalFfiError := C.signal_group_decrypt_message( 56 | &resp, 57 | sender.ptr, 58 | BytesToBuffer(ctext), 59 | wrapSenderKeyStore(store)) 60 | if signalFfiError != nil { 61 | return nil, wrapCallbackError(signalFfiError, ctx) 62 | } 63 | return CopySignalOwnedBufferToBytes(resp), nil 64 | } 65 | -------------------------------------------------------------------------------- /pkg/signalmeow/protobuf/UnidentifiedDelivery.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package signalservice; 5 | 6 | option java_package = "org.whispersystems.libsignal.protocol"; 7 | option java_outer_classname = "WhisperProtos"; 8 | 9 | message ServerCertificate { 10 | message Certificate { 11 | optional uint32 id = 1; 12 | optional bytes key = 2; 13 | } 14 | 15 | optional bytes certificate = 1; 16 | optional bytes signature = 2; 17 | } 18 | 19 | message SenderCertificate { 20 | message Certificate { 21 | optional string senderE164 = 1; 22 | optional string senderUuid = 6; 23 | optional uint32 senderDevice = 2; 24 | optional fixed64 expires = 3; 25 | optional bytes identityKey = 4; 26 | optional ServerCertificate signer = 5; 27 | } 28 | 29 | optional bytes certificate = 1; 30 | optional bytes signature = 2; 31 | } 32 | 33 | message UnidentifiedSenderMessage { 34 | 35 | message Message { 36 | enum Type { 37 | PREKEY_MESSAGE = 1; 38 | MESSAGE = 2; 39 | // Further cases should line up with Envelope.Type, even though old cases don't. 40 | 41 | // Our parser does not handle reserved in enums: DESKTOP-1569 42 | // reserved 3 to 6; 43 | 44 | SENDERKEY_MESSAGE = 7; 45 | PLAINTEXT_CONTENT = 8; 46 | } 47 | 48 | enum ContentHint { 49 | // Show an error immediately; it was important but we can't retry. 50 | DEFAULT = 0; 51 | 52 | // Sender will try to resend; delay any error UI if possible 53 | RESENDABLE = 1; 54 | 55 | // Don't show any error UI at all; this is something sent implicitly like a typing message or a receipt 56 | IMPLICIT = 2; 57 | } 58 | 59 | optional Type type = 1; 60 | optional SenderCertificate senderCertificate = 2; 61 | optional bytes content = 3; 62 | optional ContentHint contentHint = 4; 63 | optional bytes groupId = 5; 64 | } 65 | 66 | optional bytes ephemeralPublic = 1; 67 | optional bytes encryptedStatic = 2; 68 | optional bytes encryptedMessage = 3; 69 | } 70 | -------------------------------------------------------------------------------- /pkg/libsignalgo/serviceid.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Scott Weber 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | #include 23 | */ 24 | import "C" 25 | import ( 26 | "unsafe" 27 | ) 28 | 29 | type UUID [C.SignalUUID_LEN]byte 30 | 31 | func SignalServiceIdFromUUID(uuid UUID) (cPNIType, error) { 32 | var result C.SignalServiceIdFixedWidthBinaryBytes 33 | signalFfiError := C.signal_service_id_parse_from_service_id_binary(&result, BytesToBuffer(uuid[:])) 34 | if signalFfiError != nil { 35 | return nil, wrapError(signalFfiError) 36 | } 37 | return cPNIType(unsafe.Pointer(&result)), nil 38 | } 39 | 40 | func SignalPNIServiceIdFromUUID(uuid UUID) (cPNIType, error) { 41 | var result C.SignalServiceIdFixedWidthBinaryBytes 42 | // Prepend a 0x01 to the UUID to indicate that it is a PNI UUID 43 | pniUUID := append([]byte{0x01}, uuid[:]...) 44 | signalFfiError := C.signal_service_id_parse_from_service_id_binary(&result, BytesToBuffer(pniUUID)) 45 | if signalFfiError != nil { 46 | return nil, wrapError(signalFfiError) 47 | } 48 | return cPNIType(unsafe.Pointer(&result)), nil 49 | } 50 | 51 | func SignalServiceIdToUUID(serviceId *C.SignalServiceIdFixedWidthBinaryBytes) (UUID, error) { 52 | result := C.SignalOwnedBuffer{} 53 | serviceIdBytes := cPNIType(unsafe.Pointer(serviceId)) // Hack around gcc bug, not needed for clang 54 | signalFfiError := C.signal_service_id_service_id_binary(&result, serviceIdBytes) 55 | if signalFfiError != nil { 56 | return UUID{}, wrapError(signalFfiError) 57 | } 58 | UUIDBytes := CopySignalOwnedBufferToBytes(result) 59 | var uuid UUID 60 | copy(uuid[:], UUIDBytes) 61 | return uuid, nil 62 | } 63 | -------------------------------------------------------------------------------- /pkg/libsignalgo/buffer.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import ( 25 | "unsafe" 26 | ) 27 | 28 | func BorrowedMutableBuffer(length int) C.SignalBorrowedMutableBuffer { 29 | data := make([]byte, length) 30 | return C.SignalBorrowedMutableBuffer{ 31 | base: (*C.uchar)(unsafe.Pointer(&data[0])), 32 | length: C.uintptr_t(len(data)), 33 | } 34 | } 35 | 36 | func BytesToBuffer(data []byte) C.SignalBorrowedBuffer { 37 | buf := C.SignalBorrowedBuffer{ 38 | length: C.uintptr_t(len(data)), 39 | } 40 | if len(data) > 0 { 41 | buf.base = (*C.uchar)(unsafe.Pointer(&data[0])) 42 | } 43 | return buf 44 | } 45 | 46 | func EmptyBorrowedBuffer() C.SignalBorrowedBuffer { 47 | return C.SignalBorrowedBuffer{} 48 | } 49 | 50 | // TODO: Try out this code from ChatGPT that might be more memory safe 51 | // - Makes copy of data 52 | // - Sets finalizer to free memory 53 | // 54 | //type CBytesWrapper struct { 55 | // c unsafe.Pointer 56 | //} 57 | // 58 | //func CBytes(b []byte) *CBytesWrapper { 59 | // if len(b) == 0 { 60 | // return &CBytesWrapper{nil} 61 | // } 62 | // c := C.malloc(C.size_t(len(b))) 63 | // copy((*[1 << 30]byte)(c)[:], b) 64 | // return &CBytesWrapper{c} 65 | //} 66 | // 67 | //func BytesToBuffer(data []byte) C.SignalBorrowedBuffer { 68 | // cData := CBytes(data) 69 | // buf := C.SignalBorrowedBuffer{ 70 | // length: C.uintptr_t(len(data)), 71 | // } 72 | // if len(data) > 0 { 73 | // buf.base = (*C.uchar)(cData.c) 74 | // } 75 | // 76 | // // Setting finalizer here 77 | // runtime.SetFinalizer(cData, func(c *CBytesWrapper) { C.free(c.c) }) 78 | // 79 | // return buf 80 | //} 81 | // 82 | -------------------------------------------------------------------------------- /pkg/libsignalgo/senderkeyrecord.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import ( 25 | "runtime" 26 | ) 27 | 28 | type SenderKeyRecord struct { 29 | ptr *C.SignalSenderKeyRecord 30 | } 31 | 32 | func wrapSenderKeyRecord(ptr *C.SignalSenderKeyRecord) *SenderKeyRecord { 33 | sc := &SenderKeyRecord{ptr: ptr} 34 | runtime.SetFinalizer(sc, (*SenderKeyRecord).Destroy) 35 | return sc 36 | } 37 | 38 | func DeserializeSenderKeyRecord(serialized []byte) (*SenderKeyRecord, error) { 39 | var sc *C.SignalSenderKeyRecord 40 | signalFfiError := C.signal_sender_key_record_deserialize(&sc, BytesToBuffer(serialized)) 41 | if signalFfiError != nil { 42 | return nil, wrapError(signalFfiError) 43 | } 44 | return wrapSenderKeyRecord(sc), nil 45 | } 46 | 47 | func (skr *SenderKeyRecord) Serialize() ([]byte, error) { 48 | var serialized C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 49 | signalFfiError := C.signal_sender_key_record_serialize(&serialized, skr.ptr) 50 | if signalFfiError != nil { 51 | return nil, wrapError(signalFfiError) 52 | } 53 | return CopySignalOwnedBufferToBytes(serialized), nil 54 | } 55 | 56 | func (skr *SenderKeyRecord) Clone() (*SenderKeyRecord, error) { 57 | var cloned *C.SignalSenderKeyRecord 58 | signalFfiError := C.signal_sender_key_record_clone(&cloned, skr.ptr) 59 | if signalFfiError != nil { 60 | return nil, wrapError(signalFfiError) 61 | } 62 | return wrapSenderKeyRecord(cloned), nil 63 | } 64 | 65 | func (skr *SenderKeyRecord) Destroy() error { 66 | return nil 67 | //runtime.SetFinalizer(skr, nil) 68 | //return wrapError(C.signal_sender_key_record_destroy(skr.ptr)) 69 | } 70 | -------------------------------------------------------------------------------- /pkg/libsignalgo/address.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import ( 25 | "runtime" 26 | ) 27 | 28 | type Address struct { 29 | ptr *C.SignalProtocolAddress 30 | } 31 | 32 | func wrapAddress(ptr *C.SignalProtocolAddress) *Address { 33 | address := &Address{ptr: ptr} 34 | runtime.SetFinalizer(address, (*Address).Destroy) 35 | return address 36 | } 37 | 38 | func NewAddress(name string, deviceID uint) (*Address, error) { 39 | var pa *C.SignalProtocolAddress 40 | signalFfiError := C.signal_address_new(&pa, C.CString(name), C.uint(deviceID)) 41 | if signalFfiError != nil { 42 | return nil, wrapError(signalFfiError) 43 | } 44 | return wrapAddress(pa), nil 45 | } 46 | 47 | func (pk *Address) Clone() (*Address, error) { 48 | var cloned *C.SignalProtocolAddress 49 | signalFfiError := C.signal_address_clone(&cloned, pk.ptr) 50 | if signalFfiError != nil { 51 | return nil, wrapError(signalFfiError) 52 | } 53 | return wrapAddress(cloned), nil 54 | } 55 | 56 | func (pa *Address) Destroy() error { 57 | runtime.SetFinalizer(pa, nil) 58 | return wrapError(C.signal_address_destroy(pa.ptr)) 59 | } 60 | 61 | func (pa *Address) Name() (string, error) { 62 | var name *C.char 63 | signalFfiError := C.signal_address_get_name(&name, pa.ptr) 64 | if signalFfiError != nil { 65 | return "", wrapError(signalFfiError) 66 | } 67 | return CopyCStringToString(name), nil 68 | } 69 | 70 | func (pa *Address) DeviceID() (uint, error) { 71 | var deviceID C.uint 72 | signalFfiError := C.signal_address_get_device_id(&deviceID, pa.ptr) 73 | if signalFfiError != nil { 74 | return 0, wrapError(signalFfiError) 75 | } 76 | return uint(deviceID), nil 77 | } 78 | -------------------------------------------------------------------------------- /pkg/libsignalgo/go.sum: -------------------------------------------------------------------------------- 1 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 5 | github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= 6 | github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 7 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 8 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 9 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 10 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 11 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 12 | github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= 13 | github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= 14 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 18 | github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= 19 | github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 20 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 21 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 22 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 23 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 24 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 25 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 26 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 27 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 28 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 29 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 30 | -------------------------------------------------------------------------------- /pkg/libsignalgo/logging.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include <./libsignal-ffi.h> 22 | 23 | extern bool signal_log_enabled_callback(char *target, SignalLogLevel level); 24 | extern void signal_log_callback(char *target, SignalLogLevel level, char *file, uint32_t line, char *message); 25 | extern void signal_log_flush_callback(); 26 | */ 27 | import "C" 28 | 29 | // ffiLogger is the global logger object. 30 | var ffiLogger Logger 31 | 32 | //export signal_log_enabled_callback 33 | func signal_log_enabled_callback(target *C.char, level C.SignalLogLevel) C.bool { 34 | return C.bool(ffiLogger.Enabled(C.GoString(target), LogLevel(int(level)))) 35 | } 36 | 37 | //export signal_log_callback 38 | func signal_log_callback(target *C.char, level C.SignalLogLevel, file *C.char, line C.uint32_t, message *C.char) { 39 | ffiLogger.Log(C.GoString(target), LogLevel(int(level)), C.GoString(file), uint(line), C.GoString(message)) 40 | } 41 | 42 | //export signal_log_flush_callback 43 | func signal_log_flush_callback() { 44 | ffiLogger.Flush() 45 | } 46 | 47 | type LogLevel int 48 | 49 | const ( 50 | LogLevelError LogLevel = iota + 1 51 | LogLevelWarn 52 | LogLevelInfo 53 | LogLevelDebug 54 | LogLevelTrace 55 | ) 56 | 57 | type Logger interface { 58 | Enabled(target string, level LogLevel) bool 59 | Log(target string, level LogLevel, file string, line uint, message string) 60 | Flush() 61 | } 62 | 63 | func InitLogger(level LogLevel, logger Logger) { 64 | ffiLogger = logger 65 | C.signal_init_logger(C.SignalLogLevel(level), C.SignalFfiLogger{ 66 | enabled: C.SignalLogEnabledCallback(C.signal_log_enabled_callback), 67 | log: C.SignalLogCallback(C.signal_log_callback), 68 | flush: C.SignalLogFlushCallback(C.signal_log_flush_callback), 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /pkg/libsignalgo/aes256gcmsiv.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import "runtime" 25 | 26 | type AES256_GCM_SIV struct { 27 | ptr *C.SignalAes256GcmSiv 28 | } 29 | 30 | func wrapAES256_GCM_SIV(ptr *C.SignalAes256GcmSiv) *AES256_GCM_SIV { 31 | aes := &AES256_GCM_SIV{ptr: ptr} 32 | runtime.SetFinalizer(aes, (*AES256_GCM_SIV).Destroy) 33 | return aes 34 | } 35 | 36 | func NewAES256_GCM_SIV(key []byte) (*AES256_GCM_SIV, error) { 37 | var aes *C.SignalAes256GcmSiv 38 | signalFfiError := C.signal_aes256_gcm_siv_new(&aes, BytesToBuffer(key)) 39 | if signalFfiError != nil { 40 | return nil, wrapError(signalFfiError) 41 | } 42 | return wrapAES256_GCM_SIV(aes), nil 43 | } 44 | 45 | func (aes *AES256_GCM_SIV) Destroy() error { 46 | runtime.SetFinalizer(aes, nil) 47 | return wrapError(C.signal_aes256_gcm_siv_destroy(aes.ptr)) 48 | } 49 | 50 | func (aes *AES256_GCM_SIV) Encrypt(plaintext, nonce, associatedData []byte) ([]byte, error) { 51 | var encrypted C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 52 | 53 | signalFfiError := C.signal_aes256_gcm_siv_encrypt(&encrypted, aes.ptr, BytesToBuffer(plaintext), BytesToBuffer(nonce), BytesToBuffer(associatedData)) 54 | if signalFfiError != nil { 55 | return nil, wrapError(signalFfiError) 56 | } 57 | return CopySignalOwnedBufferToBytes(encrypted), nil 58 | } 59 | 60 | func (aes *AES256_GCM_SIV) Decrypt(ciphertext, nonce, associatedData []byte) ([]byte, error) { 61 | var decrypted C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 62 | signalFfiError := C.signal_aes256_gcm_siv_decrypt(&decrypted, aes.ptr, BytesToBuffer(ciphertext), BytesToBuffer(nonce), BytesToBuffer(associatedData)) 63 | if signalFfiError != nil { 64 | return nil, wrapError(signalFfiError) 65 | } 66 | return CopySignalOwnedBufferToBytes(decrypted), nil 67 | } 68 | -------------------------------------------------------------------------------- /pkg/libsignalgo/kdf_test.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "go.mau.fi/mautrix-signal/pkg/libsignalgo" 25 | ) 26 | 27 | // From PublicAPITests.swift:testHkdfSimple 28 | func TestHKDF_Simple(t *testing.T) { 29 | setupLogging() 30 | inputKeyMaterial := []byte{ 31 | 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 32 | 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 33 | } 34 | outputKeyMaterial := []byte{0x8d, 0xa4, 0xe7, 0x75} 35 | 36 | derived, err := libsignalgo.HKDFDerive(len(outputKeyMaterial), inputKeyMaterial, []byte{}, []byte{}) 37 | assert.NoError(t, err) 38 | assert.Equal(t, outputKeyMaterial, derived) 39 | } 40 | 41 | // From PublicAPITests.swift:testHkdfUsingRFCExample 42 | func TestHKDF_RFCExample(t *testing.T) { 43 | setupLogging() 44 | 45 | var inputKeyMaterial, salt, info []byte 46 | var i byte 47 | for i = 0; i <= 0x4f; i++ { 48 | inputKeyMaterial = append(inputKeyMaterial, i) 49 | } 50 | for i = 0x60; i <= 0xaf; i++ { 51 | salt = append(salt, i) 52 | } 53 | for i = 0xb0; i < 0xff; i++ { 54 | info = append(info, i) 55 | } 56 | info = append(info, 0xff) 57 | 58 | outputKeyMaterial := []byte{ 59 | 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a, 0x49, 0x34, 60 | 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, 0xa0, 0x50, 0xcc, 0x4c, 0x19, 0xaf, 0xa9, 0x7c, 61 | 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, 0x71, 0xcb, 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, 62 | 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8, 0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, 63 | 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec, 0x3e, 0x87, 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, 64 | 0x1d, 0x87, 65 | } 66 | 67 | derived, err := libsignalgo.HKDFDerive(len(outputKeyMaterial), inputKeyMaterial, salt, info) 68 | assert.NoError(t, err) 69 | assert.Equal(t, outputKeyMaterial, derived) 70 | } 71 | -------------------------------------------------------------------------------- /msgconv/signalfmt/tree.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-Signal puppeting bridge. 2 | // Copyright (C) 2023 Tulir Asokan 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package signalfmt 18 | 19 | type BodyRange struct { 20 | Start int 21 | Length int 22 | Value BodyRangeValue 23 | } 24 | 25 | // End returns the end index of the range. 26 | func (b BodyRange) End() int { 27 | return b.Start + b.Length 28 | } 29 | 30 | // Offset changes the start of the range without affecting the length. 31 | func (b BodyRange) Offset(offset int) *BodyRange { 32 | b.Start += offset 33 | return &b 34 | } 35 | 36 | // TruncateStart changes the length of the range, so it starts at the given index and ends at the same index as before. 37 | func (b BodyRange) TruncateStart(startAt int) *BodyRange { 38 | b.Length -= startAt - b.Start 39 | b.Start = startAt 40 | return &b 41 | } 42 | 43 | // TruncateEnd changes the length of the range, so it ends at or before the given index and starts at the same index as before. 44 | func (b BodyRange) TruncateEnd(maxEnd int) *BodyRange { 45 | if b.End() > maxEnd { 46 | b.Length = maxEnd - b.Start 47 | } 48 | return &b 49 | } 50 | 51 | // LinkedRangeTree is a linked tree of formatting entities. 52 | // 53 | // It's meant to parse a list of Signal body ranges into nodes that either overlap completely or not at all, 54 | // which enables more natural conversion to HTML. 55 | type LinkedRangeTree struct { 56 | Node *BodyRange 57 | Sibling *LinkedRangeTree 58 | Child *LinkedRangeTree 59 | } 60 | 61 | func ptrAdd(to **LinkedRangeTree, r *BodyRange) { 62 | if *to == nil { 63 | *to = &LinkedRangeTree{} 64 | } 65 | (*to).Add(r) 66 | } 67 | 68 | // Add adds the given formatting entity to this tree. 69 | func (lrt *LinkedRangeTree) Add(r *BodyRange) { 70 | if lrt.Node == nil { 71 | lrt.Node = r 72 | return 73 | } 74 | lrtEnd := lrt.Node.End() 75 | if r.Start >= lrtEnd { 76 | ptrAdd(&lrt.Sibling, r.Offset(-lrtEnd)) 77 | return 78 | } 79 | if r.End() > lrtEnd { 80 | ptrAdd(&lrt.Sibling, r.TruncateStart(lrtEnd).Offset(-lrtEnd)) 81 | } 82 | ptrAdd(&lrt.Child, r.TruncateEnd(lrtEnd).Offset(-lrt.Node.Start)) 83 | } 84 | -------------------------------------------------------------------------------- /pkg/signalmeow/wspb/wspb.go: -------------------------------------------------------------------------------- 1 | // Package wspb provides helpers for reading and writing protobuf messages. 2 | // Adapted from: https://github.com/nhooyr/websocket/blob/master/wspb/wspb.go 3 | package wspb 4 | 5 | import ( 6 | "bytes" 7 | "context" 8 | "fmt" 9 | "sync" 10 | 11 | "google.golang.org/protobuf/proto" 12 | "nhooyr.io/websocket" 13 | ) 14 | 15 | // Read reads a protobuf message from c into v. 16 | // It will reuse buffers in between calls to avoid allocations. 17 | func Read(ctx context.Context, c *websocket.Conn, v proto.Message) error { 18 | return read(ctx, c, v) 19 | } 20 | 21 | func read(ctx context.Context, c *websocket.Conn, v proto.Message) (err error) { 22 | defer errd_wrap(&err, "failed to read protobuf message") 23 | 24 | typ, r, err := c.Reader(ctx) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | if typ != websocket.MessageBinary { 30 | c.Close(websocket.StatusUnsupportedData, "expected binary message") 31 | return fmt.Errorf("expected binary message for protobuf but got: %v", typ) 32 | } 33 | 34 | b := pool_get() 35 | defer pool_put(b) 36 | 37 | _, err = b.ReadFrom(r) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | err = proto.Unmarshal(b.Bytes(), v) 43 | if err != nil { 44 | c.Close(websocket.StatusInvalidFramePayloadData, "failed to unmarshal protobuf") 45 | return fmt.Errorf("failed to unmarshal protobuf: %w", err) 46 | } 47 | 48 | return nil 49 | } 50 | 51 | // Write writes the protobuf message v to c. 52 | // It will reuse buffers in between calls to avoid allocations. 53 | func Write(ctx context.Context, c *websocket.Conn, v proto.Message) error { 54 | return write(ctx, c, v) 55 | } 56 | 57 | func write(ctx context.Context, c *websocket.Conn, v proto.Message) (err error) { 58 | defer errd_wrap(&err, "failed to write protobuf message") 59 | 60 | data, err := proto.Marshal(v) 61 | if err != nil { 62 | return fmt.Errorf("failed to marshal protobuf: %w", err) 63 | } 64 | 65 | return c.Write(ctx, websocket.MessageBinary, data) 66 | } 67 | 68 | // Adapted from: bpool.go 69 | var my_bpool sync.Pool 70 | 71 | // Get returns a buffer from the pool or creates a new one if 72 | // the pool is empty. 73 | func pool_get() *bytes.Buffer { 74 | b := my_bpool.Get() 75 | if b == nil { 76 | return &bytes.Buffer{} 77 | } 78 | return b.(*bytes.Buffer) 79 | } 80 | 81 | // Put returns a buffer into the pool. 82 | func pool_put(b *bytes.Buffer) { 83 | b.Reset() 84 | my_bpool.Put(b) 85 | } 86 | 87 | // Adapted from: errd.go 88 | 89 | // Wrap wraps err with fmt.Errorf if err is non nil. 90 | // Intended for use with defer and a named error return. 91 | // Inspired by https://github.com/golang/go/issues/32676. 92 | func errd_wrap(err *error, f string, v ...interface{}) { 93 | if *err != nil { 94 | *err = fmt.Errorf(f+": %w", append(v, *err)...) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /msgconv/signalfmt/html.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-Signal puppeting bridge. 2 | // Copyright (C) 2023 Tulir Asokan 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package signalfmt 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | "unicode/utf16" 23 | ) 24 | 25 | func (m Mention) Format(message string) string { 26 | return fmt.Sprintf( 27 | `%s`, 28 | m.MXID.URI().MatrixToURL(), 29 | strings.Replace(message, "\ufffc", m.Name, 1), 30 | ) 31 | } 32 | 33 | func (s Style) Format(message string) string { 34 | switch s { 35 | case StyleBold: 36 | return fmt.Sprintf("%s", message) 37 | case StyleItalic: 38 | return fmt.Sprintf("%s", message) 39 | case StyleSpoiler: 40 | return fmt.Sprintf("%s", message) 41 | case StyleStrikethrough: 42 | return fmt.Sprintf("%s", message) 43 | case StyleMonospace: 44 | if strings.ContainsRune(message, '\n') { 45 | // This is somewhat incorrect, as it won't allow inline text before/after a multiline monospace-formatted string. 46 | return fmt.Sprintf("
%s
", message) 47 | } 48 | return fmt.Sprintf("%s", message) 49 | default: 50 | return message 51 | } 52 | } 53 | 54 | type UTF16String []uint16 55 | 56 | func NewUTF16String(s string) UTF16String { 57 | return utf16.Encode([]rune(s)) 58 | } 59 | 60 | func (u UTF16String) String() string { 61 | return string(utf16.Decode(u)) 62 | } 63 | 64 | func (lrt *LinkedRangeTree) Format(message UTF16String, ctx formatContext) string { 65 | if lrt == nil || lrt.Node == nil { 66 | return ctx.TextToHTML(message.String()) 67 | } 68 | head := message[:lrt.Node.Start] 69 | headStr := ctx.TextToHTML(head.String()) 70 | inner := message[lrt.Node.Start:lrt.Node.End()] 71 | tail := message[lrt.Node.End():] 72 | ourCtx := ctx 73 | if lrt.Node.Value == StyleMonospace { 74 | ourCtx.IsInCodeblock = true 75 | } 76 | childMessage := lrt.Child.Format(inner, ourCtx) 77 | formattedChildMessage := lrt.Node.Value.Format(childMessage) 78 | siblingMessage := lrt.Sibling.Format(tail, ctx) 79 | return headStr + formattedChildMessage + siblingMessage 80 | } 81 | -------------------------------------------------------------------------------- /database/upgrades/00-initial.sql: -------------------------------------------------------------------------------- 1 | -- v0 -> v15: Latest revision 2 | 3 | CREATE TABLE portal ( 4 | chat_id TEXT, 5 | receiver TEXT, 6 | mxid TEXT, 7 | name TEXT, 8 | topic TEXT, 9 | encrypted BOOLEAN NOT NULL DEFAULT false, 10 | avatar_hash TEXT, 11 | avatar_url TEXT, 12 | name_set BOOLEAN NOT NULL DEFAULT false, 13 | avatar_set BOOLEAN NOT NULL DEFAULT false, 14 | revision INTEGER NOT NULL DEFAULT 0, 15 | expiration_time BIGINT, 16 | relay_user_id TEXT, 17 | 18 | PRIMARY KEY (chat_id, receiver) 19 | ); 20 | 21 | CREATE TABLE puppet ( 22 | uuid UUID PRIMARY KEY, 23 | number TEXT UNIQUE, 24 | name TEXT, 25 | name_quality INTEGER NOT NULL DEFAULT 0, 26 | avatar_hash TEXT, 27 | avatar_url TEXT, 28 | name_set BOOLEAN NOT NULL DEFAULT false, 29 | avatar_set BOOLEAN NOT NULL DEFAULT false, 30 | 31 | is_registered BOOLEAN NOT NULL DEFAULT false, 32 | 33 | custom_mxid TEXT, 34 | access_token TEXT, 35 | contact_info_set BOOLEAN NOT NULL DEFAULT false 36 | ); 37 | 38 | CREATE TABLE "user" ( 39 | mxid TEXT PRIMARY KEY, 40 | username TEXT, 41 | uuid UUID, 42 | management_room TEXT 43 | ); 44 | 45 | CREATE TABLE message ( 46 | mxid TEXT NOT NULL, 47 | mx_room TEXT NOT NULL, 48 | sender UUID, 49 | timestamp BIGINT, 50 | signal_chat_id TEXT, 51 | signal_receiver TEXT, 52 | 53 | PRIMARY KEY (sender, timestamp, signal_chat_id, signal_receiver), 54 | FOREIGN KEY (signal_chat_id, signal_receiver) REFERENCES portal(chat_id, receiver) ON DELETE CASCADE, 55 | FOREIGN KEY (sender) REFERENCES puppet(uuid) ON DELETE CASCADE, 56 | UNIQUE (mxid, mx_room) 57 | ); 58 | 59 | CREATE TABLE reaction ( 60 | mxid TEXT NOT NULL, 61 | mx_room TEXT NOT NULL, 62 | 63 | signal_chat_id TEXT NOT NULL, 64 | signal_receiver TEXT NOT NULL, 65 | 66 | author UUID NOT NULL, 67 | msg_author UUID NOT NULL, 68 | msg_timestamp BIGINT NOT NULL, 69 | emoji TEXT NOT NULL, 70 | 71 | PRIMARY KEY (signal_chat_id, signal_receiver, msg_author, msg_timestamp, author), 72 | CONSTRAINT reaction_message_fkey 73 | FOREIGN KEY (msg_author, msg_timestamp, signal_chat_id, signal_receiver) 74 | REFERENCES message(sender, timestamp, signal_chat_id, signal_receiver) 75 | ON DELETE CASCADE, 76 | FOREIGN KEY (author) REFERENCES puppet(uuid) ON DELETE CASCADE, 77 | UNIQUE (mxid, mx_room) 78 | ); 79 | 80 | CREATE TABLE disappearing_message ( 81 | room_id TEXT, 82 | mxid TEXT, 83 | expiration_seconds BIGINT, 84 | expiration_ts BIGINT, 85 | 86 | PRIMARY KEY (room_id, mxid) 87 | ); 88 | -------------------------------------------------------------------------------- /pkg/libsignalgo/ciphertextmessage.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import "runtime" 25 | 26 | type CiphertextMessageType uint8 27 | 28 | const ( 29 | CiphertextMessageTypeWhisper CiphertextMessageType = 2 30 | CiphertextMessageTypePreKey CiphertextMessageType = 3 31 | CiphertextMessageTypeSenderKey CiphertextMessageType = 7 32 | CiphertextMessageTypePlaintext CiphertextMessageType = 8 33 | ) 34 | 35 | type CiphertextMessage struct { 36 | ptr *C.SignalCiphertextMessage 37 | } 38 | 39 | func wrapCiphertextMessage(ptr *C.SignalCiphertextMessage) *CiphertextMessage { 40 | ciphertextMessage := &CiphertextMessage{ptr: ptr} 41 | runtime.SetFinalizer(ciphertextMessage, (*CiphertextMessage).Destroy) 42 | return ciphertextMessage 43 | } 44 | 45 | func NewCiphertextMessage(plaintext PlaintextContent) (*CiphertextMessage, error) { 46 | var ciphertextMessage *C.SignalCiphertextMessage 47 | signalFfiError := C.signal_ciphertext_message_from_plaintext_content(&ciphertextMessage, plaintext.ptr) 48 | if signalFfiError != nil { 49 | return nil, wrapError(signalFfiError) 50 | } 51 | return wrapCiphertextMessage(ciphertextMessage), nil 52 | } 53 | 54 | func (c *CiphertextMessage) Destroy() error { 55 | runtime.SetFinalizer(c, nil) 56 | return wrapError(C.signal_ciphertext_message_destroy(c.ptr)) 57 | } 58 | 59 | func (c *CiphertextMessage) Serialize() ([]byte, error) { 60 | var serialized C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 61 | signalFfiError := C.signal_ciphertext_message_serialize(&serialized, c.ptr) 62 | if signalFfiError != nil { 63 | return nil, wrapError(signalFfiError) 64 | } 65 | return CopySignalOwnedBufferToBytes(serialized), nil 66 | } 67 | 68 | func (c *CiphertextMessage) MessageType() (CiphertextMessageType, error) { 69 | var messageType C.uint8_t 70 | signalFfiError := C.signal_ciphertext_message_type(&messageType, c.ptr) 71 | if signalFfiError != nil { 72 | return 0, wrapError(signalFfiError) 73 | } 74 | return CiphertextMessageType(messageType), nil 75 | } 76 | -------------------------------------------------------------------------------- /pkg/signalmeow/group_store.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Scott Weber 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package signalmeow 18 | 19 | import ( 20 | "context" 21 | "database/sql" 22 | "errors" 23 | ) 24 | 25 | var _ GroupStore = (*SQLStore)(nil) 26 | 27 | type dbGroup struct { 28 | OurAciUuid string 29 | GroupIdentifier GroupIdentifier 30 | GroupMasterKey SerializedGroupMasterKey 31 | } 32 | 33 | type GroupStore interface { 34 | MasterKeyFromGroupIdentifier(groupIdentifier GroupIdentifier, ctx context.Context) (SerializedGroupMasterKey, error) 35 | StoreMasterKey(groupIdentifier GroupIdentifier, key SerializedGroupMasterKey, ctx context.Context) error 36 | } 37 | 38 | func scanGroup(row scannable) (*dbGroup, error) { 39 | var g dbGroup 40 | err := row.Scan(&g.OurAciUuid, &g.GroupIdentifier, &g.GroupMasterKey) 41 | if errors.Is(err, sql.ErrNoRows) { 42 | return nil, nil 43 | } else if err != nil { 44 | return nil, err 45 | } 46 | return &g, nil 47 | } 48 | 49 | func (s *SQLStore) MasterKeyFromGroupIdentifier(groupIdentifier GroupIdentifier, ctx context.Context) (SerializedGroupMasterKey, error) { 50 | loadGroupQuery := `SELECT our_aci_uuid, group_identifier, master_key FROM signalmeow_groups WHERE our_aci_uuid=$1 AND group_identifier=$2` 51 | g, err := scanGroup(s.db.QueryRow(loadGroupQuery, s.AciUuid, groupIdentifier)) 52 | if err != nil { 53 | return "", err 54 | } 55 | if g == nil { 56 | return "", nil 57 | } 58 | return g.GroupMasterKey, nil 59 | } 60 | 61 | func (s *SQLStore) StoreMasterKey(groupIdentifier GroupIdentifier, key SerializedGroupMasterKey, ctx context.Context) error { 62 | // Insert, or update if already exists 63 | storeMasterKeyQuery := ` 64 | INSERT INTO signalmeow_groups (our_aci_uuid, group_identifier, master_key) 65 | VALUES ($1, $2, $3) 66 | ON CONFLICT (our_aci_uuid, group_identifier) DO UPDATE SET 67 | master_key = excluded.master_key; 68 | ` 69 | 70 | tx, err := s.db.BeginTx(ctx, nil) 71 | if err != nil { 72 | tx.Rollback() 73 | return err 74 | } 75 | _, err = tx.Exec(storeMasterKeyQuery, s.AciUuid, groupIdentifier, key) 76 | if err != nil { 77 | tx.Rollback() 78 | return err 79 | } 80 | err = tx.Commit() 81 | return err 82 | } 83 | -------------------------------------------------------------------------------- /pkg/signalmeow/profile_key_store.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Scott Weber 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package signalmeow 18 | 19 | import ( 20 | "context" 21 | "database/sql" 22 | "errors" 23 | 24 | "go.mau.fi/mautrix-signal/pkg/libsignalgo" 25 | ) 26 | 27 | var _ ProfileKeyStore = (*SQLStore)(nil) 28 | 29 | type ProfileKeyStore interface { 30 | // LoadProfileKey loads the profile key for the given address. 31 | // If the address is not found, nil is returned. 32 | LoadProfileKey(theirUuid string, ctx context.Context) (*libsignalgo.ProfileKey, error) 33 | StoreProfileKey(theirUuid string, key libsignalgo.ProfileKey, ctx context.Context) error 34 | MyProfileKey(ctx context.Context) (*libsignalgo.ProfileKey, error) 35 | } 36 | 37 | const ( 38 | loadProfileKeyQuery = `SELECT key FROM signalmeow_profile_keys WHERE our_aci_uuid=$1 AND their_aci_uuid=$2` 39 | storeProfileKeyQuery = `INSERT INTO signalmeow_profile_keys (our_aci_uuid, their_aci_uuid, key) VALUES ($1, $2, $3) ON CONFLICT (our_aci_uuid, their_aci_uuid) DO UPDATE SET key=excluded.key` 40 | ) 41 | 42 | func scanProfileKey(row scannable) (*libsignalgo.ProfileKey, error) { 43 | var record []byte 44 | err := row.Scan(&record) 45 | if errors.Is(err, sql.ErrNoRows) { 46 | return nil, nil 47 | } else if err != nil { 48 | return nil, err 49 | } 50 | profileKey := libsignalgo.ProfileKey(record) 51 | return &profileKey, err 52 | } 53 | 54 | func (s *SQLStore) LoadProfileKey(theirUuid string, ctx context.Context) (*libsignalgo.ProfileKey, error) { 55 | return scanProfileKey(s.db.QueryRow(loadProfileKeyQuery, s.AciUuid, theirUuid)) 56 | } 57 | 58 | func (s *SQLStore) MyProfileKey(ctx context.Context) (*libsignalgo.ProfileKey, error) { 59 | return scanProfileKey(s.db.QueryRow(loadProfileKeyQuery, s.AciUuid, s.AciUuid)) 60 | } 61 | 62 | func (s *SQLStore) StoreProfileKey(theirUuid string, key libsignalgo.ProfileKey, ctx context.Context) error { 63 | tx, err := s.db.BeginTx(ctx, nil) 64 | if err != nil { 65 | tx.Rollback() 66 | return err 67 | } 68 | _, err = tx.Exec(storeProfileKeyQuery, s.AciUuid, theirUuid, key.Slice()) 69 | if err != nil { 70 | tx.Rollback() 71 | return err 72 | } 73 | err = tx.Commit() 74 | return err 75 | } 76 | -------------------------------------------------------------------------------- /pkg/libsignalgo/serializedeserializeroundtrip_test.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo_test 18 | 19 | import ( 20 | "testing" 21 | "time" 22 | 23 | "github.com/stretchr/testify/assert" 24 | 25 | "go.mau.fi/mautrix-signal/pkg/libsignalgo" 26 | ) 27 | 28 | type Serializable interface { 29 | Serialize() ([]byte, error) 30 | } 31 | 32 | func testRoundTrip[T Serializable](t *testing.T, name string, obj T, deserializer func([]byte) (T, error)) { 33 | t.Run(name, func(t *testing.T) { 34 | serialized, err := obj.Serialize() 35 | assert.NoError(t, err) 36 | 37 | deserialized, err := deserializer(serialized) 38 | assert.NoError(t, err) 39 | 40 | deserializedSerialized, err := deserialized.Serialize() 41 | assert.NoError(t, err) 42 | 43 | assert.Equal(t, serialized, deserializedSerialized) 44 | }) 45 | } 46 | 47 | // From PublicAPITests.swift:testSerializationRoundTrip 48 | func TestSenderCertificateSerializationRoundTrip(t *testing.T) { 49 | keyPair, err := libsignalgo.GenerateIdentityKeyPair() 50 | assert.NoError(t, err) 51 | 52 | testRoundTrip(t, "key pair", keyPair, libsignalgo.DeserializeIdentityKeyPair) 53 | testRoundTrip(t, "public key", keyPair.GetPublicKey(), libsignalgo.DeserializePublicKey) 54 | testRoundTrip(t, "private key", keyPair.GetPrivateKey(), libsignalgo.DeserializePrivateKey) 55 | testRoundTrip(t, "identity key", keyPair.GetIdentityKey(), libsignalgo.NewIdentityKeyFromBytes) 56 | 57 | preKeyRecord, err := libsignalgo.NewPreKeyRecord(7, keyPair.GetPublicKey(), keyPair.GetPrivateKey()) 58 | assert.NoError(t, err) 59 | testRoundTrip(t, "pre key record", preKeyRecord, libsignalgo.DeserializePreKeyRecord) 60 | 61 | publicKeySerialized, err := keyPair.GetPublicKey().Serialize() 62 | assert.NoError(t, err) 63 | signature, err := keyPair.GetPrivateKey().Sign(publicKeySerialized) 64 | assert.NoError(t, err) 65 | 66 | signedPreKeyRecord, err := libsignalgo.NewSignedPreKeyRecordFromPrivateKey( 67 | 77, 68 | time.UnixMilli(42000), 69 | keyPair.GetPrivateKey(), 70 | signature, 71 | ) 72 | assert.NoError(t, err) 73 | testRoundTrip(t, "signed pre key record", signedPreKeyRecord, libsignalgo.DeserializeSignedPreKeyRecord) 74 | } 75 | -------------------------------------------------------------------------------- /pkg/libsignalgo/signedprekeystore.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | 23 | typedef const SignalSignedPreKeyRecord const_signed_pre_key_record; 24 | 25 | extern int signal_load_signed_pre_key_callback(void *store_ctx, SignalSignedPreKeyRecord **recordp, uint32_t id, void *ctx); 26 | extern int signal_store_signed_pre_key_callback(void *store_ctx, uint32_t id, const_signed_pre_key_record *record, void *ctx); 27 | */ 28 | import "C" 29 | import ( 30 | "context" 31 | "unsafe" 32 | 33 | gopointer "github.com/mattn/go-pointer" 34 | ) 35 | 36 | type SignedPreKeyStore interface { 37 | LoadSignedPreKey(id uint32, context context.Context) (*SignedPreKeyRecord, error) 38 | StoreSignedPreKey(id uint32, signedPreKeyRecord *SignedPreKeyRecord, context context.Context) error 39 | } 40 | 41 | //export signal_load_signed_pre_key_callback 42 | func signal_load_signed_pre_key_callback(storeCtx unsafe.Pointer, keyp **C.SignalSignedPreKeyRecord, id C.uint32_t, ctxPtr unsafe.Pointer) C.int { 43 | return wrapStoreCallback(storeCtx, ctxPtr, func(store SignedPreKeyStore, ctx context.Context) error { 44 | key, err := store.LoadSignedPreKey(uint32(id), ctx) 45 | if err == nil && key != nil { 46 | *keyp = key.ptr 47 | } 48 | return err 49 | }) 50 | } 51 | 52 | //export signal_store_signed_pre_key_callback 53 | func signal_store_signed_pre_key_callback(storeCtx unsafe.Pointer, id C.uint32_t, preKeyRecord *C.const_signed_pre_key_record, ctxPtr unsafe.Pointer) C.int { 54 | return wrapStoreCallback(storeCtx, ctxPtr, func(store SignedPreKeyStore, ctx context.Context) error { 55 | record := SignedPreKeyRecord{ptr: (*C.SignalSignedPreKeyRecord)(unsafe.Pointer(preKeyRecord))} 56 | cloned, err := record.Clone() 57 | if err != nil { 58 | return err 59 | } 60 | return store.StoreSignedPreKey(uint32(id), cloned, ctx) 61 | }) 62 | } 63 | 64 | func wrapSignedPreKeyStore(store SignedPreKeyStore) *C.SignalSignedPreKeyStore { 65 | // TODO: This is probably a memory leak 66 | return &C.SignalSignedPreKeyStore{ 67 | ctx: gopointer.Save(store), 68 | load_signed_pre_key: C.SignalLoadSignedPreKey(C.signal_load_signed_pre_key_callback), 69 | store_signed_pre_key: C.SignalStoreSignedPreKey(C.signal_store_signed_pre_key_callback), 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /pkg/libsignalgo/sessionstore.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | 23 | typedef const SignalSessionRecord const_session_record; 24 | typedef const SignalProtocolAddress const_address; 25 | 26 | extern int signal_load_session_callback(void *store_ctx, SignalSessionRecord **recordp, const_address *address, void *ctx); 27 | extern int signal_store_session_callback(void *store_ctx, const_address *address, const_session_record *record, void *ctx); 28 | */ 29 | import "C" 30 | import ( 31 | "context" 32 | "unsafe" 33 | 34 | gopointer "github.com/mattn/go-pointer" 35 | ) 36 | 37 | type SessionStore interface { 38 | LoadSession(address *Address, ctx context.Context) (*SessionRecord, error) 39 | StoreSession(address *Address, record *SessionRecord, ctx context.Context) error 40 | } 41 | 42 | //export signal_load_session_callback 43 | func signal_load_session_callback(storeCtx unsafe.Pointer, recordp **C.SignalSessionRecord, address *C.const_address, ctxPtr unsafe.Pointer) C.int { 44 | return wrapStoreCallback(storeCtx, ctxPtr, func(store SessionStore, ctx context.Context) error { 45 | record, err := store.LoadSession( 46 | &Address{ptr: (*C.SignalProtocolAddress)(unsafe.Pointer(address))}, 47 | ctx, 48 | ) 49 | if err == nil && record != nil { 50 | *recordp = record.ptr 51 | } 52 | return err 53 | }) 54 | } 55 | 56 | //export signal_store_session_callback 57 | func signal_store_session_callback(storeCtx unsafe.Pointer, address *C.const_address, sessionRecord *C.const_session_record, ctxPtr unsafe.Pointer) C.int { 58 | return wrapStoreCallback(storeCtx, ctxPtr, func(store SessionStore, ctx context.Context) error { 59 | record := SessionRecord{ptr: (*C.SignalSessionRecord)(unsafe.Pointer(sessionRecord))} 60 | cloned, err := record.Clone() 61 | if err != nil { 62 | return err 63 | } 64 | return store.StoreSession( 65 | &Address{ptr: (*C.SignalProtocolAddress)(unsafe.Pointer(address))}, 66 | cloned, 67 | ctx, 68 | ) 69 | }) 70 | } 71 | 72 | func wrapSessionStore(store SessionStore) *C.SignalSessionStore { 73 | return &C.SignalSessionStore{ 74 | ctx: gopointer.Save(store), 75 | load_session: C.SignalLoadSession(C.signal_load_session_callback), 76 | store_session: C.SignalStoreSession(C.signal_store_session_callback), 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /custompuppet.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Tulir Asokan 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "maunium.net/go/mautrix/id" 21 | ) 22 | 23 | func (puppet *Puppet) SwitchCustomMXID(accessToken string, mxid id.UserID) error { 24 | puppet.CustomMXID = mxid 25 | puppet.AccessToken = accessToken 26 | puppet.Update() 27 | err := puppet.StartCustomMXID(false) 28 | if err != nil { 29 | return err 30 | } 31 | // TODO leave rooms with default puppet 32 | return nil 33 | } 34 | 35 | func (puppet *Puppet) ClearCustomMXID() { 36 | save := puppet.CustomMXID != "" || puppet.AccessToken != "" 37 | puppet.bridge.puppetsLock.Lock() 38 | if puppet.CustomMXID != "" && puppet.bridge.puppetsByCustomMXID[puppet.CustomMXID] == puppet { 39 | delete(puppet.bridge.puppetsByCustomMXID, puppet.CustomMXID) 40 | } 41 | puppet.bridge.puppetsLock.Unlock() 42 | puppet.CustomMXID = "" 43 | puppet.AccessToken = "" 44 | puppet.customIntent = nil 45 | puppet.customUser = nil 46 | if save { 47 | puppet.Update() 48 | } 49 | } 50 | 51 | func (puppet *Puppet) StartCustomMXID(reloginOnFail bool) error { 52 | newIntent, newAccessToken, err := puppet.bridge.DoublePuppet.Setup(puppet.CustomMXID, puppet.AccessToken, reloginOnFail) 53 | if err != nil { 54 | puppet.ClearCustomMXID() 55 | return err 56 | } 57 | puppet.bridge.puppetsLock.Lock() 58 | puppet.bridge.puppetsByCustomMXID[puppet.CustomMXID] = puppet 59 | puppet.bridge.puppetsLock.Unlock() 60 | if puppet.AccessToken != newAccessToken { 61 | puppet.AccessToken = newAccessToken 62 | puppet.Update() 63 | } 64 | puppet.customIntent = newIntent 65 | puppet.customUser = puppet.bridge.GetUserByMXID(puppet.CustomMXID) 66 | return nil 67 | } 68 | 69 | func (user *User) tryAutomaticDoublePuppeting() { 70 | if !user.bridge.Config.CanAutoDoublePuppet(user.MXID) { 71 | return 72 | } 73 | user.log.Debug().Msg("Checking if double puppeting needs to be enabled") 74 | puppet := user.bridge.GetPuppetBySignalID(user.SignalID) 75 | if len(puppet.CustomMXID) > 0 { 76 | user.log.Debug().Msg("User already has double-puppeting enabled") 77 | // Custom puppet already enabled 78 | return 79 | } 80 | puppet.CustomMXID = user.MXID 81 | err := puppet.StartCustomMXID(true) 82 | if err != nil { 83 | user.log.Warn().Err(err).Msg("Failed to login with shared secret") 84 | } else { 85 | // TODO leave rooms with default puppet 86 | user.log.Debug().Msg("Successfully automatically enabled custom puppet") 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pkg/libsignalgo/plaintextcontent.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import "runtime" 25 | 26 | type PlaintextContent struct { 27 | ptr *C.SignalPlaintextContent 28 | } 29 | 30 | func wrapPlaintextContent(ptr *C.SignalPlaintextContent) *PlaintextContent { 31 | plaintextContent := &PlaintextContent{ptr: ptr} 32 | runtime.SetFinalizer(plaintextContent, (*PlaintextContent).Destroy) 33 | return plaintextContent 34 | } 35 | 36 | func PlaintextContentFromDecryptionErrorMessage(message DecryptionErrorMessage) (*PlaintextContent, error) { 37 | var pc *C.SignalPlaintextContent 38 | signalFfiError := C.signal_plaintext_content_from_decryption_error_message(&pc, message.ptr) 39 | if signalFfiError != nil { 40 | return nil, wrapError(signalFfiError) 41 | } 42 | return wrapPlaintextContent(pc), nil 43 | } 44 | 45 | func DeserializePlaintextContent(plaintextContentBytes []byte) (*PlaintextContent, error) { 46 | var pc *C.SignalPlaintextContent 47 | signalFfiError := C.signal_plaintext_content_deserialize(&pc, BytesToBuffer(plaintextContentBytes)) 48 | if signalFfiError != nil { 49 | return nil, wrapError(signalFfiError) 50 | } 51 | return wrapPlaintextContent(pc), nil 52 | } 53 | 54 | func (pk *PlaintextContent) Clone() (*PlaintextContent, error) { 55 | var cloned *C.SignalPlaintextContent 56 | signalFfiError := C.signal_plaintext_content_clone(&cloned, pk.ptr) 57 | if signalFfiError != nil { 58 | return nil, wrapError(signalFfiError) 59 | } 60 | return wrapPlaintextContent(cloned), nil 61 | } 62 | 63 | func (p *PlaintextContent) Destroy() error { 64 | runtime.SetFinalizer(p, nil) 65 | return wrapError(C.signal_plaintext_content_destroy(p.ptr)) 66 | } 67 | 68 | func (pc *PlaintextContent) Serialize() ([]byte, error) { 69 | var serialized C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 70 | signalFfiError := C.signal_plaintext_content_serialize(&serialized, pc.ptr) 71 | if signalFfiError != nil { 72 | return nil, wrapError(signalFfiError) 73 | } 74 | return CopySignalOwnedBufferToBytes(serialized), nil 75 | } 76 | 77 | func (pc *PlaintextContent) GetBody() ([]byte, error) { 78 | var body C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 79 | signalFfiError := C.signal_plaintext_content_get_body(&body, pc.ptr) 80 | if signalFfiError != nil { 81 | return nil, wrapError(signalFfiError) 82 | } 83 | return CopySignalOwnedBufferToBytes(body), nil 84 | } 85 | -------------------------------------------------------------------------------- /pkg/libsignalgo/hsmenclave.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import "runtime" 25 | 26 | type HSMEnclaveClient struct { 27 | ptr *C.SignalHsmEnclaveClient 28 | } 29 | 30 | func wrapHSMEnclaveClient(ptr *C.SignalHsmEnclaveClient) *HSMEnclaveClient { 31 | hsmEnclaveClient := &HSMEnclaveClient{ptr: ptr} 32 | runtime.SetFinalizer(hsmEnclaveClient, (*HSMEnclaveClient).Destroy) 33 | return hsmEnclaveClient 34 | } 35 | 36 | func NewHSMEnclaveClient(trustedPublicKey, trustedCodeHashes []byte) (*HSMEnclaveClient, error) { 37 | var cds *C.SignalHsmEnclaveClient 38 | signalFfiError := C.signal_hsm_enclave_client_new(&cds, BytesToBuffer(trustedPublicKey), BytesToBuffer(trustedCodeHashes)) 39 | if signalFfiError != nil { 40 | return nil, wrapError(signalFfiError) 41 | } 42 | return wrapHSMEnclaveClient(cds), nil 43 | } 44 | 45 | func (hsm *HSMEnclaveClient) Destroy() error { 46 | runtime.SetFinalizer(hsm, nil) 47 | return wrapError(C.signal_hsm_enclave_client_destroy(hsm.ptr)) 48 | } 49 | 50 | func (hsm *HSMEnclaveClient) InitialRequest() ([]byte, error) { 51 | var resp C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 52 | signalFfiError := C.signal_hsm_enclave_client_initial_request(&resp, hsm.ptr) 53 | if signalFfiError != nil { 54 | return nil, wrapError(signalFfiError) 55 | } 56 | return CopySignalOwnedBufferToBytes(resp), nil 57 | } 58 | 59 | func (hsm *HSMEnclaveClient) CompleteHandshake(handshakeReceived []byte) error { 60 | signalFfiError := C.signal_hsm_enclave_client_complete_handshake(hsm.ptr, BytesToBuffer(handshakeReceived)) 61 | return wrapError(signalFfiError) 62 | } 63 | 64 | func (hsm *HSMEnclaveClient) EstablishedSend(plaintext []byte) ([]byte, error) { 65 | var resp C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 66 | signalFfiError := C.signal_hsm_enclave_client_established_send(&resp, hsm.ptr, BytesToBuffer(plaintext)) 67 | if signalFfiError != nil { 68 | return nil, wrapError(signalFfiError) 69 | } 70 | return CopySignalOwnedBufferToBytes(resp), nil 71 | } 72 | 73 | func (cds *HSMEnclaveClient) EstablishedReceive(ciphertext []byte) ([]byte, error) { 74 | var resp C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 75 | signalFfiError := C.signal_hsm_enclave_client_established_recv(&resp, cds.ptr, BytesToBuffer(ciphertext)) 76 | if signalFfiError != nil { 77 | return nil, wrapError(signalFfiError) 78 | } 79 | return CopySignalOwnedBufferToBytes(resp), nil 80 | } 81 | -------------------------------------------------------------------------------- /pkg/libsignalgo/publickey.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import "runtime" 25 | 26 | type PublicKey struct { 27 | ptr *C.SignalPublicKey 28 | } 29 | 30 | func wrapPublicKey(ptr *C.SignalPublicKey) *PublicKey { 31 | publicKey := &PublicKey{ptr: ptr} 32 | runtime.SetFinalizer(publicKey, (*PublicKey).Destroy) 33 | return publicKey 34 | } 35 | 36 | func (pk *PublicKey) Clone() (*PublicKey, error) { 37 | var cloned *C.SignalPublicKey 38 | signalFfiError := C.signal_publickey_clone(&cloned, pk.ptr) 39 | if signalFfiError != nil { 40 | return nil, wrapError(signalFfiError) 41 | } 42 | return wrapPublicKey(cloned), nil 43 | } 44 | 45 | func DeserializePublicKey(keyData []byte) (*PublicKey, error) { 46 | var pk *C.SignalPublicKey 47 | signalFfiError := C.signal_publickey_deserialize(&pk, BytesToBuffer(keyData)) 48 | if signalFfiError != nil { 49 | return nil, wrapError(signalFfiError) 50 | } 51 | return wrapPublicKey(pk), nil 52 | } 53 | 54 | func (pk *PublicKey) Serialize() ([]byte, error) { 55 | var serialized C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 56 | signalFfiError := C.signal_publickey_serialize(&serialized, pk.ptr) 57 | if signalFfiError != nil { 58 | return nil, wrapError(signalFfiError) 59 | } 60 | return CopySignalOwnedBufferToBytes(serialized), nil 61 | } 62 | 63 | func (k *PublicKey) Destroy() error { 64 | return nil 65 | //runtime.SetFinalizer(k, nil) 66 | //return wrapError(C.signal_publickey_destroy(k.ptr)) 67 | } 68 | 69 | func (k *PublicKey) Compare(other *PublicKey) (int, error) { 70 | var comparison C.int 71 | signalFfiError := C.signal_publickey_compare(&comparison, k.ptr, other.ptr) 72 | if signalFfiError != nil { 73 | return 0, wrapError(signalFfiError) 74 | } 75 | return int(comparison), nil 76 | } 77 | 78 | func (k *PublicKey) Bytes() ([]byte, error) { 79 | var pub C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 80 | signalFfiError := C.signal_publickey_get_public_key_bytes(&pub, k.ptr) 81 | if signalFfiError != nil { 82 | return nil, wrapError(signalFfiError) 83 | } 84 | return CopySignalOwnedBufferToBytes(pub), nil 85 | } 86 | 87 | func (k *PublicKey) Verify(message, signature []byte) (bool, error) { 88 | var verify C.bool 89 | signalFfiError := C.signal_publickey_verify(&verify, k.ptr, BytesToBuffer(message), BytesToBuffer(signature)) 90 | if signalFfiError != nil { 91 | return false, wrapError(signalFfiError) 92 | } 93 | return bool(verify), nil 94 | } 95 | -------------------------------------------------------------------------------- /pkg/signalmeow/go.sum: -------------------------------------------------------------------------------- 1 | github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= 2 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 5 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 6 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 7 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 8 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 9 | github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= 10 | github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 11 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 12 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 13 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 14 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 15 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 16 | github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= 17 | github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= 18 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 19 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 20 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 21 | github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= 22 | github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 23 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 24 | go.mau.fi/util v0.2.1 h1:eazulhFE/UmjOFtPrGg6zkF5YfAyiDzQb8ihLMbsPWw= 25 | go.mau.fi/util v0.2.1/go.mod h1:MjlzCQEMzJ+G8RsPawHzpLB8rwTo3aPIjG5FzBvQT/c= 26 | golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= 27 | golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 28 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 29 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 30 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 31 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 32 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 33 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 34 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 35 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 36 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 37 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 38 | nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= 39 | nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= 40 | -------------------------------------------------------------------------------- /pkg/libsignalgo/prekeystore.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | 23 | typedef const SignalPreKeyRecord const_pre_key_record; 24 | 25 | extern int signal_load_pre_key_callback(void *store_ctx, SignalPreKeyRecord **recordp, uint32_t id, void *ctx); 26 | extern int signal_store_pre_key_callback(void *store_ctx, uint32_t id, const_pre_key_record *record, void *ctx); 27 | extern int signal_remove_pre_key_callback(void *store_ctx, uint32_t id, void *ctx); 28 | */ 29 | import "C" 30 | import ( 31 | "context" 32 | "unsafe" 33 | 34 | gopointer "github.com/mattn/go-pointer" 35 | ) 36 | 37 | type PreKeyStore interface { 38 | LoadPreKey(id uint32, ctx context.Context) (*PreKeyRecord, error) 39 | StorePreKey(id uint32, preKeyRecord *PreKeyRecord, ctx context.Context) error 40 | RemovePreKey(id uint32, ctx context.Context) error 41 | } 42 | 43 | //export signal_load_pre_key_callback 44 | func signal_load_pre_key_callback(storeCtx unsafe.Pointer, keyp **C.SignalPreKeyRecord, id C.uint32_t, ctxPtr unsafe.Pointer) C.int { 45 | return wrapStoreCallback(storeCtx, ctxPtr, func(store PreKeyStore, ctx context.Context) error { 46 | key, err := store.LoadPreKey(uint32(id), ctx) 47 | if err == nil && key != nil { 48 | *keyp = key.ptr 49 | } 50 | return err 51 | }) 52 | } 53 | 54 | //export signal_store_pre_key_callback 55 | func signal_store_pre_key_callback(storeCtx unsafe.Pointer, id C.uint32_t, preKeyRecord *C.const_pre_key_record, ctxPtr unsafe.Pointer) C.int { 56 | return wrapStoreCallback(storeCtx, ctxPtr, func(store PreKeyStore, ctx context.Context) error { 57 | record := PreKeyRecord{ptr: (*C.SignalPreKeyRecord)(unsafe.Pointer(preKeyRecord))} 58 | cloned, err := record.Clone() 59 | if err != nil { 60 | return err 61 | } 62 | return store.StorePreKey(uint32(id), cloned, ctx) 63 | }) 64 | } 65 | 66 | //export signal_remove_pre_key_callback 67 | func signal_remove_pre_key_callback(storeCtx unsafe.Pointer, id C.uint32_t, ctxPtr unsafe.Pointer) C.int { 68 | return wrapStoreCallback(storeCtx, ctxPtr, func(store PreKeyStore, ctx context.Context) error { 69 | return store.RemovePreKey(uint32(id), ctx) 70 | }) 71 | } 72 | 73 | func wrapPreKeyStore(store PreKeyStore) *C.SignalPreKeyStore { 74 | // TODO: This is probably a memory leak 75 | return &C.SignalPreKeyStore{ 76 | ctx: gopointer.Save(store), 77 | load_pre_key: C.SignalLoadPreKey(C.signal_load_pre_key_callback), 78 | store_pre_key: C.SignalStorePreKey(C.signal_store_pre_key_callback), 79 | remove_pre_key: C.SignalRemovePreKey(C.signal_remove_pre_key_callback), 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /pkg/libsignalgo/fingerprint.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import "runtime" 25 | 26 | type FingerprintVersion uint32 27 | 28 | const ( 29 | FingerprintVersionV1 FingerprintVersion = 1 30 | FingerprintVersionV2 FingerprintVersion = 2 31 | ) 32 | 33 | type Fingerprint struct { 34 | ptr *C.SignalFingerprint 35 | } 36 | 37 | func wrapFingerprint(ptr *C.SignalFingerprint) *Fingerprint { 38 | fingerprint := &Fingerprint{ptr: ptr} 39 | runtime.SetFinalizer(fingerprint, (*Fingerprint).Destroy) 40 | return fingerprint 41 | } 42 | 43 | func NewFingerprint(iterations, version FingerprintVersion, localIdentifier []byte, localKey *PublicKey, remoteIdentifier []byte, remoteKey *PublicKey) (*Fingerprint, error) { 44 | var pa *C.SignalFingerprint 45 | signalFfiError := C.signal_fingerprint_new(&pa, C.uint32_t(iterations), C.uint32_t(version), BytesToBuffer(localIdentifier), localKey.ptr, BytesToBuffer(remoteIdentifier), remoteKey.ptr) 46 | if signalFfiError != nil { 47 | return nil, wrapError(signalFfiError) 48 | } 49 | return wrapFingerprint(pa), nil 50 | } 51 | 52 | func (f *Fingerprint) Clone() (*Fingerprint, error) { 53 | var cloned *C.SignalFingerprint 54 | signalFfiError := C.signal_fingerprint_clone(&cloned, f.ptr) 55 | if signalFfiError != nil { 56 | return nil, wrapError(signalFfiError) 57 | } 58 | return wrapFingerprint(cloned), nil 59 | } 60 | 61 | func (f *Fingerprint) Destroy() error { 62 | runtime.SetFinalizer(f, nil) 63 | return wrapError(C.signal_fingerprint_destroy(f.ptr)) 64 | } 65 | 66 | func (f *Fingerprint) ScannableEncoding() ([]byte, error) { 67 | var scannableEncoding C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 68 | signalFfiError := C.signal_fingerprint_scannable_encoding(&scannableEncoding, f.ptr) 69 | if signalFfiError != nil { 70 | return nil, wrapError(signalFfiError) 71 | } 72 | return CopySignalOwnedBufferToBytes(scannableEncoding), nil 73 | } 74 | 75 | func (f *Fingerprint) DisplayString() (string, error) { 76 | var displayString *C.char 77 | signalFfiError := C.signal_fingerprint_display_string(&displayString, f.ptr) 78 | if signalFfiError != nil { 79 | return "", wrapError(signalFfiError) 80 | } 81 | return CopyCStringToString(displayString), nil 82 | } 83 | 84 | func (k *Fingerprint) Compare(fingerprint1, fingerprint2 []byte) (bool, error) { 85 | var compare C.bool 86 | signalFfiError := C.signal_fingerprint_compare(&compare, BytesToBuffer(fingerprint1), BytesToBuffer(fingerprint2)) 87 | if signalFfiError != nil { 88 | return false, wrapError(signalFfiError) 89 | } 90 | return bool(compare), nil 91 | } 92 | -------------------------------------------------------------------------------- /pkg/signalmeow/sender_key_store.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Scott Weber 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package signalmeow 18 | 19 | import ( 20 | "context" 21 | "database/sql" 22 | "errors" 23 | "fmt" 24 | 25 | "github.com/google/uuid" 26 | 27 | "go.mau.fi/mautrix-signal/pkg/libsignalgo" 28 | ) 29 | 30 | var _ libsignalgo.SenderKeyStore = (*SQLStore)(nil) 31 | 32 | const ( 33 | loadSenderKeyQuery = `SELECT key_record FROM signalmeow_sender_keys WHERE our_aci_uuid=$1 AND sender_uuid=$2 AND sender_device_id=$3 AND distribution_id=$4` 34 | storeSenderKeyQuery = `INSERT INTO signalmeow_sender_keys (our_aci_uuid, sender_uuid, sender_device_id, distribution_id, key_record) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (our_aci_uuid, sender_uuid, sender_device_id, distribution_id) DO UPDATE SET key_record=excluded.key_record` 35 | ) 36 | 37 | func scanSenderKey(row scannable) (*libsignalgo.SenderKeyRecord, error) { 38 | var key []byte 39 | err := row.Scan(&key) 40 | if errors.Is(err, sql.ErrNoRows) { 41 | return nil, nil 42 | } else if err != nil { 43 | return nil, err 44 | } 45 | return libsignalgo.DeserializeSenderKeyRecord(key) 46 | } 47 | 48 | func (s *SQLStore) LoadSenderKey(sender libsignalgo.Address, distributionID uuid.UUID, ctx context.Context) (*libsignalgo.SenderKeyRecord, error) { 49 | distributionIdString := distributionID.String() 50 | if distributionIdString == "" { 51 | return nil, errors.New(fmt.Sprintf("distributionID did not parse: %v", distributionID)) 52 | } 53 | senderUuid, err := sender.Name() 54 | if err != nil { 55 | return nil, err 56 | } 57 | deviceId, err := sender.DeviceID() 58 | if err != nil { 59 | return nil, err 60 | } 61 | return scanSenderKey(s.db.QueryRow(loadSenderKeyQuery, s.AciUuid, senderUuid, deviceId, distributionIdString)) 62 | } 63 | 64 | func (s *SQLStore) StoreSenderKey(sender libsignalgo.Address, distributionID uuid.UUID, record *libsignalgo.SenderKeyRecord, ctx context.Context) error { 65 | distributionIdString := distributionID.String() 66 | if distributionIdString == "" { 67 | return errors.New(fmt.Sprintf("distributionID did not parse: %v", distributionID)) 68 | } 69 | senderUuid, err := sender.Name() 70 | if err != nil { 71 | return err 72 | } 73 | deviceId, err := sender.DeviceID() 74 | if err != nil { 75 | return err 76 | } 77 | serialized, err := record.Serialize() 78 | if err != nil { 79 | return err 80 | } 81 | tx, err := s.db.BeginTx(ctx, nil) 82 | if err != nil { 83 | tx.Rollback() 84 | return err 85 | } 86 | _, err = tx.Exec(storeSenderKeyQuery, s.AciUuid, senderUuid, deviceId, distributionIdString, serialized) 87 | if err != nil { 88 | _ = tx.Rollback() 89 | return err 90 | } 91 | err = tx.Commit() 92 | return err 93 | } 94 | -------------------------------------------------------------------------------- /pkg/signalmeow/device.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Scott Weber 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package signalmeow 18 | 19 | import ( 20 | "context" 21 | "errors" 22 | "fmt" 23 | "net/url" 24 | "sync" 25 | 26 | "go.mau.fi/mautrix-signal/pkg/libsignalgo" 27 | "go.mau.fi/mautrix-signal/pkg/signalmeow/web" 28 | ) 29 | 30 | // Note: right now, the parent `Device` struct is in store.go 31 | type DeviceData struct { 32 | AciIdentityKeyPair *libsignalgo.IdentityKeyPair 33 | PniIdentityKeyPair *libsignalgo.IdentityKeyPair 34 | RegistrationId int 35 | PniRegistrationId int 36 | AciUuid string 37 | PniUuid string 38 | DeviceId int 39 | Number string 40 | Password string 41 | } 42 | 43 | func (d *DeviceData) BasicAuthCreds() (string, string) { 44 | username := fmt.Sprintf("%s.%d", d.AciUuid, d.DeviceId) 45 | password := d.Password 46 | return username, password 47 | } 48 | 49 | // DeviceConnection exists on a Device, and holds websockets, cached credentials, 50 | // and other data that is used to communicate with the Signal servers and other clients. 51 | type DeviceConnection struct { 52 | // cached data (not persisted) 53 | SenderCertificate *libsignalgo.SenderCertificate 54 | GroupCredentials *GroupCredentials 55 | GroupCache *GroupCache 56 | ProfileCache *ProfileCache 57 | GroupCallCache *map[string]bool 58 | LastContactRequestTime *int64 59 | 60 | // mutexes 61 | EncryptionMutex sync.Mutex 62 | 63 | // Network interfaces 64 | AuthedWS *web.SignalWebsocket 65 | UnauthedWS *web.SignalWebsocket 66 | WSCancel context.CancelFunc 67 | 68 | IncomingSignalMessageHandler func(IncomingSignalMessage) error 69 | } 70 | 71 | func (d *DeviceConnection) ConnectAuthedWS(ctx context.Context, data DeviceData, requestHandler web.RequestHandlerFunc) (chan web.SignalWebsocketConnectionStatus, error) { 72 | if d.AuthedWS != nil { 73 | return nil, errors.New("authed websocket already connected") 74 | } 75 | username, password := data.BasicAuthCreds() 76 | username = url.QueryEscape(username) 77 | password = url.QueryEscape(password) 78 | path := web.WebsocketPath + 79 | "?login=" + username + 80 | "&password=" + password 81 | authedWS := web.NewSignalWebsocket(ctx, "authed", path, &username, &password) 82 | statusChan := authedWS.Connect(ctx, &requestHandler) 83 | d.AuthedWS = authedWS 84 | return statusChan, nil 85 | } 86 | 87 | func (d *DeviceConnection) ConnectUnauthedWS(ctx context.Context, data DeviceData) (chan web.SignalWebsocketConnectionStatus, error) { 88 | if d.UnauthedWS != nil { 89 | return nil, errors.New("unauthed websocket already connected") 90 | } 91 | unauthedWS := web.NewSignalWebsocket(ctx, "unauthed", web.WebsocketPath, nil, nil) 92 | statusChan := unauthedWS.Connect(ctx, nil) 93 | d.UnauthedWS = unauthedWS 94 | 95 | return statusChan, nil 96 | } 97 | -------------------------------------------------------------------------------- /msgconv/signalfmt/convert.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-Signal puppeting bridge. 2 | // Copyright (C) 2023 Tulir Asokan 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package signalfmt 18 | 19 | import ( 20 | "html" 21 | "strings" 22 | 23 | "golang.org/x/exp/maps" 24 | "golang.org/x/exp/slices" 25 | "maunium.net/go/mautrix/event" 26 | "maunium.net/go/mautrix/id" 27 | 28 | signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf" 29 | ) 30 | 31 | type UserInfo struct { 32 | MXID id.UserID 33 | Name string 34 | } 35 | 36 | type FormatParams struct { 37 | GetUserInfo func(uuid string) UserInfo 38 | } 39 | 40 | type formatContext struct { 41 | IsInCodeblock bool 42 | } 43 | 44 | func (ctx formatContext) TextToHTML(text string) string { 45 | if ctx.IsInCodeblock { 46 | return html.EscapeString(text) 47 | } 48 | return event.TextToHTML(text) 49 | } 50 | 51 | func Parse(message string, ranges []*signalpb.BodyRange, params *FormatParams) *event.MessageEventContent { 52 | content := &event.MessageEventContent{ 53 | MsgType: event.MsgText, 54 | Body: message, 55 | Mentions: &event.Mentions{}, 56 | } 57 | if len(ranges) == 0 { 58 | return content 59 | } 60 | // LinkedRangeTree.Add depends on the ranges being sorted by increasing start index and then decreasing length. 61 | slices.SortFunc(ranges, func(a, b *signalpb.BodyRange) int { 62 | if *a.Start == *b.Start { 63 | if *a.Length == *b.Length { 64 | return 0 65 | } else if *a.Length < *b.Length { 66 | return 1 67 | } else { 68 | return -1 69 | } 70 | } else if *a.Start < *b.Start { 71 | return -1 72 | } else { 73 | return 1 74 | } 75 | }) 76 | 77 | lrt := &LinkedRangeTree{} 78 | mentions := map[id.UserID]struct{}{} 79 | utf16Message := NewUTF16String(message) 80 | maxLength := len(utf16Message) 81 | for _, r := range ranges { 82 | br := BodyRange{ 83 | Start: int(*r.Start), 84 | Length: int(*r.Length), 85 | }.TruncateEnd(maxLength) 86 | switch rv := r.GetAssociatedValue().(type) { 87 | case *signalpb.BodyRange_Style_: 88 | br.Value = Style(rv.Style) 89 | case *signalpb.BodyRange_MentionUuid: 90 | userInfo := params.GetUserInfo(rv.MentionUuid) 91 | if userInfo.MXID == "" { 92 | continue 93 | } 94 | mentions[userInfo.MXID] = struct{}{} 95 | // This could replace the wrong thing if there's a mention without fffc. 96 | // Maybe use NewUTF16String and do index replacements for the plaintext body too, 97 | // or just replace the plaintext body by parsing the generated HTML. 98 | content.Body = strings.Replace(content.Body, "\uFFFC", userInfo.Name, 1) 99 | br.Value = Mention(userInfo) 100 | } 101 | lrt.Add(br) 102 | } 103 | 104 | content.Mentions.UserIDs = maps.Keys(mentions) 105 | content.FormattedBody = lrt.Format(utf16Message, formatContext{}) 106 | content.Format = event.FormatHTML 107 | //content.Body = format.HTMLToText(content.FormattedBody) 108 | return content 109 | } 110 | -------------------------------------------------------------------------------- /pkg/libsignalgo/kyberprekeystore.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Scott Weber 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | 23 | typedef const SignalKyberPreKeyRecord const_kyber_pre_key_record; 24 | 25 | extern int signal_load_kyber_pre_key_callback(void *store_ctx, SignalKyberPreKeyRecord **recordp, uint32_t id, void *ctx); 26 | extern int signal_store_kyber_pre_key_callback(void *store_ctx, uint32_t id, const_kyber_pre_key_record *record, void *ctx); 27 | extern int signal_mark_kyber_pre_key_used_callback(void *store_ctx, uint32_t id, void *ctx); 28 | */ 29 | import "C" 30 | import ( 31 | "context" 32 | "unsafe" 33 | 34 | gopointer "github.com/mattn/go-pointer" 35 | ) 36 | 37 | type KyberPreKeyStore interface { 38 | LoadKyberPreKey(id uint32, context context.Context) (*KyberPreKeyRecord, error) 39 | StoreKyberPreKey(id uint32, kyberPreKeyRecord *KyberPreKeyRecord, context context.Context) error 40 | MarkKyberPreKeyUsed(id uint32, context context.Context) error 41 | } 42 | 43 | //export signal_load_kyber_pre_key_callback 44 | func signal_load_kyber_pre_key_callback(storeCtx unsafe.Pointer, keyp **C.SignalKyberPreKeyRecord, id C.uint32_t, ctxPtr unsafe.Pointer) C.int { 45 | return wrapStoreCallback(storeCtx, ctxPtr, func(store KyberPreKeyStore, ctx context.Context) error { 46 | key, err := store.LoadKyberPreKey(uint32(id), ctx) 47 | if err == nil && key != nil { 48 | *keyp = key.ptr 49 | } 50 | return err 51 | }) 52 | } 53 | 54 | //export signal_store_kyber_pre_key_callback 55 | func signal_store_kyber_pre_key_callback(storeCtx unsafe.Pointer, id C.uint32_t, preKeyRecord *C.const_kyber_pre_key_record, ctxPtr unsafe.Pointer) C.int { 56 | return wrapStoreCallback(storeCtx, ctxPtr, func(store KyberPreKeyStore, ctx context.Context) error { 57 | record := KyberPreKeyRecord{ptr: (*C.SignalKyberPreKeyRecord)(unsafe.Pointer(preKeyRecord))} 58 | cloned, err := record.Clone() 59 | if err != nil { 60 | return err 61 | } 62 | return store.StoreKyberPreKey(uint32(id), cloned, ctx) 63 | }) 64 | } 65 | 66 | //export signal_mark_kyber_pre_key_used_callback 67 | func signal_mark_kyber_pre_key_used_callback(storeCtx unsafe.Pointer, id C.uint32_t, ctxPtr unsafe.Pointer) C.int { 68 | return wrapStoreCallback(storeCtx, ctxPtr, func(store KyberPreKeyStore, ctx context.Context) error { 69 | err := store.MarkKyberPreKeyUsed(uint32(id), ctx) 70 | return err 71 | }) 72 | } 73 | 74 | func wrapKyberPreKeyStore(store KyberPreKeyStore) *C.SignalKyberPreKeyStore { 75 | // TODO: This is probably a memory leak 76 | return &C.SignalKyberPreKeyStore{ 77 | ctx: gopointer.Save(store), 78 | load_kyber_pre_key: C.SignalLoadKyberPreKey(C.signal_load_kyber_pre_key_callback), 79 | store_kyber_pre_key: C.SignalStoreKyberPreKey(C.signal_store_kyber_pre_key_callback), 80 | mark_kyber_pre_key_used: C.SignalMarkKyberPreKeyUsed(C.signal_mark_kyber_pre_key_used_callback), 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /pkg/libsignalgo/hsmenclave_test.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "go.mau.fi/mautrix-signal/pkg/libsignalgo" 25 | ) 26 | 27 | var nullHash = []byte{ 28 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 29 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | } 31 | 32 | func getKeyBytes(t *testing.T) []byte { 33 | validKey, err := libsignalgo.GenerateIdentityKeyPair() 34 | assert.NoError(t, err) 35 | keyBytes, err := validKey.GetPublicKey().Bytes() 36 | assert.NoError(t, err) 37 | return keyBytes 38 | } 39 | 40 | // From HsmEnclaveTests.swift:testCreateClient 41 | // From HsmEnclaveTests.swift:testCreateClientFailsWithNoHashes 42 | func TestCreateHSMClient(t *testing.T) { 43 | setupLogging() 44 | hashes := []byte{ 45 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 46 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 47 | // 48 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 49 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 50 | } 51 | t.Run("Succeeds with hashes", func(t *testing.T) { 52 | client, err := libsignalgo.NewHSMEnclaveClient(getKeyBytes(t), hashes) 53 | assert.NoError(t, err) 54 | 55 | initialMessage, err := client.InitialRequest() 56 | assert.NoError(t, err) 57 | assert.Len(t, initialMessage, 112) 58 | }) 59 | 60 | t.Run("Fails with no hashes", func(t *testing.T) { 61 | _, err := libsignalgo.NewHSMEnclaveClient(getKeyBytes(t), []byte{}) 62 | assert.Error(t, err) 63 | }) 64 | } 65 | 66 | // From HsmEnclaveTests.swift:testCompleteHandshakeWithoutInitialRequest 67 | func TestHSMCompleteHandshakeWithoutInitialRequest(t *testing.T) { 68 | setupLogging() 69 | client, err := libsignalgo.NewHSMEnclaveClient(getKeyBytes(t), nullHash) 70 | assert.NoError(t, err) 71 | err = client.CompleteHandshake([]byte{0x01, 0x02, 0x03}) 72 | assert.Error(t, err) 73 | } 74 | 75 | // From HsmEnclaveTests.swift:testEstablishedSendFailsPriorToEstablishment 76 | func TestHSMEstablishedSendFailsPriorToEstablishment(t *testing.T) { 77 | setupLogging() 78 | client, err := libsignalgo.NewHSMEnclaveClient(getKeyBytes(t), nullHash) 79 | assert.NoError(t, err) 80 | _, err = client.EstablishedSend([]byte{0x01, 0x02, 0x03}) 81 | assert.Error(t, err) 82 | } 83 | 84 | // From HsmEnclaveTests.swift:testEstablishedRecvFailsPriorToEstablishment 85 | func TestHSMEstablishedReceiveFailsPriorToEstablishment(t *testing.T) { 86 | setupLogging() 87 | client, err := libsignalgo.NewHSMEnclaveClient(getKeyBytes(t), nullHash) 88 | assert.NoError(t, err) 89 | _, err = client.EstablishedReceive([]byte{0x01, 0x02, 0x03}) 90 | assert.Error(t, err) 91 | } 92 | -------------------------------------------------------------------------------- /pkg/libsignalgo/senderkeydistributionmessage.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import ( 25 | "runtime" 26 | "unsafe" 27 | 28 | "github.com/google/uuid" 29 | ) 30 | 31 | func ProcessSenderKeyDistributionMessage(message *SenderKeyDistributionMessage, fromSender *Address, store SenderKeyStore, ctx *CallbackContext) error { 32 | signalFfiError := C.signal_process_sender_key_distribution_message( 33 | fromSender.ptr, 34 | message.ptr, 35 | wrapSenderKeyStore(store), 36 | ) 37 | return wrapCallbackError(signalFfiError, ctx) 38 | } 39 | 40 | type SenderKeyDistributionMessage struct { 41 | ptr *C.SignalSenderKeyDistributionMessage 42 | } 43 | 44 | func wrapSenderKeyDistributionMessage(ptr *C.SignalSenderKeyDistributionMessage) *SenderKeyDistributionMessage { 45 | sc := &SenderKeyDistributionMessage{ptr: ptr} 46 | runtime.SetFinalizer(sc, (*SenderKeyDistributionMessage).Destroy) 47 | return sc 48 | } 49 | 50 | func NewSenderKeyDistributionMessage(sender *Address, distributionID uuid.UUID, store SenderKeyStore, ctx *CallbackContext) (*SenderKeyDistributionMessage, error) { 51 | var skdm *C.SignalSenderKeyDistributionMessage 52 | signalFfiError := C.signal_sender_key_distribution_message_create( 53 | &skdm, 54 | sender.ptr, 55 | (*[C.SignalUUID_LEN]C.uchar)(unsafe.Pointer(&distributionID)), 56 | wrapSenderKeyStore(store), 57 | ) 58 | if signalFfiError != nil { 59 | return nil, wrapCallbackError(signalFfiError, ctx) 60 | } 61 | return wrapSenderKeyDistributionMessage(skdm), nil 62 | } 63 | 64 | func DeserializeSenderKeyDistributionMessage(serialized []byte) (*SenderKeyDistributionMessage, error) { 65 | var skdm *C.SignalSenderKeyDistributionMessage 66 | signalFfiError := C.signal_sender_key_distribution_message_deserialize(&skdm, BytesToBuffer(serialized)) 67 | if signalFfiError != nil { 68 | return nil, wrapError(signalFfiError) 69 | } 70 | return wrapSenderKeyDistributionMessage(skdm), nil 71 | } 72 | 73 | func (sc *SenderKeyDistributionMessage) Destroy() error { 74 | runtime.SetFinalizer(sc, nil) 75 | return wrapError(C.signal_sender_key_distribution_message_destroy(sc.ptr)) 76 | } 77 | 78 | func (sc *SenderKeyDistributionMessage) Serialize() ([]byte, error) { 79 | var serialized C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 80 | signalFfiError := C.signal_sender_key_distribution_message_serialize(&serialized, sc.ptr) 81 | if signalFfiError != nil { 82 | return nil, wrapError(signalFfiError) 83 | } 84 | return CopySignalOwnedBufferToBytes(serialized), nil 85 | } 86 | 87 | func (sc *SenderKeyDistributionMessage) Process(sender *Address, store SenderKeyStore, ctx *CallbackContext) error { 88 | signalFfiError := C.signal_process_sender_key_distribution_message( 89 | sender.ptr, 90 | sc.ptr, 91 | wrapSenderKeyStore(store), 92 | ) 93 | if signalFfiError != nil { 94 | return wrapCallbackError(signalFfiError, ctx) 95 | } 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /pkg/libsignalgo/senderkeystore.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | 23 | typedef const SignalProtocolAddress const_address; 24 | 25 | typedef const SignalSenderKeyRecord const_sender_key_record; 26 | typedef const uint8_t const_uuid_bytes[16]; 27 | 28 | extern int signal_load_sender_key_callback(void *store_ctx, SignalSenderKeyRecord**, const_address*, const_uuid_bytes*, void *ctx); 29 | extern int signal_store_sender_key_callback(void *store_ctx, const_address*, const_uuid_bytes*, const_sender_key_record*, void *ctx); 30 | */ 31 | import "C" 32 | import ( 33 | "context" 34 | "unsafe" 35 | 36 | "github.com/google/uuid" 37 | gopointer "github.com/mattn/go-pointer" 38 | ) 39 | 40 | type SenderKeyStore interface { 41 | LoadSenderKey(sender Address, distributionID uuid.UUID, ctx context.Context) (*SenderKeyRecord, error) 42 | StoreSenderKey(sender Address, distributionID uuid.UUID, record *SenderKeyRecord, ctx context.Context) error 43 | } 44 | 45 | func wrapSenderKeyStore(store SenderKeyStore) *C.SignalSenderKeyStore { 46 | // TODO this is probably a memory leak since I'm never getting rid of the 47 | // saved pointer. 48 | return &C.SignalSenderKeyStore{ 49 | ctx: gopointer.Save(store), 50 | load_sender_key: C.SignalLoadSenderKey(C.signal_load_sender_key_callback), 51 | store_sender_key: C.SignalStoreSenderKey(C.signal_store_sender_key_callback), 52 | } 53 | } 54 | 55 | //export signal_load_sender_key_callback 56 | func signal_load_sender_key_callback(storeCtx unsafe.Pointer, recordp **C.SignalSenderKeyRecord, address *C.const_address, distributionIDBytes *C.const_uuid_bytes, ctxPtr unsafe.Pointer) C.int { 57 | return wrapStoreCallback(storeCtx, ctxPtr, func(store SenderKeyStore, ctx context.Context) error { 58 | distributionID := uuid.UUID(*(*[16]byte)(unsafe.Pointer(distributionIDBytes))) 59 | record, err := store.LoadSenderKey( 60 | Address{ptr: (*C.SignalProtocolAddress)(unsafe.Pointer(address))}, 61 | distributionID, 62 | ctx, 63 | ) 64 | if err == nil && record != nil { 65 | *recordp = record.ptr 66 | } 67 | return err 68 | }) 69 | } 70 | 71 | //export signal_store_sender_key_callback 72 | func signal_store_sender_key_callback(storeCtx unsafe.Pointer, address *C.const_address, distributionIDBytes *C.const_uuid_bytes, senderKeyRecord *C.const_sender_key_record, ctxPtr unsafe.Pointer) C.int { 73 | return wrapStoreCallback(storeCtx, ctxPtr, func(store SenderKeyStore, ctx context.Context) error { 74 | distributionID := uuid.UUID(*(*[16]byte)(unsafe.Pointer(distributionIDBytes))) 75 | record := SenderKeyRecord{ptr: (*C.SignalSenderKeyRecord)(unsafe.Pointer(senderKeyRecord))} 76 | cloned, err := record.Clone() 77 | if err != nil { 78 | return err 79 | } 80 | 81 | return store.StoreSenderKey( 82 | Address{ptr: (*C.SignalProtocolAddress)(unsafe.Pointer(address))}, 83 | distributionID, 84 | cloned, 85 | ctx, 86 | ) 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /pkg/libsignalgo/privatekey.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import "runtime" 25 | 26 | type PrivateKey struct { 27 | ptr *C.SignalPrivateKey 28 | } 29 | 30 | func wrapPrivateKey(ptr *C.SignalPrivateKey) *PrivateKey { 31 | privateKey := &PrivateKey{ptr: ptr} 32 | runtime.SetFinalizer(privateKey, (*PrivateKey).Destroy) 33 | return privateKey 34 | } 35 | 36 | func GeneratePrivateKey() (*PrivateKey, error) { 37 | var pk *C.SignalPrivateKey 38 | signalFfiError := C.signal_privatekey_generate(&pk) 39 | if signalFfiError != nil { 40 | return nil, wrapError(signalFfiError) 41 | } 42 | return wrapPrivateKey(pk), nil 43 | } 44 | 45 | func DeserializePrivateKey(keyData []byte) (*PrivateKey, error) { 46 | var pk *C.SignalPrivateKey 47 | signalFfiError := C.signal_privatekey_deserialize(&pk, BytesToBuffer(keyData)) 48 | if signalFfiError != nil { 49 | return nil, wrapError(signalFfiError) 50 | } 51 | return wrapPrivateKey(pk), nil 52 | } 53 | 54 | func (pk *PrivateKey) Clone() (*PrivateKey, error) { 55 | var cloned *C.SignalPrivateKey 56 | signalFfiError := C.signal_privatekey_clone(&cloned, pk.ptr) 57 | if signalFfiError != nil { 58 | return nil, wrapError(signalFfiError) 59 | } 60 | return wrapPrivateKey(cloned), nil 61 | } 62 | 63 | func (pk *PrivateKey) Destroy() error { 64 | return nil // TODO fix this 65 | //runtime.SetFinalizer(pk, nil) 66 | //return wrapError(C.signal_privatekey_destroy(pk.ptr)) 67 | } 68 | 69 | func (pk *PrivateKey) GetPublicKey() (*PublicKey, error) { 70 | var pub *C.SignalPublicKey 71 | signalFfiError := C.signal_privatekey_get_public_key(&pub, pk.ptr) 72 | if signalFfiError != nil { 73 | return nil, wrapError(signalFfiError) 74 | } 75 | return wrapPublicKey(pub), nil 76 | } 77 | 78 | func (pk *PrivateKey) Serialize() ([]byte, error) { 79 | var serialized C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 80 | signalFfiError := C.signal_privatekey_serialize(&serialized, pk.ptr) 81 | if signalFfiError != nil { 82 | return nil, wrapError(signalFfiError) 83 | } 84 | return CopySignalOwnedBufferToBytes(serialized), nil 85 | } 86 | 87 | func (pk *PrivateKey) Sign(message []byte) ([]byte, error) { 88 | var signed C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 89 | signalFfiError := C.signal_privatekey_sign(&signed, pk.ptr, BytesToBuffer(message)) 90 | if signalFfiError != nil { 91 | return nil, wrapError(signalFfiError) 92 | } 93 | return CopySignalOwnedBufferToBytes(signed), nil 94 | } 95 | 96 | func (pk *PrivateKey) Agree(publicKey *PublicKey) ([]byte, error) { 97 | var agreed C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 98 | signalFfiError := C.signal_privatekey_agree(&agreed, pk.ptr, publicKey.ptr) 99 | if signalFfiError != nil { 100 | return nil, wrapError(signalFfiError) 101 | } 102 | return CopySignalOwnedBufferToBytes(agreed), nil 103 | } 104 | -------------------------------------------------------------------------------- /pkg/libsignalgo/prekeybundle.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import ( 25 | "runtime" 26 | "time" 27 | ) 28 | 29 | func ProcessPreKeyBundle(bundle *PreKeyBundle, forAddress *Address, sessionStore SessionStore, identityStore IdentityKeyStore, ctx *CallbackContext) error { 30 | var now C.uint64_t = C.uint64_t(time.Now().Unix()) 31 | signalFfiError := C.signal_process_prekey_bundle( 32 | bundle.ptr, 33 | forAddress.ptr, 34 | wrapSessionStore(sessionStore), 35 | wrapIdentityKeyStore(identityStore), 36 | now, 37 | ) 38 | return wrapCallbackError(signalFfiError, ctx) 39 | } 40 | 41 | type PreKeyBundle struct { 42 | ptr *C.SignalPreKeyBundle 43 | } 44 | 45 | func wrapPreKeyBundle(ptr *C.SignalPreKeyBundle) *PreKeyBundle { 46 | bundle := &PreKeyBundle{ptr: ptr} 47 | runtime.SetFinalizer(bundle, (*PreKeyBundle).Destroy) 48 | return bundle 49 | } 50 | 51 | func NewPreKeyBundle( 52 | registrationID uint32, 53 | deviceID uint32, 54 | preKeyID uint32, 55 | preKey *PublicKey, 56 | signedPreKeyID uint32, 57 | signedPreKey *PublicKey, 58 | signedPreKeySignature []byte, 59 | kyberPreKeyID uint32, 60 | kyberPreKey *KyberPublicKey, 61 | kyberPreKeySignature []byte, 62 | identityKey *IdentityKey, 63 | ) (*PreKeyBundle, error) { 64 | var pkb *C.SignalPreKeyBundle 65 | var zero uint32 = 0 66 | var kyberSignatureBuffer = EmptyBorrowedBuffer() 67 | if preKey == nil { 68 | preKey = &PublicKey{ptr: nil} 69 | preKeyID = ^zero 70 | } 71 | if kyberPreKey == nil { 72 | kyberPreKey = &KyberPublicKey{ptr: nil} 73 | kyberPreKeyID = ^zero 74 | } else { 75 | kyberSignatureBuffer = BytesToBuffer(kyberPreKeySignature) 76 | } 77 | signalFfiError := C.signal_pre_key_bundle_new( 78 | &pkb, 79 | C.uint32_t(registrationID), 80 | C.uint32_t(deviceID), 81 | C.uint32_t(preKeyID), 82 | preKey.ptr, 83 | C.uint32_t(signedPreKeyID), 84 | signedPreKey.ptr, 85 | BytesToBuffer(signedPreKeySignature), 86 | identityKey.publicKey.ptr, 87 | C.uint32_t(kyberPreKeyID), 88 | kyberPreKey.ptr, 89 | kyberSignatureBuffer, 90 | ) 91 | if signalFfiError != nil { 92 | return nil, wrapError(signalFfiError) 93 | } 94 | return wrapPreKeyBundle(pkb), nil 95 | } 96 | 97 | func (pkb *PreKeyBundle) Clone() (*PreKeyBundle, error) { 98 | var cloned *C.SignalPreKeyBundle 99 | signalFfiError := C.signal_pre_key_bundle_clone(&cloned, pkb.ptr) 100 | if signalFfiError != nil { 101 | return nil, wrapError(signalFfiError) 102 | } 103 | return wrapPreKeyBundle(cloned), nil 104 | } 105 | 106 | func (pkb *PreKeyBundle) Destroy() error { 107 | runtime.SetFinalizer(pkb, nil) 108 | return wrapError(C.signal_pre_key_bundle_destroy(pkb.ptr)) 109 | } 110 | 111 | func (pkb *PreKeyBundle) GetIdentityKey() (*IdentityKey, error) { 112 | var pk *C.SignalPublicKey 113 | signalFfiError := C.signal_pre_key_bundle_get_identity_key(&pk, pkb.ptr) 114 | if signalFfiError != nil { 115 | return nil, wrapError(signalFfiError) 116 | } 117 | return NewIdentityKeyFromPublicKey(wrapPublicKey(pk)) 118 | } 119 | -------------------------------------------------------------------------------- /pkg/libsignalgo/error.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import ( 25 | "fmt" 26 | ) 27 | 28 | type ErrorCode int 29 | 30 | const ( 31 | ErrorCodeUnknownError ErrorCode = 1 32 | ErrorCodeInvalidState ErrorCode = 2 33 | ErrorCodeInternalError ErrorCode = 3 34 | ErrorCodeNullParameter ErrorCode = 4 35 | ErrorCodeInvalidArgument ErrorCode = 5 36 | ErrorCodeInvalidType ErrorCode = 6 37 | ErrorCodeInvalidUtf8String ErrorCode = 7 38 | ErrorCodeProtobufError ErrorCode = 10 39 | ErrorCodeLegacyCiphertextVersion ErrorCode = 21 40 | ErrorCodeUnknownCiphertextVersion ErrorCode = 22 41 | ErrorCodeUnrecognizedMessageVersion ErrorCode = 23 42 | ErrorCodeInvalidMessage ErrorCode = 30 43 | ErrorCodeSealedSenderSelfSend ErrorCode = 31 44 | ErrorCodeInvalidKey ErrorCode = 40 45 | ErrorCodeInvalidSignature ErrorCode = 41 46 | ErrorCodeInvalidAttestationData ErrorCode = 42 47 | ErrorCodeFingerprintVersionMismatch ErrorCode = 51 48 | ErrorCodeFingerprintParsingError ErrorCode = 52 49 | ErrorCodeUntrustedIdentity ErrorCode = 60 50 | ErrorCodeInvalidKeyIdentifier ErrorCode = 70 51 | ErrorCodeSessionNotFound ErrorCode = 80 52 | ErrorCodeInvalidRegistrationId ErrorCode = 81 53 | ErrorCodeInvalidSession ErrorCode = 82 54 | ErrorCodeInvalidSenderKeySession ErrorCode = 83 55 | ErrorCodeDuplicatedMessage ErrorCode = 90 56 | ErrorCodeCallbackError ErrorCode = 100 57 | ErrorCodeVerificationFailure ErrorCode = 110 58 | ) 59 | 60 | type SignalError struct { 61 | Code ErrorCode 62 | Message string 63 | } 64 | 65 | func (e *SignalError) Error() string { 66 | return fmt.Sprintf("%d: %s", e.Code, e.Message) 67 | } 68 | 69 | func wrapCallbackError(signalError *C.SignalFfiError, ctx *CallbackContext) error { 70 | if signalError == nil { 71 | return nil 72 | } 73 | 74 | defer C.signal_error_free(signalError) 75 | 76 | errorType := C.signal_error_get_type(signalError) 77 | if ErrorCode(errorType) == ErrorCodeCallbackError { 78 | return ctx.Error 79 | } else { 80 | return wrapSignalError(signalError, errorType) 81 | } 82 | } 83 | 84 | func wrapError(signalError *C.SignalFfiError) error { 85 | if signalError == nil { 86 | return nil 87 | } 88 | 89 | defer C.signal_error_free(signalError) 90 | 91 | return wrapSignalError(signalError, C.signal_error_get_type(signalError)) 92 | } 93 | 94 | func wrapSignalError(signalError *C.SignalFfiError, errorType C.uint32_t) error { 95 | var messageBytes *C.char 96 | getMessageError := C.signal_error_get_message(signalError, &messageBytes) 97 | if getMessageError != nil { 98 | // Ignore any errors from this, it will just end up being an empty 99 | // string. 100 | C.signal_error_free(getMessageError) 101 | } 102 | return &SignalError{Code: ErrorCode(errorType), Message: CopyCStringToString(messageBytes)} 103 | } 104 | -------------------------------------------------------------------------------- /pkg/libsignalgo/sessionrecord.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import ( 25 | "runtime" 26 | "time" 27 | ) 28 | 29 | type SessionRecord struct { 30 | ptr *C.SignalSessionRecord 31 | } 32 | 33 | func wrapSessionRecord(ptr *C.SignalSessionRecord) *SessionRecord { 34 | sessionRecord := &SessionRecord{ptr: ptr} 35 | runtime.SetFinalizer(sessionRecord, (*SessionRecord).Destroy) 36 | return sessionRecord 37 | } 38 | 39 | func DeserializeSessionRecord(serialized []byte) (*SessionRecord, error) { 40 | var ptr *C.SignalSessionRecord 41 | signalFfiError := C.signal_session_record_deserialize(&ptr, BytesToBuffer(serialized)) 42 | if signalFfiError != nil { 43 | return nil, wrapError(signalFfiError) 44 | } 45 | return wrapSessionRecord(ptr), nil 46 | } 47 | 48 | func (sr *SessionRecord) Clone() (*SessionRecord, error) { 49 | var clone *C.SignalSessionRecord 50 | signalFfiError := C.signal_session_record_clone(&clone, sr.ptr) 51 | if signalFfiError != nil { 52 | return nil, wrapError(signalFfiError) 53 | } 54 | return wrapSessionRecord(clone), nil 55 | } 56 | 57 | func (sr *SessionRecord) Destroy() error { 58 | return nil //TODO: fix double free 59 | //runtime.SetFinalizer(sr, nil) 60 | //return wrapError(C.signal_session_record_destroy(sr.ptr)) 61 | } 62 | 63 | func (sr *SessionRecord) ArchiveCurrentState() error { 64 | return wrapError(C.signal_session_record_archive_current_state(sr.ptr)) 65 | } 66 | 67 | func (sr *SessionRecord) CurrentRatchetKeyMatches(key *PublicKey) (bool, error) { 68 | var result C.bool 69 | signalFfiError := C.signal_session_record_current_ratchet_key_matches(&result, sr.ptr, key.ptr) 70 | if signalFfiError != nil { 71 | return false, wrapError(signalFfiError) 72 | } 73 | return bool(result), nil 74 | } 75 | 76 | func (sr *SessionRecord) HasCurrentState() (bool, error) { 77 | var result C.bool 78 | signalFfiError := C.signal_session_record_has_usable_sender_chain(&result, sr.ptr, C.uint64_t(time.Now().Unix())) 79 | if signalFfiError != nil { 80 | return false, wrapError(signalFfiError) 81 | } 82 | return bool(result), nil 83 | } 84 | 85 | func (sr *SessionRecord) Serialize() ([]byte, error) { 86 | var serialized C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 87 | signalFfiError := C.signal_session_record_serialize(&serialized, sr.ptr) 88 | if signalFfiError != nil { 89 | return nil, wrapError(signalFfiError) 90 | } 91 | return CopySignalOwnedBufferToBytes(serialized), nil 92 | } 93 | 94 | func (sr *SessionRecord) GetLocalRegistrationID() (uint32, error) { 95 | var result C.uint32_t 96 | signalFfiError := C.signal_session_record_get_local_registration_id(&result, sr.ptr) 97 | if signalFfiError != nil { 98 | return 0, wrapError(signalFfiError) 99 | } 100 | return uint32(result), nil 101 | } 102 | 103 | func (sr *SessionRecord) GetRemoteRegistrationID() (uint32, error) { 104 | var result C.uint32_t 105 | signalFfiError := C.signal_session_record_get_remote_registration_id(&result, sr.ptr) 106 | if signalFfiError != nil { 107 | return 0, wrapError(signalFfiError) 108 | } 109 | return uint32(result), nil 110 | } 111 | -------------------------------------------------------------------------------- /pkg/signalmeow/upgrades/00-latest.sql: -------------------------------------------------------------------------------- 1 | -- v0 -> v5: Latest revision 2 | CREATE TABLE signalmeow_device ( 3 | aci_uuid TEXT PRIMARY KEY, 4 | 5 | aci_identity_key_pair bytea NOT NULL, 6 | registration_id INTEGER NOT NULL CHECK ( registration_id >= 0 AND registration_id < 4294967296 ), 7 | 8 | pni_uuid TEXT NOT NULL, 9 | pni_identity_key_pair bytea NOT NULL, 10 | pni_registration_id INTEGER NOT NULL CHECK ( pni_registration_id >= 0 AND pni_registration_id < 4294967296 ), 11 | 12 | device_id INTEGER NOT NULL, 13 | number TEXT NOT NULL DEFAULT '', 14 | password TEXT NOT NULL DEFAULT '' 15 | ); 16 | 17 | CREATE TABLE signalmeow_pre_keys ( 18 | aci_uuid TEXT NOT NULL, 19 | key_id INTEGER NOT NULL, 20 | uuid_kind TEXT NOT NULL, 21 | is_signed BOOLEAN NOT NULL, 22 | key_pair bytea NOT NULL, 23 | uploaded BOOLEAN NOT NULL, 24 | 25 | PRIMARY KEY (aci_uuid, uuid_kind, is_signed, key_id), 26 | FOREIGN KEY (aci_uuid) REFERENCES signalmeow_device (aci_uuid) ON DELETE CASCADE ON UPDATE CASCADE 27 | ); 28 | 29 | CREATE TABLE signalmeow_identity_keys ( 30 | our_aci_uuid TEXT NOT NULL, 31 | their_aci_uuid TEXT NOT NULL, 32 | their_device_id INTEGER NOT NULL, 33 | key bytea NOT NULL, 34 | trust_level TEXT NOT NULL, 35 | 36 | PRIMARY KEY (our_aci_uuid, their_aci_uuid, their_device_id), 37 | FOREIGN KEY (our_aci_uuid) REFERENCES signalmeow_device (aci_uuid) ON DELETE CASCADE ON UPDATE CASCADE 38 | ); 39 | 40 | CREATE TABLE signalmeow_sessions ( 41 | our_aci_uuid TEXT NOT NULL, 42 | their_aci_uuid TEXT NOT NULL, 43 | their_device_id INTEGER NOT NULL, 44 | record bytea NOT NULL, 45 | 46 | PRIMARY KEY (our_aci_uuid, their_aci_uuid, their_device_id), 47 | FOREIGN KEY (our_aci_uuid) REFERENCES signalmeow_device (aci_uuid) ON DELETE CASCADE ON UPDATE CASCADE 48 | ); 49 | 50 | CREATE TABLE signalmeow_profile_keys ( 51 | our_aci_uuid TEXT NOT NULL, 52 | their_aci_uuid TEXT NOT NULL, 53 | key bytea NOT NULL, 54 | 55 | PRIMARY KEY (our_aci_uuid, their_aci_uuid), 56 | FOREIGN KEY (our_aci_uuid) REFERENCES signalmeow_device (aci_uuid) ON DELETE CASCADE ON UPDATE CASCADE 57 | ); 58 | 59 | CREATE TABLE signalmeow_sender_keys ( 60 | our_aci_uuid TEXT NOT NULL, 61 | sender_uuid TEXT NOT NULL, 62 | sender_device_id INTEGER NOT NULL, 63 | distribution_id TEXT NOT NULL, 64 | key_record bytea NOT NULL, 65 | 66 | PRIMARY KEY (our_aci_uuid, sender_uuid, sender_device_id, distribution_id), 67 | FOREIGN KEY (our_aci_uuid) REFERENCES signalmeow_device (aci_uuid) ON DELETE CASCADE ON UPDATE CASCADE 68 | ); 69 | 70 | CREATE TABLE signalmeow_groups ( 71 | our_aci_uuid TEXT NOT NULL, 72 | group_identifier TEXT NOT NULL, 73 | master_key TEXT NOT NULL, 74 | 75 | PRIMARY KEY (our_aci_uuid, group_identifier) 76 | ); 77 | 78 | CREATE TABLE signalmeow_contacts ( 79 | our_aci_uuid TEXT NOT NULL, 80 | aci_uuid TEXT NOT NULL, 81 | e164_number TEXT, 82 | contact_name TEXT, 83 | contact_avatar_hash TEXT, 84 | profile_key bytea, 85 | profile_name TEXT, 86 | profile_about TEXT, 87 | profile_about_emoji TEXT, 88 | profile_avatar_hash TEXT, 89 | 90 | PRIMARY KEY (our_aci_uuid, aci_uuid), 91 | FOREIGN KEY (our_aci_uuid) REFERENCES signalmeow_device (aci_uuid) ON DELETE CASCADE ON UPDATE CASCADE 92 | ); 93 | 94 | CREATE TABLE signalmeow_kyber_pre_keys ( 95 | aci_uuid TEXT NOT NULL, 96 | key_id INTEGER NOT NULL, 97 | uuid_kind TEXT NOT NULL, 98 | key_pair bytea NOT NULL, 99 | is_last_resort BOOLEAN NOT NULL, 100 | 101 | PRIMARY KEY (aci_uuid, uuid_kind, key_id), 102 | FOREIGN KEY (aci_uuid) REFERENCES signalmeow_device (aci_uuid) ON DELETE CASCADE ON UPDATE CASCADE 103 | ); 104 | -------------------------------------------------------------------------------- /database/user.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Scott Weber 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package database 18 | 19 | import ( 20 | "database/sql" 21 | 22 | "go.mau.fi/util/dbutil" 23 | log "maunium.net/go/maulogger/v2" 24 | "maunium.net/go/mautrix/id" 25 | ) 26 | 27 | type UserQuery struct { 28 | db *Database 29 | log log.Logger 30 | } 31 | 32 | func (uq *UserQuery) New() *User { 33 | return &User{ 34 | db: uq.db, 35 | log: uq.log, 36 | } 37 | } 38 | 39 | type User struct { 40 | db *Database 41 | log log.Logger 42 | 43 | MXID id.UserID 44 | SignalUsername string 45 | SignalID string 46 | ManagementRoom id.RoomID 47 | } 48 | 49 | func (u *User) sqlVariables() []any { 50 | var username, signalID, managementRoom *string 51 | if u.SignalUsername != "" { 52 | username = &u.SignalUsername 53 | } 54 | if u.SignalID != "" { 55 | signalID = &u.SignalID 56 | } 57 | if u.ManagementRoom != "" { 58 | managementRoom = (*string)(&u.ManagementRoom) 59 | } 60 | return []any{u.MXID, username, signalID, managementRoom} 61 | } 62 | 63 | func (u *User) Insert() error { 64 | q := `INSERT INTO "user" (mxid, username, uuid, management_room) VALUES ($1, $2, $3, $4)` 65 | _, err := u.db.Exec(q, u.sqlVariables()...) 66 | return err 67 | } 68 | 69 | func (u *User) Update() error { 70 | q := `UPDATE "user" SET username=$2, uuid=$3, management_room=$4 WHERE mxid=$1` 71 | _, err := u.db.Exec(q, u.sqlVariables()...) 72 | return err 73 | } 74 | 75 | func (u *User) Scan(row dbutil.Scannable) *User { 76 | var username, managementRoom, signalID sql.NullString 77 | err := row.Scan( 78 | &u.MXID, 79 | &username, 80 | &signalID, 81 | &managementRoom, 82 | ) 83 | if err != nil { 84 | if err != sql.ErrNoRows { 85 | u.log.Errorln("Database scan failed:", err) 86 | } 87 | return nil 88 | } 89 | u.SignalUsername = username.String 90 | u.SignalID = signalID.String 91 | u.ManagementRoom = id.RoomID(managementRoom.String) 92 | return u 93 | } 94 | 95 | func (uq *UserQuery) GetByMXID(mxid id.UserID) *User { 96 | q := `SELECT mxid, username, uuid, management_room FROM "user" WHERE mxid=$1` 97 | row := uq.db.QueryRow(q, mxid) 98 | if row == nil { 99 | return nil 100 | } 101 | return uq.New().Scan(row) 102 | } 103 | 104 | func (uq *UserQuery) GetByUsername(username string) *User { 105 | q := `SELECT mxid, username, uuid, management_room FROM "user" WHERE username=$1` 106 | row := uq.db.QueryRow(q, username) 107 | if row == nil { 108 | return nil 109 | } 110 | return uq.New().Scan(row) 111 | } 112 | 113 | func (uq *UserQuery) GetBySignalID(uuid string) *User { 114 | q := `SELECT mxid, username, uuid, management_room FROM "user" WHERE uuid=$1` 115 | row := uq.db.QueryRow(q, uuid) 116 | if row == nil { 117 | return nil 118 | } 119 | return uq.New().Scan(row) 120 | } 121 | 122 | func (uq *UserQuery) AllLoggedIn() []*User { 123 | q := `SELECT mxid, username, uuid, management_room FROM "user" WHERE username IS NOT NULL` 124 | rows, err := uq.db.Query(q) 125 | if err != nil { 126 | uq.log.Errorln("Database query failed:", err) 127 | return nil 128 | } 129 | defer rows.Close() 130 | 131 | var users []*User 132 | for rows.Next() { 133 | u := uq.New().Scan(rows) 134 | if u == nil { 135 | continue 136 | } 137 | users = append(users, u) 138 | } 139 | return users 140 | } 141 | -------------------------------------------------------------------------------- /database/reaction.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Scott Weber 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package database 18 | 19 | import ( 20 | "database/sql" 21 | "errors" 22 | 23 | "go.mau.fi/util/dbutil" 24 | log "maunium.net/go/maulogger/v2" 25 | "maunium.net/go/mautrix/id" 26 | ) 27 | 28 | type ReactionQuery struct { 29 | db *Database 30 | log log.Logger 31 | } 32 | 33 | func (mq *ReactionQuery) New() *Reaction { 34 | return &Reaction{ 35 | db: mq.db, 36 | log: mq.log, 37 | } 38 | } 39 | 40 | type Reaction struct { 41 | db *Database 42 | log log.Logger 43 | 44 | MXID id.EventID 45 | MXRoom id.RoomID 46 | 47 | SignalChatID string 48 | SignalReceiver string 49 | 50 | Author string 51 | MsgAuthor string 52 | MsgTimestamp uint64 53 | Emoji string 54 | } 55 | 56 | func (r *Reaction) Insert(txn dbutil.Execable) { 57 | if txn == nil { 58 | txn = r.db 59 | } 60 | _, err := txn.Exec(` 61 | INSERT INTO reaction (mxid, mx_room, signal_chat_id, signal_receiver, author, msg_author, msg_timestamp, emoji) 62 | VALUES ($1, $2, $3, $4, $5, $6, $7, $8) 63 | `, 64 | r.MXID.String(), r.MXRoom, r.SignalChatID, r.SignalReceiver, r.Author, r.MsgAuthor, r.MsgTimestamp, r.Emoji, 65 | ) 66 | r.log.Debugfln("Inserting reaction", r.MXID, r.MXRoom, r.SignalChatID, r.SignalReceiver, r.Author, r.MsgAuthor, r.MsgTimestamp, r.Emoji) 67 | if err != nil { 68 | r.log.Warnfln("Failed to insert %s, %s: %v", r.SignalChatID, r.MXID, err) 69 | } 70 | } 71 | 72 | func (r *Reaction) Delete(txn dbutil.Execable) { 73 | if txn == nil { 74 | txn = r.db 75 | } 76 | _, err := txn.Exec(` 77 | DELETE FROM reaction 78 | WHERE signal_chat_id=$1 AND signal_receiver=$2 AND author=$3 AND msg_author=$4 AND msg_timestamp=$5 79 | `, 80 | r.SignalChatID, r.SignalReceiver, r.Author, r.MsgAuthor, r.MsgTimestamp, 81 | ) 82 | if err != nil { 83 | r.log.Warnfln("Failed to delete %s, %s: %v", r.SignalChatID, r.MXID, err) 84 | } 85 | } 86 | 87 | func (r *Reaction) Scan(row dbutil.Scannable) *Reaction { 88 | err := row.Scan(&r.MXID, &r.MXRoom, &r.SignalChatID, &r.SignalReceiver, &r.Author, &r.MsgAuthor, &r.MsgTimestamp, &r.Emoji) 89 | if err != nil { 90 | if !errors.Is(err, sql.ErrNoRows) { 91 | r.log.Errorln("Database scan failed:", err) 92 | } 93 | return nil 94 | } 95 | return r 96 | } 97 | 98 | func (rq *ReactionQuery) maybeScan(row *sql.Row) *Reaction { 99 | if row == nil { 100 | return nil 101 | } 102 | return rq.New().Scan(row) 103 | } 104 | 105 | func (rq *ReactionQuery) GetByMXID(mxid id.EventID, roomID id.RoomID) *Reaction { 106 | const getReactionByMXIDQuery = ` 107 | SELECT mxid, mx_room, signal_chat_id, signal_receiver, author, msg_author, msg_timestamp, emoji FROM reaction 108 | WHERE mxid=$1 and mx_room=$2 109 | ` 110 | return rq.maybeScan(rq.db.QueryRow(getReactionByMXIDQuery, mxid, roomID)) 111 | } 112 | 113 | func (rq *ReactionQuery) GetBySignalID(signalChatID string, signalReceiver string, author string, msgAuthor string, msgTimestamp uint64) *Reaction { 114 | const getReactionBySignalIDQuery = ` 115 | SELECT mxid, mx_room, signal_chat_id, signal_receiver, author, msg_author, msg_timestamp, emoji FROM reaction 116 | WHERE signal_chat_id=$1 AND signal_receiver=$2 AND author=$3 AND msg_author=$4 AND msg_timestamp=$5 117 | ` 118 | return rq.maybeScan(rq.db.QueryRow(getReactionBySignalIDQuery, signalChatID, signalReceiver, author, msgAuthor, msgTimestamp)) 119 | } 120 | -------------------------------------------------------------------------------- /pkg/libsignalgo/message.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import ( 25 | "runtime" 26 | ) 27 | 28 | func Decrypt(message *Message, fromAddress *Address, sessionStore SessionStore, identityStore IdentityKeyStore, ctx *CallbackContext) ([]byte, error) { 29 | var decrypted C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 30 | signalFfiError := C.signal_decrypt_message( 31 | &decrypted, 32 | message.ptr, 33 | fromAddress.ptr, 34 | wrapSessionStore(sessionStore), 35 | wrapIdentityKeyStore(identityStore), 36 | ) 37 | if signalFfiError != nil { 38 | return nil, wrapCallbackError(signalFfiError, ctx) 39 | } 40 | return CopySignalOwnedBufferToBytes(decrypted), nil 41 | } 42 | 43 | type Message struct { 44 | ptr *C.SignalMessage 45 | } 46 | 47 | func wrapMessage(ptr *C.SignalMessage) *Message { 48 | message := &Message{ptr: ptr} 49 | runtime.SetFinalizer(message, (*Message).Destroy) 50 | return message 51 | } 52 | 53 | func DeserializeMessage(serialized []byte) (*Message, error) { 54 | var m *C.SignalMessage 55 | signalFfiError := C.signal_message_deserialize(&m, BytesToBuffer(serialized)) 56 | if signalFfiError != nil { 57 | return nil, wrapError(signalFfiError) 58 | } 59 | return wrapMessage(m), nil 60 | } 61 | 62 | func (m *Message) Clone() (*Message, error) { 63 | var cloned *C.SignalMessage 64 | signalFfiError := C.signal_message_clone(&cloned, m.ptr) 65 | if signalFfiError != nil { 66 | return nil, wrapError(signalFfiError) 67 | } 68 | return wrapMessage(cloned), nil 69 | } 70 | 71 | func (m *Message) Destroy() error { 72 | runtime.SetFinalizer(m, nil) 73 | return wrapError(C.signal_message_destroy(m.ptr)) 74 | } 75 | 76 | func (m *Message) GetBody() ([]byte, error) { 77 | var body C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 78 | signalFfiError := C.signal_message_get_body(&body, m.ptr) 79 | if signalFfiError != nil { 80 | return nil, wrapError(signalFfiError) 81 | } 82 | return CopySignalOwnedBufferToBytes(body), nil 83 | } 84 | 85 | func (m *Message) Serialize() ([]byte, error) { 86 | var serialized C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 87 | signalFfiError := C.signal_message_get_serialized(&serialized, m.ptr) 88 | if signalFfiError != nil { 89 | return nil, wrapError(signalFfiError) 90 | } 91 | return CopySignalOwnedBufferToBytes(serialized), nil 92 | } 93 | 94 | func (m *Message) GetMessageVersion() (uint32, error) { 95 | var messageVersion C.uint32_t 96 | signalFfiError := C.signal_message_get_message_version(&messageVersion, m.ptr) 97 | if signalFfiError != nil { 98 | return 0, wrapError(signalFfiError) 99 | } 100 | return uint32(messageVersion), nil 101 | } 102 | 103 | func (m *Message) GetCounter() (uint32, error) { 104 | var counter C.uint32_t 105 | signalFfiError := C.signal_message_get_counter(&counter, m.ptr) 106 | if signalFfiError != nil { 107 | return 0, wrapError(signalFfiError) 108 | } 109 | return uint32(counter), nil 110 | } 111 | 112 | func (m *Message) VerifyMAC(sender, receiver *PublicKey, macKey []byte) (bool, error) { 113 | var result C.bool 114 | signalFfiError := C.signal_message_verify_mac(&result, m.ptr, sender.ptr, receiver.ptr, BytesToBuffer(macKey)) 115 | if signalFfiError != nil { 116 | return false, wrapError(signalFfiError) 117 | } 118 | return bool(result), nil 119 | } 120 | -------------------------------------------------------------------------------- /pkg/libsignalgo/authcredential.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Scott Weber 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | #include 23 | */ 24 | import "C" 25 | import ( 26 | "unsafe" 27 | ) 28 | 29 | // type AuthCredential [C.SignalAUTH_CREDENTIAL_LEN]byte 30 | // type AuthCredentialResponse [C.SignalAUTH_CREDENTIAL_RESPONSE_LEN]byte 31 | type AuthCredentialWithPni [C.SignalAUTH_CREDENTIAL_WITH_PNI_LEN]byte 32 | type AuthCredentialWithPniResponse [C.SignalAUTH_CREDENTIAL_WITH_PNI_RESPONSE_LEN]byte 33 | type AuthCredentialPresentation []byte 34 | 35 | func (ac *AuthCredentialWithPni) Slice() []byte { 36 | return (*ac)[:] 37 | } 38 | 39 | func ReceiveAuthCredentialWithPni( 40 | serverPublicParams ServerPublicParams, 41 | aci UUID, 42 | pni UUID, 43 | redemptionTime uint64, 44 | authCredResponse AuthCredentialWithPniResponse, 45 | ) (*AuthCredentialWithPni, error) { 46 | c_result := [C.SignalAUTH_CREDENTIAL_WITH_PNI_LEN]C.uchar{} 47 | c_serverPublicParams := (*[C.SignalSERVER_PUBLIC_PARAMS_LEN]C.uchar)(unsafe.Pointer(&serverPublicParams[0])) 48 | c_aci, err := SignalServiceIdFromUUID(aci) 49 | if err != nil { 50 | return nil, err 51 | } 52 | var c_pni cPNIType 53 | if len(pni) != 16 { 54 | c_pni = nil 55 | } else { 56 | c_pni, err = SignalPNIServiceIdFromUUID(pni) 57 | if err != nil { 58 | return nil, err 59 | } 60 | } 61 | c_authCredResponse := (*[C.SignalAUTH_CREDENTIAL_WITH_PNI_RESPONSE_LEN]C.uchar)(unsafe.Pointer(&authCredResponse[0])) 62 | 63 | signalFfiError := C.signal_server_public_params_receive_auth_credential_with_pni_as_aci( 64 | &c_result, 65 | c_serverPublicParams, 66 | c_aci, 67 | c_pni, 68 | C.uint64_t(redemptionTime), 69 | c_authCredResponse, 70 | ) 71 | if signalFfiError != nil { 72 | return nil, wrapError(signalFfiError) 73 | } 74 | result := AuthCredentialWithPni(C.GoBytes(unsafe.Pointer(&c_result), C.int(C.SignalAUTH_CREDENTIAL_WITH_PNI_LEN))) 75 | return &result, nil 76 | } 77 | 78 | func NewAuthCredentialWithPniResponse(b []byte) (*AuthCredentialWithPniResponse, error) { 79 | borrowedBuffer := BytesToBuffer(b) 80 | signalFfiError := C.signal_auth_credential_with_pni_response_check_valid_contents(borrowedBuffer) 81 | if signalFfiError != nil { 82 | return nil, wrapError(signalFfiError) 83 | } 84 | authCred := AuthCredentialWithPniResponse(b) 85 | return &authCred, nil 86 | } 87 | 88 | func CreateAuthCredentialWithPniPresentation( 89 | serverPublicParams ServerPublicParams, 90 | randomness Randomness, 91 | groupSecretParams GroupSecretParams, 92 | authCredWithPni AuthCredentialWithPni, 93 | ) (*AuthCredentialPresentation, error) { 94 | var c_result C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 95 | c_serverPublicParams := (*[C.SignalSERVER_PUBLIC_PARAMS_LEN]C.uchar)(unsafe.Pointer(&serverPublicParams[0])) 96 | c_randomness := (*[C.SignalRANDOMNESS_LEN]C.uchar)(unsafe.Pointer(&randomness[0])) 97 | c_groupSecretParams := (*[C.SignalGROUP_SECRET_PARAMS_LEN]C.uchar)(unsafe.Pointer(&groupSecretParams[0])) 98 | c_authCredWithPni := (*[C.SignalAUTH_CREDENTIAL_WITH_PNI_LEN]C.uchar)(unsafe.Pointer(&authCredWithPni[0])) 99 | 100 | signalFfiError := C.signal_server_public_params_create_auth_credential_with_pni_presentation_deterministic( 101 | &c_result, 102 | c_serverPublicParams, 103 | c_randomness, 104 | c_groupSecretParams, 105 | c_authCredWithPni, 106 | ) 107 | if signalFfiError != nil { 108 | return nil, wrapError(signalFfiError) 109 | } 110 | result := AuthCredentialPresentation(CopySignalOwnedBufferToBytes(c_result)) 111 | return &result, nil 112 | } 113 | -------------------------------------------------------------------------------- /pkg/signalmeow/misc.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Scott Weber 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package signalmeow 18 | 19 | import ( 20 | "encoding/base64" 21 | "encoding/hex" 22 | "errors" 23 | "strings" 24 | 25 | "github.com/rs/zerolog" 26 | 27 | "go.mau.fi/mautrix-signal/pkg/libsignalgo" 28 | "go.mau.fi/mautrix-signal/pkg/signalmeow/web" 29 | ) 30 | 31 | // signalmeow Logging 32 | 33 | var zlog zerolog.Logger = zerolog.New(zerolog.ConsoleWriter{}).With().Timestamp().Logger() 34 | 35 | func SetLogger(l zerolog.Logger) { 36 | zlog = l 37 | setupFFILogging() 38 | web.SetLogger(l.With().Str("component", "signalmeow/web").Logger()) 39 | } 40 | 41 | // libsignalgo Logging 42 | 43 | type FFILogger struct{} 44 | 45 | func (FFILogger) Enabled(target string, level libsignalgo.LogLevel) bool { return true } 46 | 47 | func (FFILogger) Log(target string, level libsignalgo.LogLevel, file string, line uint, message string) { 48 | var evt *zerolog.Event 49 | switch level { 50 | case libsignalgo.LogLevelError: 51 | evt = zlog.Error() 52 | case libsignalgo.LogLevelWarn: 53 | evt = zlog.Warn() 54 | case libsignalgo.LogLevelInfo: 55 | evt = zlog.Info() 56 | case libsignalgo.LogLevelDebug: 57 | evt = zlog.Debug() 58 | case libsignalgo.LogLevelTrace: 59 | evt = zlog.Trace() 60 | default: 61 | panic("invalid log level from libsignal") 62 | } 63 | 64 | evt.Str("component", "libsignal"). 65 | Str("target", target). 66 | Str("file", file). 67 | Uint("line", line). 68 | Msg(message) 69 | } 70 | 71 | func (FFILogger) Flush() {} 72 | 73 | // Ensure FFILogger implements the Logger interface 74 | var _ libsignalgo.Logger = FFILogger{} 75 | 76 | var loggingSetup = false 77 | 78 | func setupFFILogging() { 79 | if !loggingSetup { 80 | libsignalgo.InitLogger(libsignalgo.LogLevelInfo, FFILogger{}) 81 | loggingSetup = true 82 | } 83 | } 84 | 85 | // Other misc things 86 | 87 | func serverPublicParams() libsignalgo.ServerPublicParams { 88 | serverPublicParamsBase64 := "AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P" 89 | serverPublicParamsBytes, err := base64.StdEncoding.DecodeString(serverPublicParamsBase64) 90 | if err != nil { 91 | panic(err) 92 | } 93 | var serverPublicParams libsignalgo.ServerPublicParams 94 | copy(serverPublicParams[:], serverPublicParamsBytes) 95 | return serverPublicParams 96 | } 97 | 98 | func convertUUIDToByteUUID(uuid string) (*libsignalgo.UUID, error) { 99 | uuid = strings.Replace(uuid, "-", "", -1) 100 | uuidBytes, err := hex.DecodeString(uuid) 101 | if err != nil { 102 | return nil, err 103 | } 104 | if len(uuidBytes) != 16 { 105 | return nil, errors.New("invalid UUID length") 106 | } 107 | byteUUID := libsignalgo.UUID(uuidBytes) 108 | return &byteUUID, nil 109 | } 110 | 111 | func convertByteUUIDToUUID(uuidBytes libsignalgo.UUID) string { 112 | uuid := hex.EncodeToString(uuidBytes[:4]) + "-" + 113 | hex.EncodeToString(uuidBytes[4:6]) + "-" + 114 | hex.EncodeToString(uuidBytes[6:8]) + "-" + 115 | hex.EncodeToString(uuidBytes[8:10]) + "-" + 116 | hex.EncodeToString(uuidBytes[10:]) 117 | // ensure uuid is lowercase 118 | uuid = strings.ToLower(uuid) 119 | return uuid 120 | } 121 | -------------------------------------------------------------------------------- /pkg/libsignalgo/servercertificate.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import "runtime" 25 | 26 | type ServerCertificate struct { 27 | ptr *C.SignalServerCertificate 28 | } 29 | 30 | func wrapServerCertificate(ptr *C.SignalServerCertificate) *ServerCertificate { 31 | serverCertificate := &ServerCertificate{ptr: ptr} 32 | runtime.SetFinalizer(serverCertificate, (*ServerCertificate).Destroy) 33 | return serverCertificate 34 | } 35 | 36 | // NewServerCertificate should only be used for testing (at least according to 37 | // the Swift bindings). 38 | func NewServerCertificate(keyID uint32, publicKey *PublicKey, trustRoot *PrivateKey) (*ServerCertificate, error) { 39 | var serverCertificate *C.SignalServerCertificate 40 | signalFfiError := C.signal_server_certificate_new(&serverCertificate, C.uint32_t(keyID), publicKey.ptr, trustRoot.ptr) 41 | if signalFfiError != nil { 42 | return nil, wrapError(signalFfiError) 43 | } 44 | return wrapServerCertificate(serverCertificate), nil 45 | } 46 | 47 | func DeserializeServerCertificate(serialized []byte) (*ServerCertificate, error) { 48 | var serverCertificate *C.SignalServerCertificate 49 | signalFfiError := C.signal_server_certificate_deserialize(&serverCertificate, BytesToBuffer(serialized)) 50 | if signalFfiError != nil { 51 | return nil, wrapError(signalFfiError) 52 | } 53 | return wrapServerCertificate(serverCertificate), nil 54 | } 55 | 56 | func (sc *ServerCertificate) Clone() (*ServerCertificate, error) { 57 | var cloned *C.SignalServerCertificate 58 | signalFfiError := C.signal_server_certificate_clone(&cloned, sc.ptr) 59 | if signalFfiError != nil { 60 | return nil, wrapError(signalFfiError) 61 | } 62 | return wrapServerCertificate(cloned), nil 63 | } 64 | 65 | func (sc *ServerCertificate) Destroy() error { 66 | runtime.SetFinalizer(sc, nil) 67 | return wrapError(C.signal_server_certificate_destroy(sc.ptr)) 68 | } 69 | 70 | func (sc *ServerCertificate) Serialize() ([]byte, error) { 71 | var serialized C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 72 | signalFfiError := C.signal_server_certificate_get_serialized(&serialized, sc.ptr) 73 | if signalFfiError != nil { 74 | return nil, wrapError(signalFfiError) 75 | } 76 | return CopySignalOwnedBufferToBytes(serialized), nil 77 | } 78 | 79 | func (sc *ServerCertificate) GetCertificate() ([]byte, error) { 80 | var certificate C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 81 | signalFfiError := C.signal_server_certificate_get_certificate(&certificate, sc.ptr) 82 | if signalFfiError != nil { 83 | return nil, wrapError(signalFfiError) 84 | } 85 | return CopySignalOwnedBufferToBytes(certificate), nil 86 | } 87 | 88 | func (sc *ServerCertificate) GetSignature() ([]byte, error) { 89 | var signature C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 90 | signalFfiError := C.signal_server_certificate_get_signature(&signature, sc.ptr) 91 | if signalFfiError != nil { 92 | return nil, wrapError(signalFfiError) 93 | } 94 | return CopySignalOwnedBufferToBytes(signature), nil 95 | } 96 | 97 | func (sc *ServerCertificate) GetKeyId() (uint32, error) { 98 | var keyID C.uint32_t 99 | signalFfiError := C.signal_server_certificate_get_key_id(&keyID, sc.ptr) 100 | if signalFfiError != nil { 101 | return 0, wrapError(signalFfiError) 102 | } 103 | return uint32(keyID), nil 104 | } 105 | 106 | func (sc *ServerCertificate) GetKey() (*PublicKey, error) { 107 | var key *C.SignalPublicKey 108 | signalFfiError := C.signal_server_certificate_get_key(&key, sc.ptr) 109 | if signalFfiError != nil { 110 | return nil, wrapError(signalFfiError) 111 | } 112 | return wrapPublicKey(key), nil 113 | } 114 | -------------------------------------------------------------------------------- /pkg/libsignalgo/privatekey_test.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | 24 | "go.mau.fi/mautrix-signal/pkg/libsignalgo" 25 | ) 26 | 27 | // From PublicAPITests.swift:testPkOperations 28 | func TestPrivateKeyOperations(t *testing.T) { 29 | setupLogging() 30 | var err error 31 | var privateKey *libsignalgo.PrivateKey 32 | 33 | t.Run("test generate", func(t *testing.T) { 34 | privateKey, err = libsignalgo.GeneratePrivateKey() 35 | assert.NoError(t, err) 36 | assert.NotNil(t, privateKey) 37 | }) 38 | 39 | var privateKeyBytes []byte 40 | t.Run("serialize", func(t *testing.T) { 41 | privateKeyBytes, err = privateKey.Serialize() 42 | assert.NoError(t, err) 43 | assert.NotNil(t, privateKeyBytes) 44 | }) 45 | 46 | var publicKey *libsignalgo.PublicKey 47 | t.Run("get public key", func(t *testing.T) { 48 | publicKey, err = privateKey.GetPublicKey() 49 | assert.NoError(t, err) 50 | assert.NotNil(t, publicKey) 51 | }) 52 | 53 | var publicKeyBytes []byte 54 | t.Run("serialize public key", func(t *testing.T) { 55 | publicKeyBytes, err = publicKey.Serialize() 56 | assert.NoError(t, err) 57 | assert.NotNil(t, publicKeyBytes) 58 | 59 | assert.EqualValues(t, 5, publicKeyBytes[0]) 60 | assert.Len(t, publicKeyBytes, 33) 61 | }) 62 | 63 | var publicKeyRaw []byte 64 | t.Run("get public key raw", func(t *testing.T) { 65 | publicKeyRaw, err = publicKey.Bytes() 66 | assert.NoError(t, err) 67 | assert.NotNil(t, publicKeyRaw) 68 | 69 | assert.Len(t, publicKeyRaw, 32) 70 | assert.Equal(t, publicKeyRaw[0:31], publicKeyBytes[1:32]) 71 | }) 72 | 73 | var privateKeyReloaded *libsignalgo.PrivateKey 74 | var publicKeyReloaded *libsignalgo.PublicKey 75 | t.Run("deserialize private key", func(t *testing.T) { 76 | privateKeyReloaded, err = libsignalgo.DeserializePrivateKey(privateKeyBytes) 77 | assert.NoError(t, err) 78 | assert.NotNil(t, privateKeyReloaded) 79 | 80 | publicKeyReloaded, err = privateKeyReloaded.GetPublicKey() 81 | assert.NoError(t, err) 82 | assert.NotNil(t, publicKeyReloaded) 83 | 84 | assert.Equal(t, publicKey, publicKeyReloaded) 85 | 86 | serializedPublicKey, err := publicKey.Serialize() 87 | assert.NoError(t, err) 88 | serializedPublicKeyReloaded, err := publicKeyReloaded.Serialize() 89 | assert.NoError(t, err) 90 | assert.Equal(t, serializedPublicKey, serializedPublicKeyReloaded) 91 | }) 92 | 93 | t.Run("sign", func(t *testing.T) { 94 | message := []byte{0x01, 0x02, 0x03} 95 | signature, err := privateKey.Sign(message) 96 | assert.NoError(t, err) 97 | 98 | valid, err := publicKey.Verify(message, signature) 99 | assert.NoError(t, err) 100 | assert.True(t, valid) 101 | 102 | signature[5] ^= 1 103 | 104 | valid, err = publicKey.Verify(message, signature) 105 | assert.NoError(t, err) 106 | assert.False(t, valid) 107 | 108 | signature[5] ^= 1 109 | 110 | valid, err = publicKey.Verify(message, signature) 111 | assert.NoError(t, err) 112 | assert.True(t, valid) 113 | 114 | message[1] ^= 1 115 | 116 | valid, err = publicKey.Verify(message, signature) 117 | assert.NoError(t, err) 118 | assert.False(t, valid) 119 | 120 | message[1] ^= 1 121 | 122 | valid, err = publicKey.Verify(message, signature) 123 | assert.NoError(t, err) 124 | assert.True(t, valid) 125 | }) 126 | 127 | t.Run("agree", func(t *testing.T) { 128 | privateKey2, err := libsignalgo.GeneratePrivateKey() 129 | assert.NoError(t, err) 130 | 131 | publicKey2, err := privateKey2.GetPublicKey() 132 | assert.NoError(t, err) 133 | 134 | sharedSecret1, err := privateKey.Agree(publicKey2) 135 | assert.NoError(t, err) 136 | sharedSecret2, err := privateKey2.Agree(publicKey) 137 | assert.NoError(t, err) 138 | 139 | assert.Equal(t, sharedSecret1, sharedSecret2) 140 | }) 141 | } 142 | -------------------------------------------------------------------------------- /pkg/libsignalgo/decryptionerrormessage.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Sumner Evans 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package libsignalgo 18 | 19 | /* 20 | #cgo LDFLAGS: -lsignal_ffi -ldl 21 | #include "./libsignal-ffi.h" 22 | */ 23 | import "C" 24 | import ( 25 | "runtime" 26 | "time" 27 | ) 28 | 29 | type DecryptionErrorMessage struct { 30 | ptr *C.SignalDecryptionErrorMessage 31 | } 32 | 33 | func wrapDecryptionErrorMessage(ptr *C.SignalDecryptionErrorMessage) *DecryptionErrorMessage { 34 | decryptionErrorMessage := &DecryptionErrorMessage{ptr: ptr} 35 | runtime.SetFinalizer(decryptionErrorMessage, (*DecryptionErrorMessage).Destroy) 36 | return decryptionErrorMessage 37 | } 38 | 39 | func DeserializeDecryptionErrorMessage(messageBytes []byte) (*DecryptionErrorMessage, error) { 40 | var dem *C.SignalDecryptionErrorMessage 41 | signalFfiError := C.signal_decryption_error_message_deserialize(&dem, BytesToBuffer(messageBytes)) 42 | if signalFfiError != nil { 43 | return nil, wrapError(signalFfiError) 44 | } 45 | return wrapDecryptionErrorMessage(dem), nil 46 | } 47 | 48 | func DecryptionErrorMessageForOriginalMessage(originalBytes []byte, originalType uint8, originalTs uint64, originalSenderDeviceID uint) (*DecryptionErrorMessage, error) { 49 | var dem *C.SignalDecryptionErrorMessage 50 | signalFfiError := C.signal_decryption_error_message_for_original_message(&dem, BytesToBuffer(originalBytes), C.uint8_t(originalType), C.uint64_t(originalTs), C.uint32_t(originalSenderDeviceID)) 51 | if signalFfiError != nil { 52 | return nil, wrapError(signalFfiError) 53 | } 54 | return wrapDecryptionErrorMessage(dem), nil 55 | } 56 | 57 | func DecryptionErrorMessageFromSerializedContent(serialized []byte) (*DecryptionErrorMessage, error) { 58 | var dem *C.SignalDecryptionErrorMessage 59 | signalFfiError := C.signal_decryption_error_message_extract_from_serialized_content(&dem, BytesToBuffer(serialized)) 60 | if signalFfiError != nil { 61 | return nil, wrapError(signalFfiError) 62 | } 63 | return wrapDecryptionErrorMessage(dem), nil 64 | } 65 | 66 | func (dem *DecryptionErrorMessage) Clone() (*DecryptionErrorMessage, error) { 67 | var cloned *C.SignalDecryptionErrorMessage 68 | signalFfiError := C.signal_decryption_error_message_clone(&cloned, dem.ptr) 69 | if signalFfiError != nil { 70 | return nil, wrapError(signalFfiError) 71 | } 72 | return wrapDecryptionErrorMessage(cloned), nil 73 | } 74 | 75 | func (dem *DecryptionErrorMessage) Destroy() error { 76 | runtime.SetFinalizer(dem, nil) 77 | return wrapError(C.signal_decryption_error_message_destroy(dem.ptr)) 78 | } 79 | 80 | func (dem *DecryptionErrorMessage) Serialize() ([]byte, error) { 81 | var serialized C.SignalOwnedBuffer = C.SignalOwnedBuffer{} 82 | signalFfiError := C.signal_decryption_error_message_serialize(&serialized, dem.ptr) 83 | if signalFfiError != nil { 84 | return nil, wrapError(signalFfiError) 85 | } 86 | return CopySignalOwnedBufferToBytes(serialized), nil 87 | } 88 | 89 | func (dem *DecryptionErrorMessage) GetTimestamp() (time.Time, error) { 90 | var ts C.uint64_t 91 | signalFfiError := C.signal_decryption_error_message_get_timestamp(&ts, dem.ptr) 92 | if signalFfiError != nil { 93 | return time.Time{}, wrapError(signalFfiError) 94 | } 95 | return time.UnixMilli(int64(ts)), nil 96 | } 97 | 98 | func (dem *DecryptionErrorMessage) GetDeviceID() (uint32, error) { 99 | var deviceID C.uint32_t 100 | signalFfiError := C.signal_decryption_error_message_get_device_id(&deviceID, dem.ptr) 101 | if signalFfiError != nil { 102 | return 0, wrapError(signalFfiError) 103 | } 104 | return uint32(deviceID), nil 105 | } 106 | 107 | func (dem *DecryptionErrorMessage) GetRatchetKey() (*PublicKey, error) { 108 | var pk *C.SignalPublicKey 109 | signalFfiError := C.signal_decryption_error_message_get_ratchet_key(&pk, dem.ptr) 110 | if signalFfiError != nil { 111 | return nil, wrapError(signalFfiError) 112 | } 113 | return wrapPublicKey(pk), nil 114 | } 115 | -------------------------------------------------------------------------------- /pkg/signalmeow/contact_store.go: -------------------------------------------------------------------------------- 1 | // mautrix-signal - A Matrix-signal puppeting bridge. 2 | // Copyright (C) 2023 Scott Weber 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | package signalmeow 18 | 19 | import ( 20 | "context" 21 | "database/sql" 22 | "errors" 23 | ) 24 | 25 | var _ ContactStore = (*SQLStore)(nil) 26 | 27 | type ContactStore interface { 28 | LoadContact(ctx context.Context, theirUuid string) (*Contact, error) 29 | LoadContactByE164(ctx context.Context, e164 string) (*Contact, error) 30 | StoreContact(ctx context.Context, contact Contact) error 31 | AllContacts(ctx context.Context) ([]Contact, error) 32 | } 33 | 34 | func scanContact(row scannable) (*Contact, error) { 35 | var contact Contact 36 | err := row.Scan( 37 | &contact.UUID, 38 | &contact.E164, 39 | &contact.ContactName, 40 | &contact.ContactAvatarHash, 41 | &contact.ProfileKey, 42 | &contact.ProfileName, 43 | &contact.ProfileAbout, 44 | &contact.ProfileAboutEmoji, 45 | &contact.ProfileAvatarHash, 46 | ) 47 | if errors.Is(err, sql.ErrNoRows) { 48 | return nil, nil 49 | } else if err != nil { 50 | return nil, err 51 | } 52 | return &contact, err 53 | } 54 | 55 | var commonSelectQuery = ` 56 | SELECT 57 | aci_uuid, 58 | e164_number, 59 | contact_name, 60 | contact_avatar_hash, 61 | profile_key, 62 | profile_name, 63 | profile_about, 64 | profile_about_emoji, 65 | profile_avatar_hash 66 | FROM signalmeow_contacts 67 | ` 68 | 69 | func (s *SQLStore) LoadContact(ctx context.Context, theirUuid string) (*Contact, error) { 70 | contactQuery := commonSelectQuery + 71 | `WHERE our_aci_uuid = $1 AND aci_uuid = $2` 72 | return scanContact(s.db.QueryRow(contactQuery, s.AciUuid, theirUuid)) 73 | } 74 | 75 | func (s *SQLStore) LoadContactByE164(ctx context.Context, e164 string) (*Contact, error) { 76 | contactQuery := commonSelectQuery + 77 | `WHERE our_aci_uuid = $1 AND e164_number = $2` 78 | return scanContact(s.db.QueryRow(contactQuery, s.AciUuid, e164)) 79 | } 80 | 81 | func (s *SQLStore) AllContacts(ctx context.Context) ([]Contact, error) { 82 | contactQuery := commonSelectQuery + 83 | `WHERE our_aci_uuid = $1` 84 | rows, err := s.db.Query(contactQuery, s.AciUuid) 85 | if err != nil { 86 | return nil, err 87 | } 88 | defer rows.Close() 89 | var contacts []Contact 90 | for rows.Next() { 91 | contact, err := scanContact(rows) 92 | if err != nil { 93 | return nil, err 94 | } 95 | contacts = append(contacts, *contact) 96 | } 97 | return contacts, nil 98 | } 99 | 100 | func (s *SQLStore) StoreContact(ctx context.Context, contact Contact) error { 101 | storeContactQuery := ` 102 | INSERT INTO signalmeow_contacts ( 103 | our_aci_uuid, 104 | aci_uuid, 105 | e164_number, 106 | contact_name, 107 | contact_avatar_hash, 108 | profile_key, 109 | profile_name, 110 | profile_about, 111 | profile_about_emoji, 112 | profile_avatar_hash 113 | ) 114 | VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) 115 | ON CONFLICT (our_aci_uuid, aci_uuid) DO UPDATE SET 116 | e164_number = excluded.e164_number, 117 | contact_name = excluded.contact_name, 118 | contact_avatar_hash = excluded.contact_avatar_hash, 119 | profile_key = excluded.profile_key, 120 | profile_name = excluded.profile_name, 121 | profile_about = excluded.profile_about, 122 | profile_about_emoji = excluded.profile_about_emoji, 123 | profile_avatar_hash = excluded.profile_avatar_hash 124 | ` 125 | tx, err := s.db.BeginTx(ctx, nil) 126 | if err != nil { 127 | tx.Rollback() 128 | return err 129 | } 130 | _, err = tx.Exec( 131 | storeContactQuery, 132 | s.AciUuid, 133 | contact.UUID, 134 | contact.E164, 135 | contact.ContactName, 136 | contact.ContactAvatarHash, 137 | contact.ProfileKey, 138 | contact.ProfileName, 139 | contact.ProfileAbout, 140 | contact.ProfileAboutEmoji, 141 | contact.ProfileAvatarHash, 142 | ) 143 | if err != nil { 144 | tx.Rollback() 145 | return err 146 | } 147 | err = tx.Commit() 148 | return err 149 | } 150 | --------------------------------------------------------------------------------