How can I use Keychain in Mac Catalyst app on Mac? - swift

I can't write/read from Keychain in a Mac-Catalyst app on Mac, it returns errors 34018 and 25300 respectively. Is there a way to make Keychain work on Mac in a Catalyst app?
Xcode: 11.0, MacOS: 10.15
Here is a sample code, it works on iOS but not on Mac. The code prints "My secretive bee 🐝" to indicate that we have successfully written this text to Keychain and then read from it.
override func viewDidLoad() {
super.viewDidLoad()
let itemKey = "My key"
let itemValue = "My secretive bee 🐝"
deleteFromKeychain(itemKey: itemKey)
addToKeychain(itemKey: itemKey, itemValue: itemValue)
readFromKeychain(itemKey: itemKey)
}
func deleteFromKeychain(itemKey: String) {
let queryDelete: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
]
let resultCodeDelete = SecItemDelete(queryDelete as CFDictionary)
if resultCodeDelete != noErr {
print("Error deleting from Keychain: \(resultCodeDelete)")
}
}
func addToKeychain(itemKey: String, itemValue: String) {
guard let valueData = itemValue.data(using: String.Encoding.utf8) else {
print("Error saving text to Keychain")
return
}
let queryAdd: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
kSecValueData as String: valueData as AnyObject,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
]
let resultCode = SecItemAdd(queryAdd as CFDictionary, nil)
if resultCode != noErr {
print("Error saving to Keychain: \(resultCode)")
}
}
func readFromKeychain(itemKey: String) {
let queryLoad: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
kSecReturnData as String: kCFBooleanTrue,
kSecMatchLimit as String: kSecMatchLimitOne,
]
var result: AnyObject?
let resultCodeLoad = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
if resultCodeLoad == noErr {
if let result = result as? Data,
let keyValue = NSString(data: result,
encoding: String.Encoding.utf8.rawValue) as? String {
// Found successfully
print(keyValue)
}
} else {
print("Error loading from Keychain: \(resultCodeLoad)")
}
}

I enabled the keychain sharing from signing and capabilities section in xcode, and now I am able to store values in keychain.

Related

how to link a nickname with a keychain

Before, I was storing sensitive data(email, nickame, and password) in a json file. Now I am storing the email and the password into keychains ( kSecAttrAccount is the email)
But I have no ideea how can I store the nickname in the keychain. Or where can I store the nickname so it gets linked with a specific keychain.
import Foundation
import SwiftUI
func save(account: String, password: String) {
do {
try KeychainManager.save(
service: "loseamp",
account: account,
password: password.data(using: .utf8) ?? Data())
} catch {
print(error)
}
}
func getPassword(account: String, password: String) {
guard let data = KeychainManager.get(
service: "loseamp",
account: account
) else {
print("Failed to read password")
return
}
let password = String(decoding: data, as: UTF8.self)
print("read password here: \(password)")
}
class KeychainManager {
enum KeychainError: Error {
case duplicateEntry
case unknown(OSStatus)
}
static func save(service: String, account: String, password: Data) throws {
// service, account, password, class, data
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecValueData as String: password as AnyObject,
kSecClass as String: kSecClassGenericPassword
]
let status = SecItemAdd(query as CFDictionary, nil)
guard status != errSecDuplicateItem else {
throw KeychainError.duplicateEntry
}
guard status == errSecSuccess else {
throw KeychainError.unknown(status)
}
}
static func get(service: String, account: String) -> Data? {
// service, account, password, class, data
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecReturnData as String: kCFBooleanTrue,
kSecClass as String: kSecClassGenericPassword,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
print("Read status: \(status)")
return result as? Data
}
func update(service: String, account: String, password: Data) {
let query = [
kSecAttrService: service as AnyObject,
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount: account as AnyObject,
] as CFDictionary
let updatedData = [kSecValueData: password] as CFDictionary
SecItemUpdate(query, updatedData)
}
func delete(service: String, account: String) {
let query = [
kSecAttrService: service as AnyObject,
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account as AnyObject
] as CFDictionary
SecItemDelete(query)
}
func isEmailDuplicate(service: String, account: String) -> Bool {
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecClass as String: kSecClassGenericPassword
]
let status = SecItemAdd(query as CFDictionary, nil)
return status == errSecDuplicateItem
}
}

