Share between an iOS extension and its containing app with the keychain?
Asked Answered
H

5

27

I understand I can share data between my share extension and its containing app by enabling app groups and using NSUserDefaults (see Sharing data between an iOS 8 share extension and main app).

However, the data I am storing is sensitive, so I hoped to use the keychain. So the user would enter account information in the containing app, and then the share extension would read that data to perform the intended sharing action.

Does anyone know if this is possible? My first crack at it suggests that the extension and the containing app have separate keychains (saving the data with a key in the containing app returns null when attempting to return data for that key in the extension).

Thanks!

P.S. Using Lockbox for Keychain access, but I could ditch it if it's too much of an abstraction to make it work. https://github.com/granoff/Lockbox

Hospitalet answered 1/11, 2014 at 22:27 Comment(0)
A
29

To make the Keychain shared in Xcode 8.

1) In your App target in Capabilities find and turn on "Keychain Sharing", add a Keychain Group key (a reverse-domain style string like com.myappdomain.myappname)

2) Do exactly the same for the extension target. Make sure the Keychain Group key is the same for both - the app and the extension.

Add and retrieve data from Keychain in your usual way, no special changes required in the code. For example, here's how I put data into Keychain in the main app (a little old-fashioned but still works in Swift 3):

let login = loginString
let domain = domainString
let passwordData: Data = passwordString.data(using: String.Encoding.utf8, allowLossyConversion: false)!
let keychainQuery: [NSString: NSObject] = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrAccount: login as NSObject,  // login and domain strings help identify
    kSecAttrService: domain as NSObject, // the required record in the Keychain
    kSecValueData: passwordData as NSObject]
SecItemDelete(keychainQuery as CFDictionary) //Deletes the item just in case it already exists
let keychainSaveStatus: OSStatus = SecItemAdd(keychainQuery as CFDictionary, nil)

And then retrieve it in the extension:

let keychainQuery: [NSString: NSObject] = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrAccount: login as NSObject,
    kSecAttrService: domain as NSObject,
    kSecReturnData: kCFBooleanTrue,
    kSecMatchLimit: kSecMatchLimitOne]
var rawResult: AnyObject?
let keychain_get_status: OSStatus = SecItemCopyMatching(keychainQuery as CFDictionary, &rawResult)

if (keychain_get_status == errSecSuccess) {
    if let retrievedData = rawResult as? Data,
        let password = String(data: retrievedData, encoding: String.Encoding.utf8) {
       // "password" contains the password string now
    }
}

Note that you will still need to pass "login" and "domain" over to the extension in order to identify the correct record. This can be done via NSUserDefaults. See this answer on how to do this.

