Apple has provided Generic Keychain sample which is written in Swift, I want to go ahead with Objective-C.
I have enabled keychain sharing in both the apps and on canOpenUrl I am able to invoke application B from A, now I want to share username and password from app A to app B. App ID is same for both the applications.
I have looked at various tutorials also don't want to use any third party project.
Could not came to know how to pass the parameter from app A to app B.
Enable Keychain sharing:
Turn on the Keychain Sharing capability.
Select developer team
Specify Keychain group name to something meaningful (for example testKeychainG1)
Open .entitlements file and replace $(AppIdentifierPrefix) with your APP ID (for example AB123CDE45.testKeychainG1)
Accessing Keychain (Retrieve shared items):
let itemKey = "Item Key"
let keychainAccessGroupName = "AB123CDE45.testKeychainG1"
let query:[String:Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey,
kSecReturnData as String: kCFBooleanTrue,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecAttrAccessGroup as String: keychainAccessGroupName
]
var result: AnyObject?
let resultCodeLoad = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(query 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: \(resultCodeLoad)")
}
step 1:
Set URL Schemes and add the AppA's URL Scheme to the AppB's info.plist like this:<key>LSApplicationQueriesSchemes</key>
<array>
<string>Aapp_Scheme</string>
</array>
step 2:
In app A:
let url = URL.init(string: "B_Scheme://name=Obama&password=Trump");
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: ["":""], completionHandler: nil);
}
step 3:
In app B's AppDelegate.swift add the code:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
var name = "", password = "";
let param = url.absoluteString.replacingOccurrences(of: "B_Scheme://", with: "");
let paramArray = param.components(separatedBy: "&");
for temp in paramArray {
if (temp.range(of: "name=") != nil){
name = temp.replacingOccurrences(of: "name=", with: "");
}
else if (temp.range(of: "password=") != nil){
password = temp.replacingOccurrences(of: "password=", with: "");
}
}
if name == "Obama" && password == "Trump" {
print("get param success!");
}
return true;
}
Related
Im trying to generate a privatekey that only is accessible when either devicecode or current set of biometrics( that is already registered on device) is used.
It works when i have a finger registered on device, then its all good. But if i delete my registered "touchid-finger", and try to generate a new key, then it returns nil
Errorcode is -25293
Code example:
func generateKey() -> SecKey?{
var error: Unmanaged<CFError>?
let accessControl = SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
[.devicePasscode,.or,.biometryCurrentSet],
nil)
let attributes:[String : Any] = [kSecAttrType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String:4096,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent:true,
kSecAttrCanSign: true,
kSecAttrApplicationTag: "yes.its.my.tag",
kSecAttrAccessControl:accessControl!]]
let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error)
if(error != nil || privateKey == nil) {
fatalError("explode Kittens")
}
return privateKey
}
fyi.Its actually works on simulators but not on real devices.
Am i doing something wrong? Is this working as intended? is it a bug? (sooo many questions :D )
I'm generating a private key, you need to add in ACL object parameter kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly. This will allow you to get a private key using a passcode or biometric data.
guard let aclObject = SecAccessControlCreateWithFlags(
kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.userPresence, .privateKeyUsage], nil) else {
return ""
}
// private key parameters
let privateKeyParams: [String: AnyObject] = [
kSecAttrAccessControl as String: aclObject as AnyObject, //protect with touch id
kSecAttrIsPermanent as String: true as AnyObject
]
// global parameters for our key generation
let parameters: [String: AnyObject] = [
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecAttrKeyType as String: kSecMessECCKeyType,
kSecAttrKeySizeInBits as String: kSecMessECCKeySize as AnyObject,
kSecAttrLabel as String: kSecMessECCSignLabel as AnyObject,
kSecPrivateKeyAttrs as String: privateKeyParams as AnyObject
]
guard let eCCPrivKey = SecKeyCreateRandomKey(parameters as CFDictionary, nil) else {
print("ECC KeyGen Error!")
return ""
}
guard let eCCPubKey = SecKeyCopyPublicKey(eCCPrivKey) else {
print("ECC Pub KeyGen Error")
return ""
}
"The problem relates to how the item is /created/, and that’s not what these flags control.
Indeed, for .biometryCurrentSet to have any meaning at the time of use, there must actually be a current set of biometrics at the time of creation, and I think that’s the source of the errSecAuthFailed.
My recommendation is manually fall back to using just .devicePasscode if there’s no biometrics available. Two ways: A. Catch the error and retry B. Preflight the request using the LocalAuthentication framework."
Apple says no can do :D
I have an application where I generate shared secret between iOS / android / nodejs using ECDH over secp521r1 curve. To ensure that it works I wrote a test that uses data generated with nodejs and that worked until iOS 13.
The source code of the test was:
let publicKey = "BABBvZ56c4bj1Zo73LIt/bBVa3jvGTA1fceoOG/M9TeXHx5ffCggRteEVS+bwrgQWPOwJPHhevNenaVn32ZnhztS0QFBqKGZTF1pKNSvuj+PDKQ625TauNroq+LQdeS+Pn6GVHL0iW5pp84NZ06L97VZ9HYm+g2lMnlUFV8hco2CmwBqHQ=="
let privateKey = "AXn994UN59QCEqmCmXmmNZ3hVZPlMwzTIeBupJGG4CqDWfWLuCTui7qiBfQtCFcQ1ks4NNB/tHEZUJ+bB97+pkJ3"
let otherBase64 = "BAAzWyzdh2e+ZNUCFt4oDADURb8+m9WA7gbWtTo57ZP3U23VuvMnRHf+12GpTSV8A5pt+vZfaR2cT02P+LPRc/kGzgAT2IYIgDz/cKbzMi520ZLa0GYk1xzCuNqFhdBZmrB5w0ymsPLdJzIG1QZ3xu7OufEipm5D41abphLLnbH+OyTX6w=="
let expectedShared = "AQkTOOHPcvlXufR2dm1FHaIJRlTgmxTJMI+h0kJ+nMVNopIP+opSqUNmflsgnJzT8JTodd/eehaaq5vvYdDVciIQ"
// iOS secKey is reconstructed by concatenating public and private key
let otherDataKey = Data.init(base64Encoded: otherBase64)!
var concatenatedKey = Data.init(base64Encoded: publicKey)!
concatenatedKey.append(Data.init(base64Encoded: privateKey)!)
// generate private key
var attributes: [String:Any] =
[
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrKeySizeInBits as String: 521,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
]
var error: Unmanaged<CFError>?
guard let secKey = SecKeyCreateWithData(concatenatedKey as CFData, attributes as CFDictionary, &error) else {
XCTAssertTrue(false)
return
}
// generate other public key
attributes[kSecAttrKeyClass as String] = kSecAttrKeyClassPublic
guard let otherKey = SecKeyCreateWithData(otherDataKey as CFData, attributes as CFDictionary, nil) else {
XCTAssertTrue(false)
return
}
// generate shared secret
let exchangeOptions: [String: Any] = [:]
guard let shared = SecKeyCopyKeyExchangeResult(secKey, SecKeyAlgorithm.ecdhKeyExchangeStandardX963SHA256, otherKey, exchangeOptions as CFDictionary, &error) else {
XCTAssertTrue(false)
return
}
// generate shared secret
XCTAssertEqual((shared as Data).base64EncodedString(), expectedShared);
With iOS 13 I was forced to modify the content of my exchangeOptions dictionary as discussed here (SecKeyCopyKeyExchangeResult() function return an error, "kSecKeyKeyExchangeParameterRequestedSize is missing")
let exchangeOptions: [String: Any] = [SecKeyKeyExchangeParameter.requestedSize.rawValue as String: 66]
The problem is that with this option, the result of SecKeyCopyKeyExchangeResult does not match anymorewith nodejs one (which is also true on iOS 12)
I finally found a solution... In iOS <= 12, leaving exchange parameters empty when trying to use ecdhKeyExchangeStandardX963SHA256 algorithm was falling back to using SecKeyAlgorithm.ecdhKeyExchangeCofactor.
Therefore the fix to reproduce previous behavior is to modify the SecKeyCopyKeyExchangeResult with
// generate shared secret
let exchangeOptions: [String: Any] = [:]
guard let shared = SecKeyCopyKeyExchangeResult(secKey, SecKeyAlgorithm.ecdhKeyExchangeCofactor, otherKey, exchangeOptions as CFDictionary, &error) else {
XCTAssertTrue(false)
return
}
This works at least for iOS 10 to 13
I am following the Apple documentation for adding a password to the keychain located here -> https://developer.apple.com/documentation/security/keychain_services/keychain_items/adding_a_password_to_the_keychain
When I run the following code it works as expected and the status comes back as 0.
let credentials = Credentials(username: "testUserName", password: "testPassword")
let server = "www.example.com"
let account = credentials.username
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)
print(status)
When I modify the code with hardcoded strings it fails with a status of -50.
let credentials = Credentials(username: "testUserName", password: "testPassword")
let server = "www.example.com"
//let account = credentials.username
//let password = credentials.password.data(using: String.Encoding.utf8)!
let account = "testUserName"
let password = "testPassword"
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)
print("Keychain Save Status: \(status)")
Can someone explain this to me? I also tried explicitly setting the strings to utf8 format with let account = "testUsername".utf8. It doesn't make sense to me that this would fail if the value is a valid string.
Also does anyone have the link to the status code descriptions? I found the descriptions but it doesn't give the associated number code https://developer.apple.com/documentation/security/1542001-security_framework_result_codes
YES, I know my answer on the other track. As per my own experience, I'm using below keychain wrapper in Swift and Objective - C. Happy and Lazy Coding! :)
Swift - Locksmith - https://github.com/matthewpalmer/Locksmith
Objective-C - SSKeychain - https://github.com/samsoffes/sskeychain
//Generate Device UUID
func CreateApplicationDeviceUUID() -> String{
let DeviceUUID = NSUUID().uuidString
print("DeviceUUD==\(DeviceUUID)")
return DeviceUUID
}
//Retrive Device Unique UUID
let keyChainID = Locksmith.loadDataForUserAccount(userAccount: Bundle.main.object(forInfoDictionaryKey:"CFBundleName") as! String)
let retriveuuid = keyChainID?[RDGlobalFunction.deviceAppUUID] //RDGlobalFunction.deviceAppUUID is a Key of KeyChain Value Storage
if(retriveuuid == nil){
let uuid = CreateApplicationDeviceUUID()
do{
try Locksmith.saveData(data: [RDGlobalFunction.deviceAppUUID : uuid], forUserAccount: Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String) //Locksmith - SSkeyChain Thirdparty KeyChain Wrapper
}catch{
//Catch Error
}
}
Other Reference Link:
https://www.raywenderlich.com/179924/secure-ios-user-data-keychain-biometrics-face-id-touch-id
https://medium.com/ios-os-x-development/securing-user-data-with-keychain-for-ios-e720e0f9a8e2
https://code.tutsplus.com/tutorials/securing-ios-data-at-rest--cms-28528
I figured out that if I change
let password = "testPassword"
to
let password = "testPassword".data(using: String.Encoding.utf8)!
then it will work as expected. It seems that the kSecValueData parameter must be a utf8 encoded string.
I know we can pass key/value pairs in deep link URL. but can we pass an image as string as a value for a particular key ? I know about inter app communication through shared container. In my case there is a framework created by us which other developer can integrate in their apps. Through framework the user can send an image to our application(if its installed). So shared container will not work here.
Any help will be appreciated.
Is there any limit on the length of the url?
Thanks
Pass base64StrImage from source application
func gotToApp() {
let data = UIImagePNGRepresentation(#imageLiteral(resourceName: "img"))
let base64Str = data!.base64EncodedString()
if UIApplication.shared.canOpenURL(URL(string: "deep://")!) {
UIApplication.shared.open(URL(string: "deep://?img=\(base64Str)")!, options: ["img": #imageLiteral(resourceName: "img")]) { (finish) in
}
}
}
Get Image In Destination Application.
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
print(url.queryParameters!["img"])
return true
}
extension URL {
public var queryParameters: [String: String]? {
guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true), let queryItems = components.queryItems else {
return nil
}
var parameters = [String: String]()
for item in queryItems {
parameters[item.name] = item.value
}
return parameters
}
}
I am new to ios developpement.
I have built and apps that saves data in the core data. What I would like to do is sharing this data with my Ipad, or my kids Iphone.
The devices are not on the same network or closed to each other, so the shared process will be via Email or Imessage.
the app will be installed on all devices to be abel to send/receive data.
I would like to be sure that the only way to do that is to use UIActivityViewController.
I didn't start coding part yet ( Sharing part), I am still doing some research and some good advices.
Thank you for helping
// Full code
after doing a lot of search here is my code , I don't know if there is a better way to do it but here is how I solved :
1 - creating a Json String
let savedData = ["Something": 1]
let jsonObject: [String: Any] = [
"type_id": 1,
"model_id": true,
"Ok": [
"startDate": "10-04-2015 12:45",
"endDate": "10-04-2015 16:00"
],
"custom": savedData
]
2 - Saving in as file
let objectsToShare = [jsonObject as AnyObject]
let data: Data? = try? JSONSerialization.data(withJSONObject: objectsToShare, options: .prettyPrinted)
let filePath = NSTemporaryDirectory() + "/" + NSUUID().uuidString + ".kis"
do
{
try data?.write(to: URL(fileURLWithPath: filePath))
}
catch {
}
print (filePath)
// create activity view controller
let activityItem:NSURL = NSURL(fileURLWithPath:filePath)
let activityViewController = UIActivityViewController(activityItems: [activityItem], applicationActivities: nil)
self.present(activityViewController, animated: true, completion: nil)
3 - update info.plist
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>LSItemContentTypes</key>
<array>
<string>XXXSharingData.kis</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleTypeName</key>
<string>kis file</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeIdentifier</key>
<string>XXXSharingData.kis</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.mime-type</key>
<string>application/pry</string>
<key>public.filename-extension</key>
<string>kis</string>
</dict>
</dict>
</array>
finally when sending/receiving file as attachment via email and opened in my app : open method appdelegate, I was able to see the Json string
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
do
{
let dictionary = try NSString(contentsOf: url, encoding: String.Encoding.utf8.rawValue)
print (dictionary)
}
catch {
}
return true
}
Well there are in general 2 principles overall. You will either wan't to send difference of data or the whole data itself.
To begin with I would just go with sending whole packet on demand because the difference means the target device will need to firsts report what state it is in.
So to transfer the whole core data I guess it should be possible to simply zip the whole folder where your database is stored, change the zip extension to some custom name and simply send this file. Then unzip this file on the other side to the corresponding location.
But this is not really good because it is not portable to any other system (Android, Remote storage...) and will have versioning issues: If you update your data model then your new app will crash when importing old database.
So I would say sending any standard data chunk would be the best: Simply create a conversion of all your core data entities to dictionaries and then serialise this data into a JSON. Ergo something like this:
var JSONDictionary: [String: Any] = [String: Any]()
JSONDictionary["users"] = User.fetchAll().map { $0.toDictionary() }
JSONDictionary["tags"] = Tag.fetchAll().map { $0.toDictionary() }
JSONDictionary["notifications"] = Notification.fetchAll().map { $0.toDictionary() }
let data: Data? = try? JSONSerialization.data(withJSONObject: JSONDictionary, options: .prettyPrinted)
let filePath = NSTemporaryDirectory() + "/" + NSUUID().uuidString + ".myApplicationFileExtension"
Data().write(to: URL(fileURLWithPath: filePath))
So at this point you have your file which is easily sharable and can be reversed on the other side. You can then choose to merge data, overwrite it...
EDIT: Adding more description from comments and updated question
I am not sure what your data structure it but as it seems I expect something like:
class MyObject {
enum ObjectType {
case a,b,c
var id: String {
switch self {
case .a: return "1"
case .b: return "2"
case .c: return "3"
}
}
static let supportedValues: [ObjectType] = [.a, .b, .c]
}
var savedData: [String: Any]?
var type: ObjectType = .a
var isModel: Bool = false
var startDate: Date?
var endDate: Date?
func toDictionary() -> [String: Any] {
var dictionary: [String: Any] = [String: Any]()
dictionary["custom"] = savedData
dictionary["type_id"] = type.id
dictionary["model_id"] = isModel
dictionary["startDate"] = startDate?.timeIntervalSince1970
dictionary["endDate"] = endDate?.timeIntervalSince1970
return dictionary
}
func loadWithDescriptor(_ descriptor: Any?) {
if let dictionary = descriptor as? [String: Any] {
savedData = dictionary["custom"] as? [String: Any]
type = ObjectType.supportedValues.first(where: { $0.id == dictionary["type_id"] as? String }) ?? .a
isModel = dictionary["model_id"] as? Bool ?? false
startDate = {
guard let interval = dictionary["startDate"] as? TimeInterval else {
return nil
}
return Date(timeIntervalSince1970: interval)
}()
endDate = {
guard let interval = dictionary["endDate"] as? TimeInterval else {
return nil
}
return Date(timeIntervalSince1970: interval)
}()
}
}
}
So this will now give you the capability to transition from and to dictionary (not JSON).
The activity controller is not an issue and saving to file does not seem to be much of an issue as well. I am not sure about a few properties but it should look something like this:
func saveToTemporaryFile(myConcreteObjects: [MyObject]) -> String {
let dictionaryObjects: [[String: Any]] = myConcreteObjects.map { $0.toDictionary() }
let path = NSTemporaryDirectory() + "/" + NSUUID().uuidString + ".kis"
let data: Data? = try? JSONSerialization.data(withJSONObject: dictionaryObjects, options: .prettyPrinted)
try? data?.write(to: URL(fileURLWithPath: path))
return path
}
If by chance you have different objects then this method should accept Any object which should be constructed as:
var myDataObject: [String: Any] = [String: Any]()
myDataObject["myObjects"] = myConcreteObjects.map { $0.toDictionary() }
...
To load your JSON back from URL you simply need to construct your data. Since you got the URL don't bother with strings:
func loadItemsFrom(url: URL) -> [MyObject]? {
guard let data = try? Data(contentsOf: url) else {
// Error, could not load data
return nil
}
guard let array = (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)) as? [Any] else {
// Error, could not convert to JSON or invalid type
return nil
}
return array.map { descriptor in
let newItem = MyObject()
newItem.loadWithDescriptor(descriptor)
return newItem
}
}
This constructor with getting data from URL is very powerful. It will work even with remote servers if you have access to internet/network. So in those cases make sure you do this operations on a separate thread or your UI may be blocked until the data is received.
I hope this clears a few things...
Based on your description I assume you want to do something like Apple Handoff. Basically when you change some data on one device to continue working with an up to date copy from another device.
In my opinion you could achieve this by having some kind of backend that synchronise data between client and server. Also, the backend should send push notifications to the other devices to update the local copy. The application can be updated silently, without user interaction, by using Silent Push Notifications.
You can have a look at Cloudant or Couchbase which basically offer a solution of synchronising data between client and server. By using this approach you minimise the need of writing the code for the backend part of the application and also for the data and network layers from your client app. Both solutions have SDKs for iOS in Swift so you can integrate them easily.
Hope it helps!