tls sasl
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s

This commit is contained in:
wt committed 2026-09-02 11:46:11 +07:00
1 parent e3fcee3351
commit d4b4c92759
29 files changed
+2177 -61

No files matched your search

+27 -5
View File
@@ -1913,6 +1913,10 @@ final class AppModel: ObservableObject {
errorMessage = nil
}
func clearInformationalMessage() {
informationalMessage = nil
}
private func beginMediaSendActivity() -> UUID {
let token = mediaSendActivity.begin()
isSendingAttachment = mediaSendActivity.isActive
@@ -3100,14 +3104,32 @@ final class AppModel: ObservableObject {
cursor: lastSuccessfulMAMCursor
)
}
// One-time cleanup of phantom «Не удалось расшифровать сообщение»
// placeholders: outgoing echoes the client could not decrypt back
// (broken encrypt-to-self sessions) used to be rendered as
// undecryptable bubbles. XMPPService now drops those echoes, so purge
// the leftovers from the local store instead of keeping the noise.
let phantomPlaceholderIDs = Set(
snapshot.messages
.filter { $0.direction == .outgoing && $0.security == .decryptionFailed }
.map(\.clientID)
)
for message in snapshot.messages
where phantomPlaceholderIDs.contains(message.clientID) {
modelContext.delete(message)
}
messages = snapshot.messages.filter {
!locallyDeletedMessageIDs.contains(
Self.localDeletionKey(
messageID: $0.clientID,
conversationID: $0.conversationID
))
!phantomPlaceholderIDs.contains($0.clientID)
&& !locallyDeletedMessageIDs.contains(
Self.localDeletionKey(
messageID: $0.clientID,
conversationID: $0.conversationID
))
}
rebuildMessageIndex()
if !phantomPlaceholderIDs.isEmpty {
schedulePersist()
}
let avatarJIDs = Set(snapshot.conversations.filter { !$0.isGroup }.map(\.jid) + [jid])
for avatarJID in avatarJIDs {
if let data = await avatarCache.data(for: avatarJID) {
+7 -1
View File
@@ -14,8 +14,14 @@ struct OMEMODevice: Identifiable, Hashable, Sendable {
let trust: Trust
let isActive: Bool
let isOwn: Bool
let isOMEMO2: Bool
var id: String { "\(jid)|\(deviceID)" }
var id: String { "\(jid)|\(deviceID)|\(isOMEMO2 ? "omemo2" : "legacy")" }
/// Short protocol label for the encryption screen badge.
var protocolName: String {
isOMEMO2 ? "OMEMO 2" : "OMEMO"
}
var formattedFingerprint: String {
fingerprint.chunked(every: 8).joined(separator: " ")
@@ -0,0 +1,45 @@
import Foundation
/// Order in which Luma prefers SASL mechanisms during authentication.
/// Channel-binding SCRAM first (tls-exporter / tls-server-end-point prove
/// the exchange runs over the client's own TLS channel, defeating MITM
/// relays), then the strongest plain SCRAM variant, and PLAIN as the
/// last-resort fallback. The server must advertise a mechanism for it to be
/// eligible — Martin's `SaslModule.guessSaslMechanism` walks exactly this
/// order, and the PLUS mechanisms additionally gate on available binding
/// data in `isAllowedToUse`.
enum SASLMechanismPreference {
static let order: [String] = [
"SCRAM-SHA-512-PLUS",
"SCRAM-SHA-256-PLUS",
"SCRAM-SHA-1-PLUS",
"SCRAM-SHA-512",
"SCRAM-SHA-256",
"SCRAM-SHA-1",
"PLAIN",
]
/// The most secure mechanism from `order` that the server advertises,
/// or nil when no password-based mechanism is offered. When
/// `allowsChannelBinding` is false the -PLUS variants are skipped, since
/// without binding data the exchange would fail.
static func selected(
among offered: [String],
allowsChannelBinding: Bool = true
) -> String? {
order.first { candidate in
guard offered.contains(candidate) else { return false }
return allowsChannelBinding || !candidate.hasSuffix("-PLUS")
}
}
/// True when `mechanism` is the strongest one available among
/// `offered`: the server offers nothing better than the chosen one.
static func isStrongestAvailable(_ mechanism: String, among offered: [String]) -> Bool {
guard let chosenRank = order.firstIndex(of: mechanism) else { return false }
for stronger in order[..<chosenRank] where offered.contains(stronger) {
return false
}
return offered.contains(mechanism)
}
}
@@ -72,6 +72,14 @@ struct ServerInformation: Equatable, Sendable {
let externalServices: [ExternalService]
let connectionStats: ConnectionStats
let saslMethods: [String]
/// TLS version negotiated with the server during this connection
/// (probed from a parallel handshake when the library does not expose it).
let tlsVersion: String?
/// Cipher suite negotiated alongside the TLS version.
let tlsCipher: String?
/// SASL mechanism actually selected for authentication (the strongest
/// one the server advertises).
let saslMechanism: String?
let supportsStreamManagement: Bool
let supportsCarbons: Bool
let supportsClientState: Bool