@@ -12,12 +12,8 @@ enum KeychainService {
1212 deletePassword(for: connectionID)
1313
1414 guard let data = password.data(using: .utf8) else { return false }
15− let query: [String: Any] = [
16− kSecClass as String: kSecClassGenericPassword,
17− kSecAttrService as String: service,
18− kSecAttrAccount as String: account,
19− kSecValueData as String: data
20− ]
15+ var query = baseQuery(for: connectionID)
16+ query[kSecValueData as String] = data
2117 let status = SecItemAdd(query as CFDictionary, nil)
2218 if status != errSecSuccess {
2319 logger.error("Failed to save password for \(account): OSStatus \(status)")
@@ -27,13 +23,9 @@ enum KeychainService {
2723
2824 static func loadPassword(for connectionID: UUID) -> String? {
2925 let account = connectionID.uuidString
30− let query: [String: Any] = [
31− kSecClass as String: kSecClassGenericPassword,
32− kSecAttrService as String: service,
33− kSecAttrAccount as String: account,
34− kSecReturnData as String: true,
35− kSecMatchLimit as String: kSecMatchLimitOne
36− ]
26+ var query = baseQuery(for: connectionID)
27+ query[kSecReturnData as String] = true
28+ query[kSecMatchLimit as String] = kSecMatchLimitOne
3729
3830 var result: AnyObject?
3931 let status = SecItemCopyMatching(query as CFDictionary, &result)
@@ -47,15 +39,23 @@ enum KeychainService {
4739 @discardableResult
4840 static func deletePassword(for connectionID: UUID) -> Bool {
4941 let account = connectionID.uuidString
50− let query: [String: Any] = [
51− kSecClass as String: kSecClassGenericPassword,
52− kSecAttrService as String: service,
53− kSecAttrAccount as String: account
54− ]
42+ let query = baseQuery(for: connectionID)
5543 let status = SecItemDelete(query as CFDictionary)
5644 if status != errSecSuccess && status != errSecItemNotFound {
5745 logger.error("Failed to delete password for \(account): OSStatus \(status)")
5846 }
5947 return status == errSecSuccess || status == errSecItemNotFound
6048 }
49+
50+ /// Base query targeting the data-protection keychain.
51+ /// Items stored here are scoped by bundle identifier rather than by
52+ /// code-signing identity, so they survive ad-hoc re-signing across builds.
53+ private static func baseQuery(for connectionID: UUID) -> [String: Any] {
54+ [
55+ kSecClass as String: kSecClassGenericPassword,
56+ kSecAttrService as String: service,
57+ kSecAttrAccount as String: connectionID.uuidString,
58+ kSecUseDataProtectionKeychain as String: true
59+ ]
60+ }
6161}