SecKeyVerifySignature failed ecdsaSignatureDigestX962

I'm trying to sign and verify a challenge with elliptic curve algorithms using Apple tools.
Using the SecKeyCreateWithData I can use/import already generated public/private keys, this works great. Then I call the sign() function which takes SecKeyAlgorithm parameter. In my case is ecdsaSignatureDigestX962 because I use secp256r1 curve (NIST P-256). So the signature doesn't failed but then the verify always crash with :
Can't verify/wrong signature Unmanaged<CFErrorRef>(_value: Error Domain=NSOSStatusErrorDomain Code=-67808 "EC signature verification failed (ccerr -7)" UserInfo={NSDescription=EC signature verification failed (ccerr -7)})
Here is my complete code if someone has an idea :
import UIKit
class SecureEnclave: UIViewController {
var publicKey: SecKey!
var privateKey: SecKey!
var signature: Data!
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 10.0, *) {
var error: Unmanaged<CFError>?
//Step 1: Private Key
let privKeyUInt8: [UInt8] = [0x04,0x**,0x**,0x**,0x**,0x**,0x**,0x**,0x**,0x**,0x**,0x**,0x**,0x**,0x**,0x**,0xde,0x0a,0x37,0x02,0xfc,0xc6,0x04,0x5b,0x03,0xd7,0x12,0x03,0x26,0x6a,0x4b,0x3d,0x05,0x55,0x5d,0x90,0xe1,0xa4,0xcf,0xd1,0x78,0xf7,0x95,0xda,0xa3,0x9c,0x**,0x2c,0x37,0x4f,0x**,0xfa,0x28,0x2e,0x64,0x7a,0x22,0x7f,0x47,0x9a,0x98,0x1a,0x2c,0x9b,0x2d,0x28,0x96,0xe0,0x**,0x07,0x33,0x06,0x10,0x5a,0x95,0x85,0x9c,0xc3,0xfd,0x43,0xf4,0x81,0x95,0xf4,0xe5,0x6d,0xb2,0x**,0x**,0x**,0x87,0x6d,0xc1,0x52,0x89,0xd3,0x05]
let CFPrivData = CFDataCreate(nil, privKeyUInt8, privKeyUInt8.count)
let optionsPrivKey: [String: Any] = [
kSecAttrKeyType as String : kSecAttrKeyTypeEC,
kSecAttrKeyClass as String : kSecAttrKeyClassPrivate,
]
guard let privKey = SecKeyCreateWithData(CFPrivData!, optionsPrivKey as CFDictionary, &error) else {
let error = error!.takeRetainedValue() as Error
return print(error)
}
self.privateKey = privKey
//Step 2: Public Key
let pubKeyUInt8: [UInt8] = [0x04,0x09,0x44,0x11,0xc6,0xbe,0x9f,0x31,0x88,0xa0,0x23,0xe7,0xf1,0x77,0x13,0xef,0xde,0x0a,0x37,0x02,0xfc,0xc6,0x04,0x5b,0x03,0xd7,0x12,0x03,0x26,0x6a,0x4b,0x3d,0x05,0x55,0x5d,0x90,0xe1,0xa4,0xcf,0xd1,0x78,0xf7,0x95,0xda,0xa3,0x9c,0x18,0x2c,0x37,0x4f,0x1b,0xfa,0x28,0x2e,0x64,0x7a,0x22,0x7f,0x47,0x9a,0x98,0x1a,0x2c,0x9b,0x2d]
let CFPubData = CFDataCreate(nil, pubKeyUInt8, pubKeyUInt8.count)
let optionsPubKey: [String: Any] = [kSecAttrKeyType as String: kSecAttrKeyTypeEC,
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
kSecAttrKeySizeInBits as String: 256]
guard let pubKey = SecKeyCreateWithData(CFPubData!, optionsPubKey as CFDictionary, &error) else {
let error = error!.takeRetainedValue() as Error
return print(error)
}
self.publicKey = pubKey
//Step 3: Signing/Verifing
let challengeString = "Hello"
let challengeData = challengeString.data(using: .utf8)
self.sign(algorithm: .ecdsaSignatureDigestX962, data: challengeData!)
} else {
print("unsupported")
}
}
private func sign(algorithm: SecKeyAlgorithm, data: Data) {
guard SecKeyIsAlgorithmSupported(self.privateKey!, .sign, algorithm) else {
print("Algorith not supported - Can't sign")
return
}
// SecKeyCreateSignature call is blocking when the used key
// is protected by biometry authentication. If that's not the case,
// dispatching to a background thread isn't necessary.
DispatchQueue.global().async {
var error: Unmanaged<CFError>?
let signature = SecKeyCreateSignature(self.privateKey!, algorithm, data as CFData, &error) as Data?
DispatchQueue.main.async {
self.signature = signature
guard signature != nil else {
print((error!.takeRetainedValue() as Error).localizedDescription)
return
}
print("Signature : OK !")
//Step 4: Verifing
let algorithm: SecKeyAlgorithm = .ecdsaSignatureDigestX962
guard SecKeyIsAlgorithmSupported(self.publicKey, .verify, algorithm) else {
print("Algorith not supported - Can't verify")
return
}
var error: Unmanaged<CFError>?
guard SecKeyVerifySignature(self.publicKey, algorithm, data as CFData, self.signature as CFData, &error) else {
print("Can't verify/wrong signature \(error!)")
return
}
print("Signature check: OK")
}
}
}
}

