saving item to key chain gets not found error - ios

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

Related

Keys from the Secure Enclave returns empty string

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:

How to skip Authentication UI when reading from keychain

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.

Store accessToken in iOS keychain

I'm looking for the simples way to store/load an accessToken and refreshToken in the iOS Keychain.
So far I've come to this:
enum Key: String {
case accessToken = "some.keys.accessToken"
case refreshToken = "some.keys.refreshToken"
fileprivate var tag: Data {
rawValue.data(using: .utf8)!
}
}
enum KeychainError: Error {
case storeFailed
case loadFailed
}
func store(key: Key, value: String) throws {
let addQuery: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: key.tag,
kSecValueRef as String: value
]
let status = SecItemAdd(addQuery as CFDictionary, nil)
guard status == errSecSuccess else {
print("Store key: '\(key.rawValue)' in Keychain failed with status: \(status.description)")
throw KeychainError.storeFailed
}
}
func load(key: Key) throws -> String? {
let getQuery: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: key.tag,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecReturnRef as String: true
]
var item: CFTypeRef?
let status = SecItemCopyMatching(getQuery as CFDictionary, &item)
guard status == errSecSuccess else {
print("Load key: '\(key.rawValue)' in Keychain failed with status: \(status.description)")
throw KeychainError.loadFailed
}
return item as? String
}
But this fail with messages:
When running store:
Store key: 'some.keys.accessToken' in Keychain failed with status: -50
When running load:
Load key: 'some.keys.accessToken' in Keychain failed with status: -25300
What am I doing wrong here?
As per recommendations of Apple, you should use a kSecClassGenericPassword class to store arbitrary data, i.e. tokens, securely. To do that properly, you'll need a String identifier to store under the kSecAttrAccount key and a Data representation of the secure value to store under the kSecValueData key. You can easily transform your string value to Data by doing the following (assuming the token contains UTF8 data).
tokenString.data(using: .utf8)
// or
Data(tokenString.utf8)
First some nice to haves.
/// Errors that can be thrown when the Keychain is queried.
enum KeychainError: LocalizedError {
/// The requested item was not found in the Keychain.
case itemNotFound
/// Attempted to save an item that already exists.
/// Update the item instead.
case duplicateItem
/// The operation resulted in an unexpected status.
case unexpectedStatus(OSStatus)
}
/// A service that can be used to group the tokens
/// as the kSecAttrAccount should be unique.
let service = "com.bundle.stuff.token-service"
Inserting a token into the Keychain.
func insertToken(_ token: Data, identifier: String, service: String = service) throws {
let attributes = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: identifier,
kSecValueData: token
] as CFDictionary
let status = SecItemAdd(attributes, nil)
guard status == errSecSuccess else {
if status == errSecDuplicateItem {
throw KeychainError.duplicateItem
}
throw KeychainError.unexpectedStatus(status)
}
}
The retrieval will be done as follows.
func getToken(identifier: String, service: String = service) throws -> String {
let query = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: identifier,
kSecMatchLimit: kSecMatchLimitOne,
kSecReturnData: true
] as CFDictionary
var result: AnyObject?
let status = SecItemCopyMatching(query, &result)
guard status == errSecSuccess else {
if status == errSecItemNotFound {
// Technically could make the return optional and return nil here
// depending on how you like this to be taken care of
throw KeychainError.itemNotFound
}
throw KeychainError.unexpectedStatus(status)
}
// Lots of bang operators here, due to the nature of Keychain functionality.
// You could work with more guards/if let or others.
return String(data: result as! Data, encoding: .utf8)!
}
Note that a generic password has certain specifications, as mentioned before, and I guess the most important one, is that the kSecAttrAccount flag must be unique for each token you store. You cannot store access token A and access token B for the same identifier. This will cause the .duplicateItem error to trigger.
I'd also like to point out that the OSStatus website is very useful for getting more info about your error code. Besides the website there is also the SecCopyErrorMessageString(OSStatus, UnsafeMutableRawPointer?) function that can get you more information about your error code.
Now that technically answers your question, but below I've added some more nice to haves. Update updates a token value for an existing item, make sure the item exists before calling update!. Upsert inserts a token when it doesn't already exist, if it does, it will update the token value. Delete will remove the token value from the Keychain.
func updateToken(_ token: Data, identifier: String, service: String = service) throws {
let query = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: identifier
] as CFDictionary
let attributes = [
kSecValueData: token
] as CFDictionary
let status = SecItemUpdate(query, attributes)
guard status == errSecSuccess else {
if status == errSecItemNotFound {
throw KeychainError.itemNotFound
}
throw KeychainError.unexpectedStatus(status)
}
}
func upsertToken(_ token: Data, identifier: String, service: String = service) throws {
do {
_ = try getToken(identifier: identifier, service: service)
try updateToken(token, identifier: identifier, service: service)
} catch KeychainError.itemNotFound {
try insertToken(token, identifier: identifier, service: service)
}
}
func deleteToken(identifier: String, service: String = service) throws {
let query = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: identifier
] as CFDictionary
let status = SecItemDelete(query)
guard status == errSecSuccess || status == errSecItemNotFound else {
throw KeychainError.unexpectedStatus(status)
}
}
Please try the following edits, marked with comments:
enum KeychainError: Error {
case storeFailed
case loadFailed
}
func store(key: Key, value: String) throws {
let addQuery: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: key.tag,
// use kSecValueData, converting your String to Data
kSecValueData as Data: value.data(using: .utf8)
]
let status = SecItemAdd(addQuery as CFDictionary, nil)
guard status == errSecSuccess else {
print("Store key: '\(key.rawValue)' in Keychain failed with status: \(status.description)")
throw KeychainError.storeFailed
}
}
func load(key: Key) throws -> String? {
let getQuery: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: key.tag,
// remove kSecAttrKeyType and add kSecReturnData
kSecReturnData as String: kCFBooleanTrue
]
// this is different
var item: AnyObject?
let status: OSStatus = withUnsafeMutablePointer(to:&item)
{ (result: UnsafeMutablePointer<AnyObject?>?) -> OSStatus in
return SecItemCopyMatching(getQuery as CFDictionary, result)
}
guard status == errSecSuccess else {
print("Load key: '\(key.rawValue)' in Keychain failed with status: \(status.description)")
throw KeychainError.loadFailed
}
// convert Data to String
guard let itemData = item as? Data else { throw KeychainError.loadFailed }
return String(decoding: itemData, as: UTF8.self)
}
how does this change the behavior?
A couple comments:
I'm not sure why you're using kSecAttrKeyType property to store a
String
I'm unsure of the use of kSecValueRef vs. kSecValueData, only
making this suggestion based on what has worked for me
Hope this helps!?

Sharing account details across iOS app and WatchOS 7 app

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

Keychain - OSStatus return -34018

iam trying save a value to Keychain. Code is right, but OSStatus returns code -34018.
In other project same code works right.
Code:
fileprivate func save(key: String, value: String) -> Bool {
guard let data: Data = value.data(using: String.Encoding.utf8) else {
return false
}
let query = [
kSecClass as String: kSecClassGenericPassword as String,
kSecAttrAccount as String: key,
kSecValueData as String: data,
] as [String : Any]
SecItemDelete(query as CFDictionary)
let status: OSStatus = SecItemAdd(query as CFDictionary, nil)
return status == noErr
}
Any idea, please?
Is there any error with project settings or?

Resources