2023-12-17 15:54:35 +02:00
|
|
|
// mautrix-signal - A Matrix-signal puppeting bridge.
|
|
|
|
|
// Copyright (C) 2023 Scott Weber
|
|
|
|
|
//
|
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
//
|
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
|
//
|
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
2023-05-04 18:04:50 -04:00
|
|
|
package signalmeow
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2025-01-18 02:58:43 +02:00
|
|
|
"crypto/hmac"
|
2023-05-04 18:04:50 -04:00
|
|
|
"encoding/base64"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
2025-11-24 17:38:01 +02:00
|
|
|
mrand "math/rand/v2"
|
2024-01-02 21:35:42 -07:00
|
|
|
"net/http"
|
2023-05-04 18:04:50 -04:00
|
|
|
"net/url"
|
|
|
|
|
"time"
|
|
|
|
|
|
2024-09-13 12:27:26 +03:00
|
|
|
"github.com/coder/websocket"
|
2024-01-03 23:07:25 +02:00
|
|
|
"github.com/google/uuid"
|
2024-01-06 10:44:36 -07:00
|
|
|
"github.com/rs/zerolog"
|
2024-01-09 13:14:37 +02:00
|
|
|
"go.mau.fi/util/exerrors"
|
|
|
|
|
"go.mau.fi/util/random"
|
2023-12-17 15:21:21 +02:00
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
|
|
2023-05-04 18:04:50 -04:00
|
|
|
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
|
|
|
|
signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
|
2024-01-05 13:44:41 +02:00
|
|
|
"go.mau.fi/mautrix-signal/pkg/signalmeow/store"
|
2024-03-22 21:24:30 +02:00
|
|
|
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
2023-05-08 13:47:01 -04:00
|
|
|
"go.mau.fi/mautrix-signal/pkg/signalmeow/web"
|
2023-05-04 18:04:50 -04:00
|
|
|
"go.mau.fi/mautrix-signal/pkg/signalmeow/wspb"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type ConfirmDeviceResponse struct {
|
2024-01-03 23:07:25 +02:00
|
|
|
ACI uuid.UUID `json:"uuid"`
|
|
|
|
|
PNI uuid.UUID `json:"pni,omitempty"`
|
|
|
|
|
DeviceID int `json:"deviceId"`
|
2023-05-04 18:04:50 -04:00
|
|
|
}
|
|
|
|
|
|
2023-05-07 11:40:46 -04:00
|
|
|
type ProvisioningState int
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
StateProvisioningError ProvisioningState = iota
|
|
|
|
|
StateProvisioningURLReceived
|
|
|
|
|
StateProvisioningDataReceived
|
|
|
|
|
)
|
|
|
|
|
|
2023-12-30 11:49:08 -07:00
|
|
|
func (s ProvisioningState) String() string {
|
|
|
|
|
switch s {
|
|
|
|
|
case StateProvisioningError:
|
|
|
|
|
return "StateProvisioningError"
|
|
|
|
|
case StateProvisioningURLReceived:
|
|
|
|
|
return "StateProvisioningURLReceived"
|
|
|
|
|
case StateProvisioningDataReceived:
|
|
|
|
|
return "StateProvisioningDataReceived"
|
|
|
|
|
default:
|
|
|
|
|
return fmt.Sprintf("ProvisioningState(%d)", s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-04 18:04:50 -04:00
|
|
|
// Enum for the provisioningUrl, ProvisioningMessage, and error
|
|
|
|
|
type ProvisioningResponse struct {
|
2023-05-07 11:40:46 -04:00
|
|
|
State ProvisioningState
|
2024-01-03 23:07:25 +02:00
|
|
|
ProvisioningURL string
|
2024-01-05 13:44:41 +02:00
|
|
|
ProvisioningData *store.DeviceData
|
2023-05-04 18:04:50 -04:00
|
|
|
Err error
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-18 02:58:43 +02:00
|
|
|
func PerformProvisioning(ctx context.Context, deviceStore store.DeviceStore, deviceName string, allowBackup bool) chan ProvisioningResponse {
|
2024-01-06 11:37:52 -07:00
|
|
|
log := zerolog.Ctx(ctx).With().Str("action", "perform provisioning").Logger()
|
2024-06-03 22:34:33 +03:00
|
|
|
c := make(chan ProvisioningResponse, 4)
|
2023-05-04 18:04:50 -04:00
|
|
|
go func() {
|
|
|
|
|
defer close(c)
|
|
|
|
|
|
2024-08-10 23:36:19 +03:00
|
|
|
timeoutCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
2023-05-04 18:04:50 -04:00
|
|
|
defer cancel()
|
2025-01-15 23:40:50 +02:00
|
|
|
ws, resp, err := web.OpenWebsocket(timeoutCtx, (&url.URL{
|
|
|
|
|
Scheme: "wss",
|
|
|
|
|
Host: web.APIHostname,
|
|
|
|
|
Path: web.WebsocketProvisioningPath,
|
|
|
|
|
}).String())
|
2023-05-04 18:04:50 -04:00
|
|
|
if err != nil {
|
2024-01-06 11:37:52 -07:00
|
|
|
log.Err(err).Any("resp", resp).Msg("error opening provisioning websocket")
|
2023-05-07 11:40:46 -04:00
|
|
|
c <- ProvisioningResponse{State: StateProvisioningError, Err: err}
|
2023-05-07 17:40:36 -04:00
|
|
|
return
|
2023-05-04 18:04:50 -04:00
|
|
|
}
|
|
|
|
|
defer ws.Close(websocket.StatusInternalError, "Websocket StatusInternalError")
|
|
|
|
|
provisioningCipher := NewProvisioningCipher()
|
|
|
|
|
|
2025-01-18 02:58:43 +02:00
|
|
|
provisioningURL, err := startProvisioning(timeoutCtx, ws, provisioningCipher, allowBackup)
|
2023-11-01 00:10:36 -04:00
|
|
|
if err != nil {
|
2024-01-06 11:37:52 -07:00
|
|
|
log.Err(err).Msg("startProvisioning error")
|
2023-11-01 00:10:36 -04:00
|
|
|
c <- ProvisioningResponse{State: StateProvisioningError, Err: err}
|
|
|
|
|
return
|
|
|
|
|
}
|
2024-01-09 13:14:37 +02:00
|
|
|
c <- ProvisioningResponse{State: StateProvisioningURLReceived, ProvisioningURL: provisioningURL, Err: err}
|
2023-05-04 18:04:50 -04:00
|
|
|
|
2024-08-10 23:36:19 +03:00
|
|
|
provisioningMessage, err := continueProvisioning(timeoutCtx, ws, provisioningCipher)
|
2023-07-27 18:13:33 -04:00
|
|
|
if err != nil {
|
2024-01-06 11:37:52 -07:00
|
|
|
log.Err(err).Msg("continueProvisioning error")
|
2023-07-27 18:13:33 -04:00
|
|
|
c <- ProvisioningResponse{State: StateProvisioningError, Err: err}
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-05-04 18:04:50 -04:00
|
|
|
ws.Close(websocket.StatusNormalClosure, "")
|
|
|
|
|
|
2024-01-09 13:14:37 +02:00
|
|
|
aciPublicKey := exerrors.Must(libsignalgo.DeserializePublicKey(provisioningMessage.GetAciIdentityKeyPublic()))
|
|
|
|
|
aciPrivateKey := exerrors.Must(libsignalgo.DeserializePrivateKey(provisioningMessage.GetAciIdentityKeyPrivate()))
|
|
|
|
|
aciIdentityKeyPair := exerrors.Must(libsignalgo.NewIdentityKeyPair(aciPublicKey, aciPrivateKey))
|
|
|
|
|
pniPublicKey := exerrors.Must(libsignalgo.DeserializePublicKey(provisioningMessage.GetPniIdentityKeyPublic()))
|
|
|
|
|
pniPrivateKey := exerrors.Must(libsignalgo.DeserializePrivateKey(provisioningMessage.GetPniIdentityKeyPrivate()))
|
|
|
|
|
pniIdentityKeyPair := exerrors.Must(libsignalgo.NewIdentityKeyPair(pniPublicKey, pniPrivateKey))
|
2023-06-22 17:28:11 -04:00
|
|
|
profileKey := libsignalgo.ProfileKey(provisioningMessage.GetProfileKey())
|
2023-05-04 18:04:50 -04:00
|
|
|
|
|
|
|
|
username := *provisioningMessage.Number
|
2024-01-09 13:14:37 +02:00
|
|
|
password := random.String(22)
|
2023-05-04 18:04:50 -04:00
|
|
|
code := provisioningMessage.ProvisioningCode
|
2025-11-24 17:38:01 +02:00
|
|
|
aciRegistrationID := mrand.IntN(16383) + 1
|
|
|
|
|
pniRegistrationID := mrand.IntN(16383) + 1
|
2024-03-19 19:15:30 +02:00
|
|
|
aciSignedPreKey := GenerateSignedPreKey(1, aciIdentityKeyPair)
|
|
|
|
|
pniSignedPreKey := GenerateSignedPreKey(1, pniIdentityKeyPair)
|
|
|
|
|
aciPQLastResortPreKey := GenerateKyberPreKeys(1, 1, aciIdentityKeyPair)[0]
|
|
|
|
|
pniPQLastResortPreKey := GenerateKyberPreKeys(1, 1, pniIdentityKeyPair)[0]
|
2023-12-13 15:45:22 +00:00
|
|
|
deviceResponse, err := confirmDevice(
|
|
|
|
|
ctx,
|
|
|
|
|
username,
|
|
|
|
|
password,
|
|
|
|
|
*code,
|
2024-03-25 21:25:00 +02:00
|
|
|
aciRegistrationID,
|
|
|
|
|
pniRegistrationID,
|
2023-12-13 15:45:22 +00:00
|
|
|
aciSignedPreKey,
|
|
|
|
|
pniSignedPreKey,
|
2024-01-02 23:47:18 +02:00
|
|
|
aciPQLastResortPreKey,
|
|
|
|
|
pniPQLastResortPreKey,
|
2023-12-30 20:47:28 +01:00
|
|
|
aciIdentityKeyPair,
|
|
|
|
|
deviceName,
|
2023-12-13 15:45:22 +00:00
|
|
|
)
|
2023-05-07 22:15:46 -04:00
|
|
|
if err != nil {
|
2024-01-06 11:37:52 -07:00
|
|
|
log.Err(err).Msg("confirmDevice error")
|
2023-05-07 22:15:46 -04:00
|
|
|
c <- ProvisioningResponse{State: StateProvisioningError, Err: err}
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-05-04 18:04:50 -04:00
|
|
|
|
|
|
|
|
deviceId := 1
|
2024-01-03 23:07:25 +02:00
|
|
|
if deviceResponse.DeviceID != 0 {
|
|
|
|
|
deviceId = deviceResponse.DeviceID
|
2023-05-04 18:04:50 -04:00
|
|
|
}
|
|
|
|
|
|
2024-01-05 13:44:41 +02:00
|
|
|
data := &store.DeviceData{
|
2024-01-03 23:07:25 +02:00
|
|
|
ACIIdentityKeyPair: aciIdentityKeyPair,
|
|
|
|
|
PNIIdentityKeyPair: pniIdentityKeyPair,
|
2024-03-25 21:25:00 +02:00
|
|
|
ACIRegistrationID: aciRegistrationID,
|
|
|
|
|
PNIRegistrationID: pniRegistrationID,
|
2024-01-03 23:07:25 +02:00
|
|
|
ACI: deviceResponse.ACI,
|
|
|
|
|
PNI: deviceResponse.PNI,
|
|
|
|
|
DeviceID: deviceId,
|
2023-05-05 10:26:27 -04:00
|
|
|
Number: *provisioningMessage.Number,
|
|
|
|
|
Password: password,
|
2024-11-13 16:38:12 +02:00
|
|
|
MasterKey: provisioningMessage.GetMasterKey(),
|
2025-01-18 02:58:43 +02:00
|
|
|
AccountEntropyPool: libsignalgo.AccountEntropyPool(provisioningMessage.GetAccountEntropyPool()),
|
|
|
|
|
EphemeralBackupKey: libsignalgo.BytesToBackupKey(provisioningMessage.GetEphemeralBackupKey()),
|
|
|
|
|
MediaRootBackupKey: libsignalgo.BytesToBackupKey(provisioningMessage.GetMediaRootBackupKey()),
|
2023-05-04 18:04:50 -04:00
|
|
|
}
|
2025-01-18 02:58:43 +02:00
|
|
|
if provisioningMessage.GetAccountEntropyPool() != "" {
|
|
|
|
|
var masterKey []byte
|
|
|
|
|
masterKey, err = libsignalgo.AccountEntropyPool(provisioningMessage.GetAccountEntropyPool()).DeriveSVRKey()
|
2024-11-18 15:18:44 +02:00
|
|
|
if err != nil {
|
|
|
|
|
log.Err(err).Msg("Failed to derive master key from account entropy pool")
|
|
|
|
|
} else {
|
|
|
|
|
log.Debug().Msg("Derived master key from account entropy pool")
|
|
|
|
|
}
|
2025-01-18 02:58:43 +02:00
|
|
|
if data.MasterKey == nil {
|
|
|
|
|
data.MasterKey = masterKey
|
|
|
|
|
} else if !hmac.Equal(data.MasterKey, masterKey) {
|
|
|
|
|
log.Warn().Msg("Master key mismatch")
|
|
|
|
|
}
|
2024-11-18 15:18:44 +02:00
|
|
|
}
|
2023-05-07 10:07:48 -04:00
|
|
|
|
|
|
|
|
// Store the provisioning data
|
2024-01-04 01:06:45 +02:00
|
|
|
err = deviceStore.PutDevice(ctx, data)
|
2023-05-07 17:40:36 -04:00
|
|
|
if err != nil {
|
2024-01-06 11:37:52 -07:00
|
|
|
log.Err(err).Msg("error storing new device")
|
2023-05-07 17:40:36 -04:00
|
|
|
c <- ProvisioningResponse{State: StateProvisioningError, Err: err}
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-05-07 10:07:48 -04:00
|
|
|
|
2024-01-04 01:06:45 +02:00
|
|
|
device, err := deviceStore.DeviceByACI(ctx, data.ACI)
|
2023-05-31 16:39:09 -04:00
|
|
|
if err != nil {
|
2024-01-06 11:37:52 -07:00
|
|
|
log.Err(err).Msg("error retrieving new device")
|
2023-05-31 16:39:09 -04:00
|
|
|
c <- ProvisioningResponse{State: StateProvisioningError, Err: err}
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-11-17 16:25:43 -05:00
|
|
|
|
|
|
|
|
// In case this is an existing device, we gotta clear out keys
|
2024-01-04 01:06:45 +02:00
|
|
|
device.ClearDeviceKeys(ctx)
|
2023-11-17 16:25:43 -05:00
|
|
|
|
|
|
|
|
// Store identity keys?
|
2024-03-25 21:25:00 +02:00
|
|
|
_, err = device.IdentityKeyStore.SaveIdentityKey(ctx, device.ACIServiceID(), device.ACIIdentityKeyPair.GetIdentityKey())
|
2024-01-06 14:53:48 -07:00
|
|
|
if err != nil {
|
|
|
|
|
c <- ProvisioningResponse{
|
|
|
|
|
State: StateProvisioningError,
|
2024-03-21 16:24:03 +02:00
|
|
|
Err: fmt.Errorf("error saving identity key: %w", err),
|
2024-01-06 14:53:48 -07:00
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
2024-03-25 21:25:00 +02:00
|
|
|
_, err = device.IdentityKeyStore.SaveIdentityKey(ctx, device.PNIServiceID(), device.PNIIdentityKeyPair.GetIdentityKey())
|
2023-05-31 16:39:09 -04:00
|
|
|
if err != nil {
|
2024-01-06 14:53:48 -07:00
|
|
|
c <- ProvisioningResponse{
|
|
|
|
|
State: StateProvisioningError,
|
|
|
|
|
Err: fmt.Errorf("error saving identity key: %w", err),
|
|
|
|
|
}
|
2023-05-31 16:39:09 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-12 22:54:38 +00:00
|
|
|
// Store signed prekeys (now that we have a device)
|
2024-03-19 19:15:30 +02:00
|
|
|
device.ACIPreKeyStore.StoreSignedPreKey(ctx, 1, aciSignedPreKey)
|
|
|
|
|
device.PNIPreKeyStore.StoreSignedPreKey(ctx, 1, pniSignedPreKey)
|
|
|
|
|
device.ACIPreKeyStore.StoreLastResortKyberPreKey(ctx, 1, aciPQLastResortPreKey)
|
|
|
|
|
device.PNIPreKeyStore.StoreLastResortKyberPreKey(ctx, 1, pniPQLastResortPreKey)
|
2023-12-12 22:54:38 +00:00
|
|
|
|
2023-06-22 17:28:11 -04:00
|
|
|
// Store our profile key
|
2024-03-22 21:24:30 +02:00
|
|
|
err = device.RecipientStore.StoreRecipient(ctx, &types.Recipient{
|
|
|
|
|
ACI: data.ACI,
|
|
|
|
|
PNI: data.PNI,
|
|
|
|
|
E164: data.Number,
|
|
|
|
|
Profile: types.Profile{
|
|
|
|
|
Key: profileKey,
|
|
|
|
|
},
|
|
|
|
|
})
|
2023-06-22 17:28:11 -04:00
|
|
|
if err != nil {
|
2024-01-06 14:53:48 -07:00
|
|
|
c <- ProvisioningResponse{
|
|
|
|
|
State: StateProvisioningError,
|
|
|
|
|
Err: fmt.Errorf("error storing profile key: %w", err),
|
|
|
|
|
}
|
2023-06-22 17:28:11 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-07 10:07:48 -04:00
|
|
|
// Return the provisioning data
|
2023-05-07 11:40:46 -04:00
|
|
|
c <- ProvisioningResponse{State: StateProvisioningDataReceived, ProvisioningData: data}
|
2023-05-04 18:04:50 -04:00
|
|
|
}()
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns the provisioningUrl and an error
|
2025-01-18 02:58:43 +02:00
|
|
|
func startProvisioning(ctx context.Context, ws *websocket.Conn, provisioningCipher *ProvisioningCipher, allowBackup bool) (string, error) {
|
2024-01-06 10:44:36 -07:00
|
|
|
log := zerolog.Ctx(ctx).With().Str("action", "start provisioning").Logger()
|
2023-05-04 18:04:50 -04:00
|
|
|
pubKey := provisioningCipher.GetPublicKey()
|
|
|
|
|
|
|
|
|
|
msg := &signalpb.WebSocketMessage{}
|
|
|
|
|
err := wspb.Read(ctx, ws, msg)
|
|
|
|
|
if err != nil {
|
2024-01-06 10:44:36 -07:00
|
|
|
log.Err(err).Msg("error reading websocket message")
|
2023-05-04 18:04:50 -04:00
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure the message is a request and has a valid verb and path
|
2024-01-09 13:14:37 +02:00
|
|
|
if msg.GetType() != signalpb.WebSocketMessage_REQUEST || msg.GetRequest().GetVerb() != http.MethodPut || msg.GetRequest().GetPath() != "/v1/address" {
|
|
|
|
|
return "", fmt.Errorf("unexpected websocket message: %v", msg)
|
|
|
|
|
}
|
2023-05-04 18:04:50 -04:00
|
|
|
|
2024-12-02 13:14:44 +02:00
|
|
|
var provisioningBody signalpb.ProvisioningAddress
|
2024-01-09 13:14:37 +02:00
|
|
|
err = proto.Unmarshal(msg.GetRequest().GetBody(), &provisioningBody)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("failed to unmarshal provisioning UUID: %w", err)
|
|
|
|
|
}
|
2023-05-04 18:04:50 -04:00
|
|
|
|
2025-11-17 15:40:36 +02:00
|
|
|
linkCapabilities := []string{"backup4,backup5"}
|
2025-01-18 02:58:43 +02:00
|
|
|
if !allowBackup {
|
|
|
|
|
linkCapabilities = []string{}
|
|
|
|
|
}
|
2024-01-09 13:14:37 +02:00
|
|
|
provisioningURL := (&url.URL{
|
|
|
|
|
Scheme: "sgnl",
|
|
|
|
|
Host: "linkdevice",
|
|
|
|
|
RawQuery: url.Values{
|
2024-12-02 13:14:44 +02:00
|
|
|
"uuid": []string{provisioningBody.GetAddress()},
|
|
|
|
|
"pub_key": []string{base64.StdEncoding.EncodeToString(exerrors.Must(pubKey.Serialize()))},
|
2025-01-18 02:58:43 +02:00
|
|
|
"capabilities": linkCapabilities,
|
2024-01-09 13:14:37 +02:00
|
|
|
}.Encode(),
|
|
|
|
|
}).String()
|
|
|
|
|
|
|
|
|
|
// Create and send response
|
|
|
|
|
response := web.CreateWSResponse(ctx, msg.GetRequest().GetId(), 200)
|
|
|
|
|
err = wspb.Write(ctx, ws, response)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Err(err).Msg("error writing websocket message")
|
|
|
|
|
return "", err
|
2023-05-04 18:04:50 -04:00
|
|
|
}
|
2024-01-09 13:14:37 +02:00
|
|
|
return provisioningURL, nil
|
2023-05-04 18:04:50 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func continueProvisioning(ctx context.Context, ws *websocket.Conn, provisioningCipher *ProvisioningCipher) (*signalpb.ProvisionMessage, error) {
|
2024-01-06 10:44:36 -07:00
|
|
|
log := zerolog.Ctx(ctx).With().Str("action", "continue provisioning").Logger()
|
2023-05-04 18:04:50 -04:00
|
|
|
envelope := &signalpb.ProvisionEnvelope{}
|
|
|
|
|
msg := &signalpb.WebSocketMessage{}
|
|
|
|
|
err := wspb.Read(ctx, ws, msg)
|
|
|
|
|
if err != nil {
|
2024-01-06 10:44:36 -07:00
|
|
|
log.Err(err).Msg("error reading websocket message")
|
2023-05-04 18:04:50 -04:00
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Wait for provisioning message in a request, then send a response
|
|
|
|
|
if *msg.Type == signalpb.WebSocketMessage_REQUEST &&
|
2024-01-02 21:35:42 -07:00
|
|
|
*msg.Request.Verb == http.MethodPut &&
|
2023-05-04 18:04:50 -04:00
|
|
|
*msg.Request.Path == "/v1/message" {
|
|
|
|
|
|
|
|
|
|
err = proto.Unmarshal(msg.Request.Body, envelope)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-06 10:44:36 -07:00
|
|
|
response := web.CreateWSResponse(ctx, *msg.Request.Id, 200)
|
2023-05-04 18:04:50 -04:00
|
|
|
err = wspb.Write(ctx, ws, response)
|
|
|
|
|
if err != nil {
|
2024-01-06 10:44:36 -07:00
|
|
|
log.Err(err).Msg("error writing websocket message")
|
2023-05-04 18:04:50 -04:00
|
|
|
return nil, err
|
|
|
|
|
}
|
2023-05-08 14:47:00 -04:00
|
|
|
} else {
|
2023-07-27 18:13:33 -04:00
|
|
|
err = fmt.Errorf("invalid provisioning message, type: %v, verb: %v, path: %v", *msg.Type, *msg.Request.Verb, *msg.Request.Path)
|
2024-01-06 10:44:36 -07:00
|
|
|
log.Err(err).Msg("problem reading websocket message")
|
2023-07-27 18:13:33 -04:00
|
|
|
return nil, err
|
2023-05-04 18:04:50 -04:00
|
|
|
}
|
2023-07-27 18:13:33 -04:00
|
|
|
provisioningMessage, err := provisioningCipher.Decrypt(envelope)
|
|
|
|
|
return provisioningMessage, err
|
2023-05-04 18:04:50 -04:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 20:12:28 +03:00
|
|
|
var signalCapabilities = map[string]any{
|
2025-08-29 12:12:00 +03:00
|
|
|
"attachmentBackfill": true,
|
2025-10-05 00:28:12 +03:00
|
|
|
"spqr": true,
|
2024-09-03 20:12:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var signalCapabilitiesBody = exerrors.Must(json.Marshal(signalCapabilities))
|
|
|
|
|
|
|
|
|
|
func (cli *Client) RegisterCapabilities(ctx context.Context) error {
|
2025-11-24 15:03:02 +02:00
|
|
|
resp, err := cli.AuthedWS.SendRequest(ctx, http.MethodPut, "/v1/devices/capabilities", signalCapabilitiesBody, nil)
|
2024-09-03 20:12:28 +03:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2025-11-24 15:03:02 +02:00
|
|
|
return web.DecodeWSResponseBody(ctx, nil, resp)
|
2024-09-03 20:12:28 +03:00
|
|
|
}
|
|
|
|
|
|
2025-01-19 20:47:07 +02:00
|
|
|
func (cli *Client) Unlink(ctx context.Context) error {
|
2025-11-24 15:03:02 +02:00
|
|
|
resp, err := cli.AuthedWS.SendRequest(ctx, http.MethodDelete, fmt.Sprintf("/v1/devices/%d", cli.Store.DeviceID), nil, nil)
|
2025-01-19 20:47:07 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2025-11-24 15:03:02 +02:00
|
|
|
return web.DecodeWSResponseBody(ctx, nil, resp)
|
2025-01-19 20:47:07 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-12 22:54:38 +00:00
|
|
|
func confirmDevice(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
username string,
|
|
|
|
|
password string,
|
|
|
|
|
code string,
|
2024-03-25 21:25:00 +02:00
|
|
|
aciRegistrationID int,
|
|
|
|
|
pniRegistrationID int,
|
2023-12-12 22:54:38 +00:00
|
|
|
aciSignedPreKey *libsignalgo.SignedPreKeyRecord,
|
|
|
|
|
pniSignedPreKey *libsignalgo.SignedPreKeyRecord,
|
2023-12-13 15:45:22 +00:00
|
|
|
aciPQLastResortPreKey *libsignalgo.KyberPreKeyRecord,
|
|
|
|
|
pniPQLastResortPreKey *libsignalgo.KyberPreKeyRecord,
|
2023-12-30 20:47:28 +01:00
|
|
|
aciIdentityKeyPair *libsignalgo.IdentityKeyPair,
|
|
|
|
|
deviceName string,
|
2023-12-12 22:54:38 +00:00
|
|
|
) (*ConfirmDeviceResponse, error) {
|
2024-01-06 11:37:52 -07:00
|
|
|
log := zerolog.Ctx(ctx).With().Str("action", "confirm device").Logger()
|
|
|
|
|
ctx = log.WithContext(ctx)
|
2023-12-30 20:47:28 +01:00
|
|
|
encryptedDeviceName, err := EncryptDeviceName(deviceName, aciIdentityKeyPair.GetPublicKey())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to encrypt device name: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-15 23:40:50 +02:00
|
|
|
ws, resp, err := web.OpenWebsocket(ctx, (&url.URL{
|
|
|
|
|
Scheme: "wss",
|
|
|
|
|
Host: web.APIHostname,
|
|
|
|
|
Path: web.WebsocketPath,
|
|
|
|
|
}).String())
|
2023-07-27 18:13:33 -04:00
|
|
|
if err != nil {
|
2024-01-06 11:37:52 -07:00
|
|
|
log.Err(err).Any("resp", resp).Msg("error opening websocket")
|
2023-07-27 18:13:33 -04:00
|
|
|
return nil, err
|
|
|
|
|
}
|
2023-05-04 18:04:50 -04:00
|
|
|
defer ws.Close(websocket.StatusInternalError, "Websocket StatusInternalError")
|
|
|
|
|
|
2024-04-01 02:15:31 +03:00
|
|
|
aciSignedPreKeyJson, err := SignedPreKeyToJSON(aciSignedPreKey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to convert signed ACI prekey to JSON: %w", err)
|
|
|
|
|
}
|
|
|
|
|
pniSignedPreKeyJson, err := SignedPreKeyToJSON(pniSignedPreKey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to convert signed PNI prekey to JSON: %w", err)
|
|
|
|
|
}
|
2023-12-13 15:45:22 +00:00
|
|
|
|
2024-04-01 02:15:31 +03:00
|
|
|
aciPQLastResortPreKeyJson, err := KyberPreKeyToJSON(aciPQLastResortPreKey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to convert ACI kyber last resort prekey to JSON: %w", err)
|
|
|
|
|
}
|
|
|
|
|
pniPQLastResortPreKeyJson, err := KyberPreKeyToJSON(pniPQLastResortPreKey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to convert PNI kyber last resort prekey to JSON: %w", err)
|
|
|
|
|
}
|
2023-12-12 22:54:38 +00:00
|
|
|
|
2024-01-06 11:37:52 -07:00
|
|
|
data := map[string]any{
|
2023-12-12 22:54:38 +00:00
|
|
|
"verificationCode": code,
|
2024-01-06 11:37:52 -07:00
|
|
|
"accountAttributes": map[string]any{
|
2023-12-30 20:47:28 +01:00
|
|
|
"fetchesMessages": true,
|
|
|
|
|
"name": encryptedDeviceName,
|
2024-03-25 21:25:00 +02:00
|
|
|
"registrationId": aciRegistrationID,
|
|
|
|
|
"pniRegistrationId": pniRegistrationID,
|
2024-09-03 20:12:28 +03:00
|
|
|
"capabilities": signalCapabilities,
|
2023-05-09 17:09:15 -04:00
|
|
|
},
|
2023-12-12 22:54:38 +00:00
|
|
|
"aciSignedPreKey": aciSignedPreKeyJson,
|
|
|
|
|
"pniSignedPreKey": pniSignedPreKeyJson,
|
2023-12-13 15:45:22 +00:00
|
|
|
"aciPqLastResortPreKey": aciPQLastResortPreKeyJson,
|
|
|
|
|
"pniPqLastResortPreKey": pniPQLastResortPreKeyJson,
|
2023-05-04 18:04:50 -04:00
|
|
|
}
|
2023-12-12 22:54:38 +00:00
|
|
|
|
2023-05-04 18:04:50 -04:00
|
|
|
jsonBytes, err := json.Marshal(data)
|
|
|
|
|
if err != nil {
|
2024-01-06 11:37:52 -07:00
|
|
|
return nil, fmt.Errorf("failed to marshal JSON: %w", err)
|
2023-05-04 18:04:50 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-25 17:21:19 -04:00
|
|
|
// Create and send request TODO: Use SignalWebsocket
|
2024-01-02 21:35:42 -07:00
|
|
|
request := web.CreateWSRequest(http.MethodPut, "/v1/devices/link", jsonBytes, &username, &password)
|
2023-06-25 17:21:19 -04:00
|
|
|
one := uint64(1)
|
|
|
|
|
request.Id = &one
|
2023-05-31 16:39:09 -04:00
|
|
|
msg_type := signalpb.WebSocketMessage_REQUEST
|
|
|
|
|
message := &signalpb.WebSocketMessage{
|
|
|
|
|
Type: &msg_type,
|
|
|
|
|
Request: request,
|
|
|
|
|
}
|
|
|
|
|
err = wspb.Write(ctx, ws, message)
|
2023-05-04 18:04:50 -04:00
|
|
|
if err != nil {
|
2024-01-06 11:37:52 -07:00
|
|
|
return nil, fmt.Errorf("failed on write protobuf data to websocket: %w", err)
|
2023-05-04 18:04:50 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
receivedMsg := &signalpb.WebSocketMessage{}
|
|
|
|
|
err = wspb.Read(ctx, ws, receivedMsg)
|
|
|
|
|
if err != nil {
|
2024-01-06 11:37:52 -07:00
|
|
|
return nil, fmt.Errorf("failed to read from websocket after devices call: %w", err)
|
2023-05-04 18:04:50 -04:00
|
|
|
}
|
|
|
|
|
|
2023-05-07 22:15:46 -04:00
|
|
|
status := int(*receivedMsg.Response.Status)
|
|
|
|
|
if status < 200 || status >= 300 {
|
2024-01-06 11:37:52 -07:00
|
|
|
return nil, fmt.Errorf("non-200 status code (%d) from devices response: %s", status, *receivedMsg.Response.Message)
|
2023-05-04 18:04:50 -04:00
|
|
|
}
|
|
|
|
|
|
2023-12-12 22:54:38 +00:00
|
|
|
// unmarshal JSON response into ConfirmDeviceResponse
|
2023-05-04 18:04:50 -04:00
|
|
|
deviceResp := ConfirmDeviceResponse{}
|
2023-12-12 22:54:38 +00:00
|
|
|
err = json.Unmarshal(receivedMsg.Response.Body, &deviceResp)
|
|
|
|
|
if err != nil {
|
2024-01-06 11:37:52 -07:00
|
|
|
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
|
2023-05-04 18:04:50 -04:00
|
|
|
}
|
|
|
|
|
|
2023-05-07 22:15:46 -04:00
|
|
|
return &deviceResp, nil
|
2023-05-04 18:04:50 -04:00
|
|
|
}
|