store facebook information into Firebase Database using Facebook IOS swift SDK

I'm trying to store facebook user data into firebase database but I keep getting the error "Cannot convert Any? to expected type String"
((FBSDKAccessToken.current()) != nil){
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, picture.type(large), email"]).start(completionHandler: { (connection, result, error) -> Void in
if (error == nil && result != nil) {
guard let fbData = result as? [String:Any] else { return }
let fbid = fbData["id"]
let name = fbData["name"]
self.ref.child("users").child(fbid).setValue([
"id": fbid,
"name": name
])
}
})
I also want to store the picture url into the database. How can I do this?
Using Facebook IOS Swift SDK and Firebase
Try my I implement this function. This is from the production app and it works well for us. I also recommend uploading profile image in Firebase storage or other storage, because after a while the profile image url is not valid.
class func getAllFacebookData(success: ((_ result: [String : Any]) -> Void)?, fail: ((_ error: Error) -> Void)?) {
guard !isGetDataFromFacebook else { return }
DispatchQueue.global(qos: .background).async {
guard let tokenString = FBSDKAccessToken.current()?.tokenString else { return }
guard let req = FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "name,age_range,birthday,gender,email,first_name,last_name,picture.width(1000).height(1000),work,education,hometown,location, friends"], tokenString: tokenString, version: nil, httpMethod: "GET") else { return }
req.start { (connection, result, error) in
if error == nil {
guard let _result = result as? [String : Any] else { return }
let _picture = _result["picture"] as? [String : Any]
let _pictureData = _picture?["data"] as? [String : Any]
let _isSilhouette = _pictureData?["is_silhouette"] as? Bool
let userPref = UserDefaults.standard
userPref.set(_isSilhouette, forKey: "UserHasSilhouetteImage")
userPref.synchronize()
debugPrint("facebook result", _result)
isGetDataFromFacebook = true
syncUserInfoInDatabase(_result)
success?(_result)
} else {
debugPrint("request", error!)
fail?(error!)
}
}
}
}
fileprivate class func syncUserInfoInDatabase(_ userInfo: [String : Any]) {
let realmManager = RealmManager()
guard let currentUser = realmManager.getCurrentUser() else { return }
guard let userInfoModel = createUserInfoModel(userInfo) else { return }
do {
let realm = try Realm()
try realm.write {
currentUser.info = userInfoModel
}
} catch {
debugPrint("realm syncUserInfoInDatabase error", error.localizedDescription)
}
savePhoto(userInfo)
let firebaseDatabaseGeneralManager = FirebaseDatabaseGeneralManager()
firebaseDatabaseGeneralManager.updateCurrentUser(success: nil, fail: nil)
// crete a personal settings
let firUserSettingsDatabaseManager = FIRUserSettingsDatabaseManager()
firUserSettingsDatabaseManager.createStartPeopleFilterSettings(success: nil, fail: nil)
let userSearchLocationModel = UserSearchLocationModel()
userSearchLocationModel.userID = currentUser.id
userSearchLocationModel.birthdayTimeStamp = currentUser.birthdayTimeStamp
userSearchLocationModel.gender = currentUser.gender
switch currentUser.gender {
case UserPeopleFilterSettings.FilterGenderMode.female.description:
userSearchLocationModel.genderIndex = UserPeopleFilterSettings.FilterGenderMode.female.index
case UserPeopleFilterSettings.FilterGenderMode.male.description:
userSearchLocationModel.genderIndex = UserPeopleFilterSettings.FilterGenderMode.male.index
default: break
}
let firPeopleSearchDatabaseManager = FIRPeopleSearchDatabaseManager()
firPeopleSearchDatabaseManager.saveUserSearchLocationModel(userSearchLocationModel, success: nil, fail: nil)
}
private class func savePhoto(_ userInfo: [String : Any]) {
if let pictureDict = userInfo["picture"] as? [String : Any], let pictureDataDict = pictureDict["data"] as? [String : Any] {
if let urlPath = pictureDataDict["url"] as? String {
let firImageDatabaseManager = FIRImageDatabaseManager()
firImageDatabaseManager.saveProfileImage(urlPath, fileName: nil, isFacebookPhoto: true, realmSaved: nil)
}
}
}

