I am trying to skip Authentication UI by using kSecUseAuthenticationUISkip ..like the query below
func readExistingCredentials(server: String, completion: #escaping (_: Int,_: String,_: String,_: String) -> Void) {
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrServer as String: server,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnAttributes as String: true,
kSecUseOperationPrompt as String: "This allows you to securely sign into your app",
kSecUseAuthenticationUI as String: kSecUseAuthenticationUISkip,
kSecReturnData as String: true]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else {
return completion(400 ,"","", KeychainError(status: status).localizedDescription)
}
guard let existingItem = item as? [String: Any],
let passwordData = existingItem[kSecValueData as String] as? Data,
let password = String(data: passwordData, encoding: String.Encoding.utf8),
let account = existingItem[kSecAttrAccount as String] as? String
else {
return completion(400 ,"","", KeychainError(status: errSecInternalError).localizedDescription)
}
return completion(200, account, password, "successful")
}
but somehow it's retuning from first guard statement with error below
"The specified item could not be found in the keychain."
Not sure if this is right way or not but any help would be appreciated.
Related
When I save items to the keychain I get a success but after fetching it I get an error not found.
enum KeychainError: Error {
case itemNotFound
case duplicateItem
case invalidItemFormat
case unexpectedStatus(OSStatus)
}
final class KeyChainManager {
static let service = "www.test.de"
static func save(password: String, account: String) throws {
let passwordData = password.data(using: String.Encoding.utf8)!
let query: [String: Any] = [
kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: account,
kSecAttrServer as String: service,
kSecValueData as String: passwordData
]
let status = SecItemAdd(
query as CFDictionary,
nil
)
if status == errSecDuplicateItem {
throw KeychainError.duplicateItem
}
guard status == errSecSuccess else {
throw KeychainError.unexpectedStatus(status)
}
print("success")
}
static func readPassword( account: String) throws -> Data {
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecClass as String: kSecClassGenericPassword,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnData as String: kCFBooleanTrue
]
var itemCopy: AnyObject?
let status = SecItemCopyMatching(
query as CFDictionary,
&itemCopy
)
guard status != errSecItemNotFound else {
throw KeychainError.itemNotFound
}
guard status == errSecSuccess else {
throw KeychainError.unexpectedStatus(status)
}
guard let password = itemCopy as? Data else {
throw KeychainError.invalidItemFormat
}
return password
}
}
If I try
try KeyChainManager.save(password: input.password ?? "", account: input.username ?? "")
I get success but if I try to load the password it stops on this line
throw KeychainError.itemNotFound
I am trying to use the code below to store credentials to my Keychain manager and it seems to save successfully, but when I try to retrieve the code, it is returning an empty string instead. I can't seem to figure out what's wrong, but I'm sure it's minor. Any help is appreciated.
Code for storage
let credentials = Credentials(email: txtEmail.text ?? "", password: txtPassword.text ?? "")
KeychainManager.storeCredentials(credentials: credentials)
When I placed a breakpoint, I debugged to make sure that it is passing in actual values into the storeCredentials function
Code for retrieval
var credentials = KeychainManager.fetchCredentials()
txtEmail.text = credentials.email
txtPassword.text = credentials.password
Keychain manager
class KeychainManager {
static let server = "xyz"
enum KeychainError: Error {
case noPassword
case unexpectedPasswordData
case unhandledError(status: OSStatus)
}
static func storeCredentials(credentials: Credentials) {
print("Storing user credentials...")
let account = credentials.email
let password = credentials.password.data(using: String.Encoding.utf8)!
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: account,
kSecAttrServer as String: server,
kSecValueData as String: password]
let status = SecItemAdd(query as CFDictionary, nil)
if status == errSecSuccess { print("Successfully added credentials") } else { print(status.description) }
}
static func fetchCredentials() -> Credentials {
var email = ""
var pass = ""
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrServer as String: server,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnAttributes as String: true,
kSecReturnData as String: true]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
if status != errSecItemNotFound { print("No Password") }
if status == errSecSuccess { print(status) }
if let existingItem = item as? [String : Any],
let passwordData = existingItem[kSecValueData as String] as? Data,
let password = String(data: passwordData, encoding: String.Encoding.utf8),
let account = existingItem[kSecAttrAccount as String] as? String {
print("Successful retrieval of credentials: ")
print("Email: " + account)
print("Password: " + password)
email = account
pass = password
} else {
print("Unexpected password data")
}
let credentials = Credentials(email: email, password: pass)
return credentials
}
}
struct Credentials {
var email: String
var password: String
}
The logs show:
Successful retrieval of credentials:
Email:
Password:
I am looking for a way to share account credentials like userID and accessToken between my iOS app and my WatchOS app. As i understand i can use the WatchConnectivity framework but that does not seem reliable. For example if my iOS app is killed i could not find a way to force wake the app to fetch the data.
The other think that i tried was keychain sharing which also does not seem to work and gives
keyStore.retrieve SecItemCopyMatching error -25300
error
Below is the code that i am using, which i got from this tutorial.
class KeyStore {
let account = "accessToken"
let group = "[TeamID].[BundleID]" //Setup same as in keychain sharing options in capabilities
func store(token : String) {
let data = token.data(using: .utf8)!
let addquery: [String: Any] = [kSecClass as String: kSecClassGenericPassword as String,
kSecAttrAccount as String: account,
kSecValueData as String: data,
kSecAttrSynchronizable as String : kCFBooleanTrue!,
kSecAttrAccessGroup as String : group
]
SecItemDelete(addquery as CFDictionary)
let status : OSStatus = SecItemAdd(addquery as CFDictionary, nil)
guard status == errSecSuccess else {
os_log("store: whoops")
return
}
}
func clear() {
let addquery: [String: Any] = [kSecClass as String: kSecClassGenericPassword as String,
kSecAttrAccount as String: account,
kSecAttrSynchronizable as String : kCFBooleanTrue!,
kSecAttrAccessGroup as String : group
]
SecItemDelete(addquery as CFDictionary)
}
func retrieve() -> String? {
let getquery: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecReturnData as String: kCFBooleanTrue!,
kSecMatchLimit as String : kSecMatchLimitOne,
kSecAttrSynchronizable as String : kCFBooleanTrue!,
kSecAttrAccessGroup as String : group
]
var item: CFTypeRef?
let status = SecItemCopyMatching(getquery as CFDictionary, &item)
guard status == errSecSuccess else {
os_log("keyStore.retrieve SecItemCopyMatching error \(status)")
return nil
}
guard let data = item as? Data? else {
os_log("keyStore.retrieve not data")
return nil
}
return String(data: data!, encoding: String.Encoding.utf8)
}
func getAllKeychainItems() throws {
let classes = [kSecClassGenericPassword as String, // Generic password items
kSecClassInternetPassword as String, // Internet password items
kSecClassCertificate as String, // Certificate items
kSecClassKey as String, // Cryptographic key items
kSecClassIdentity as String,
kSecAttrAccount as String] // Identity items
classes.forEach { secClass in
let items = getAllKeyChainItemsOfClass( secClass )
NSLog(items.description)
}
}
func getAllKeyChainItemsOfClass(_ secClass: String) -> [String: AnyObject] {
let query: [String: Any] = [
kSecClass as String : secClass,
kSecReturnData as String : true,
kSecReturnAttributes as String : true,
kSecReturnRef as String : true,
kSecMatchLimit as String: kSecMatchLimitAll
]
var result: AnyObject?
let lastResultCode = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
var values = [String: AnyObject]()
if lastResultCode == noErr {
let array = result as? Array<Dictionary<String, Any>>
for item in array! {
if let key = item[kSecAttrAccount as String] as? String,
let value = item[kSecValueData as String] as? Data {
values[key] = String(data: value, encoding:.utf8) as AnyObject?
}
else if let key = item[kSecAttrLabel as String] as? String,
let value = item[kSecValueRef as String] {
values[key] = value as AnyObject
}
}
}
return values
}
}
Any references that could help me share login credentials even when the app is killed would really help
I have a problem updating and deleting keychain.
public func clearKeychain(username: String) throws -> Any?{
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: username
]
let status = SecItemDelete(query as CFDictionary)
if status != errSecSuccess {
throw KeychainError.unhandledError(status: status)
}
print("Clear Keychain")
return status
}
public func updateKeychain(username: String, password: String) throws -> Any?{
let credentials = Credentials.init(username: username, password: password)
let data = credentials.password.data(using: .utf8)!
// store password as data and if you want to store username
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: username,
kSecValueData as String: data]
let fields: [String: Any] = [
kSecAttrAccount as String: username,
kSecValueData as String: data
]
let status = SecItemUpdate(query as CFDictionary, fields as CFDictionary)
guard status == errSecSuccess else {
throw KeychainError.unhandledError(status: status) }
print("Updated Password")
return status
}
Usage:
let _ = (try? keychain.clearKeychain(username: "KeychainUser")) as Any?
let _ = (try? keychain.updateKeychain(username: "KeychainUser", password: "123456789")) as Any?
Can't delete or update keychain. I have no problem creating and getting the value of the keychain, but I have a problem when updating or deleting. No error is shown. And status returns nil.
To update & delete your item, you need to search particular type of data. So you don't require to pass data in query. Just update your query as follow will fix issue on updating data.
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword]
By default, keychain services deletes all keychain items that match
the search parameters. If you want to delete a specific item that you
already have a reference or persistent reference to, add that to the
search dictionary as the value for the kSecMatchItemList key. In this
way, you limit the deletion to only the specified item.
I was trying to store two keys private and public in KeyChain and when I try to do so my result valuable return 0 which I am assuming that mean it was store ,but when I try to get back to decrypt a message I am getting it back as nil so if there is a way to check if the keys were store based SecItemCopyMatching? but I am not getting any error while creating them
let tagName = "PrivateKeyTag"
let privkey = "key"
let privkeyData = Data(privkey!.utf8)
let privateFilter: [String : Any] = [
(kSecClass as String) : kSecClassKey,
(kSecAttrKeyType as String) : kSecAttrKeyTypeRSA,
(kSecAttrApplicationTag as String) : tagName,
(kSecValueData as String) : privkeyData,
(kSecAttrKeyClass as String) : kSecAttrKeyClassPrivate,
// kSecAttrKeySizeInBits as String: 2048,
(kSecReturnPersistentRef as String): true,
] as [String : Any]
let result = SecItemAdd(privateFilter as CFDictionary, nil)
if ((result != noErr) && (result != errSecDuplicateItem)) {
NSLog("Cannot add key to keychain, status \(result).")
}
let getquery: [String: Any] = [kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tag,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
]
var item: CFTypeRef?
let status = SecItemCopyMatching(getquery as CFDictionary, &item)
guard status == errSecSuccess else {
print("key not found")
return
}
let key = item as! SecKey
When you create a cryptographic key, you can set the parameter kSecAttrIsPermanent to true which will automatically store the key in the default keychain. This will clean your code a bit so you no longer have to deal with the SecItemAdd() and all the error handling with that. So here is a simpler way to do what you're trying to do.
To create a key and query a key
let tag = "com.example.keys.mykey".data(using: .utf8)!
let attributes: [String: Any] =
[kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
kSecPrivateKeyAttrs as String:
[kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: tag]
]
let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, nil)
let query: [String: Any] = [kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tag,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecReturnRef as String: true]
var item: CFTypeRef?
let status2 = SecItemCopyMatching(query as CFDictionary, &item)
guard status2 == errSecSuccess else { print("error1"); return }
let key = item as! SecKey
Running this code I believe will accomplish what you are trying to do in the code you provided in the question. I tried running the code you gave and the version of Xcode and swift I am using gives compiler errors.
If you run the code in this answer you will notice that no errors are printed to the console, indicating that the key was successfully found in the default keychain.