Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to allow all applications to access keychain item without prompt

Tags:

macos

keychain

There is an option in Keychain Access to allow all applications to access keychain item without restrictions. Keychain Access - Access Control tab

I don't know how to set it programmatically. I have tried to create and set new SecAccessRef with empty ACL, doesn't really change anything (using SecItemUpdate updating kSecAttrAccess) . I also tried getting all ACL lists for all authorisation tags for item and setting ACL contents to an empty array for that ACL/tag combinations. I was able to clear allowed apps list but this didn't allowed all applications to access item without restrictions. I don't see a way to set this using keychain api.

So my question is how to manipulate Access Object or its ACLs to allow unrestricted access to keychain item or at least unrestricted read?

like image 574
solgar Avatar asked Jan 24 '17 16:01

solgar


2 Answers

As @mike-c stated in his answer, it seems that the solution cannot be achieved with newer Keychain APIs that were introduced for supporting iOS and iCloud in the first place. Especially the access control API is no longer compatible with the original way macOS used to treat it. And there were a ton of different APIs introduced on top of that, as shown in session 711 from WWDC 2014: Keychain and Authentication with Touch ID (from 09:40), which makes the understanding even harder.

@mike-c’s answer contains many hints but doesn’t show the full picture. Similarly to this question on Apple Developer Forums, it tells that the appropriate access control for the Keychain item has to be set at the point of creation. It’s a bit unfortunate that the old Keychain APIs are no longer mentioned in the new documentation a so I had to dig into an archived version of the Keychain Services Programming Guide.

Creating Access Object

To describe the access configuration, we have to work with so-called access objects (SecAccess), which have associated access control lists (SecACL). To learn more about ACLs, see the explanation in the old or new (and less detailed) version of the documentation. For the purposes of this answer, ACLs define which application can access the Keychain item for a given operation.

Using SecAccessCreate(…) you can create a new access object with a predefined system default configuration, which is based on the provided parameters. By default, it holds three ACLs. I tried to add a new ACL as suggested in @mike-c’s answer but run into a strange behavior where the access to the item was sometimes granted and sometimes not.

The approach which did the trick is described in the „Advanced Topics“ section of this documentation page. Basically, instead of adding a new ACL, we modify one of the existing ones.

This function creates an access object configured with unrestricted access for all applications. It’s written in Swift by keeping the style of the Keychain C API. It’s quite awkward, but consistency won this time.

/// Creates an access object with the system default configuration which has an altered ACL
/// to allow access for all applications.
///
/// - Parameter descriptor: The name of the item as it should appear in security dialogs.
/// - Parameter accessRef: The pointer to the new access object.
/// - Returns: A result code.
func SecAccessCreateForAllApplications(
    descriptor: CFString,
    accessRef outerAccessRef: UnsafeMutablePointer<SecAccess?>
) -> OSStatus {
    var accessRef: SecAccess?

    // Create an access object with access granted to no application (2nd parameter).
    // It comes configured with 3 default ACLs.
    let accessCreateStatus = SecAccessCreate(
        descriptor,
        [] as CFArray, // No application has access
        &accessRef
    )

    guard accessCreateStatus == errSecSuccess else { return accessCreateStatus }
    guard let access = accessRef else { return accessCreateStatus }

    // Extract the default ACLs from the created access object for the *decrypt* authorization tag.
    guard let aclList = SecAccessCopyMatchingACLList(
        access,
        kSecACLAuthorizationDecrypt
    ) as? [SecACL] else { return errSecInvalidACL }

    // There should be exactly one ACL for the *decrypt* authorization tag.
    guard aclList.count == 1 else { return errSecInvalidACL }
    guard let decryptACL = aclList.first else { return errSecInvalidACL }

    // Extract all authorizations from the default ACL for the *decrypt* authorization tag.
    let allAuthorizations = SecACLCopyAuthorizations(decryptACL)

    // Remove the default ACL for the *decrypt* authorization tag from the access object.
    let aclRemoveStatus = SecACLRemove(decryptACL)

    guard aclRemoveStatus == errSecSuccess else { return aclRemoveStatus }

    // Create a new ACL with access for all applications and add it to the access object.
    var newDecryptACLRef: SecACL?
    let aclCreateStatus = SecACLCreateWithSimpleContents(
        access,
        nil, // All applications have access
        descriptor,
        [], // Empty prompt selector
        &newDecryptACLRef
    )

    guard aclCreateStatus == errSecSuccess else { return aclCreateStatus }
    guard let newDecryptACL = newDecryptACLRef else { return aclCreateStatus }

    // Set the authorizations extracted from the default ACL to the newly created ACL.
    let aclUpdateAuthorizationStatus = SecACLUpdateAuthorizations(newDecryptACL, allAuthorizations)

    guard aclUpdateAuthorizationStatus == errSecSuccess else { return aclUpdateAuthorizationStatus }

    // Finally, write the access to the outer pointer.
    outerAccessRef.initialize(to: access)

    return errSecSuccess
}