Saving array of [String: Any] to user default crash occasionally?

I try to save an array of [String: Any] to user default, and for some situations it works, but others do not. I use the following to save to the default:
static func savingQueueToDisk(){
let queueDict = App.delegate?.queue?.map({ (track) -> [String: Any] in
return track.dict
})
if let queueDict = queueDict{
UserDefaults.standard.set(queueDict, forKey: App.UserDefaultKey.queue)
UserDefaults.standard.synchronize()
}
}
Queue is an array of Track, which is defined as follows:
class Track {
var dict: [String: Any]!
init(dict: [String: Any]) {
self.dict = dict
}
var album: Album?{
guard let albumDict = self.dict[AlbumKey] as? [String: Any] else{
return nil
}
return Album(dict: albumDict)
}
var artists: [Artist]?{
guard let artistsDict = self.dict[ArtistsKey] as? [[String: Any]] else{
return nil
}
let artists = artistsDict.map { (artistdict) -> Artist in
return Artist(dict: artistdict)
}
return artists
}
var id: String!{
return self.dict[IdKey] as! String
}
var name: String?{
return self.dict[NameKey] as? String
}
var uri: String?{
return self.dict[URIKey] as? String
}
}
I got different output when retrieving from the same API
Crashing output:
http://www.jsoneditoronline.org/?id=cb45af75a79aff64995e01e5efc0e7b6
Valid output:
http://www.jsoneditoronline.org/?id=0939823a4ac261bd4cb088663c092b20
It turns out it's not safe to just store an array of [String: Any] to the user defaults directly, and it might break based on the data it contains, and hence complaining about can't set none-property-list to user defaults. I solve this by first convert the array of [String: Any] to Data using JSONSerializationand now it can be saved correctly.
Solution:
//saving current queue in the app delegate to disk
static func savingQueueToDisk(){
if let queue = App.delegate?.queue{
let queueDict = queue.map({ (track) -> [String: Any] in
return track.dict
})
if let data = try? JSONSerialization.data(withJSONObject: queueDict, options: []){
UserDefaults.standard.set(data, forKey: App.UserDefaultKey.queue)
UserDefaults.standard.synchronize()
}else{
print("data invalid")
}
}
}
//retriving queue form disk
static func retrivingQueueFromDisk() -> [Track]?{
if let queueData = UserDefaults.standard.value(forKey: App.UserDefaultKey.queue) as? Data{
guard let jsonObject = try? JSONSerialization.jsonObject(with: queueData, options: []) else{
return nil
}
guard let queueDicts = jsonObject as? [[String: Any]] else{
return nil
}
let tracks = queueDicts.map({ (trackDict) -> Track in
return Track(dict: trackDict)
})
return tracks
}
return nil
}

