Sharing account details across iOS app and WatchOS 7 app - ios

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

Related

saving item to key chain gets not found error

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

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.

How to check if Key was store in KeyChain in Swift

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.

Retrieve SecKey from NSData

I encountered an issue with creating SecKey from NSData. Basically my client-server communication is based on signature created with private key and verified on the server with public key.
I am implementing session transfer between two devices and in order to continue communication I need those keys to be transferred as well. I am converting SecKey to NSData and sending it via bluetooth, but on other side I cannot convert NSData to SecKey back to use encryption.
Could you help please?
More complete example (swift 4, iOS 10+) - assuming you have a Base64 encoded string. Note that the other side of the connection needs to also be creating key payloads using the same format (i.e. RSA - PKCS #1, also verify key size ). This function handles public or private keys (security caveats omitted for brevity).
// Extract secKey from encoded string - defaults to extracting public keys
func decodeSecKeyFromBase64(encodedKey: String, isPrivate: Bool = false) -> SecKey? {
var keyClass = kSecAttrKeyClassPublic
if isPrivate {
keyClass = kSecAttrKeyClassPrivate
}
let attributes: [String:Any] =
[
kSecAttrKeyClass as String: keyClass,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
]
guard let secKeyData = Data.init(base64Encoded: encodedKey) else {
print("Error: invalid encodedKey, cannot extract data")
return nil
}
guard let secKey = SecKeyCreateWithData(secKeyData as CFData, attributes as CFDictionary, nil) else {
print("Error: Problem in SecKeyCreateWithData()")
return nil
}
return secKey
}
Starting from iOS 10 you can use the following code:
let attributes: [String:Any] = [
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: blockSize,
]
secKey = SecKeyCreateWithData(secKeyData as CFData, attributes as CFDictionary, nil)
what I use with success ...
func obtainKeyData(tag: String) -> NSData? {
var keyRef: AnyObject?
let query: Dictionary<String, AnyObject> = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecReturnData): kCFBooleanTrue as CFBoolean,
String(kSecClass): kSecClassKey as CFStringRef,
String(kSecAttrApplicationTag): tag as CFStringRef,
]
let result: NSData?
switch SecItemCopyMatching(query, &keyRef) {
case noErr:
result = keyRef as? NSData
default:
result = nil
}
return result
}
func insertPublicKey(publicTag: String, data: NSData) -> SecKeyRef? {
let query: Dictionary<String, AnyObject> = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecClass): kSecClassKey as CFStringRef,
String(kSecAttrApplicationTag): publicTag as CFStringRef,
String(kSecValueData): data as CFDataRef,
String(kSecReturnPersistentRef): true as CFBooleanRef]
var persistentRef: AnyObject?
let status = SecItemAdd(query, &persistentRef)
if status != noErr && status != errSecDuplicateItem {
return nil
}
return obtainKey(publicTag)
}
func obtainKey(tag: String) -> SecKey? {
var keyRef: AnyObject?
let query: Dictionary<String, AnyObject> = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecReturnRef): kCFBooleanTrue as CFBoolean,
String(kSecClass): kSecClassKey as CFStringRef,
String(kSecAttrApplicationTag): tag as CFStringRef,
]
let status = SecItemCopyMatching(query, &keyRef)
switch status {
case noErr:
if let ref = keyRef {
return (ref as! SecKeyRef)
}
default:
break
}
return nil
}
There is no easy way to transfer private part of the key pair( it is possible, but try to avoid it )

Enumerate all Keychain items in my iOS application

