This commit is contained in:
wt committed 2026-07-30 20:07:51 +07:00
commit 92660a4ba0
228 files changed
+24496

No files matched your search

@@ -0,0 +1,24 @@
import Foundation
import Security
enum CertificateTrustEvaluator {
/// Evaluates the peer as a TLS server for the XMPP service domain.
///
/// Martin 3.2.4's legacy SocketConnector creates an SSL client-certificate
/// policy in its default path. Luma supplies the correct server policy
/// without weakening hostname or certificate-chain validation.
static func evaluateServerTrust(_ trust: SecTrust, expectedDomain: String) -> Bool {
let policy = SecPolicyCreateSSL(true, expectedDomain as CFString)
guard SecTrustSetPolicies(trust, policy) == errSecSuccess else {
return false
}
var evaluationError: CFError?
let trusted = SecTrustEvaluateWithError(trust, &evaluationError)
if !trusted, let evaluationError {
let description = CFErrorCopyDescription(evaluationError) as String
print("Luma TLS trust failed for \(expectedDomain): \(description)")
}
return trusted
}
}
@@ -0,0 +1,85 @@
import Foundation
import Security
final class CredentialVault {
private let service = "app.luma.chat.xmpp"
func save(password: String, for jid: String) throws {
let account = jid.lowercased()
let passwordData = Data(password.utf8)
let baseQuery: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: account
]
let updateStatus = SecItemUpdate(
baseQuery as CFDictionary,
[kSecValueData: passwordData] as CFDictionary
)
if updateStatus == errSecSuccess {
return
}
guard updateStatus == errSecItemNotFound else {
throw CredentialVaultError.unhandled(updateStatus)
}
var insertion = baseQuery
insertion[kSecValueData] = passwordData
#if !os(macOS)
insertion[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
#endif
let addStatus = SecItemAdd(insertion as CFDictionary, nil)
guard addStatus == errSecSuccess else {
throw CredentialVaultError.unhandled(addStatus)
}
}
func password(for jid: String) throws -> String? {
let query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: jid.lowercased(),
kSecReturnData: true,
kSecMatchLimit: kSecMatchLimitOne
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
if status == errSecItemNotFound {
return nil
}
guard status == errSecSuccess,
let data = item as? Data,
let value = String(data: data, encoding: .utf8) else {
throw CredentialVaultError.unhandled(status)
}
return value
}
func deletePassword(for jid: String) throws {
let query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: jid.lowercased()
]
let status = SecItemDelete(query as CFDictionary)
guard status == errSecSuccess || status == errSecItemNotFound else {
throw CredentialVaultError.unhandled(status)
}
}
}
enum CredentialVaultError: LocalizedError {
case unhandled(OSStatus)
var errorDescription: String? {
switch self {
case .unhandled(let status):
let message = SecCopyErrorMessageString(status, nil) as String?
return message ?? "Не удалось обратиться к Keychain (\(status))."
}
}
}