I have been working in iOS autofill credential extension since long days. I have checked so many iOS articles and videos. But I am not able to show credential on quick type bar, reset things are successfully integrated. Can any give me quick help?
Using this video and url which was shared from apple:
https://developer.apple.com/videos/play/wwdc2018/721
https://developer.apple.com/documentation/authenticationservices
I am using below code to save credential to Keychain for particular domain.
let keychain = Keychain(server: "instagram.com", protocolType: .https, authenticationType: .htmlForm)
keychain["emailAddress"] = "Password"
And use this code for save domain:
func savedomain(domain: String, account: String, password: String, completion: ((Bool, SharedWebCredentialsManagerError?) -> Void)? = nil) {
SecAddSharedWebCredential(domain as CFString, account as CFString, password as CFString?) { error in
guard let error = error else {
completion?(true, nil)
return
}
let errorDescription = CFErrorCopyDescription(error) as String
let saveFailedError = SharedWebCredentialsManagerError.saveFailed(errorDescription)
completion?(false, saveFailedError)
}
}
I have created autofill extension and getting saved credentials, but not able to display credential on quick type bar in safari for instagram.com
I have implemented autofill extension for all social sites, Sharing my source code to save emailID-Password with domain.
class func save(key: String, data: Data) -> OSStatus {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data ] as [String : Any]
SecItemDelete(query as CFDictionary)
return SecItemAdd(query as CFDictionary, nil)
}
Above "Save" function i have written in my custom KeyChainManager class, Also i added below code in KeyChainManager class which is as below.
extension Data {
init<T>(from value: T) {
var value = value
self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
func to<T>(type: T.Type) -> T {
return self.withUnsafeBytes { $0.load(as: T.self) }
}
}
I am saving my data from VC by calling our KeyChainManager class like below:
let email = (txtEmail?.text ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
let password = (txtPassword?.text ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
let domain = (txtDomain?.text ?? "").lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
let user = User(email: email, password: password, key: self.key, domain: domain, identifier: self.keyIdentifier)
let data = (try? JSONEncoder().encode(user)) ?? Data()
let _ = KeyChain.save(key: "\(self.keyIdentifier)", data: data)
This all stuff is for saving our credentials, Now major point is how to list all saved credentials in our extension's CredentialProviderViewController.swift.
For that i added below method in KeyChainManager class :
class func load(key: String) -> Data? {
let query = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
kSecReturnData as String : kCFBooleanTrue!,
kSecMatchLimit as String : kSecMatchLimitOne ] as [String : Any]
var dataTypeRef: AnyObject? = nil
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
if status == noErr {
return dataTypeRef as! Data?
} else {
return nil
}
}
And calling this function from extension's CredentialProviderViewController.swift like this:
users.removeAll()
for i in 0..<1000000 {
if let encodedData = KeyChain.load(key: "\(i)") {
let user = try! JSONDecoder().decode(User.self, from: encodedData)
if user.key == key && ((serviceIdentifier.contains(user.domain ?? "")) ) {
users.append(user)
}
}else{
break
}
}
I hope this content helps you as i spent many days to create just one demo :) :)
Comment below, If its helps you :) :)
Thanks,
Anjali.
You need to populate ASCredentialIdentityStore in order for the quicktype bar to work. See the description of ASCredentialProviderViewController:
Optionally add ASPasswordCredentialIdentity instances to the shared ASCredentialIdentityStore to make identities available directly in the QuickType bar.
This is also described in the WWDC presentation you reference.
How I implemented population of ASCredentialIdentityStore:
var firstItem = ASPasswordCredentialIdentity(serviceIdentifier: ASCredentialServiceIdentifier(identifier: "https://online.somebank.com/auth/login", type: .URL), user: "login#bank.com", recordIdentifier: nil)
ASCredentialIdentityStore.shared.replaceCredentialIdentities(with: [firstItem]) { result, error in
if result {
print("saved")
}
}
In my case everything works perfect.
So I fetch all passwords from remote and then populate ASCredentialIdentityStore with available passwords.
Related
I'm trying to map a "user" retrieved from my Firestore database to a user struct I've defined in my code but I don't think I properly understand the mapping features in swift.
How do I go about mapping the user retrieved into the struct?
Struct
struct User {
// Properties
var firstName: String
var lastName: String
var userName: String
var email: String
init(firstName: String, lastName: String, userName: String, email: String) {
self.firstName = firstName
self.lastName = lastName
self.userName = userName
self.email = email
}
}
Function that gets user from Firestore
func getUser(UID: String) {
// Firebase setup
settings.areTimestampsInSnapshotsEnabled = true
db.settings = settings
db.collection("users").document(UID).getDocument { (document, error) in
if let error = error {
print("Error: \(error.localizedDescription)")
} else {
print(document!.data()!)
}
}
}
A queried Firestore document is of type [String: Any], so configure your initializer to accept that kind of dictionary. Because you're working with a database return, there is never a guarantee that the data will be fully intact, so I would suggest making your initializer failable, which just means that it can return nil (it can fail).
I took your example and made the email property optional. In the example below, only three of the four properties are necessary to instantiate the User object.
let info: [String: Any] = ["hasFriends": true]
let firestoreData: [String: Any] = ["firstName": "lance", "lastName": "stevenson", "userName": "lstevenson", "info": info]
struct User {
var firstName: String
var lastName: String
var userName: String
var info: [String: Any]
var email: String?
var hasFriends: Bool
init?(data: [String: Any]) {
guard let firstName = data["firstName"] as? String,
let lastName = data["lastName"] as? String,
let userName = data["userName"] as? String,
let info = data["info"] as? [String: Any],
let hasFriends = info["hasFriends"] as? Bool else {
return nil
}
self.firstName = firstName
self.lastName = lastName
self.userName = userName
self.info = info
self.hasFriends = hasFriends
self.email = data["email"] as? String // User will be created even if this is nil
}
}
if let user = User(data: firestoreData) {
print(user.hasFriends) // true
}
I would not suggest that you use Swift's mapping tool for this because you will likely be dealing with varying types of values within the same dictionary for different models. And the code to map this dictionary with those variables in an initializer would not be pretty.
i set up Google Analytics for my iOS-App and it is basically working fine (Screen Tracking, Purchases,...), except the install Campaign tracking is not ok.
I saw a similar question here which is not solved:
Google analytics iOS campaign tracking testing on development
This is the guide i used to implement (is in Objective-C):
https://developers.google.com/analytics/devguides/collection/ios/v3/campaigns#general-campaigns
I see in my Google Analytics Dashboard the referrer installs, but no campaign installs.
This is a test Url
https://click.google-analytics.com/redirect?tid=UA-51157298-2&url=https%3A%2F%2Fitunes.apple.com%2Fat%2Fapp%2Fbikersos-motorrad-unfall-sos%2Fid980886530&aid=com.BikerApps.BikerSOS&idfa=%{idfa}&cs=test_source&cm=test_medium&cn=test_campaign&cc=test_campaign_content&ck=test_term
which i generated here:
https://developers.google.com/analytics/devguides/collection/ios/v3/campaigns#url-builder
My Code which should track the install is the following:
AppDelegate:
//Method where app parses any URL parameters used in the launch
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
//Track google Analytics URL
GAService.shared.trackURL(url)
//some other handlers here ....
return true/false
}
GAService Singleton:
#objc public class GAService : NSObject {
private let trackID : String = "UA-xxxxxx-x"
var trackGoogleAnalytics : Bool = Constants.Analytics.trackGoogleAnalytics
private let UTM_SOURCE_KEY : String = "xxx"
private let UTM_MEDIUM_KEY : String = "xxx"
private let UTM_CAMPAIGN_KEY : String = "xxx"
var trackUncaughtExceptions : Bool = true
var tracker : GAITracker? = nil
static let shared = GAService()
var UtmSource : String {
get {
if let utm = UserDefaults.standard.string(forKey: UTM_SOURCE_KEY) {
return utm
}
return ""
}
set {
if UserDefaults.standard.string(forKey: UTM_SOURCE_KEY) == nil {
UserDefaults.standard.set(newValue, forKey: UTM_SOURCE_KEY)
UserDefaults.standard.synchronize()
}
}
}
var UtmCampaign : String {
get {
if let utm = UserDefaults.standard.string(forKey: UTM_CAMPAIGN_KEY) {
return utm
}
return ""
}
set {
if UserDefaults.standard.string(forKey: UTM_CAMPAIGN_KEY) == nil {
UserDefaults.standard.set(newValue, forKey: UTM_CAMPAIGN_KEY)
UserDefaults.standard.synchronize()
}
}
}
var UtmMedium : String {
get {
if let utm = UserDefaults.standard.string(forKey: UTM_MEDIUM_KEY) {
return utm
}
return ""
}
set {
if UserDefaults.standard.string(forKey: UTM_MEDIUM_KEY) == nil {
UserDefaults.standard.set(newValue, forKey: UTM_MEDIUM_KEY)
UserDefaults.standard.synchronize()
}
}
}
private override init() {
super.init()
if(trackGoogleAnalytics) {
tracker = GAI.sharedInstance().tracker(withTrackingId: trackID)
tracker!.allowIDFACollection = true
GAI.sharedInstance().trackUncaughtExceptions = trackUncaughtExceptions
GAI.sharedInstance().logger.logLevel = GAILogLevel.error
GAI.sharedInstance().dispatchInterval = 1
}
}
public func trackURL(_ url : URL){
let urlString = url.absoluteString
// setCampaignParametersFromUrl: parses Google Analytics campaign ("UTM")
// parameters from a string url into a Map that can be set on a Tracker.
let hitParams : GAIDictionaryBuilder = GAIDictionaryBuilder()
// Set campaign data on the map, not the tracker directly because it only
// needs to be sent once.
hitParams.setCampaignParametersFromUrl(urlString)
// Campaign source is the only required campaign field. If previous call
// did not set a campaign source, use the hostname as a referrer instead.
if((hitParams.get(kGAICampaignSource) != nil) && (url.host ?? "").length() != 0) {
hitParams.set("referrer", forKey: kGAICampaignMedium)
hitParams.set(url.host, forKey: kGAICampaignSource)
}
let hitParamsDict : [AnyHashable : Any] = hitParams.build() as Dictionary as [AnyHashable : Any]
if(hitParamsDict.count > 0) {
// A screen name is required for a screen view.
let source : String? = hitParams.get(kGAICampaignSource)
let medium : String? = hitParams.get(kGAICampaignMedium)
let campaign : String? = hitParams.get(kGAICampaignName)
if(source != nil || medium != nil || campaign != nil) {
tracker?.set(kGAIScreenName, value: "openUrl")
GAService.shared.UtmSource = source ?? ""
GAService.shared.UtmMedium = medium ?? ""
GAService.shared.UtmCampaign = campaign ?? ""
}
// SDK Version 3.08 and up.
//[tracker send:[[[GAIDictionaryBuilder createScreenView] setAll:hitParamsDict] build]];
let sendDict : [AnyHashable : Any] = GAIDictionaryBuilder.createScreenView().setAll(hitParamsDict).build() as Dictionary as [AnyHashable : Any]
tracker?.send(sendDict)
tracker?.set(kGAIScreenName, value: nil)
}
}
As i have to set a screenName for such a tracking i defined openUrl as a constant and set it to nil afterwards i sent the dictionary.
I hope anybody can see what i'm doing wrong
thank you in advance
I used it with deep link where I decide the navigation:
let tracker1 = GAI.sharedInstance().tracker(withTrackingId: "...")
let hitParams = GAIDictionaryBuilder()
hitParams.setCampaignParametersFromUrl(path)
let medium = self.getQueryStringParameter(url: path, param: "utm_medium")
hitParams.set(medium, forKey: kGAICampaignMedium)
hitParams.set(path, forKey: kGAICampaignSource)
let hitParamsDict = hitParams.build()
tracker1?.allowIDFACollection = true
tracker1?.set(kGAIScreenName, value: "...")
tracker1?.send(GAIDictionaryBuilder.createScreenView().setAll(hitParamsDict as? [AnyHashable : Any]).build() as? [AnyHashable : Any])
// this is for url utm_medium parameter
/*
func getQueryStringParameter(url: String, param: String) -> String? {
guard let url = URLComponents(string: url) else { return nil }
return url.queryItems?.first(where: { $0.name == param })?.value
}*/
then I saw the source on google analytics.
I think that Google Analytics docs are very poor on this subject. If you look carefully in what is written in the main guide for "Campaign Measurement" it says "After an app has been installed, it may be launched by referrals from ad campaigns, websites, or other apps" and Google gives an example with custom url scheme which is handled.
Hopefully, there is another guide https://developers.google.com/analytics/solutions/mobile-campaign-deep-link which describes how to use deep linking for campaign measurement.
So, if you want to track campaign measurement you must use either Universal Links mechanism or custom url scheme for your application.
Universal Links mechanism is the preferred way if you have backend and hence can decide where to redirect user: in iTunes or in application.
Assume, you have a site link https://example.com/openapp which redirects user to iTunes Connect. You should insert you Universal Link address (https://example.com/openapp) in the
generator https://developers.google.com/analytics/devguides/collection/ios/v3/campaigns#url-builder.
I'm attempting to write a prototype that generates a set of Ecliptic Curve keys (256 bit), and then signs a message using the private key. I have code that generates and manages the keys which works well, but when I try and call SecKeyRawSign, I'm getting a -50 errSecParam error. The code to generate the keys looks like this:
private func generateKeyPair() throws {
var error: Unmanaged<CFError>? = nil
let acl = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.touchIDAny, .privateKeyUsage], &error)
guard error == nil else {
throw MessageError(message: "Could not create ACL: \(error)")
}
// We don't want the public key stored in the ecure enclave, so we create it as
// non permament and add it manually to the keychain later
let publicKeyParameters: [CFString: Any] = [
kSecAttrIsPermanent: false,
kSecAttrApplicationTag: ViewController.KeyTag,
kSecAttrLabel: ViewController.PublicLabel
]
let privateKeyParameters: [CFString: Any] = [
kSecAttrIsPermanent: true,
kSecAttrApplicationTag: ViewController.KeyTag,
kSecAttrLabel: ViewController.PrivateLabel,
kSecAttrAccessControl: acl!
]
var parameters: [CFString: Any] = [
kSecAttrKeyType: kSecAttrKeyTypeEC,
kSecAttrKeySizeInBits: NSNumber(value: 256),
kSecPublicKeyAttrs: publicKeyParameters,
kSecPrivateKeyAttrs: privateKeyParameters
]
// On the simulator we can't use the Secure Enclave
if hasSecureEnclave() {
parameters[kSecAttrTokenID] = kSecAttrTokenIDSecureEnclave
}
var pubKeyRef, privKeyRef: SecKey?
var result = SecKeyGeneratePair(parameters as CFDictionary, &pubKeyRef, &privKeyRef)
guard result == noErr else {
throw MessageError(message: "Could not create key pair: \(result)")
}
parameters = [
kSecClass: kSecClassKey,
kSecAttrKeyType: kSecAttrKeyTypeEC,
kSecAttrApplicationTag: ViewController.KeyTag,
kSecAttrLabel: ViewController.PublicLabel,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecValueRef: pubKeyRef!
]
result = SecItemAdd(parameters as CFDictionary, nil)
guard result == noErr else {
throw MessageError(message: "Could not add public key to keychain: \(result)")
}
}
The code to the signing looks like this:
private func signWithPrivateKey(_ text: String, _ key: SecKey) throws -> String? {
var digest = Data(count: Int(CC_SHA256_DIGEST_LENGTH))
let data = text.data(using: .utf8)!
let _ = digest.withUnsafeMutableBytes { digestBytes in
data.withUnsafeBytes { dataBytes in
CC_SHA256(dataBytes, CC_LONG(data.count), digestBytes)
}
}
var signature = Data(count: SecKeyGetBlockSize(key))
var signatureLength = signature.count
let result = signature.withUnsafeMutableBytes { signatureBytes in
digest.withUnsafeBytes { digestBytes in
SecKeyRawSign(key,
SecPadding.PKCS1SHA256,
digestBytes,
digest.count,
signatureBytes,
&signatureLength)
}
}
guard result == noErr else {
throw MessageError(message: "Could not sign data: \(result)")
}
return signature.base64EncodedString()
}
Obviously the final guard in the sign function is being tripped, and it's returning errSecParam.
Has anyone successfully done data signing in iOS using EC keys? If so, do you see anything obvious here? Tangentially is there a way to get more information on the error itself.
Edit: To add an important detail, if I do nothing but change this code to generate 2048 bit RSA keys instead, the code works fine. Keys generate and the message is signed. It's only with 256 bit EC keys that it fails. Is there some alternate method for doing ECDSA in iOS?
I solved this. The buffer I was creating to hold the signature was too small. I changed it to use SecKeyGetBlockSize() * 4, and then reduce the buffer to signatureLenght after the call. My only question at this point is if there would be a better way figure out the length (other than calling SecKeyRawSign, letting it fail, then adjusting buffer size to the returned size).
The new sign code looks like this:
private func signWithPrivateKey(_ text: String, _ key: SecKey) throws -> String? {
var digest = Data(count: Int(CC_SHA256_DIGEST_LENGTH))
let data = text.data(using: .utf8)!
let _ = digest.withUnsafeMutableBytes { digestBytes in
data.withUnsafeBytes { dataBytes in
CC_SHA256(dataBytes, CC_LONG(data.count), digestBytes)
}
}
var signature = Data(count: SecKeyGetBlockSize(key) * 4)
var signatureLength = signature.count
let result = signature.withUnsafeMutableBytes { signatureBytes in
digest.withUnsafeBytes { digestBytes in
SecKeyRawSign(key,
SecPadding.PKCS1SHA256,
digestBytes,
digest.count,
signatureBytes,
&signatureLength)
}
}
let count = signature.count - signatureLength
signature.removeLast(count)
guard result == noErr else {
throw MessageError(message: "Could not sign data: \(result)")
}
return signature.base64EncodedString()
}
I am writing an iOS app in Swift and the app collects user input from multiple app screens and in the last screen its supposed to POST the information collected to the server via API.
Now my question is, what is the best way to manage the collected data on the app? Should I use plist to save my form data ? It also has one image which I want to upload to my server from the final screen. How should I go about this?
PS: I also read about http://developer.apple.com/CoreData, but I'm not sure if this is the right way to go forward.
Any suggestion is greatly appreciated.
UPDATE: to save your time - this is Swift 1.2 solution. I didn't test it on Swift 2 (likely secureValue flow have to be updated)
Looks like you are talking about user's details/profile (correct me if I wrong), for this amount of data - using NSUserDefault is totally ok.
For user preferences (if that the case!!) I would use something like Preference Manager:
import Foundation
import Security
class PreferencesManager {
class func saveValue(value: AnyObject?, key: String) {
NSUserDefaults.standardUserDefaults().setObject(value, forKey: key)
NSUserDefaults.standardUserDefaults().synchronize()
}
class func loadValueForKey(key: String) -> AnyObject? {
let r : AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey(key)
return r
}
class func saveSecureValue(value: String?, key: String) {
var dict = dictForKey(key)
if let v = value {
var data: NSData = v.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
(SecItemDelete(dict as NSDictionary as CFDictionary))
dict[kSecValueData as NSString] = v.dataUsingEncoding(NSUTF8StringEncoding,
allowLossyConversion:false);
var status = SecItemAdd(dict as NSDictionary as CFDictionary, nil)
} else {
var status = SecItemDelete(dict as NSDictionary as CFDictionary)
}
}
class func loadSecureValueForKey(key: String) -> String? {
var dict = dictForKey(key)
dict[kSecReturnData as NSString] = kCFBooleanTrue
var dataRef: Unmanaged<AnyObject>?
var value: NSString? = nil
var status = SecItemCopyMatching(dict as NSDictionary as CFDictionary, &dataRef)
if 0 == status {
let opaque = dataRef?.toOpaque()
if let op = opaque {
value = NSString(data: Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue(),
encoding: NSUTF8StringEncoding)
}
}
let val :String? = value as? String
return val
}
class private func dictForKey(key: String) -> NSMutableDictionary {
var dict = NSMutableDictionary()
dict[kSecClass as NSString] = kSecClassGenericPassword as NSString
dict[kSecAttrService as NSString] = key
return dict
}
}
You can use
PreferencesManager.saveSecureValue for secure data (like password etc) and
PreferencesManager.saveValue for the rest of values
I've been trying to use the iOS 8 Network Extension Framework to setup a VPN connection when the users presses a UIButton. I've used the following tutorial: http://ramezanpour.net/post/2014/08/03/configure-and-manage-vpn-connections-programmatically-in-ios-8/. I also looked at this post Can't setup VPN connection using Network Extension Framework iOS 8 in Swift, which is the same behavior I am getting. When I run the app I get prompted for the vpn password and shared secret when installing the profile even though they were set with all the other required values in the code. And if I enter these details when installing the profile it still does not work. When trying to connect using the app it gives a "there's no sharedSecret" error. In the referenced post, the problem was apparently solved by rewriting the code that saves and accesses the keychain data in OBJ-C. I would like to get this to work in swift or understand why it does not work in swift.
This is the code for the connection
let manager = NEVPNManager.sharedManager()
#IBAction func connectToVpn(sender: AnyObject) {
println("in call vpn")
manager.loadFromPreferencesWithCompletionHandler { (error) -> Void in
if((error) != nil) {
println("VPN Preferences error: 1")
}
else {
var p = NEVPNProtocolIPSec()
p.username = "billy"
p.serverAddress = "xxx.xxx.xxx.xxx"
p.passwordReference = self.loadkeychain("vpnpassword")
println(p.passwordReference)
p.authenticationMethod = NEVPNIKEAuthenticationMethod.SharedSecret
p.sharedSecretReference = self.loadkeychain("sharedSecret")
println(p.sharedSecretReference)
p.localIdentifier = "vpn"
p.remoteIdentifier = "vpn"
p.disconnectOnSleep = false
println("everything is set")
self.manager.`protocol` = p
self.manager.onDemandEnabled = true
self.manager.localizedDescription = "VPN"
self.manager.saveToPreferencesWithCompletionHandler({ (error) -> Void in
if((error) != nil) {
println("VPN Preferences error: 2")
println(error)
}
else {
var startError: NSError?
self.manager.connection.startVPNTunnelAndReturnError(&startError)
if((startError) != nil) {
println("VPN Preferences error: 3")
println(startError)}
else {
println("Start VPN")
}
}
})
}
}
}
This is the code to save and retrieve from keychain
func savekeychain(key: String, value: String) -> Bool {
let valueData = value.dataUsingEncoding(NSUTF8StringEncoding,
allowLossyConversion: false)
let service = NSBundle.mainBundle().bundleIdentifier!
let secItem = [
kSecClass as! String :
kSecClassGenericPassword as! String,
kSecAttrService as! String : service,
kSecAttrAccount as! String : key,
kSecValueData as! String : valueData!,
]
var result: Unmanaged<AnyObject>? = nil
let status = Int(SecItemAdd(secItem, &result))
switch status{
case Int(errSecSuccess):
println("Successfully stored the value")
case Int(errSecDuplicateItem):
println("This item is already saved. Cannot duplicate it")
default:
println("An error occurred with code \(status)")
}
return true
}
func loadkeychain(keyToSearchFor: String) -> NSData
let service = NSBundle.mainBundle().bundleIdentifier!
let query = [
kSecClass as! String :
kSecClassGenericPassword as! String,
kSecAttrService as! String : service,
kSecAttrAccount as! String : keyToSearchFor,
kSecReturnData as! String : kCFBooleanTrue,
]
var data: NSData!
var returnedData: Unmanaged<AnyObject>? = nil
let results = Int(SecItemCopyMatching(query, &returnedData))
if results == Int(errSecSuccess){
data = returnedData!.takeRetainedValue() as! NSData
let value = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Value = \(value)")
println("DATA = \(data)")
} else {
println("Error happened with code: \(results)")
}
return data
}
Ended up changing my functions that save and retrieve data from the keychain to OBJ-C methods as suggested by the referenced post and this did resolve the issues. Some testing indicated that both the swift and OBJ-C methods returned the same values, so I'm not sure why the swift methods cause the stated behavior. One other thing I noticed is that saving values to the keychain seems a little flaky, if you delete a key and then re-add it with a different value it seems to not work, requiring a reset of the keychain to the default. I still would like to figure out why the swift methods do not seem to work correctly.
I know i am late but it might help someone we need shared secret reference in secItemCopyMatchig you need add kSecReturnPersistentRef and set to true.
Below block helps you may be.
enum VPNKeychain {
/// Returns a persistent reference for a generic password keychain item, adding it to
/// (or updating it in) the keychain if necessary.
///
/// This delegates the work to two helper routines depending on whether the item already
/// exists in the keychain or not.
///
/// - Parameters:
/// - service: The service name for the item.
/// - account: The account for the item.
/// - password: The desired password.
/// - Returns: A persistent reference to the item.
/// - Throws: Any error returned by the Security framework.
static func persistentReferenceFor(service: String, account: String, password: Data) throws -> Data {
var copyResult: CFTypeRef? = nil
let err = SecItemCopyMatching([
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: account,
kSecReturnPersistentRef: true,
kSecReturnData: true
] as NSDictionary, ©Result)
switch err {
case errSecSuccess:
return try self.persistentReferenceByUpdating(copyResult: copyResult!, service: service, account: account, password: password)
case errSecItemNotFound:
return try self.persistentReferenceByAdding(service: service, account:account, password: password)
default:
try throwOSStatus(err)
// `throwOSStatus(_:)` only returns in the `errSecSuccess` case. We know we're
// not in that case but the compiler can't figure that out, alas.
fatalError()
}
}
/// Returns a persistent reference for a generic password keychain item by updating it
/// in the keychain if necessary.
///
/// - Parameters:
/// - copyResult: The result from the `SecItemCopyMatching` done by `persistentReferenceFor(service:account:password:)`.
/// - service: The service name for the item.
/// - account: The account for the item.
/// - password: The desired password.
/// - Returns: A persistent reference to the item.
/// - Throws: Any error returned by the Security framework.
private static func persistentReferenceByUpdating(copyResult: CFTypeRef, service: String, account: String, password: Data) throws -> Data {
let copyResult = copyResult as! [String:Any]
let persistentRef = copyResult[kSecValuePersistentRef as String] as! NSData as Data
let currentPassword = copyResult[kSecValueData as String] as! NSData as Data
if password != currentPassword {
let err = SecItemUpdate([
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: account,
] as NSDictionary, [
kSecValueData: password
] as NSDictionary)
try throwOSStatus(err)
}
return persistentRef
}
/// Returns a persistent reference for a generic password keychain item by adding it to
/// the keychain.
///
/// - Parameters:
/// - service: The service name for the item.
/// - account: The account for the item.
/// - password: The desired password.
/// - Returns: A persistent reference to the item.
/// - Throws: Any error returned by the Security framework.
private static func persistentReferenceByAdding(service: String, account: String, password: Data) throws -> Data {
var addResult: CFTypeRef? = nil
let err = SecItemAdd([
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: account,
kSecValueData: password,
kSecReturnPersistentRef: true,
] as NSDictionary, &addResult)
try throwOSStatus(err)
return addResult! as! NSData as Data
}
/// Throws an error if a Security framework call has failed.
///
/// - Parameter err: The error to check.
private static func throwOSStatus(_ err: OSStatus) throws {
guard err == errSecSuccess else {
throw NSError(domain: NSOSStatusErrorDomain, code: Int(err), userInfo: nil)
}
}
}