What's the easiest way to programmatically (from within my app) get all items stored in the keychain?
It probably has something to do with SecItemCopyMatching(), but the documentation for that function is not very clear (and I failed to find a decent sample on the web).
SecItemCopyMatching is the right call for that. First we build our query dictionary so that the items' attributes are returned in dictionaries, and that all items are returned:
NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnAttributes,
(__bridge id)kSecMatchLimitAll, (__bridge id)kSecMatchLimit,
nil];
As SecItemCopyMatching requires at least the class of the returned SecItems, we create an array with all the classes…
NSArray *secItemClasses = [NSArray arrayWithObjects:
(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecClassInternetPassword,
(__bridge id)kSecClassCertificate,
(__bridge id)kSecClassKey,
(__bridge id)kSecClassIdentity,
nil];
...and for each class, set the class in our query, call SecItemCopyMatching, and log the result.
for (id secItemClass in secItemClasses) {
[query setObject:secItemClass forKey:(__bridge id)kSecClass];
CFTypeRef result = NULL;
SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
NSLog(#"%#", (__bridge id)result);
if (result != NULL) CFRelease(result);
}
In production code, you should check that the OSStatus returned by SecItemCopyMatching is either errSecItemNotFound (no items found) or errSecSuccess (at least one item was found).
A Swift 4 update to #Cosmin's Swift 3 answer.
open func getAllKeyChainItemsOfClass(_ secClass: String) -> [String:String] {
let query: [String: Any] = [
kSecClass as String : secClass,
kSecReturnData as String : kCFBooleanTrue,
kSecReturnAttributes as String : kCFBooleanTrue,
kSecReturnRef as String : kCFBooleanTrue,
kSecMatchLimit as String: kSecMatchLimitAll
]
var result: AnyObject?
let lastResultCode = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
var values = [String:String]()
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)
}
}
}
return values
}
Swift 3+ version that returns also the keys (kSecAttrAccount):
open func getAllKeyChainItemsOfClass(_ secClass: String) -> [String:String] {
let query: [String: Any] = [
kSecClass : secClass,
kSecReturnData : kCFBooleanTrue,
kSecReturnAttributes : kCFBooleanTrue,
kSecReturnRef : kCFBooleanTrue,
kSecMatchLimit : kSecMatchLimitAll
]
var result: AnyObject?
let lastResultCode = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
var values = [String:String]()
if lastResultCode == noErr {
let array = result as? Array<Dictionary<String, Any>>
for item in array! {
if let key = item[kSecAttrAccount] as? String,
let value = item[kSecValueData] as? Data {
values[key] = String(data: value, encoding:.utf8)
}
}
}
return values
}
The other Swift code snippets all seem a bit convoluted. You don't really have to mess around the MutablePointers that much, and you probably want to have proper error management. I implemented my version in Swift just by tweaking the code in the Apple documentation. Here it is in for those using Xcode 11.
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword, // change the kSecClass for your needs
kSecMatchLimit as String: kSecMatchLimitAll,
kSecReturnAttributes as String: true,
kSecReturnRef as String: true]
var items_ref: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &items_ref)
guard status != errSecItemNotFound else { throw KeychainError.noPassword }
guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) }
let items = items_ref as! Array<Dictionary<String, Any>>
// Now loop over the items and do something with each item
for item in items {
// Sample code: prints the account name
print(item[kSecAttrAccount as String] as? String)
}
Swift 3 version with xcode 9.1
func getAllKeyChainItemsOfClass(_ secClass: String) -> [String:String] {
let query: [String: Any] = [
kSecClass as String : secClass,
kSecReturnData as String : kCFBooleanTrue,
kSecReturnAttributes as String : kCFBooleanTrue,
kSecReturnRef as String : kCFBooleanTrue,
kSecMatchLimit as String : kSecMatchLimitAll
]
var result: AnyObject?
let lastResultCode = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
var values = [String:String]()
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)
}
}
}
return values
}
Can be called like :
debugPrint(getAllKeyChainItemsOfClass(kSecClassGenericPassword as String))
Updated to include kSecClassIdentity and kSecClassCertificate information in dictionary
I also don't think calling withUnsafeMutablePointer(to:_:) is necessary.
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))
}
// this also works, although I am not sure if it is as save as calling withUnsafeMutabePointer
// let lastResultCode = SecItemCopyMatching(query as CFDictionary, &result)
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?
}
// including identities and certificates in dictionary
else if let key = item[kSecAttrLabel as String] as? String,
let value = item[kSecValueRef as String] {
values[key] = value as AnyObject
}
}
}
return values
}

Resources