Avidity answered 11/1, 2017 at 10:26 Comment(8)
Am I missing something or does your code not even include the kSecAttrAccessGroup key?! This answer is not helpful.Housetop
@ChristianBeer the code is just an example, not a solution. You can always make edits if you believe the answer needs improvement.Avidity
dear @ChristianBeer, I've answered this question 3 years ago and pasted a working code from my old project as an example. There could be many reasons why kSecAttrAccessGroup is not in there, probably it was not needed in that specific case, I don't remember. I'm not working in that area atm and it is going to take hours for me to create a sample project and refine the code until it works smoothly. If you have your hands in this area right now and spotted a mistake, fixing an existing answer or posting an answer of your own would be an important contribution.Avidity
Sorry, Vitalli but this is like answering a question with some unrelated sentence. It just doesn't help. You explained the solution correctly, but then provide code that's not helpful. I just think that's not how StackOverflow should be. Just don't show code if it's not related.Housetop
@ChristianBeer After double-checking the docs, I recalled that kSecAttrAccessGroup is needed only in cases when an app participates in 2 or more access groups. If kSecAttrAccessGroup is not specified, the iOS defaults to the app's first access group. That's why this key can be safely omitted in most cases. If you have other opinion - please give me your arguments. BTW, the code has been working for years inside a real app passing credentials to an app extension, making it possible for the extension to upload files to server. Why "unrelated"? Have you tried and found it not working?Avidity
Here is a link to the official docu developer.apple.com/documentation/security/keychain_services/… If I understood things correct, then this "App Group" configuration is not necessary and Keychain Sharing alone is sufficient. As Vitalii pointed out kSecAttrAccessGroup is not necessary if you use Keychain Sharing as it will be the default where all apps then store their data which might nor might not be what you are looking for yet should work.Invert
In my case I have an ios app Share Extension, I only maneged to make it work by adding the kSecAttrAccessGroup: "group.lol" attribute! Don't really understand why it is needed but seems to not work withoutMarie
EDIT, I found out why : developer.apple.com/documentation/security/keychain_services/… as said here it seems that it is necessary to specify the kSecAttrAccessGroupif we use App groups ! (and you're correct if we use Keychain Sharing on the other hand, it is not necessary to specify kSecAttrAccessGroup -> because, the documentation, says that it knows the default access group lol)Marie
H
9

This can be done. It is a combination of creating a framework to do the Keychain access, and turning on "Activate Keychain Sharing" under "Capabilities". This link told me what I needed to know: http://swiftandpainless.com/ios8-share-extension-with-a-shared-keychain/

Hospitalet answered 2/11, 2014 at 6:6 Comment(4)
It's very helpful to not link to other websites or at least add the relevant source to the answer, that link is not available anymore.Privet
There's quite a bit of info there, I'm not up for distilling and pasting, but here's the Internet Archive version: web.archive.org/web/20141028160328/http://dasdev.de/2014/08/12/…Hospitalet
@JimBiancolo The whole point of posting on Stack Overflow is to be helpful. When people come to this answer and find a broken link it doesn't help anyone. Please focus on putting time and effort into writing high quality content.Glyphography
@CharlieFish Well, as I was answering my own question I thought this was better than nothing. And I thought it was better to credit (via link) the person who actually provided the info rather than rewriting their content and pasting it here. And last I checked The Wayback Machine had a copy. All that said, if links aren't welcome here I won't do it again, sorry.Hospitalet
M
3

I enabled the app groups functionality for both the app and the target of the Share Extension.

Simply go to Singing&Capabilities and add the "App Group" Capability for both targets. Then you should create once a group id identifier (It's called "Create a new container") for example let's create group.lolrandomname. Then you should select/enable this app group id in both targets.

Now in code, you must now add this attribute every time you want to use the shared keychain : kSecAttrAccessGroup: "group.lolrandomname",

And it works correctly : I can access to same stored items from the App and from my Share Extension :)

Here is an example of storing something in the shared Keychain (all identical except one attribute in query) :

let myAwesomeString = "hello world"

let query = [kSecClass: kSecClassGenericPassword,
             kSecAttrAccount: "com.myapp.an-awesome-string",
             kSecAttrAccessGroup: "group.lolrandomname",  // <<-- this!
             kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
             kSecValueData: myAwesomeString.data(using: .utf8)!] as [String: Any]

let status = SecItemAdd(query as CFDictionary, nil)

guard status == errSecSuccess else {
    print(status.humanReadable)
    throw KeyStoreError("Unable to store item: \(status.humanReadable)")
}

For reading and deleting please see SecItemCopyMatching and SecItemDelete, but don't forget, as I said, to put the kSecAttrAccessGroup key in the query!

Marie answered 15/7, 2021 at 17:22 Comment(2)
can you explain in detail?Benzvi
@Benzvi I just edited my answer, hope this helps!Marie
Q
0

Using the standard Objective-C KeychainItemWrapper class and with an entry of #import "KeychainItemWrapper.h" in the bridging header:

    func btnSaveAction() {

    let appGroupID = "group.com.yourcompany.appid"
    let keychain = KeychainItemWrapper(identifier: "Password", accessGroup:appGroupID)
    keychain.setObject(self.txtfldPassword.text!, forKey:kSecValueData)
    keychain.setObject(self.txtfldEmail.text!, forKey:kSecAttrAccount)

    }

On the Watch extension side (Swift):

override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)

    let appGroupID = "group.com.yourcompany.appid"
    let keychain = KeychainItemWrapper(identifier: "Password", accessGroup:appGroupID)
    println(keychain.objectForKey(kSecAttrAccount))
    println(keychain.objectForKey(kSecValueData))

}

In Objective C, watchkit extension:

NSString *appGroupID = @"group.com.yourcompany.appid";
KeychainItemWrapper *keychain = [[KeychainItemWrapper alloc] initWithIdentifier:@"Password" accessGroup:appGroupID];
[keychain setObject:(__bridge id)(kSecAttrAccessibleWhenUnlocked) forKey:(__bridge id)(kSecAttrAccessible)];
NSLog(@"account = %@", [keychain objectForKey:(__bridge id)(kSecAttrAccount)]);
NSLog(@"password =%@", [keychain objectForKey:(__bridge id)(kSecValueData)]);

Don't forget to turn on "Keychain Sharing" under "Capabilities" for both the phone app and watch kit extension for same keychain group: "group.com.yourcompany.appid"

Quijano answered 9/6, 2015 at 11:16 Comment(0)
M
-1

Use KeychainItemWrapper Class from the following link and pass your group identifier as accessgroup.

https://developer.apple.com/library/ios/samplecode/GenericKeychain/Listings/Classes_KeychainItemWrapper_m.html

Malraux answered 7/4, 2015 at 13:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.