Skip to content

Insecure Keychain Storage

Insecure Keychain Storage

Description

The platform relies on the keychain for storage, but it's configured with insecure flags.

In this example, the secret password is stored in the keychain with unrestricted accessibility (kSecAttrAccessibleAlways), leaving it vulnerable to unauthorized access:

import Foundation
import Security

func saveSecretToKeychain() {
    let secretData = "mySecretPassword".data(using: .utf8)!
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: "myAccount",
        kSecValueData as String: secretData,
        kSecAttrAccessible as String: kSecAttrAccessibleAlways
    ]
    SecItemAdd(query as CFDictionary, nil)
}
saveSecretToKeychain()

In the following examples, incomplete specification of access control flags (SecAccessControlCreateFlags) results in insecure keychain item storage:

import Foundation
import Security

func saveSecretToKeychain() {
    let secretData = "mySecretPassword".data(using: .utf8)!
    let access = SecAccessControlCreateWithFlags(
        nil,
        kSecAttrAccessibleAlways,
        .or,
        nil
    )
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: "myAccount",
        kSecValueData as String: secretData,
        kSecAttrAccessControl as String: access!,
    ]
    SecItemAdd(query as CFDictionary, nil)
}
saveSecretToKeychain()
import Foundation
import Security

func saveSecretToKeychain() {
    let secretData = "mySecretPassword".data(using: .utf8)!
    let access = SecAccessControlCreateWithFlags(
        nil,
        kSecAttrAccessibleAlways,
        .and,
        nil
    )
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: "myAccount",
        kSecValueData as String: secretData,
        kSecAttrAccessControl as String: access!,
    ]
    SecItemAdd(query as CFDictionary, nil)
}
saveSecretToKeychain()

In these examples, it's critical to specify access control flags properly to ensure secure keychain item storage. Without specific flags like kSecAccessControlUserPresence, the keychain items remain vulnerable to unauthorized access.

Recommendation

When simple access control requirements suffice, you can directly specify the accessibility level using kSecAttrAccessible. Omitting kSecAttrAccessControl is acceptable in these cases.

Use Case Protection
Storing data always accessible kSecAttrAccessibleAlways
Storing data accessible after first unlock kSecAttrAccessibleAfterFirstUnlock
Storing data only accessible when device unlocked kSecAttrAccessibleWhenUnlocked

Example:

import Foundation
import Security

func saveSecretToKeychain() {
    let secretData = "mySecretPassword".data(using: .utf8)!
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: "myAccount",
        kSecValueData as String: secretData,
        kSecAttrAccessible as String: kSecAttrAccessibleAlways
    ]
    SecItemAdd(query as CFDictionary, nil)
}
saveSecretToKeychain()

For more complex security requirements, such as demanding user presence or implementing application-specific passwords, use SecAccessControlCreateWithFlags to create access control with additional flags.

Use Case Protection Flags
Dealing with sensitive data that requires a user presence kSecAttrAccessibleWhenUnlocked kSecAccessControlUserPresence
Dealing with sensitive data that requires a specific password for extra security kSecAttrAccessibleWhenPasscodeSet kSecAccessControlApplicationPassword

Example:

import Foundation
import Security

func saveSecretToKeychain() {
    let secretData = "mySecretPassword".data(using: .utf8)!
    let access = SecAccessControlCreateWithFlags(
        nil,
        kSecAttrAccessibleWhenUnlocked,
        kSecAccessControlUserPresence,
        nil
    )
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: "myAccount",
        kSecValueData as String: secretData,
        kSecAttrAccessControl as String: access!,
    ]
    SecItemAdd(query as CFDictionary, nil)
}
saveSecretToKeychain()

Standards

  • OWASP_MASVS_L2:
    • MSTG_AUTH_8
  • GDPR:
    • ART_5
    • ART_25
    • ART_32
  • PCI_STANDARDS:
    • REQ_6_2
    • REQ_6_3
    • REQ_8_3
    • REQ_11_3
  • SOC2_CONTROLS:
    • CC_2_1
    • CC_4_1
    • CC_6_1
    • CC_7_1
    • CC_7_2
    • CC_7_4
    • CC_7_5