Writing Keychain Item

The second part is fairly simple. It turns out you can specify an access object when creating a Keychain item using SecItemAdd(…). Put the custom access object as a value for the key kSecAttrAccess into the attributes dictionary, and it will be applied.

This code writes the Keychain item with the custom access object configured:

var accessRef: SecAccess?

let accessCreateStatus = SecAccessCreateForAllApplications(
    descriptor: "" as CFString, // Actually not necessary
    accessRef: &accessRef
)

guard accessCreateStatus == errSecSuccess else { exit(EXIT_FAILURE) }
guard let access = accessRef else { exit(EXIT_FAILURE) }

let attributes: [CFString: Any] = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrAccount: "Foo",
    kSecValueData: "bar".data(using: .utf8)!,
    kSecAttrAccess: access
]

let itemAddStatus = SecItemAdd(attributes as CFDictionary, nil)

guard itemAddStatus == errSecSuccess else { exit(EXIT_FAILURE) }

Reading Keychain Item

When revealing the Keychain item in the Keychain Access app, it is correctly marked as accessible to all applications.

You can read the item from another app without any dialog being presented. Here is the code to read and decrypt the item’s payload:

let attributes: [CFString: Any] = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrAccount: "Foo",
    kSecReturnData: kCFBooleanTrue as Any,
    kSecMatchLimit: kSecMatchLimitOne
]

var dataRef: AnyObject?
let status = SecItemCopyMatching(attributes as CFDictionary, &dataRef)

guard status == errSecSuccess else { exit(EXIT_FAILURE) }
guard let data = dataRef as? Data else { exit(EXIT_FAILURE) }
guard let string = String(data: data, encoding: .utf8) else { exit(EXIT_FAILURE) }

print(string) // => bar

I still haven’t mastered the legacy Keychain API, so any feedback on the presented approach is welcome.

like image 153
Lukas Kubanek Avatar answered Sep 24 '22 09:09

Lukas Kubanek


From my experience most of the more recent "convenience" methods in the Security API which deposit items into a keychain:

  • SecKeychainAddGenericPassword()
  • SecKeychainAddInternetPassword()
  • SecKeyGeneratePair()

add a change_acl authorization ACL entry on passwords, private keys with an empty array of trusted applications - meaning no application can subsequently modify the ACLs without a user prompt.

So, in general, it appears you can't modify the ACLs on most existing keychain items without a user prompt.

But, if you use older Security API methods to add items to a keychain (to which you can supply an SecAccessRef of your own creation):

  • SecKeychainItemCreateFromContent()
  • SecKeyCreatePair() (deprecated in OS X 10.7 but still works)

then you can effectively set those items to "Allow all applications to access this item" which may be useful depending on your application.

That is, for these older functions you can:

  1. Create an SecAccessRef using SecAccessCreate() or SecAccessCreateWithOwnerAndACL()
  2. Add a single ACL to the SecAccessRef with Any authorization, no prompt behavior (SecKeychainPromptSelector = 0) and a NULL trusted application list using SecACLCreateWithSimpleContents()
  3. Pass the SecAccessRef when creating the keychain item(s) with the above APIs to achieve "Allow all applications to access this item"
like image 37
Mike C. Avatar answered Sep 25 '22 09:09

Mike C.