From e369a56c314a62bbdd08cf0bf671ef09397285ac Mon Sep 17 00:00:00 2001 From: Scott Weber Date: Thu, 11 Apr 2024 14:31:27 -0400 Subject: [PATCH] Minor incoming messages refactor and send m.notice on decryption errors (#496) We were falling through without propagating errors anywhere. I caught most decryption errors and send them along in the new Err field in DecryptionResult, to be then passed along to the bridge as a new type of chat event. I'm going to do another pass to catch any last decryption errors, but this should be a big improvement over what we currently do. --- pkg/signalmeow/events/message.go | 18 +- pkg/signalmeow/receiving.go | 791 +++++++++++++++++-------------- user.go | 13 + 3 files changed, 459 insertions(+), 363 deletions(-) diff --git a/pkg/signalmeow/events/message.go b/pkg/signalmeow/events/message.go index e8ba1cb1..0db0f247 100644 --- a/pkg/signalmeow/events/message.go +++ b/pkg/signalmeow/events/message.go @@ -28,12 +28,13 @@ type SignalEvent interface { isSignalEvent() } -func (*ChatEvent) isSignalEvent() {} -func (*Receipt) isSignalEvent() {} -func (*ReadSelf) isSignalEvent() {} -func (*Call) isSignalEvent() {} -func (*ContactList) isSignalEvent() {} -func (*ACIFound) isSignalEvent() {} +func (*ChatEvent) isSignalEvent() {} +func (*DecryptionError) isSignalEvent() {} +func (*Receipt) isSignalEvent() {} +func (*ReadSelf) isSignalEvent() {} +func (*Call) isSignalEvent() {} +func (*ContactList) isSignalEvent() {} +func (*ACIFound) isSignalEvent() {} type MessageInfo struct { Sender uuid.UUID @@ -47,6 +48,11 @@ type ChatEvent struct { Event signalpb.ChatEventContent } +type DecryptionError struct { + Sender uuid.UUID + Err error +} + type Receipt struct { Sender uuid.UUID Content *signalpb.ReceiptMessage diff --git a/pkg/signalmeow/receiving.go b/pkg/signalmeow/receiving.go index 55a44e85..ba299146 100644 --- a/pkg/signalmeow/receiving.go +++ b/pkg/signalmeow/receiving.go @@ -263,10 +263,8 @@ func (cli *Client) incomingRequestHandler(ctx context.Context, req *signalpb.Web }, nil } -// TODO: we should split this up into multiple functions func (cli *Client) incomingAPIMessageHandler(ctx context.Context, req *signalpb.WebSocketRequestMessage) (*web.SimpleResponse, error) { log := *zerolog.Ctx(ctx) - responseCode := 200 envelope := &signalpb.Envelope{} err := proto.Unmarshal(req.Body, envelope) if err != nil { @@ -274,11 +272,6 @@ func (cli *Client) incomingAPIMessageHandler(ctx context.Context, req *signalpb. return nil, err } destinationServiceID, err := libsignalgo.ServiceIDFromString(envelope.GetDestinationServiceId()) - if err != nil { - log.Err(err).Str("destination_service_id", envelope.GetDestinationServiceId()).Msg("Failed to parse destination service ID") - return nil, err - } - var result *DecryptionResult log.Trace(). Uint64("timestamp", envelope.GetTimestamp()). Str("destination_service_id", envelope.GetDestinationServiceId()). @@ -289,184 +282,38 @@ func (cli *Client) incomingAPIMessageHandler(ctx context.Context, req *signalpb. Str("envelope_type", signalpb.Envelope_Type_name[int32(envelope.GetType())]). Msg("Received envelope") - switch *envelope.Type { - case signalpb.Envelope_UNIDENTIFIED_SENDER: - if destinationServiceID != cli.Store.ACIServiceID() { - log.Warn().Stringer("destination_service_id", destinationServiceID). - Msg("Received UNIDENTIFIED_SENDER envelope for non-ACI destination") - break - } - usmc, err := libsignalgo.SealedSenderDecryptToUSMC( - ctx, - envelope.GetContent(), - cli.Store.ACIIdentityStore, - ) - if err != nil || usmc == nil { - if err == nil { - err = fmt.Errorf("usmc is nil") - } - log.Err(err).Msg("SealedSenderDecryptToUSMC error") - return nil, err - } + result := cli.decryptEnvelope(ctx, envelope) - messageType, err := usmc.GetMessageType() - if err != nil { - log.Err(err).Msg("GetMessageType error") - } - senderCertificate, err := usmc.GetSenderCertificate() - if err != nil { - log.Err(err).Msg("GetSenderCertificate error") - } - senderUUID, err := senderCertificate.GetSenderUUID() - if err != nil { - log.Err(err).Msg("GetSenderUUID error") - } - senderDeviceID, err := senderCertificate.GetDeviceID() - if err != nil { - log.Err(err).Msg("GetDeviceID error") - } - senderAddress, err := libsignalgo.NewACIServiceID(senderUUID).Address(uint(senderDeviceID)) - if err != nil { - log.Err(err).Msg("NewAddress error") - } - senderE164, err := senderCertificate.GetSenderE164() - if err != nil { - log.Err(err).Msg("GetSenderE164 error") - } - usmcContents, err := usmc.GetContents() - if err != nil { - log.Err(err).Msg("GetContents error") - } - log = log.With(). - Stringer("sender_uuid", senderUUID). - Uint32("sender_device_id", senderDeviceID). - Str("sender_e164", senderE164). - Logger() - ctx = log.WithContext(ctx) - log.Trace().Msg("Received SealedSender message") - - cli.Store.RecipientStore.UpdateRecipientE164(ctx, senderUUID, uuid.Nil, senderE164) - - switch messageType { - case libsignalgo.CiphertextMessageTypeSenderKey: - log.Trace().Msg("SealedSender messageType is CiphertextMessageTypeSenderKey") - decryptedText, err := libsignalgo.GroupDecrypt( - ctx, - usmcContents, - senderAddress, - cli.Store.SenderKeyStore, - ) - if err != nil { - if strings.Contains(err.Error(), "message with old counter") { - log.Warn().Msg("Duplicate message, ignoring") - } else { - log.Err(err).Msg("GroupDecrypt error") - } - } else { - err = stripPadding(&decryptedText) - if err != nil { - return nil, fmt.Errorf("stripPadding error: %v", err) - } - content := signalpb.Content{} - err = proto.Unmarshal(decryptedText, &content) - if err != nil { - log.Err(err).Msg("Unmarshal error") - } - result = &DecryptionResult{ - SenderAddress: senderAddress, - Content: &content, - SealedSender: true, - } - } + err = cli.handleDecryptedResult(ctx, result, destinationServiceID) + if err != nil { + log.Err(err).Msg("handleDecryptedResult error") + return nil, err + } - case libsignalgo.CiphertextMessageTypePreKey: - log.Trace().Msg("SealedSender messageType is CiphertextMessageTypePreKey") - result, err = cli.prekeyDecrypt(ctx, destinationServiceID, senderAddress, usmcContents) - if err != nil { - log.Err(err).Msg("Sealed sender prekey ciphertext decryption error") - } + return &web.SimpleResponse{ + Status: 200, + }, nil +} - case libsignalgo.CiphertextMessageTypeWhisper: - log.Trace().Msg("SealedSender messageType is CiphertextMessageTypeWhisper") - message, err := libsignalgo.DeserializeMessage(usmcContents) - if err != nil { - log.Err(err).Msg("Sealed sender whisper deserialize error") - } - decryptedText, err := libsignalgo.Decrypt( - ctx, - message, - senderAddress, - cli.Store.ACISessionStore, - cli.Store.ACIIdentityStore, - ) - if err != nil { - log.Err(err).Msg("Sealed sender whisper decryption error") - } else { - err = stripPadding(&decryptedText) - if err != nil { - return nil, fmt.Errorf("stripPadding error: %v", err) - } - content := signalpb.Content{} - err = proto.Unmarshal(decryptedText, &content) - if err != nil { - log.Err(err).Msg("Unmarshal error") - } - result = &DecryptionResult{ - SenderAddress: senderAddress, - Content: &content, - SealedSender: true, - } - } +func (cli *Client) decryptEnvelope( + ctx context.Context, + envelope *signalpb.Envelope, +) DecryptionResult { + log := zerolog.Ctx(ctx) - case libsignalgo.CiphertextMessageTypePlaintext: - log.Debug().Msg("SealedSender messageType is CiphertextMessageTypePlaintext") - // TODO: handle plaintext (usually DecryptionErrorMessage) and retries - // when implementing SenderKey groups - - //plaintextContent, err := libsignalgo.DeserializePlaintextContent(usmcContents) - //if err != nil { - // log.Err(err).Msg("DeserializePlaintextContent error") - //} - //body, err := plaintextContent.GetBody() - //if err != nil { - // log.Err(err).Msg("PlaintextContent GetBody error") - //} - //content := signalpb.Content{} - //err = proto.Unmarshal(body, &content) - //if err != nil { - // log.Err(err).Msg("PlaintextContent Unmarshal error") - //} - //result = &DecryptionResult{ - // SenderAddress: *senderAddress, - // Content: &content, - // SealedSender: true, - //} - - return &web.SimpleResponse{ - Status: responseCode, - }, nil - - default: - log.Warn().Msg("SealedSender messageType is unknown") - } + destinationServiceID, err := libsignalgo.ServiceIDFromString(envelope.GetDestinationServiceId()) + if err != nil { + log.Err(err).Str("destination_service_id", envelope.GetDestinationServiceId()).Msg("Failed to parse destination service ID") + return DecryptionResult{Err: err, Urgent: envelope.GetUrgent()} + } + var result *DecryptionResult - // If we couldn't decrypt with specific decryption methods, try sealedSenderDecrypt - if result == nil || responseCode != 200 { - log.Debug().Msg("Didn't decrypt with specific methods, trying sealedSenderDecrypt") - var err error - result, err = cli.sealedSenderDecrypt(ctx, envelope) - if err != nil { - if strings.Contains(err.Error(), "self send of a sealed sender message") { - log.Debug().Msg("Message sent by us, ignoring") - } else { - log.Err(err).Msg("sealedSenderDecrypt error") - } - } else { - log.Trace(). - Any("sender_address", result.SenderAddress). - Any("content", result.Content). - Msg("SealedSender decrypt result") - } + switch *envelope.Type { + case signalpb.Envelope_UNIDENTIFIED_SENDER: + result, err = cli.decryptUnidentifiedSenderEnvelope(ctx, destinationServiceID, envelope) + if err != nil { + log.Err(err).Msg("decryptUnidentifiedSenderEnvelope error") + return DecryptionResult{Err: err, Urgent: envelope.GetUrgent()} } case signalpb.Envelope_PREKEY_BUNDLE: @@ -475,7 +322,7 @@ func (cli *Client) incomingAPIMessageHandler(ctx context.Context, req *signalpb. uint(*envelope.SourceDevice), ) if err != nil { - return nil, fmt.Errorf("NewAddress error: %v", err) + return DecryptionResult{Err: fmt.Errorf("NewAddress error: %v", err), Urgent: envelope.GetUrgent()} } result, err = cli.prekeyDecrypt(ctx, destinationServiceID, sender, envelope.Content) if err != nil { @@ -499,15 +346,21 @@ func (cli *Client) incomingAPIMessageHandler(ctx context.Context, req *signalpb. uint(*envelope.SourceDevice), ) if err != nil { - return nil, fmt.Errorf("NewAddress error: %w", err) + return DecryptionResult{Err: fmt.Errorf("NewAddress error: %v", err), Urgent: envelope.GetUrgent()} } sessionStore := cli.Store.SessionStore(destinationServiceID) if sessionStore == nil { - return nil, fmt.Errorf("no session store for destination service ID %s", destinationServiceID) + return DecryptionResult{ + Err: fmt.Errorf("no session store for destination service ID %s", destinationServiceID), + Urgent: envelope.GetUrgent(), + } } identityStore := cli.Store.IdentityStore(destinationServiceID) if identityStore == nil { - return nil, fmt.Errorf("no identity store for destination service ID %s", destinationServiceID) + return DecryptionResult{ + Err: fmt.Errorf("no identity store for destination service ID %s", destinationServiceID), + Urgent: envelope.GetUrgent(), + } } decryptedText, err := libsignalgo.Decrypt( ctx, @@ -525,7 +378,7 @@ func (cli *Client) incomingAPIMessageHandler(ctx context.Context, req *signalpb. } else { err = stripPadding(&decryptedText) if err != nil { - return nil, fmt.Errorf("stripPadding error: %v", err) + return DecryptionResult{Err: fmt.Errorf("stripPadding error: %v", err), Urgent: envelope.GetUrgent()} } content := signalpb.Content{} err = proto.Unmarshal(decryptedText, &content) @@ -539,226 +392,448 @@ func (cli *Client) incomingAPIMessageHandler(ctx context.Context, req *signalpb. } case signalpb.Envelope_RECEIPT: + log.Warn().Msg("Received RECEIPT envelope, not yet implemented") // TODO: handle receipt case signalpb.Envelope_KEY_EXCHANGE: - responseCode = 400 + log.Warn().Msg("Received KEY_EXCHANGE envelope, not supported") case signalpb.Envelope_UNKNOWN: - responseCode = 400 + log.Warn().Msg("Received UNKNOWN envelope, not supported") default: log.Warn().Msg("Received actual unknown envelope type") - responseCode = 400 } + if result == nil { + log.Warn().Msg("Decryption result is nil") + return DecryptionResult{ + Err: fmt.Errorf("decryption result is nil"), + Urgent: envelope.GetUrgent(), + } + } + result.Urgent = envelope.GetUrgent() + return *result +} + +func (cli *Client) decryptUnidentifiedSenderEnvelope(ctx context.Context, destinationServiceID libsignalgo.ServiceID, envelope *signalpb.Envelope) (*DecryptionResult, error) { + log := zerolog.Ctx(ctx) + var result *DecryptionResult + + if destinationServiceID != cli.Store.ACIServiceID() { + log.Warn().Stringer("destination_service_id", destinationServiceID). + Msg("Received UNIDENTIFIED_SENDER envelope for non-ACI destination") + // Return nil, nil to skip the rest of the handling and return 200 OK to the server + return nil, nil + } + usmc, err := libsignalgo.SealedSenderDecryptToUSMC( + ctx, + envelope.GetContent(), + cli.Store.ACIIdentityStore, + ) + if err != nil || usmc == nil { + if err == nil { + err = fmt.Errorf("usmc is nil") + } + log.Err(err).Msg("SealedSenderDecryptToUSMC error") + return nil, err + } + + messageType, err := usmc.GetMessageType() + if err != nil { + log.Err(err).Msg("GetMessageType error") + } + senderCertificate, err := usmc.GetSenderCertificate() + if err != nil { + log.Err(err).Msg("GetSenderCertificate error") + } + senderUUID, err := senderCertificate.GetSenderUUID() + if err != nil { + log.Err(err).Msg("GetSenderUUID error") + } + senderDeviceID, err := senderCertificate.GetDeviceID() + if err != nil { + log.Err(err).Msg("GetDeviceID error") + } + senderAddress, err := libsignalgo.NewACIServiceID(senderUUID).Address(uint(senderDeviceID)) + if err != nil { + log.Err(err).Msg("NewAddress error") + } + senderE164, err := senderCertificate.GetSenderE164() + if err != nil { + log.Err(err).Msg("GetSenderE164 error") + } + usmcContents, err := usmc.GetContents() + if err != nil { + log.Err(err).Msg("GetContents error") + } + newLog := log.With(). + Stringer("sender_uuid", senderUUID). + Uint32("sender_device_id", senderDeviceID). + Str("sender_e164", senderE164). + Logger() + log = &newLog + ctx = log.WithContext(ctx) + log.Trace().Msg("Received SealedSender message") + + cli.Store.RecipientStore.UpdateRecipientE164(ctx, senderUUID, uuid.Nil, senderE164) + + switch messageType { + case libsignalgo.CiphertextMessageTypeSenderKey: + log.Trace().Msg("SealedSender messageType is CiphertextMessageTypeSenderKey") + decryptedText, err := libsignalgo.GroupDecrypt( + ctx, + usmcContents, + senderAddress, + cli.Store.SenderKeyStore, + ) + if err != nil { + if strings.Contains(err.Error(), "message with old counter") { + log.Warn().Msg("Duplicate message, ignoring") + } else { + log.Err(err).Msg("GroupDecrypt error") + } + } else { + err = stripPadding(&decryptedText) + if err != nil { + return nil, fmt.Errorf("stripPadding error: %v", err) + } + content := signalpb.Content{} + err = proto.Unmarshal(decryptedText, &content) + if err != nil { + log.Err(err).Msg("Unmarshal error") + } + result = &DecryptionResult{ + SenderAddress: senderAddress, + Content: &content, + SealedSender: true, + } + } - // Handle content that is now decrypted - if result != nil && result.Content != nil { - content := result.Content - log.Trace().Any("raw_data", content).Msg("Raw event data") - - name, _ := result.SenderAddress.Name() - deviceId, _ := result.SenderAddress.DeviceID() - log = log.With(). - Str("sender_name", name). - Uint("sender_device_id", deviceId). - Str("destination_service_id", destinationServiceID.String()). - Logger() - ctx = log.WithContext(ctx) - log.Debug().Msg("Decrypted message") - printContentFieldString(ctx, content, "Decrypted content fields") - - // If there's a sender key distribution message, process it - if content.GetSenderKeyDistributionMessage() != nil { - log.Debug().Msg("content includes sender key distribution message") - skdm, err := libsignalgo.DeserializeSenderKeyDistributionMessage(content.GetSenderKeyDistributionMessage()) + case libsignalgo.CiphertextMessageTypePreKey: + log.Trace().Msg("SealedSender messageType is CiphertextMessageTypePreKey") + result, err = cli.prekeyDecrypt(ctx, destinationServiceID, senderAddress, usmcContents) + if err != nil { + log.Err(err).Msg("Sealed sender prekey ciphertext decryption error") + } + + case libsignalgo.CiphertextMessageTypeWhisper: + log.Trace().Msg("SealedSender messageType is CiphertextMessageTypeWhisper") + message, err := libsignalgo.DeserializeMessage(usmcContents) + if err != nil { + log.Err(err).Msg("Sealed sender whisper deserialize error") + } + decryptedText, err := libsignalgo.Decrypt( + ctx, + message, + senderAddress, + cli.Store.ACISessionStore, + cli.Store.ACIIdentityStore, + ) + if err != nil { + log.Err(err).Msg("Sealed sender whisper decryption error") + } else { + err = stripPadding(&decryptedText) if err != nil { - log.Err(err).Msg("DeserializeSenderKeyDistributionMessage error") - return nil, err + return nil, fmt.Errorf("stripPadding error: %v", err) } - err = libsignalgo.ProcessSenderKeyDistributionMessage( - ctx, - skdm, - result.SenderAddress, - cli.Store.SenderKeyStore, - ) + content := signalpb.Content{} + err = proto.Unmarshal(decryptedText, &content) if err != nil { - log.Err(err).Msg("ProcessSenderKeyDistributionMessage error") - return nil, err + log.Err(err).Msg("Unmarshal error") + } + result = &DecryptionResult{ + SenderAddress: senderAddress, + Content: &content, + SealedSender: true, } } + case libsignalgo.CiphertextMessageTypePlaintext: + log.Debug().Msg("SealedSender messageType is CiphertextMessageTypePlaintext") + // TODO: handle plaintext (usually DecryptionErrorMessage) and retries + // when implementing SenderKey groups + + //plaintextContent, err := libsignalgo.DeserializePlaintextContent(usmcContents) + //if err != nil { + // log.Err(err).Msg("DeserializePlaintextContent error") + //} + //body, err := plaintextContent.GetBody() + //if err != nil { + // log.Err(err).Msg("PlaintextContent GetBody error") + //} + //content := signalpb.Content{} + //err = proto.Unmarshal(body, &content) + //if err != nil { + // log.Err(err).Msg("PlaintextContent Unmarshal error") + //} + //result = &DecryptionResult{ + // SenderAddress: *senderAddress, + // Content: &content, + // SealedSender: true, + //} + + return nil, nil + + default: + log.Warn().Msg("SealedSender messageType is unknown") + } + + // If we couldn't decrypt with specific decryption methods, try sealedSenderDecrypt + if result == nil { + log.Debug().Msg("Didn't decrypt with specific methods, trying sealedSenderDecrypt") + var err error + result, err = cli.sealedSenderDecrypt(ctx, envelope) + if err != nil { + if strings.Contains(err.Error(), "self send of a sealed sender message") { + log.Debug().Msg("Message sent by us, ignoring") + } else if strings.Contains(err.Error(), "message with old counter") { + log.Info().Msg("Duplicate message, ignoring (sealedSenderDecrypt)") + } else { + log.Err(err).Msg("sealedSenderDecrypt error") + } + } else { + log.Trace(). + Any("sender_address", result.SenderAddress). + Any("content", result.Content). + Msg("SealedSender decrypt result") + } + } + return result, nil +} + +// TODO: we should split this up into multiple functions +func (cli *Client) handleDecryptedResult( + ctx context.Context, + result DecryptionResult, + destinationServiceID libsignalgo.ServiceID, +) error { + log := zerolog.Ctx(ctx) + if result.Content == nil { + log.Warn().Msg("Decrypted content is nil") + return nil + } + + // result.Err is set if there was an error during decryption and we + // should notifiy the user that the message could not be decrypted + if result.Err != nil { + log.Err(result.Err).Bool("urgent", result.Urgent).Msg("Decryption error") theirServiceID, err := result.SenderAddress.NameServiceID() if err != nil { - log.Err(err).Msg("Name error") - return nil, err + log.Err(err).Msg("Name error handling decryption error") } else if theirServiceID.Type != libsignalgo.ServiceIDTypeACI { log.Warn().Any("their_service_id", theirServiceID).Msg("Sender ServiceID is not an ACI") - return &web.SimpleResponse{ - Status: responseCode, - }, nil } - - if destinationServiceID == cli.Store.PNIServiceID() { - _, err = cli.Store.RecipientStore.LoadAndUpdateRecipient(ctx, theirServiceID.UUID, uuid.Nil, func(recipient *types.Recipient) (changed bool, err error) { - if !recipient.NeedsPNISignature { - log.Debug().Msg("Marking recipient as needing PNI signature") - recipient.NeedsPNISignature = true - return true, nil - } - return false, nil + // Only send decryption error event if the message was urgent, + // to prevent spamming errors for typing notifications and whatnot + if result.Urgent { + cli.handleEvent(&events.DecryptionError{ + Sender: theirServiceID.UUID, + Err: result.Err, }) - if err != nil { - log.Err(err).Msg("Failed to set needs_pni_signature flag after receiving message to PNI service ID") + } + // Intentionally not returning here. In most cases there's nothing besides the error so + // nothing else will happen, but if there is content we still want to do what we can with it + } + + content := result.Content + log.Trace().Any("raw_data", content).Msg("Raw event data") + + name, _ := result.SenderAddress.Name() + deviceId, _ := result.SenderAddress.DeviceID() + newLog := log.With(). + Str("sender_name", name). + Uint("sender_device_id", deviceId). + Str("destination_service_id", destinationServiceID.String()). + Logger() + log = &newLog + ctx = log.WithContext(ctx) + log.Debug().Msg("Decrypted message") + printContentFieldString(ctx, content, "Decrypted content fields") + + // If there's a sender key distribution message, process it + if content.GetSenderKeyDistributionMessage() != nil { + log.Debug().Msg("content includes sender key distribution message") + skdm, err := libsignalgo.DeserializeSenderKeyDistributionMessage(content.GetSenderKeyDistributionMessage()) + if err != nil { + log.Err(err).Msg("DeserializeSenderKeyDistributionMessage error") + return err + } + err = libsignalgo.ProcessSenderKeyDistributionMessage( + ctx, + skdm, + result.SenderAddress, + cli.Store.SenderKeyStore, + ) + if err != nil { + log.Err(err).Msg("ProcessSenderKeyDistributionMessage error") + return err + } + } + + theirServiceID, err := result.SenderAddress.NameServiceID() + if err != nil { + log.Err(err).Msg("Name error") + return err + } else if theirServiceID.Type != libsignalgo.ServiceIDTypeACI { + log.Warn().Any("their_service_id", theirServiceID).Msg("Sender ServiceID is not an ACI") + return nil + } + + if destinationServiceID == cli.Store.PNIServiceID() { + _, err = cli.Store.RecipientStore.LoadAndUpdateRecipient(ctx, theirServiceID.UUID, uuid.Nil, func(recipient *types.Recipient) (changed bool, err error) { + if !recipient.NeedsPNISignature { + log.Debug().Msg("Marking recipient as needing PNI signature") + recipient.NeedsPNISignature = true + return true, nil } + return false, nil + }) + if err != nil { + log.Err(err).Msg("Failed to set needs_pni_signature flag after receiving message to PNI service ID") + } + } + + if content.GetPniSignatureMessage() != nil { + log.Debug().Msg("Content includes PNI signature message") + err = cli.handlePNISignatureMessage(ctx, theirServiceID, content.GetPniSignatureMessage()) + if err != nil { + log.Err(err). + Hex("pni_raw", content.GetPniSignatureMessage().GetPni()). + Stringer("aci", theirServiceID.UUID). + Msg("Failed to verify ACI-PNI mapping") } + } - if content.GetPniSignatureMessage() != nil { - log.Debug().Msg("Content includes PNI signature message") - err = cli.handlePNISignatureMessage(ctx, theirServiceID, content.GetPniSignatureMessage()) + // TODO: handle more sync messages + if content.SyncMessage != nil { + if content.SyncMessage.Keys != nil { + cli.Store.MasterKey = content.SyncMessage.Keys.GetMaster() + err = cli.Store.DeviceStore.PutDevice(ctx, &cli.Store.DeviceData) if err != nil { - log.Err(err). - Hex("pni_raw", content.GetPniSignatureMessage().GetPni()). - Stringer("aci", theirServiceID.UUID). - Msg("Failed to verify ACI-PNI mapping") + log.Err(err).Msg("Failed to save device after receiving master key") + } else { + log.Info().Msg("Received master key") + go cli.SyncStorage(ctx) } + } else if content.SyncMessage.GetFetchLatest().GetType() == signalpb.SyncMessage_FetchLatest_STORAGE_MANIFEST { + log.Debug().Msg("Received storage manifest fetch latest notice") + go cli.SyncStorage(ctx) } - - // TODO: handle more sync messages - if content.SyncMessage != nil { - if content.SyncMessage.Keys != nil { - cli.Store.MasterKey = content.SyncMessage.Keys.GetMaster() - err = cli.Store.DeviceStore.PutDevice(ctx, &cli.Store.DeviceData) + syncSent := content.SyncMessage.GetSent() + if syncSent.GetMessage() != nil || syncSent.GetEditMessage() != nil { + destination := syncSent.DestinationServiceId + var syncDestinationServiceID libsignalgo.ServiceID + if destination != nil { + syncDestinationServiceID, err = libsignalgo.ServiceIDFromString(*destination) if err != nil { - log.Err(err).Msg("Failed to save device after receiving master key") - } else { - log.Info().Msg("Received master key") - go cli.SyncStorage(ctx) + log.Err(err).Msg("Sync message destination parse error") + return err } - } else if content.SyncMessage.GetFetchLatest().GetType() == signalpb.SyncMessage_FetchLatest_STORAGE_MANIFEST { - log.Debug().Msg("Received storage manifest fetch latest notice") - go cli.SyncStorage(ctx) } - syncSent := content.SyncMessage.GetSent() - if syncSent.GetMessage() != nil || syncSent.GetEditMessage() != nil { - destination := syncSent.DestinationServiceId - var syncDestinationServiceID libsignalgo.ServiceID - if destination != nil { - syncDestinationServiceID, err = libsignalgo.ServiceIDFromString(*destination) - if err != nil { - log.Err(err).Msg("Sync message destination parse error") - return nil, err - } + if destination == nil && syncSent.GetMessage().GetGroupV2() == nil && syncSent.GetEditMessage().GetDataMessage().GetGroupV2() == nil { + log.Warn().Msg("sync message sent destination is nil") + } else if content.SyncMessage.Sent.Message != nil { + // TODO handle expiration start ts, and maybe the sync message ts? + cli.incomingDataMessage(ctx, content.SyncMessage.Sent.Message, cli.Store.ACI, syncDestinationServiceID) + } else if content.SyncMessage.Sent.EditMessage != nil { + cli.incomingEditMessage(ctx, content.SyncMessage.Sent.EditMessage, cli.Store.ACI, syncDestinationServiceID) + } + } + if content.SyncMessage.Contacts != nil { + log.Debug().Msg("Recieved sync message contacts") + blob := content.SyncMessage.Contacts.Blob + if blob != nil { + contactsBytes, err := DownloadAttachment(ctx, blob) + if err != nil { + log.Err(err).Msg("Contacts Sync DownloadAttachment error") } - if destination == nil && syncSent.GetMessage().GetGroupV2() == nil && syncSent.GetEditMessage().GetDataMessage().GetGroupV2() == nil { - log.Warn().Msg("sync message sent destination is nil") - } else if content.SyncMessage.Sent.Message != nil { - // TODO handle expiration start ts, and maybe the sync message ts? - cli.incomingDataMessage(ctx, content.SyncMessage.Sent.Message, cli.Store.ACI, syncDestinationServiceID) - } else if content.SyncMessage.Sent.EditMessage != nil { - cli.incomingEditMessage(ctx, content.SyncMessage.Sent.EditMessage, cli.Store.ACI, syncDestinationServiceID) + // unmarshall contacts + contacts, avatars, err := unmarshalContactDetailsMessages(contactsBytes) + if err != nil { + log.Err(err).Msg("Contacts Sync unmarshalContactDetailsMessages error") } - } - if content.SyncMessage.Contacts != nil { - log.Debug().Msg("Recieved sync message contacts") - blob := content.SyncMessage.Contacts.Blob - if blob != nil { - contactsBytes, err := DownloadAttachment(ctx, blob) - if err != nil { - log.Err(err).Msg("Contacts Sync DownloadAttachment error") + log.Debug().Int("contact_count", len(contacts)).Msg("Contacts Sync received contacts") + convertedContacts := make([]*types.Recipient, 0, len(contacts)) + for i, signalContact := range contacts { + if signalContact.Aci == nil || *signalContact.Aci == "" { + // TODO lookup PNI via CDSI and store that when ACI is missing? + log.Info(). + Any("contact", signalContact). + Msg("Signal Contact UUID is nil, skipping") + continue } - // unmarshall contacts - contacts, avatars, err := unmarshalContactDetailsMessages(contactsBytes) + contact, err := cli.StoreContactDetailsAsContact(ctx, signalContact, &avatars[i]) if err != nil { - log.Err(err).Msg("Contacts Sync unmarshalContactDetailsMessages error") + log.Err(err).Msg("StoreContactDetailsAsContact error") + continue } - log.Debug().Int("contact_count", len(contacts)).Msg("Contacts Sync received contacts") - convertedContacts := make([]*types.Recipient, 0, len(contacts)) - for i, signalContact := range contacts { - if signalContact.Aci == nil || *signalContact.Aci == "" { - // TODO lookup PNI via CDSI and store that when ACI is missing? - log.Info(). - Any("contact", signalContact). - Msg("Signal Contact UUID is nil, skipping") - continue - } - contact, err := cli.StoreContactDetailsAsContact(ctx, signalContact, &avatars[i]) - if err != nil { - log.Err(err).Msg("StoreContactDetailsAsContact error") - continue - } - convertedContacts = append(convertedContacts, contact) - } - cli.handleEvent(&events.ContactList{ - Contacts: convertedContacts, - }) + convertedContacts = append(convertedContacts, contact) } - } - if content.SyncMessage.Read != nil { - cli.handleEvent(&events.ReadSelf{ - Messages: content.SyncMessage.GetRead(), + cli.handleEvent(&events.ContactList{ + Contacts: convertedContacts, }) } - - } - - var sendDeliveryReceipt bool - if content.DataMessage != nil { - sendDeliveryReceipt = cli.incomingDataMessage(ctx, content.DataMessage, theirServiceID.UUID, theirServiceID) - } else if content.EditMessage != nil { - sendDeliveryReceipt = cli.incomingEditMessage(ctx, content.EditMessage, theirServiceID.UUID, theirServiceID) } - if sendDeliveryReceipt { - // TODO send delivery receipts after actually bridging instead of here - err = cli.sendDeliveryReceipts(ctx, []uint64{content.DataMessage.GetTimestamp()}, theirServiceID.UUID) - if err != nil { - log.Err(err).Msg("sendDeliveryReceipts error") - } + if content.SyncMessage.Read != nil { + cli.handleEvent(&events.ReadSelf{ + Messages: content.SyncMessage.GetRead(), + }) } - if content.TypingMessage != nil { - var groupID types.GroupIdentifier - if content.TypingMessage.GetGroupId() != nil { - gidBytes := content.TypingMessage.GetGroupId() - groupID = types.GroupIdentifier(base64.StdEncoding.EncodeToString(gidBytes)) - } - cli.handleEvent(&events.ChatEvent{ - Info: events.MessageInfo{ - Sender: theirServiceID.UUID, - ChatID: groupOrUserID(groupID, theirServiceID), - }, - Event: content.TypingMessage, - }) + } + + var sendDeliveryReceipt bool + if content.DataMessage != nil { + sendDeliveryReceipt = cli.incomingDataMessage(ctx, content.DataMessage, theirServiceID.UUID, theirServiceID) + } else if content.EditMessage != nil { + sendDeliveryReceipt = cli.incomingEditMessage(ctx, content.EditMessage, theirServiceID.UUID, theirServiceID) + } + if sendDeliveryReceipt { + // TODO send delivery receipts after actually bridging instead of here + err = cli.sendDeliveryReceipts(ctx, []uint64{content.DataMessage.GetTimestamp()}, theirServiceID.UUID) + if err != nil { + log.Err(err).Msg("sendDeliveryReceipts error") } + } - // DM call message (group call is an opaque callMessage and a groupCallUpdate in a dataMessage) - if content.CallMessage != nil && (content.CallMessage.Offer != nil || content.CallMessage.Hangup != nil) { - cli.handleEvent(&events.Call{ - Info: events.MessageInfo{ - Sender: theirServiceID.UUID, - ChatID: theirServiceID.String(), - }, - IsRinging: content.CallMessage.Offer != nil, - }) + if content.TypingMessage != nil { + var groupID types.GroupIdentifier + if content.TypingMessage.GetGroupId() != nil { + gidBytes := content.TypingMessage.GetGroupId() + groupID = types.GroupIdentifier(base64.StdEncoding.EncodeToString(gidBytes)) } + cli.handleEvent(&events.ChatEvent{ + Info: events.MessageInfo{ + Sender: theirServiceID.UUID, + ChatID: groupOrUserID(groupID, theirServiceID), + }, + Event: content.TypingMessage, + }) + } - // Read and delivery receipts - if content.ReceiptMessage != nil { - if content.GetReceiptMessage().GetType() == signalpb.ReceiptMessage_DELIVERY && theirServiceID == cli.Store.ACIServiceID() { - // Ignore delivery receipts from other own devices - return &web.SimpleResponse{ - Status: responseCode, - }, nil - } - cli.handleEvent(&events.Receipt{ - Sender: theirServiceID.UUID, - Content: content.ReceiptMessage, - }) + // DM call message (group call is an opaque callMessage and a groupCallUpdate in a dataMessage) + if content.CallMessage != nil && (content.CallMessage.Offer != nil || content.CallMessage.Hangup != nil) { + cli.handleEvent(&events.Call{ + Info: events.MessageInfo{ + Sender: theirServiceID.UUID, + ChatID: theirServiceID.String(), + }, + IsRinging: content.CallMessage.Offer != nil, + }) + } + + // Read and delivery receipts + if content.ReceiptMessage != nil { + if content.GetReceiptMessage().GetType() == signalpb.ReceiptMessage_DELIVERY && theirServiceID == cli.Store.ACIServiceID() { + // Ignore delivery receipts from other own devices + return nil } + cli.handleEvent(&events.Receipt{ + Sender: theirServiceID.UUID, + Content: content.ReceiptMessage, + }) } - return &web.SimpleResponse{ - Status: responseCode, - }, nil + return nil } func printStructFields(message protoreflect.Message, parent string, builder *strings.Builder) { @@ -951,6 +1026,8 @@ type DecryptionResult struct { SenderAddress *libsignalgo.Address Content *signalpb.Content SealedSender bool + Err error + Urgent bool } const prodServerTrustRootStr = "BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF" diff --git a/user.go b/user.go index db5bd4cc..a579e8b5 100644 --- a/user.go +++ b/user.go @@ -858,6 +858,19 @@ func (user *User) eventHandler(rawEvt events.SignalEvent) { } else { user.log.Warn().Str("chat_id", evt.Info.ChatID).Msg("Couldn't get portal, dropping message") } + case *events.DecryptionError: + portal := user.GetPortalByChatID(evt.Sender.String()) + if portal == nil { + user.log.Warn().Stringer("chat_id", evt.Sender).Msg("Couldn't get portal for decryption error") + return + } + content := &event.MessageEventContent{MsgType: event.MsgNotice} + name := user.bridge.GetPuppetBySignalID(evt.Sender).Name + if name == "" { + name = "This user" + } + content.Body = fmt.Sprintf("%s sent a message that couldn't be decrypted. It may have been in this chat or a group chat. Please check your Signal app", name) + portal.sendMainIntentMessage(context.TODO(), content) case *events.Receipt: user.handleReceipt(evt) case *events.ReadSelf: