Error adding Key item to macOS keychain - swift

I have following code:
let keyData = UUID().uuidString.data(using: .utf8)!
var attributes: [NSString: Any] = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: keyData,
]
let st1 = SecItemDelete(attributes as CFDictionary)
attributes[kSecValueData] = keyData
let st2 = SecItemAdd(attributes as CFDictionary, nil)
I am trying to add item to the keychain with type kSecClassKey. For some reason this code works perfectly in iOS and doesn't work in macOS.
In macOS st1 is -25300 (which means The item cannot be found.) and st2 is -25299 (which means The item already exists.)
What can I do to make this code work?

The error errSecDuplicateItem (-25299) might also be returned if you miss a mandatory attribute, e.g., if you try to add a kSecClassGenericPassword key without the kSecAttrService set.
In your case I wonder why you try to store the UUID as a cryptographic key (kSecClassKey). Storing it as a generic password (kSecClassGenericPassword) instead would suffice.
let keyData = UUID().uuidString.data(using: .utf8)!
var attributes: [NSString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: "YourApp-UUID", // Determines the purpose/context of the used password/value
kSecAttrLabel: "YourApp (UUID)", // Name of the Keychain item
kSecValueData: keyData, // Actual value, that will be stored securely
]
let status = SecItemAdd(attributes as CFDictionary, nil)

Related

Error when adding/updating keychain value with kSecClass

I'm trying to add/update a keychain value using the dictionary below:
public var itemDictionary: [String: Any] {
let dictionary: [CFString: Any?] = [
kSecAttrAccessGroup: accessGroup, // nil
kSecAttrService: service, // nil
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
kSecAttrSynchronizable: synchronizable, // false
kSecAttrAccessible: access.queryValue,
kSecValueData: data
]
return dictionary.compactMapValues { $0 } as [String: Any]
}
However, both SecItemAdd and SecItemUpdate fail with an error of -25303 (errSecNoSuchAttr). After some debugging, I figured out that if I removed kSecClass from the dictionary above, that both SecItemAdd and SecItemUpdate work as expected. This doesn't seem right though as my understanding is that kSecClass is a required field. What am I doing wrong here?

Where does SecKeyCreateSignature get the key name for Keychain signing authorization dialog?

I have noticed a difference between certain keys in the Keychain with respect to how they appear in the Keychain signing dialog, and I cannot figure out why some are displayed a certain way while others are not.
Here is some test code to use identities in the Keychain to sign a sample bit of data.
func testCreateSignature() throws {
let query: [String: Any] = [kSecClass as String: kSecClassIdentity,
kSecMatchLimit as String: kSecMatchLimitAll,
kSecReturnAttributes as String: false,
kSecReturnRef as String: true,
kSecReturnData as String: true]
var resultsRef: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &resultsRef)
guard status == errSecSuccess else { throw SecurityError.unhandledError(status: status) }
guard let results = resultsRef as? [[String:Any]] else {
throw SecurityError.unexpectedCertificateData
}
let data = Data([0xDE, 0xAD, 0xBE, 0xEF])
var privateKey: SecKey!
for result in results {
let secIdentity = result[kSecValueRef as String] as! SecIdentity
try SecIdentityCopyPrivateKey(secIdentity, &privateKey).check()
var error: Unmanaged<CFError>?
let signature = SecKeyCreateSignature(privateKey, .rsaSignatureMessagePKCS1v15SHA1, data as CFData, &error)!
if let error = error {
throw error.takeRetainedValue()
}
print(signature)
}
}
When the code attempts to use one of the keys that Xcode installed for code signing, the resulting dialog looks like the following:
However, when the code attempts to use a key that I've installed, no matter what the label on the key in the Keychain is, it always looks like this:
When my app attempts to use a key to sign, I would like the user to see the name of the key the app wants to use, instead of just generic "privateKey", but I cannot find where this information might be stored on the key.
I have checked the kSecAttrLabel and kSecAttrApplicationLabel attributes of both identities and the private keys and cannot find the text that appears in the dialogs.
I found it. It is a property of the Access Control List of a Keychain item. See 'descriptor' param for SecAccessCreate.
If you do not specify a custom ACL when importing a key, it will default to "privateKey".
I was using SecPKCS12Import to import a .pfx file. I attempted to set the kSecImportExportAccess key in the options parameter to a custom SecAccess object, but it would always import with a default ACL.
I ended up refactoring the code to use SecItemImport instead to import the .pfx file and supplied a custom SecAccess instance:
static func importIdentity(contentsOf url: URL, password: String) throws {
let data = try Data.init(contentsOf: url)
var access: SecAccess!
try SecAccessCreate("License Key" as CFString, nil, &access).check()
var keychain: SecKeychain!
var outItems: CFArray?
let filename: CFString? = url.isFileURL ? url.lastPathComponent as CFString : nil
var inputFormat: SecExternalFormat = .formatPKCS12
var itemType: SecExternalItemType = .itemTypeAggregate
let unmanagedPassword = Unmanaged<AnyObject>.passRetained(password as AnyObject)
let unmanagedAccess = Unmanaged<SecAccess>.passRetained(access)
var params: SecItemImportExportKeyParameters = SecItemImportExportKeyParameters(version: UInt32(SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION),
flags: .importOnlyOne,
passphrase: unmanagedPassword,
alertTitle: nil,
alertPrompt: nil,
accessRef: unmanagedAccess,
keyUsage: nil,
keyAttributes: nil)
try SecKeychainCopyDefault(&keychain).check()
try SecItemImport(data as CFData, filename, &inputFormat, &itemType, [], &params, keychain, &outItems).check()
}
Importing the identity as above will result in "License Key" being shown in the signing dialog rather than "privateKey".

