How to allow all applications to access keychain item without prompt
Asked Answered
T

3

11

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?

Teucer answered 24/1, 2017 at 16:8 Comment(1)
did get any success in doing so? I am not able to read the ACL of the keychain item, could you please post the code snippet on how you are doing it. It would be great help to me and also to other people looking for similar answer. ThanksLondrina
A
5

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"
Aloe answered 2/2, 2018 at 20:23 Comment(0)
C
4

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.

Cambist answered 9/5, 2020 at 19:45 Comment(0)
S
2

While this seems to be missing from the Apple documentation, the dictionary of parameters provided to SecKeyGeneratePair() can have a key of kSecAttrAccess and associated value that is a SecAccessRef that provides the desired ACLs. Providing this at initial generation time will allow the ACLs to be set without a user prompt.

Subdivision answered 28/3, 2018 at 21:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.