Path: blob/main/SignalServiceKit/Messages/DecryptedIncomingEnvelope.swift
1 views
//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import LibSignalClient
/// Represents an envelope that's already been decrypted.
///
/// This might be a message from one of our linked devices, or it might be a
/// message from another user.
///
/// Identified and unidentified envelopes take different paths to produce
/// this object, but they both produce this type so that most downstream
/// logic doesn't need to care about the source of a message.
///
/// For identified envelopes, `sourceAci` is taken directly from the
/// `ValidatedIncomingEnvelope`, and then we decrypt the envelope payload to
/// populate `plaintextData`.
///
/// For unidentified envelopes, we first decrypt the envelope payload which
/// gives us the `sourceAci` and a nested payload. We then decrypt that
/// nested payload to populate `plaintextData`.
class DecryptedIncomingEnvelope {
let envelope: SSKProtoEnvelope
let timestamp: UInt64
let serverTimestamp: UInt64
let sourceAci: Aci
let sourceDeviceId: DeviceId
let localIdentity: OWSIdentity
let wasReceivedByUD: Bool
let plaintextData: Data
let content: SSKProtoContent?
init(
validatedEnvelope: ValidatedIncomingEnvelope,
updatedEnvelope: SSKProtoEnvelope,
sourceAci: Aci,
sourceDeviceId: DeviceId,
wasReceivedByUD: Bool,
plaintextData: Data,
isPlaintextCipher: Bool?,
) throws {
self.envelope = updatedEnvelope
self.timestamp = validatedEnvelope.timestamp
self.serverTimestamp = validatedEnvelope.serverTimestamp
self.sourceAci = sourceAci
self.sourceDeviceId = sourceDeviceId
self.localIdentity = validatedEnvelope.localIdentity
self.wasReceivedByUD = wasReceivedByUD
self.plaintextData = plaintextData
self.content = {
do {
return try SSKProtoContent(serializedData: plaintextData)
} catch {
owsFailDebug("Failed to deserialize content proto: \(error)")
return nil
}
}()
let hasDecryptionError = (
content?.decryptionErrorMessage != nil,
)
let hasAnythingElse = (
content?.dataMessage != nil
|| content?.syncMessage != nil
|| content?.callMessage != nil
|| content?.nullMessage != nil
|| content?.receiptMessage != nil
|| content?.typingMessage != nil
|| content?.storyMessage != nil
|| content?.pniSignatureMessage != nil
|| content?.senderKeyDistributionMessage != nil
|| content?.unknownFields != nil,
)
if hasDecryptionError, hasAnythingElse {
throw OWSGenericError("Message content must contain one type.")
}
if let isPlaintextCipher, isPlaintextCipher != hasDecryptionError {
throw OWSGenericError("Plaintext ciphers must have decryption errors.")
}
// This prevents replay attacks by the service.
if let dataMessage = content?.dataMessage, dataMessage.timestamp != timestamp {
throw OWSGenericError("Data messages must have matching timestamps \(sourceAci)")
}
if let typingMessage = content?.typingMessage, typingMessage.timestamp != timestamp {
throw OWSGenericError("Typing messages must have matching timestamps \(sourceAci)")
}
}
}