kishikawakatsumi / KeychainAccess

Simple Swift wrapper for Keychain that works on iOS, watchOS, tvOS and macOS.
MIT License
7.96k stars 788 forks source link

Using KeychainAccess inside embedded command-line tool #382

Open jeff-h opened 6 years ago

jeff-h commented 6 years ago

My macOS app stores server credentials in the keychain using KeychainAccess, and this works perfectly.

I also have written a command line tool, which is embedded into the app's bundle. I'd like this tool to also have access to the passwords stored in the Keychain by the main app.

HOWEVER, for the life of me I cannot get the two to share access to the same keychain items. The command-line tool always pops up the "auth dialog":

pic

I suspect my code is correct. In both the main app and the tool, the following does give me access to the password data from the keychain:

let keychain = Keychain(service: "serviceName", accessGroup: "24F6D23HXS.com.obfuscated.app-name")

I am guessing my problem is in the configuration of the two projects in Xcode. For the macOS app, I have "app target > Capabilities > Keychain Sharing" turned on, and the keychain group it generated there is what's used in the code above (24F6D23HXS.com.obfuscated.app-name). Turning this on also caused a provisioning profile to be generated, which seems fine.

However, the target settings for the command line tool has no Capabilities section, so I can't turn on "Keychain Sharing". Also there's no apparent way to select the provisioning profile the macOS app is using (not sure if I need to do this or not).

Can anyone give me any pointers on how to get this working?

kishikawakatsumi commented 6 years ago

@jeff-h Command line toon doesn't have a bundle. You cannot embed dynamic framework as well as general applications. The simple way to solve it is that you link statically with your app. Just drag and drop KeychainAccess.swift to your project.

jeff-h commented 6 years ago

Thanks! That's actually exactly what I did in order to get KeychainAccess into my command line tool. It works well.

The issue I'm describing above is different though; both the main app and the command line tool do have access to the keychain thanks to KeychainAccess, but my issue is with getting them to share the same keychain items, without requiring the user to enter their keychain password to unlock the keychain item.

For example, the main app will ask the user for a password to access a remote server. For unrelated reasons, I need my command line tool to have access to that exact same password. I can get the command line tool to access it, but when I do, it pops up the auth dialog as pictured above.

octomagon commented 5 years ago

Did you ever get this working? I've got the same dilemma.

jeff-h commented 5 years ago

Unfortunately not. I've wondered if it's because I don't have a paid Apple developer account — kind of hoping that when I get one this problem might go away. Is this possibly the cause in your case?

octomagon commented 5 years ago

Oh, well... I do have a paid account. It doesn't seem to make a difference. Thanks!

jeff-h commented 5 years ago

Rats, so much for that theory then. Please let me know if you find anything else related to this issue!

jannejava commented 5 years ago

How did this go? Having the same issue. However I suspect it’s hard to do. The main purpose of keychain is to protect passwords from apps stealing passwords.

jeff-h commented 5 years ago

Still no solution here.

kishikawakatsumi commented 5 years ago

Static linking is one of the solutions. Use Swift Package Manager.

jeff-h commented 5 years ago

@kishikawakatsumi just to clarify, this issue is not about how to include KeychainAccess in a command line tool. I've statically linked the Keychain.swift into my command line code from the start and it works fine, with one major exception:

The command line tool, despite being embedded into the primary app (and 'Keychain Sharing" configured) does not give seamless access to the keychain items created by the app. When the command line tool requests an item that the main app created, it pops up the auth dialog.

I'll see if I can put together a tiny demo app to illustrate the issue.

jeff-h commented 5 years ago

OK, I have finally put together a demo of the problem: https://github.com/jeff-h/sharedKeychainAccessDemo

There are three components:

I'm hoping you might have a chance to compile the sample apps, and see if you can see what I am missing.


^ this was key to getting access groups to work between two apps on macOS; I finally discovered the following in these Apple Docs: "kSecAttrAccessGroup (iOS; also macOS if kSecAttrSynchronizable specified)"

fappelman commented 3 years ago

@jeff-h This reply is probably too late, but I did some digging with the goal to get a better understanding on how the keychain works.

I have come to the conclusion that what you want is not possible.

Let me take you through my observations:

  1. In KeychainOne you write the keychain items with synchronizable set to true

  2. This makes that the items are written to the keychain iCloud

  3. The items are written with an accessGroup

  4. In order to read from the iCloud the keychain-acccess-group must be set. This is a property that is inserted into your application using codesign. You can actually view those properties using codesign:

    codesign -d --entitlements :- ~/Library/Developer/Xcode/DerivedData/KeychainOne-afabdbcccjdiuvcbtmjqjhxeqchu/Build/Products/Debug/KeychainOne.app 

    which then produces the following output:

    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>com.apple.application-identifier</key>
        <string>2XJSMU4L5Z.com.marmaladesoul.KeychainOne</string>
        <key>com.apple.developer.team-identifier</key>
        <string>2XJSMU4L5Z</string>
        <key>com.apple.security.application-groups</key>
        <string>2XJSMU4L5Z.com.marmaladesoul.appgroup</string>
        <key>com.apple.security.get-task-allow</key>
        <true/>
        <key>keychain-access-groups</key>
        <array>
            <string>2XJSMU4L5Z.com.marmaladesoul.shared</string>
        </array>
    </dict>
    </plist>

    Note the keychain-access-groups. It lists the groups that this application has access to. (I changed the name of the access group because of experiments but that can be ignored)

  5. If you check the output from KeychainTwo.app you will see a similar result. Just a different com.apple.application-identifier value.

  6. Then check the output from the CommandLineTool:

    codesign -d --entitlements :- ~/Library/Developer/Xcode/DerivedData/KeychainOne-afabdbcccjdiuvcbtmjqjhxeqchu/Build/Products/Debug/CommandLineTool

    which then produces the following output:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
         <key>com.apple.security.get-task-allow</key>
         <true/>
    </dict>
    </plist>

    As you can see it lacks the keychain-access-groups.

  7. As a result it will never be able to read the value of the password. In fact this link seems to suggest that accessing the iCloud keychain from the command line will never work.

  8. So if you want to share the passwords between the 3 applications your only option is to set the synchronizable value to false in KeychainOne.app. If you do this you will see that your command line tool will print the passwords.

jeff-h commented 3 years ago

Thanks for your research! It's never too late, but unfortunately I've forgotten the details about all this. When I circle back to it in future I will definitely trace through your comment and hopefully make sense of things.