I'm trying to add a generic password to the keychain and get back the persistent ID for it, and give it .userPresence
access control. Unfortunately, if I include that, I get paramError back from SecItemAdd. Here's the code:
@discardableResult
func
set(username: String, hostname: String?, password: String, comment: String? = nil)
throws
-> PasswordEntry
{
// Delete any existing matching password…
if let existing = try? getEntry(forUsername: username, hostname: hostname)
{
try deletePassword(withID: existing.id)
}
// Store the new password…
var label = username
if let hostname
{
label = label + "@" + hostname
}
var item: [String: Any] =
[
kSecClass as String : kSecClassGenericPassword,
kSecAttrDescription as String : "TermPass Password",
kSecAttrGeneric as String : self.bundleID.data(using: .utf8)!,
kSecAttrLabel as String : label,
kSecAttrAccount as String : username,
kSecValueData as String : password.data(using: .utf8)!,
kSecReturnData as String : true,
kSecReturnPersistentRef as String: true,
]
if self.synchronizable
{
item[kSecAttrSynchronizable as String] = kCFBooleanTrue!
}
if let hostname
{
item[kSecAttrService as String] = hostname
}
if let comment
{
item[kSecAttrComment as String] = comment
}
// Apply access control to require the user to prove presence when
// retrieving this password…
var error: Unmanaged<CFError>?
guard
let accessControl = SecAccessControlCreateWithFlags(nil,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
.userPresence,
&error)
else
{
let cfError = error!.takeUnretainedValue() as Error
throw cfError
}
item[kSecAttrAccessControl as String] = accessControl
item[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlockedThisDeviceOnly
var result: AnyObject!
let status = SecItemAdd(item as CFDictionary, &result)
try Errors.throwIfError(osstatus: status)
load()
guard
let secItem = result as? [String : Any],
let persistentRef = secItem[kSecValuePersistentRef as String] as? Data
else
{
throw Errors.malformedItem
}
let entry = PasswordEntry(id: persistentRef, username: username, hostname: hostname, password: password, comment: comment)
return entry
}
(Note that I also tried it omitting kSecAttrAccessible
, but it had no effect.)
This code works fine if I omit setting kSecAttrAccessControl
.
Any ideas? TIA!
I see a three of issues here. First, you’re asking for something that doesn’t make sense. accessControl
uses a this-device-only modifier, but you’re setting kSecAttrSynchronizable
to true. What’s the point of synchronising something if it’s limited to this device only?
If you want to use the data protection keychain but don’t want things to synchronous, replace kSecAttrSynchronizable
with kSecUseDataProtectionKeychain
.
Second, you’re setting kSecAttrAccessControl
and kSecAttrAccessible
. That’s weird because the former subsumes the latter. Note how the value you’re using to kSecAttrAccessible
is also being passed when you create accessControl
.
Third, kSecAttrSynchronizable
has a long list of caveats in the doc comments in <Security/SecItem.h>
. One of those is:
Persistent references to synchronizable items should be avoided; while they may work locally, they cannot be moved between devices, and may not resolve if the item is modified on some other device.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"