Error trying to generate a random key pair when unit testing on a real device iOS Swift

When i try to run my unit test on a real device it fails.
The returned error is
-25293 ("The user name or passphrase you entered is not correct.").
Here is my failing code:
let accessControl = SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
kSecAttrAccessibleAfterFirstUnlock,
[.privateKeyUsage],
nil)
let privateKeyAttrs = [
kSecAttrIsPermanent as String : true,
kSecAttrApplicationTag as String : tag,
kSecAttrAccessControl as String : accessControl
] as [String : Any]
let generationQuery: [String: Any] = [
kSecAttrKeyType as String : kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String : 2048,
kSecPrivateKeyAttrs as String : privateKeyAttrs]
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateRandomKey(generationQuery as CFDictionary, &error) else {
throw <MyError>
}
The SecKeyCreateRandomKey fails even if I change my Accessible protection to kSecAttrAccessibleAlways or kSecAttrAccessibleWhenUnlocked and my test pass if I just remove kSecAttrAccessControl from my privateKeyAttrs.
More details: If I run my tests on a simulator it pass; I'm using
Xcode 11.6 and swift 5.
.privateKeyUsage is specifically for creating keypairs that are stored in the Secure Enclave:
An attempt to use this constraint while generating a key pair outside the Secure Enclave fails.
Drop that option. You don't need it (and can't use it) if you're just trying to create a keypair in the keychain.

Trying to add item to Keychain using SecItemAdd() results in -50

When I try to add an item to the macOS keychain using SecItemAdd() with the parameter kSecAttrSynchronizable in a commandline Swift application, I get the error -50 (One or more parameters passed to the function were not valid). It works correctly if I remove kSecAttrSynchronizable. I've tried using KeychainAccess and I've tried to do it manually (using the keychain services API provided by macOS), the result is the same.
import Foundation
let account = "username"
let password = "password".data(using: String.Encoding.utf8)!
var query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: account,
kSecAttrServer as String: "nabeelomer.me",
kSecAttrSynchronizable as String: kCFBooleanTrue,
kSecValueData as String: password]
let status = SecItemAdd(query as CFDictionary, nil)
print(status)
Is there maybe an undocumented permission that the application needs?
Swift 4.0, macOS 10.13.3, Xcode 9.1
kSecValueData needs to be Data, not String
try
kSecValueData: password.data(using: .utf8)!

osx macOS public key import to keychain duplicate

I am trying to permanently import an RSA public key into the keychain
let params: [NSString: AnyObject] = [
kSecClass: kSecClassKey,
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrApplicationTag: tag as AnyObject,
kSecValueData: data as AnyObject,
kSecReturnPersistentRef: true as AnyObject
]
let persistKey = UnsafeMutablePointer<AnyObject?>(mutating: nil)
let status = SecItemAdd(params as CFDictionary, persistKey)
....
Unfortunately the key (regardless which) gets errSecDuplicateItem but if I select it with SecItemCopyMatching.... i get errSecItemNotFound
I tried multiple solutions like adding different attributes like account etc.. but most of this eventually resulted in unknown parameter errors.
I'm sure the key is correct cause SecKeyCreateWithData is fine with it.
edit:
Thats the way to collect the key:
let params: [NSString: AnyObject] = [
kSecClass: kSecClassKey,
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrApplicationTag: tag as AnyObject,
kSecReturnRef: true as AnyObject
]
var keyRef: AnyObject? = nil
status = SecItemCopyMatching(params, &keyRef)
The key is also not findable if you look it up manually in the keychain app
I had the same issue. The code worked fine for me on iOS but I could not get it to work for macOS either.
I then rewrote the code by using Apple's documentation https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/storing_keys_as_data
So the code to get a SecKeyRef from a public key given as Data is just:
let options: [String: Any] = [kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
kSecAttrKeySizeInBits as String : 2048]
var error: Unmanaged<CFError>?
guard let key = SecKeyCreateWithData(data as CFData,
options as CFDictionary,
&error) else {
throw error!.takeRetainedValue() as Error
}