Swift Weak Keychain Accessibility

Weak Keychain accessibility settings in Swift

Description

The kSecAttrAccessibleAlways family permits access to Keychain items while the device is locked, increasing exposure risks if a device is lost, jailbroken, or restored from a backup.

Potential impact

  • Access to Keychain items while the device is locked
  • Sensitive data exposure following device loss or backup restoration
  • Reuse of stolen tokens or passwords

Remediation

  1. Prefer kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly for sensitive items.
  2. If background access is required, select a more restrictive option such as AfterFirstUnlockThisDeviceOnly.
  3. Use device-only accessibility where possible to prevent migration through backups.

Examples

Before

swift
query[kSecAttrAccessible as String] = kSecAttrAccessibleAlways

After

swift
import Foundation
import Security

enum KeychainStorageError: Error {
    case unexpectedStatus(OSStatus)
}

func saveToken(_ token: Data) throws {
    let item: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: "com.example.app.auth",
        kSecAttrAccount as String: "access_token",
        kSecAttrAccessible as String:
            kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
        kSecValueData as String: token
    ]

    let addStatus = SecItemAdd(item as CFDictionary, nil)
    if addStatus == errSecSuccess { return }
    guard addStatus == errSecDuplicateItem else {
        throw KeychainStorageError.unexpectedStatus(addStatus)
    }

    let match: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: "com.example.app.auth",
        kSecAttrAccount as String: "access_token"
    ]
    let updates: [String: Any] = [
        kSecAttrAccessible as String:
            kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
        kSecValueData as String: token
    ]
    let updateStatus = SecItemUpdate(
        match as CFDictionary, updates as CFDictionary)
    guard updateStatus == errSecSuccess else {
        throw KeychainStorageError.unexpectedStatus(updateStatus)
    }
}

Explanation:

  • Before: Item access is permitted regardless of the device's lock state.
  • After: The code requires passcode and device-only protection and checks Keychain operation results. Existing items are updated with the same strong accessibility setting. Failures, including a missing device passcode, do not trigger a fallback to weaker accessibility.

References