Need to adjust NSJSONSerialization to iOS10

After upgrading to iOS10 users started complaining about crashes of my app.
I am testing it with iOS10 on the simulator and indeed the app crashes with a message saying "Could not cast value of type '__NSArrayI' to 'NSMutableArray'". Here's my code, please help:
import Foundation
protocol getAllListsModel: class {
func listsDownloadingComplete(downloadedLists: [ContactsList])
}
class ListsDownloader: NSObject, NSURLSessionDataDelegate{
//properties
weak var delegate: getAllListsModel!
var data : NSMutableData = NSMutableData()
func downloadLists() {
let urlPath: String = "http://..."
let url: NSURL = NSURL(string: urlPath)!
var session: NSURLSession!
let configuration = NSURLSessionConfiguration.ephemeralSessionConfiguration() //defaultSessionConfiguration()
session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
let task = session.dataTaskWithURL(url)
task.resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.data.appendData(data);
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if error != nil {
print("Failed to download data")
}else {
self.parseJSON()
print("Lists downloaded")
}
}
func parseJSON() {
var jsonResult: NSMutableArray = NSMutableArray()
do{
try jsonResult = NSJSONSerialization.JSONObjectWithData(self.data, options:NSJSONReadingOptions.AllowFragments) as! NSMutableArray
} catch let error as NSError {
print(error)
}
var jsonElement: NSDictionary = NSDictionary()
var downloadedLists: [ContactsList] = []
for i in 0...jsonResult.count-1 {
jsonElement = jsonResult[i] as! NSDictionary
let tempContactsList = ContactsList()
//the following insures none of the JsonElement values are nil through optional binding
let id = jsonElement["id"] as? String
let name = jsonElement["name"] as? String
let pin = jsonElement["pin"] as? String
let lastUpdated = jsonElement["created"] as? String
let listAdminDeviceID = jsonElement["admin"] as? String
tempContactsList.id = id
tempContactsList.name = name
tempContactsList.pin = pin
tempContactsList.lastUpdated = lastUpdated
tempContactsList.listAdmin = listAdminDeviceID
downloadedLists.append(tempContactsList)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate.listsDownloadingComplete(downloadedLists)
})
}
}
Even in iOS 9, there was no guarantee NSJSONSerialization.JSONObjectWithData(_:options:) would return mutable object or not. You should have specified NSJSONReadingOptions.MutableContainers.
And in your code, you are not modifying jsonResult, which means you have no need to declare it as NSMutableArray. Just replace NSMutableArray to NSArray, and then you have no need to specify NSJSONReadingOptions.MutableContainers.
But as vadian is suggesting, you better use Swift types rather than NSArray or NSDictionary. This code should work both in iOS 9 and 10.
func parseJSON() {
var jsonResult: [[String: AnyObject]] = [] //<- use Swift type
do{
try jsonResult = NSJSONSerialization.JSONObjectWithData(self.data, options: []) as! [[String: AnyObject]] //<- convert to Swift type, no need to specify options
} catch let error as NSError {
print(error)
}
var downloadedLists: [ContactsList] = []
for jsonElement in jsonResult { //<- your for-in usage can be simplified
let tempContactsList = ContactsList()
//the following insures none of the JsonElement values are nil through optional binding
let id = jsonElement["id"] as? String
let name = jsonElement["name"] as? String
let pin = jsonElement["pin"] as? String
let lastUpdated = jsonElement["created"] as? String
let listAdminDeviceID = jsonElement["admin"] as? String
tempContactsList.id = id
tempContactsList.name = name
tempContactsList.pin = pin
tempContactsList.lastUpdated = lastUpdated
tempContactsList.listAdmin = listAdminDeviceID
downloadedLists.append(tempContactsList)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate.listsDownloadingComplete(downloadedLists)
})
}
Try this and check it on iOS 10 devices.
(The as! conversion would cause some weird crashes when your server is malfunctioning, but that would be another issue, so I keep it there.)