|
|
|
|
Quelle KeychainManager.swift
Sprache: JAVA
|
|
import CocoaLumberjackSwift
import Foundation
import RemoteSecretProtocol
import ThreemaEssentials
/// Manager to access all items stored in Keychain.
///
/// The KeychainManager manages only generics passwords (`kSecClass` -> `kSecClassGenericPassword`),
/// the primary keys include `kSecAttrAccount` and `kSecAttrService`!
public final class KeychainManager: NSObject, KeychainManagerProtocol {
public enum KeychainManagerError: Error {
case myIdentityMissing
case threemaSafeServerMissing
case invalidIdentityBackupData
}
// TODO: (IOS-5364) Check if `RemoteSecretCryptoProtocol` would also be sufficient here
private let remoteSecretManager: RemoteSecretManagerProtocol
private let keychainProvider: KeychainProviding
// MARK: - Lifecycle
override private init() {
// no-op
fatalError("Not supported")
}
public init(remoteSecretManager: RemoteSecretManagerProtocol) {
self.remoteSecretManager = remoteSecretManager
self.keychainProvider = KeychainProvider()
super.init()
}
required init(
remoteSecretManager: RemoteSecretManagerProtocol,
keychainProvider: KeychainProviding = KeychainProvider()
) {
self.remoteSecretManager = remoteSecretManager
self.keychainProvider = keychainProvider
super.init()
}
// MARK: - Public functions
// MARK: - Locking Check
/// Is Keychain locked for first unlock accesses?
public static var isKeychainLocked: Bool {
do {
_ = try KeychainProvider().load(.identity())
return false
}
catch let KeychainProviderError.loadFailed(osStatus: osStatus)
where osStatus == errSecInteractionNotAllowed {
return true
}
catch {
return false
}
}
// MARK: - Remote Secret
public static func loadRemoteSecret() throws -> (
authenticationToken: Data,
identityHash: Data
)? {
guard let result = try KeychainProvider().load(.remoteSecret()),
let authenticationToken = result.password, let identityHash = result.generic else {
return nil
}
return (authenticationToken, identityHash)
}
public static func hasRemoteSecretInStore() -> Bool {
do {
return try KeychainProvider().load(.remoteSecret()) != nil
}
catch {
DDLogError("[KeychainManager] RS check failed.")
return false
}
}
public static func storeRemoteSecret(authenticationToken: Data, identityHash: Data) throws {
let item = KeychainItem.remoteSecret()
try KeychainProvider().store(
item,
account: item.label,
password: authenticationToken,
generic: identityHash
)
}
public static func deleteRemoteSecret() throws {
try KeychainProvider().delete(.remoteSecret())
}
// MARK: - Threema Identity
public static func loadThreemaIdentity() throws -> ThreemaIdentity? {
guard let result = try KeychainProvider().load(.identity()),
let identity = result.account else {
return nil
}
return ThreemaIdentity(identity)
}
// MARK: - Identity
public func loadIdentity() throws -> MyIdentity? {
guard let result = try keychainProvider.load(.identity()),
let account = result.account,
let password = result.password,
let generic = result.generic,
let service = result.service else {
return nil
}
let retrievedPassword = remoteSecretManager.decryptDataIfNeeded(password)
let retrievedGeneric = remoteSecretManager.decryptDataIfNeeded(generic)
guard let retrievedService = try remoteSecretManager.decryptBase64StringIfNeeded(service) else {
return nil
}
return MyIdentity(
identity: ThreemaIdentity(account),
clientKey: ThreemaClientKey(retrievedPassword),
publicKey: ThreemaPublicKey(retrievedGeneric),
serverGroup: ServerGroup(retrievedService)
)
}
public func storeIdentity(_ myIdentity: MyIdentity, thisDeviceOnly: Bool) throws {
let storingPassword =
remoteSecretManager.encryptDataIfNeeded(myIdentity.$clientKey)
let storingGeneric =
remoteSecretManager.encryptDataIfNeeded(myIdentity.$publicKey)
let storingService =
remoteSecretManager.encryptToBase64StringIfNeeded(myIdentity.$serverGroup)
try keychainProvider.store(
.identity(thisDeviceOnly: thisDeviceOnly),
account: myIdentity.$identity,
password: storingPassword,
generic: storingGeneric,
service: storingService
)
}
public func deleteIdentity() throws {
try keychainProvider.delete(.identity())
}
public func isIdentityThisDeviceOnly() throws -> Bool {
guard let result = try keychainProvider.load(.identity()) else {
throw KeychainManagerError.myIdentityMissing
}
return result.accessibility == kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
}
public func changeIdentityAccessibility(thisDeviceOnly: Bool) throws {
guard let identity = try? loadIdentity() else {
throw KeychainManagerError.myIdentityMissing
}
guard try isIdentityThisDeviceOnly() != thisDeviceOnly else {
return
}
try storeIdentity(identity, thisDeviceOnly: thisDeviceOnly)
}
// MARK: - Identity Backup
public static func loadIdentityBackup() throws -> String {
guard let result = try KeychainProvider().load(.identityBackup()),
let identityBackupData = result.password
else {
throw KeychainProviderError.loadFailed(osStatus: errSecItemNotFound)
}
guard let identityBackup = String(data: identityBackupData, encoding: .ascii) else {
throw KeychainManagerError.invalidIdentityBackupData
}
return identityBackup
}
public static func storeIdentityBackup(_ data: String) throws {
let item = KeychainItem.identityBackup()
guard let identityData = data.data(using: .ascii) else {
throw KeychainManagerError.invalidIdentityBackupData
}
try KeychainProvider().store(
item,
account: item.label,
password: identityData
)
}
public static func deleteIdentityBackup() throws {
try KeychainProvider().delete(.identityBackup())
}
// MARK: - Device Cookie
public func loadDeviceCookie() throws -> Data? {
guard let result = try keychainProvider.load(.deviceCookie()),
let deviceCookie = result.password else {
return nil
}
let loadingDeviceCookie = remoteSecretManager.decryptDataIfNeeded(deviceCookie)
return loadingDeviceCookie
}
public func storeDeviceCookie(_ cookie: Data) throws {
let item = KeychainItem.deviceCookie()
let storingCookie = remoteSecretManager.encryptDataIfNeeded(cookie)
try keychainProvider.store(
item,
account: item.label,
password: storingCookie
)
}
public static func deleteDeviceCookie() throws {
try KeychainProvider().delete(.deviceCookie())
}
// MARK: - Device Group Key
public func loadMultiDeviceGroupKey() throws -> Data? {
guard let result = try keychainProvider.load(.multiDeviceGroupKey()),
let multiDeviceGroupKey = result.password else {
return nil
}
let loadingMultiDeviceGroupKey = remoteSecretManager.decryptDataIfNeeded(multiDeviceGroupKey)
return loadingMultiDeviceGroupKey
}
public func storeMultiDeviceGroupKey(key: Data) throws {
let item = KeychainItem.multiDeviceGroupKey()
let storingKey = remoteSecretManager.encryptDataIfNeeded(key)
try keychainProvider.store(
item,
account: item.label,
password: storingKey
)
}
public static func deleteMultiDeviceGroupKey() throws {
try KeychainProvider().delete(.multiDeviceGroupKey())
}
// MARK: - Device ID for Multi Device
@available(swift, obsoleted: 1.0, renamed: "loadMultiDeviceID()", message: "Only use from Objective-C")
@objc public func loadMultiDeviceIDObjC() -> Data? {
do {
return try loadMultiDeviceID()
}
catch {
return nil
}
}
public func loadMultiDeviceID() throws -> Data? {
guard let result = try keychainProvider.load(.multiDeviceID()),
let multiDeviceID = result.password else {
return nil
}
let loadingMultiDeviceID = remoteSecretManager.decryptDataIfNeeded(multiDeviceID)
return loadingMultiDeviceID
}
@objc public func storeMultiDeviceID(id: Data) throws {
let item = KeychainItem.multiDeviceID()
let storingID = remoteSecretManager.encryptDataIfNeeded(id)
try keychainProvider.store(
item,
account: item.label,
password: storingID
)
}
public func deleteMultiDeviceID() throws {
try keychainProvider.delete(.multiDeviceID())
}
// MARK: - Forward Security Key
public func loadForwardSecurityWrappingKey() throws -> Data? {
guard let result = try keychainProvider.load(.forwardSecurityWrappingKey()),
let forwardSecurityWrappingKey = result.password else {
return nil
}
let loadingKey = remoteSecretManager.decryptDataIfNeeded(forwardSecurityWrappingKey)
return loadingKey
}
public func storeForwardSecurityWrappingKey(_ key: Data) throws {
let item = KeychainItem.forwardSecurityWrappingKey()
let storingKey = remoteSecretManager.encryptDataIfNeeded(key)
try keychainProvider.store(
item,
account: item.label,
password: storingKey
)
}
public static func deleteForwardSecurityKey() throws {
try KeychainProvider().delete(.forwardSecurityWrappingKey())
}
// MARK: - License (Work and OnPrem)
public static func loadOnPremServer() throws -> String? {
guard let result = try KeychainProvider().load(.license()),
let server = result.service else {
return nil
}
return server
}
/// Loads the license if it is stored unencrypted
///
/// Use this before the app is set up, where no remote secret manager exists: a remote secret is
/// tied to a Threema ID. A license stored while remote secret was enabled is encrypted and cannot
/// be decrypted without it, so this returns `nil` as long as there is a remote secret in the
/// keychain. Without one, ``storeLicense(_:)`` stores all values unencrypted.
/// - Returns: The stored license, or `nil` if there is none or it is encrypted
public static func loadUnencryptedLicense() throws -> ThreemaLicense? {
try loadUnencryptedLicense(keychainProvider: KeychainProvider())
}
static func loadUnencryptedLicense(keychainProvider: KeychainProviding) throws -> ThreemaLicense? {
guard try keychainProvider.load(.remoteSecret()) == nil else {
return nil
}
guard let result = try keychainProvider.load(.license()),
let user = result.account,
let passwordData = result.password,
let password = String(data: passwordData, encoding: .utf8)
else {
return nil
}
let deviceID = result.generic.flatMap {
String(data: $0, encoding: .utf8)
}
return ThreemaLicense(
user: user,
password: password,
deviceID: deviceID,
onPremServer: result.service
)
}
public func loadLicense() throws -> ThreemaLicense? {
guard let result = try keychainProvider.load(.license()),
let loadingUser = try remoteSecretManager.decryptBase64StringIfNeeded(result.account),
let loadingPassword = try remoteSecretManager.decryptDataToStringIfNeeded(result.password)
else {
return nil
}
let loadingDeviceID = try remoteSecretManager.decryptDataToStringIfNeeded(result.generic)
return ThreemaLicense(
user: loadingUser,
password: loadingPassword,
deviceID: loadingDeviceID,
onPremServer: result.service
)
}
public func storeLicense(_ license: ThreemaLicense) throws {
let storingUser = remoteSecretManager.encryptToBase64StringIfNeeded(license.user)
let storingPassword = remoteSecretManager.encryptStringIfNeeded(license.password)
let storingGeneric = remoteSecretManager.encryptStringIfNeeded(license.deviceID)
try keychainProvider.store(
.license(),
account: storingUser,
password: storingPassword,
generic: storingGeneric,
service: license.onPremServer
)
}
@objc public func deleteLicense() throws {
try keychainProvider.delete(.license())
}
// MARK: - Threema Safe Key
public func loadThreemaSafeKey() throws -> Data? {
guard let result = try keychainProvider.load(.threemaSafeKey()),
let key = result.password else {
return nil
}
let loadingKey = remoteSecretManager.decryptDataIfNeeded(key)
return loadingKey
}
public func storeThreemaSafeKey(key: Data) throws {
let storingKey = remoteSecretManager.encryptDataIfNeeded(key)
let keychainItem = KeychainItem.threemaSafeKey()
try keychainProvider.store(
keychainItem,
account: keychainItem.label,
password: storingKey
)
}
public func deleteThreemaSafeKey() throws {
try keychainProvider.delete(.threemaSafeKey())
}
// MARK: - Threema Safe Server
public func loadThreemaSafeServer() throws -> ThreemaSafeServerInfo? {
guard let result = try keychainProvider.load(.threemaSafeServer()) else {
return nil
}
guard let loadingServer = try remoteSecretManager.decryptBase64StringIfNeeded(result.service) else {
throw KeychainManagerError.threemaSafeServerMissing
}
let loadingUser = try remoteSecretManager.decryptBase64StringIfNeeded(result.account)
let loadingPassword = try remoteSecretManager.decryptDataToStringIfNeeded(result.password)
return ThreemaSafeServerInfo(
user: loadingUser,
password: loadingPassword,
server: loadingServer
)
}
public func storeThreemaSafeServer(_ server: ThreemaSafeServerInfo) throws {
let storingUser = remoteSecretManager.encryptToBase64StringIfNeeded(server.user)
let storingPassword = remoteSecretManager.encryptStringIfNeeded(server.password)
let storingServer = remoteSecretManager.encryptToBase64StringIfNeeded(server.server)
try keychainProvider.store(
.threemaSafeServer(),
account: storingUser,
password: storingPassword,
service: storingServer
)
}
public func deleteThreemaSafeServer() throws {
try keychainProvider.delete(.threemaSafeServer())
}
// MARK: - Data Backup Recovery Key
public func loadDataBackupRecoveryKey() throws -> Data? {
guard let result = try keychainProvider.load(.dataBackupRecoveryKey()),
let key = result.password else {
return nil
}
let loadingKey = remoteSecretManager.decryptDataIfNeeded(key)
return loadingKey
}
public func storeDataBackupRecoveryKey(_ key: Data) throws {
let storingKey = remoteSecretManager.encryptDataIfNeeded(key)
let keychainItem = KeychainItem.dataBackupRecoveryKey()
try keychainProvider.store(
keychainItem,
account: keychainItem.label,
password: storingKey
)
}
public func deleteDataBackupRecoveryKey() throws {
try keychainProvider.delete(.dataBackupRecoveryKey())
}
// MARK: - Push Notification Encryption Key
@available(
swift,
obsoleted: 1.0,
renamed: "loadPushNotificationEncryptionKey()",
message: "Only use from Objective-C"
)
@objc public static func loadPushNotificationEncryptionKeyObjC() -> Data? {
do {
return try loadPushNotificationEncryptionKey()
}
catch {
DDLogError("Unable to load push notification encryption key: \(error)")
return nil
}
}
public static func loadPushNotificationEncryptionKey() throws -> Data? {
try KeychainProvider().load(.pushNotificationEncryptionKey())?.password
}
public static func storePushNotificationEncryptionKey(_ key: Data) throws {
let item = KeychainItem.pushNotificationEncryptionKey()
try KeychainProvider().store(
item,
account: item.label,
password: key
)
}
// MARK: - Migrate Keychain
public func migrateToDowngrade() throws {
try KeychainMigration.migrateToDowngrade()
}
public func migrateToVersion0() throws {
try KeychainMigration.migrateToVersion0()
}
public func migrateToVersion1(myIdentity: ThreemaEssentials.ThreemaIdentity) throws {
try KeychainMigration.migrateToVersion1(myIdentity: myIdentity)
}
// MARK: - Deletion
public static func deleteAllItems() throws {
let keychainProvider = KeychainProvider()
for item in KeychainItem.allCases {
try keychainProvider.delete(item)
}
}
public static func deleteAllThisDeviceOnlyItems() throws {
let keychainProvider = KeychainProvider()
for item in KeychainItem.allCases where item.accessibility == kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly {
try keychainProvider.delete(item)
}
}
public static func deleteAllEncryptedItems() throws {
guard hasRemoteSecretInStore() else {
return
}
let keychainProvider = KeychainProvider()
for item in KeychainItem.allCases where item.mightContainEncryptedData {
try keychainProvider.delete(item)
}
}
// MARK: - Debug stuff
#if DEBUG
public static func printKeychainItems() {
let classes = [
kSecClassGenericPassword,
kSecClassInternetPassword,
kSecClassCertificate,
kSecClassIdentity,
kSecClassKey,
]
for secClass in classes {
do {
guard let items = try KeychainMigration.allItems(of: secClass) else {
continue
}
print("Found \(items.count) items")
for item in items {
print("Found item: \(item)")
}
}
catch {
print(error)
}
}
}
public static func deleteKeychainItemsExceptIdentity() {
do {
try KeychainMigration.migrateToDowngrade()
}
catch {
print(error)
}
}
#endif
}
¤ Dauer der Verarbeitung: 0.7 Sekunden
¤
*© Formatika GbR, Deutschland
|
|
2026-11-01
|
|
|
|
|
Neuigkeiten |
| Aktuelles |
| Motto des Tages |
|
Open Source Software |
|
|
|
Jenseits des Üblichen ....
|
|
Besucherstatistik |
|
|
| Statistik |
| #Sources=1104856 |
| #Domains=417692 |
|
|