Prioritize user privacy and data security in your app. Discuss best practices for data handling, user consent, and security measures to protect user information.

Posts under General subtopic

Post

Replies

Boosts

Views

Activity

Security Resources
General: Forums topic: Privacy & Security Apple Platform Security support document Developer > Security Enabling enhanced security for your app documentation article Creating enhanced security helper extensions documentation article Security Audit Thoughts forums post Cryptography: Forums tags: Security, Apple CryptoKit Security framework documentation Apple CryptoKit framework documentation Common Crypto man pages — For the full list of pages, run: % man -k 3cc For more information about man pages, see Reading UNIX Manual Pages. On Cryptographic Key Formats forums post SecItem attributes for keys forums post CryptoCompatibility sample code Keychain: Forums tags: Security Security > Keychain Items documentation TN3137 On Mac keychain APIs and implementations SecItem Fundamentals forums post SecItem Pitfalls and Best Practices forums post Investigating hard-to-reproduce keychain problems forums post App ID Prefix Change and Keychain Access forums post Smart cards and other secure tokens: Forums tag: CryptoTokenKit CryptoTokenKit framework documentation Mac-specific resources: Forums tags: Security Foundation, Security Interface Security Foundation framework documentation Security Interface framework documentation BSD Privilege Escalation on macOS Related: Networking Resources — This covers high-level network security, including HTTPS and TLS. Network Extension Resources — This covers low-level network security, including VPN and content filters. Code Signing Resources Notarisation Resources Trusted Execution Resources — This includes Gatekeeper. App Sandbox Resources Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
0
0
3.5k
Nov ’25
Privacy & Security Resources
General: Forums topic: Privacy & Security Privacy Resources Security Resources Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
0
0
293
Jul ’25
"Not authorized to send Apple events to Finder"
Hi, We are trying to open an application "xyz.app" It worked fine until 15.1.1 versions. But facing issues with 15.2 and 15.3 The application is working fine when we navigate to xyz.app/Contents/MacOS/ and run applet in this directory. But the error ""Not authorized to send Apple events to Finder"" occurs when we are trying to open the app directly. Could someone please help me understand what might be causing this issue and how to resolve it?
0
1
263
Feb ’25
Ask for help on Guideline 1.1.6-Safety-Objectionable Content
Our company developed an app that relies on the collected list to display the phone's label in the list when the user's phone receives an incoming call. However, we have been rejected. The main reason for the rejection is as follows: “ Guideline 1.1.6 - Safety - Objectionable Content The app still allows users to unblock and reveal blocked incoming numbers to identify the individual calling or texting, which is not appropriate. Specifically, your app claims to offer the call blocking functionality, but solely identifies numbers that the user has explicitly blocked themselves. Since users can choose to hide their caller ID in iPhone Settings, apps should not attempt to circumvent this iOS feature to reveal the caller's number. ” But our developers clearly stated that there is no way to bypass these settings, and CallDirectory Extensionde cannot directly "unlock hidden numbers" or bypass the built-in restrictions of IOS. We don't know how to solve this problem next, and hope to get everyone's help.
0
0
409
Jan ’25
Fraud prevention using Device Check when publishing multiple apps
I would like to confirm about fraud prevention using Device Check when publishing multiple apps. If the Team ID and Key ID are the same, will the values be shared across all apps with Device Check? With Device Check, only two keys can be created per developer account, and these two are primarily intended for key renewal in case of a leak, rather than for assigning different keys to each app, correct? If both 1 and 2 are correct, does that mean that Device Check should not be used to manage "one-time-only rewards per device" when offering them across multiple apps? Thank you very much for your confirmation.
0
0
163
Apr ’25
Receiving Apple SignIn profile info again after deleting account
We are currently trying to fix a bug when using SignIn with Apple. It appears that on some occasions we are not receiving a user's profile info (name, email) when a new account is created. After doing some investigation we believe this bug is due to the same Apple login being used as an already deleted account. ASF only appears to send profile info the very first time an Apple login is used. If that account is deleted and another is created with the same apple login we won't receive the profile info. As a result we are not in compliance with Apple's guidelines requiring that we use the provided profile info with Apple SigIn, and need to prompt users to enter it again. Is there a process in place to properly "clear" a user after their account is deleted in our system, so that the next time a user creates an account with the same Apple login, we receive their profile info again?
0
5
407
Mar ’25
App Attest development server (data-development.appattest.apple.com) returns 403 for CBOR attestation request
Hi, I’m currently implementing App Attest attestation validation on the development server. However, I’m receiving a 403 Forbidden response when I POST a CBOR-encoded payload to the following endpoint: curl -X POST -H "Content-Type: application/cbor" --data-binary @payload.cbor 'https://data-development.appattest.apple.com' Here’s how I’m generating the CBOR payload in Java: Map<String, Object> payload = new HashMap<>(); payload.put("attestation", attestationBytes); // byte[] from DCAppAttestService payload.put("clientDataHash", clientDataHash); // SHA-256 hash of the challenge (byte[]) payload.put("keyId", keyIdBytes); // Base64-decoded keyId (byte[]) payload.put("appId", TEAM_ID + "." + BUNDLE_ID); // e.g., "ABCDE12345.com.example.app" ObjectMapper cborMapper = new ObjectMapper(new CBORFactory()); byte[] cborBody = cborMapper.writeValueAsBytes(payload); I’m unsure whether the endpoint is rejecting the payload format or if the endpoint itself is incorrect for this stage. I’d appreciate clarification on the following: 1. Is https://data-development.appattest.apple.com the correct endpoint for key attestation in a development environment? 2. Should this endpoint accept CBOR-encoded payloads, or is it only for JSON-based assertion validation? 3. Is there a current official Apple documentation that lists: • the correct URLs for key attestation and assertion validation (production and development), • or any server-side example code (e.g., Java, Python) for handling attestation/validation on the backend? So far, I couldn’t find an official document that explicitly describes the expected HTTP endpoints for these operations. If there’s a newer guide or updated API reference, I’d appreciate a link. Thanks in advance for your help.
0
0
138
May ’25
XCode claims that tracking domains are not listed in the Privacy Manifest
Hi, Xcode Instruments shows multiple Points of Interest with the information that the framework is not listed in my Privacy Manifest. However, I have already included them in the Privacy Manifest under the privacy tracking domains. I have this problem with every tracking domain i listed in the Privacy Manifest's Privacy Tracking Domains. Did I make a mistake in my Privacy Manifest declaration?
0
0
101
May ’25
Handling ITMS-91061: Missing privacy manifest
An ITMS-91061: Missing privacy manifest rejection email looks as follows: ITMS-91061: Missing privacy manifest- Your app includes "<path/to/SDK>", which includes , an SDK that was identified in the documentation as a privacy-impacting third-party SDK. Starting February 12, 2025, if a new app includes a privacy-impacting SDK, or an app update adds a new privacy-impacting SDK, the SDK must include a privacy manifest file. Please contact the provider of the SDK that includes this file to get an updated SDK version with a privacy manifest. For more details about this policy, including a list of SDKs that are required to include signatures and manifests, visit: https://developer.apple.com/support/third-party-SDK-requirements. Glossary ITMS-91061: Missing privacy manifest: An email that includes the name and path of privacy-impacting SDK(s) with no privacy manifest files in your app bundle. For more information, see https://developer.apple.com/support/third-party-SDK-requirements. : The specified privacy-impacting SDK that doesn't include a privacy manifest file. If you are the developer of the rejected app, gather the name of the SDK from the email you received from Apple, then contact the SDK's provider for an updated version that includes a valid privacy manifest. After receiving an updated version of the SDK, verify the SDK includes a valid privacy manifest file at the expected location. For more information, see Adding a privacy manifest to your app or third-party SDK. If your app includes a privacy manifest file, make sure the file only describes the privacy practices of your app. Do not add the privacy practices of the SDK to your app's privacy manifest. If the email lists multiple SDKs, repeat the above process for all of them. If you are the developer of an SDK listed in the email, publish an updated version of your SDK that includes a privacy manifest file with valid keys and values. Every privacy-impacting SDK must contain a privacy manifest file that only describes its privacy practices. To learn how to add a valid privacy manifest to your SDK, see the Additional resources section below. Additional resources Privacy manifest files Describing data use in privacy manifests Describing use of required reason API Adding a privacy manifest to your app or third-party SDK TN3182: Adding privacy tracking keys to your privacy manifest TN3183: Adding required reason API entries to your privacy manifest TN3184: Adding data collection details to your privacy manifest TN3181: Debugging an invalid privacy manifest
0
0
5.8k
Mar ’25
"Apps Using Apple ID" list & Apple's Private Relay
Hello, We plan to remove our app from the App Store. This post aims to determine whether our company can rely on Private Relay to compensate our customers. Our Challenge: Gift Card Refunds with Private Relay Some customers purchased gift cards through our app using Apple's "Private Relay" during account creation. To process refunds, we need a way to identify these customers. Our system relies on email addresses, which are masked by Private Relay. Potential Solution: Apps Using Apple ID We're exploring "Apps Using Apple ID" as a possible solution for customers to share their Private Relay addresses for refund purposes. Under what circumstances will an app cease to appear in the "Apps Using Apple ID" list? What conditions must be met to initiate a new Private Relay connection for the same user and application? For example, would using the same Apple account to sign into the app on a different device trigger a new Private Relay? Thank you for your help!
0
0
357
Jan ’25
App Groups: macOS vs iOS: Working Towards Harmony
I regularly see folks confused by the difference in behaviour of app groups between macOS and iOS. There have been substantial changes in this space recently. While much of this is now covered in the official docs (r. 92322409), I’ve updated this post to go into all the gory details. If you have questions or comments, start a new thread with the details. Put it in the App & System Services > Core OS topic area and tag it with Code Signing and Entitlements. Oh, and if your question is about app group containers, also include Files and Storage. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" App Groups: macOS vs iOS: Working Towards Harmony There are two styles of app group ID: iOS-style app group IDs start with group., for example, group.eskimo1.test. macOS-style app group IDs start with your Team ID, for example, SKMME9E2Y8.eskimo1.test. This difference has been the source of numerous weird problems over the years. Starting in Feb 2025, iOS-style app group IDs are fully supported on macOS for all product types [1]. If you’re writing new code that uses app groups, use an iOS-style app group ID. If you have existing code that uses a macOS-style app group ID, consider how you might transition to the iOS style. IMPORTANT The Feb 2025 changes aren’t tied to an OS release but rather to a Developer website update. For more on this, see Feb 2025 Changes, below. [1] If your product is a standalone executable, like a daemon or agent, wrap it in an app-like structure, as explained in Signing a daemon with a restricted entitlement. iOS-Style App Group IDs An iOS-style app group ID has the following features: It starts with the group. prefix, for example, group.eskimo1.test. You allocate it on the Developer website. This assigns the app group ID to your team. You then claim access to it by listing it in the App Groups entitlement (com.apple.security.application-groups) entitlement. That claim must be authorised by a provisioning profile [1]. The Developer website will only let you include your team’s app group IDs in your profile. For more background on provisioning profiles, see TN3125 Inside Code Signing: Provisioning Profiles. iOS-style app group IDs originated on iOS with iOS 3.0. They’ve always been supported on iOS’s child platforms (iPadOS, tvOS, visionOS, and watchOS). On the Mac: They’ve been supported by Mac Catalyst since that technology was introduced. Likewise for iOS Apps on Mac. Starting in Feb 2025, they’re supported for other Mac products. [1] Strictly speaking macOS does not require that, but if your claim is not authorised by a profile then you might run into other problems. See Entitlements-Validated Flag, below. macOS-Style App Group IDs A macOS-style app group ID has the following features: It should start with your Team ID [1], for example, SKMME9E2Y8.eskimo1.test. It can’t be explicitly allocated on the Developer website. Code that isn’t sandboxed doesn’t need to claim the app group ID in the App Groups entitlement. [2] To use an app group, claim the app group ID in the App Groups entitlement. The App Groups entitlement is not restricted on macOS, meaning that this claim doesn’t need to be authorised by a provisioning profile [3]. However, if you claim an app group ID that’s not authorised in some way, you might run into problems. More on that later in this post. If you submit an app to the Mac App Store, the submission process checks that your app group IDs make sense, that is, they either start with your Team ID (macOS style) or are assigned to your team (iOS style). [1] This is “should” because, historically, macOS has not actually required it. However, that’s now changing, with things like app group container protection. [2] This was true prior to macOS 15. It may still technically be true in macOS 15 and later, but the most important thing, access to the app group container, requires the entitlement because of app group container protection. [3] Technically it’s a validation-required entitlement, something that we’ll come back to in the Entitlements-Validated Flag section. Feb 2025 Changes On 21 Feb 2025 we rolled out a change to the Developer website that completes the support for iOS-style app group IDs on the Mac. Specifically, it’s now possible to create a Mac provisioning profile that authorises the use of an iOS-style app group ID. Note This change doesn’t affect Mac Catalyst or iOS Apps on Mac, which have always been able to use iOS-style app group IDs on the Mac. Prior to this change it was possible to use an iOS-style app group ID on the Mac but that might result in some weird behaviour. Later sections of this post describe some of those problems. Of course, that information is now only of historical interest because, if you’re using an iOS-style app group, you can and should authorise that use with a provisioning profile. We also started seeding Xcode 16.3, which has since been release. This is aware of the Developer website change, and its Signing & Capabilities editor actively encourages you to use iOS-style app groups IDs in all products. Note This Xcode behaviour is the only option for iOS and its child platforms. With Xcode 16.3, it’s now the default for macOS as well. If you have existing project, enable this behaviour using the Register App Groups build setting. Finally, we updated a number of app group documentation pages, including App Groups entitlement and Configuring app groups. Crossing the Streams In some circumstances you might need to have a single app that accesses both an iOS- and a macOS-style app group. For example: You have a macOS app. You want to migrate to an iOS-style app group ID, perhaps because you want to share an app group container with a Mac Catalyst app. But you also need to access existing content in a container identified by a macOS-style app group ID. Historically this caused problems (FB16664827) but, as of Jun 2025, this is fully supported (r. 148552377). When the Developer website generates a Mac provisioning profile for an App ID with the App Groups capability, it automatically adds TEAM_ID.* to the list of app group IDs authorised by that profile (where TEAM_ID is your Team ID). This allows the app to claim access to every iOS-style app group ID associated with the App ID and any macOS-style app group IDs for that team. This helps in two circumstances: It avoids any Mac App Store Connect submission problems, because App Store Connect can see that the app’s profile authorises its use of all the it app group IDs it claims access to. Outside of App Store — for example, when you directly distribute an app using Developer ID signing — you no longer have to rely on macOS granting implicit access to macOS-style app group IDs. Rather, such access is explicitly authorised by your profile. That ensures that your entitlements remain validated, as discussed in the Entitlements-Validated Flag, below. A Historical Interlude These different styles of app group IDs have historical roots: On iOS, third-party apps have always used provisioning profiles, and thus the App Groups entitlement is restricted just like any other entitlement. On macOS, support for app groups was introduced before macOS had general support for provisioning profiles [1], and thus the App Groups entitlement is unrestricted. The unrestricted nature of this entitlement poses two problems. The first is accidental collisions. How do you prevent folks from accidentally using an app group ID that’s in use by some other developer? On iOS this is easy: The Developer website assigns each app group ID to a specific team, which guarantees uniqueness. macOS achieved a similar result by using the Team ID as a prefix. The second problem is malicious reuse. How do you prevent a Mac app from accessing the app group containers of some other team? Again, this isn’t an issue on iOS because the App Groups entitlement is restricted. On macOS the solution was for the Mac App Store to prevent you from publishing an app that used an app group ID that’s used by another team. However, this only works for Mac App Store apps. Directly distributed apps were free to access app group containers of any other app. That was considered acceptable back when the Mac App Store was first introduced. That’s no longer the case, which is why macOS 15 introduced app group container protection. See App Group Container Protection, below. [1] I’m specifically talking about provisioning profiles for directly distributed apps, that is, apps using Developer ID signing. Entitlements-Validated Flag The fact that the App Groups entitlement is unrestricted on macOS is, when you think about it, a little odd. The purpose of entitlements is to gate access to functionality. If an entitlement isn’t restricted, it’s not much of a gate! For most unrestricted entitlements that’s not a problem. Specifically, for both the App Sandbox and Hardened Runtime entitlements, those are things you opt in to, so macOS is happy to accept the entitlement at face value. After all, if you want to cheat you can just not opt in [1]. However, this isn’t the case for the App Groups entitlement, which actually gates access to functionality. Dealing with this requires macOS to walk a fine line between security and compatibility. Part of that solution is the entitlements-validated flag. When a process runs an executable, macOS checks its entitlements. There are two categories: Restricted entitlements must be authorised by a provisioning profile. If your process runs an executable that claims a restricted entitlement that’s not authorised by a profile, the system traps. Unrestricted entitlements don’t have to be authorised by a provisioning profile; they can be used by any code at any time. However, the App Groups entitlement is a special type of unrestricted entitlement called a validation-required entitlement. If a process runs an executable that claims a validation-required entitlement and that claim is not authorised by a profile, the system allows the process to continue running but clears its entitlements-validated flag. Some subsystems gate functionality on the entitlements-validated flag. For example, the data protection keychain uses entitlements as part of its access control model, but refuses to honour those entitlements if the entitlement-validated flag has been cleared. Note If you’re curious about this flag, use the procinfo subcommand of launchctl to view it. For example: % sudo launchctl procinfo `pgrep Test20230126` … code signing info = valid … entitlements validated … If the flag has been cleared, this line will be missing from the code signing info section. Historically this was a serious problem because it prevented you from creating an app that uses both app groups and the data protection keychain [2] (r. 104859788). Fortunately that’s no longer an issue because the Developer website now lets you include the App Groups entitlement in macOS provisioning profiles. [1] From the perspective of macOS checking entitlements at runtime. There are other checks: The App Sandbox is mandatory for Mac App Store apps, but that’s checked when you upload the app to App Store Connect. Directly distributed apps must be notarised to pass Gatekeeper, and the notary service requires that all executables enable the hardened runtime. [2] See TN3137 On Mac keychain APIs and implementations for more about the data protection keychain. App Groups and the Keychain The differences described above explain a historical oddity associated with keychain access. The Sharing access to keychain items among a collection of apps article says: Application groups When you collect related apps into an application group using the App Groups entitlement, they share access to a group container, and gain the ability to message each other in certain ways. You can use app group names as keychain access group names, without adding them to the Keychain Access Groups entitlement. On iOS this makes a lot of sense: The App Groups entitlement is a restricted entitlement on iOS. The Developer website assigns each iOS-style app group ID to a specific team, which guarantees uniqueness. The required group. prefix means that these keychain access groups can’t collide with other keychain access groups, which all start with an App ID prefix (there’s also Apple-only keychain access groups that start with other prefixes, like apple). However, this didn’t work on macOS [1] because the App Groups entitlement is unrestricted there. However, with the Feb 2025 changes it should now be possible to use an iOS-style app group ID as a keychain access group on macOS. Note I say “should” because I’ve not actually tried it (-: Keep in mind that standard keychain access groups are protected the same way on all platforms, using the restricted Keychain Access Groups entitlement (keychain-access-groups). [1] Except for Mac Catalyst apps and iOS Apps on Mac. Not Entirely Unsatisfied When you launch a Mac app that uses app groups you might see this log entry: type: error time: 10:41:35.858009+0000 process: taskgated-helper subsystem: com.apple.ManagedClient category: ProvisioningProfiles message: com.example.apple-samplecode.Test92322409: Unsatisfied entitlements: com.apple.security.application-groups Note The exact format of that log entry, and the circumstances under which it’s generated, varies by platform. On macOS 13.0.1 I was able to generate it by running a sandboxed app that claims a macOS-style app group ID in the App Groups entitlement and also claims some other restricted entitlement. This looks kinda worrying and can be the source of problems. It means that the App Groups entitlement claims an entitlement that’s not authorised by a provisioning profile. On iOS this would trap, but on macOS the system allows the process to continue running. It does, however, clear the entitlements-validate flag. See Entitlements-Validated Flag for an in-depth discussion of this. The easiest way to avoid this problem is to authorise your app group ID claims with a provisioning profile. If there’s some reason you can’t do that, watch out for potential problems with: The data protection keychain — See the discussion of that in the Entitlements-Validated Flag and App Groups and the Keychain sections, both above. App group container protection — See App Group Container Protection, below. App Group Container Protection macOS 15 introduced app group container protection. To access an app group container without user intervention: Claim access to the app group by listing its ID in the App Groups entitlement. Locate the container by calling the containerURL(forSecurityApplicationGroupIdentifier:) method. Ensure that at least one of the following criteria are met: Your app is deployed via the Mac App Store (A). Or via TestFlight when running on macOS 15.1 or later (B). Or the app group ID starts with your app’s Team ID (C). Or your app’s claim to the app group is authorised by a provisioning profile embedded in the app (D) [1]. If your app doesn’t follow these rules, the system prompts the user to approve its access to the container. If granted, that consent applies only for the duration of that app instance. For more on this, see: The System Integrity Protection section of the macOS Sequoia 15 Release Notes The System Integrity Protection section of the macOS Sequoia 15.1 Release Notes WWDC 2024 Session 10123 What’s new in privacy, starting at 12:23 The above criteria mean that you rarely run into the app group authorisation prompt. If you encounter a case where that happens, feel free to start a thread here on DevForums. See the top of this post for info on the topic and tags to use. Note Prior to the Feb 2025 change, things generally worked out fine when you app was deployed but you might’ve run into problems during development. That’s no longer the case. [1] This is what allows Mac Catalyst and iOS Apps on Mac to work. Revision History 2025-08-12 Added a reference to the Register App Groups build setting. 2025-07-28 Updated the Crossing the Streams section for the Jun 2025 change. Made other minor editorial changes. 2025-04-16 Rewrote the document now that iOS-style app group IDs are fully supported on the Mac. Changed the title from App Groups: macOS vs iOS: Fight! to App Groups: macOS vs iOS: Working Towards Harmony 2025-02-25 Fixed the Xcode version number mentioned in yesterday’s update. 2025-02-24 Added a quick update about the iOS-style app group IDs on macOS issue. 2024-11-05 Further clarified app group container protection. Reworked some other sections to account for this new reality. 2024-10-29 Clarified the points in App Group Container Protection. 2024-10-23 Fleshed out the discussion of app group container protection on macOS 15. 2024-09-04 Added information about app group container protection on macOS 15. 2023-01-31 Renamed the Not Entirely Unsatisfactory section to Not Entirely Unsatisfied. Updated it to describe the real impact of that log message. 2022-12-12 First posted.
0
0
5.1k
Aug ’25
App Tracking Transparency Issue
Hello! I need to implement requestTrackingAuthorization into my app but I'm not exactly sure how, the engine I use handles all of the Xcode stuff for me so I'm not very experienced in that sector nor do I have an Xcode project for this app, any help?
0
0
254
Dec ’24
Screen recording detect in MACOs Apps
Hi, My MACOS app has sensitive content and dont want user to take screenshot or to record the screen. I tries window.sharingType=none. With this user can still record the screen. I know that user can record with external device. But we dont want him to record using screen capture. Can you please tell me how to detect when screen recording is active in MACOs apps? or how to prevent screen recording in MACOs apps. Thanks
0
1
376
Feb ’25
Sign in with Apple
Hey there, I’m currently exploring the possibility of integrating Sign in with Apple into my iOS app and backend. Regarding the iOS app, I’ve read that when a user is signed in, you always need to call getCredentialState on the app’s launch. Is this true? If so, how is it possible to sign the user out then? I intend to incorporate SwiftData and CloudKit into my application. In light of this, I’m curious about your approach to user management. Specifically, I’m wondering if you would store the user’s data in a Redis database after successful authentication on the backend. Or, would you separate the user data and save it using SwiftData/ CloudKit?
0
1
359
Feb ’25
DCDevice.current.generateToken Is it safe to cache tokens for less than 1s ?
We have a crash on DCDevice.current.isSupported We want to try to make a serial queue to generate tokens but the side effect would be the same token would be used on multiple server API requests that are made within a few ms of each other? Is this safe or will the Apple server immediately reject the same token being reused? Can you share how long tokens are safe to use for? Here is the code we want to try final actor DeviceTokenController: NSObject { static var shared: DeviceTokenController = .init() private var tokenGenerationTask: Task<Data?, Never>? var ephemeralDeviceToken: Data? { get async { // Re-using the token for short periods of time if let existingTask = tokenGenerationTask { return await existingTask.value } let task = Task<Data?, Never> { guard DCDevice.current.isSupported else { return nil } do { return try await DCDevice.current.generateToken() } catch { Log("Failed to generate ephemeral device token", error) return nil } } tokenGenerationTask = task let result = await task.value tokenGenerationTask = nil return result } } }
0
1
567
Jul ’25
SecItem: Pitfalls and Best Practices
I regularly help developers with keychain problems, both here on DevForums and for my Day Job™ in DTS. Over the years I’ve learnt a lot about the API, including many pitfalls and best practices. This post is my attempt to collect that experience in one place. If you have questions or comments about any of this, put them in a new thread and apply the Security tag so that I see it. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" SecItem: Pitfalls and Best Practices It’s just four functions, how hard can it be? The SecItem API seems very simple. After all, it only has four function calls, how hard can it be? In reality, things are not that easy. Various factors contribute to making this API much trickier than it might seem at first glance. This post explains some of the keychain’s pitfalls and then goes on to explain various best practices. Before reading this, make sure you understand the fundamentals by reading its companion post, SecItem: Fundamentals. Pitfalls Lets start with some common pitfalls. Queries and Uniqueness Constraints The relationship between query dictionaries and uniqueness constraints is a major source of problems with the keychain API. Consider code like this: var copyResult: CFTypeRef? = nil let query = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecAttrAccount: "mrgumby", kSecAttrGeneric: Data("SecItemHints".utf8), ] as NSMutableDictionary let err = SecItemCopyMatching(query, &copyResult) if err == errSecItemNotFound { query[kSecValueData] = Data("opendoor".utf8) let err2 = SecItemAdd(query, nil) if err2 == errSecDuplicateItem { fatalError("… can you get here? …") } } Can you get to the fatal error? At first glance this might not seem possible because you’ve run your query and it’s returned errSecItemNotFound. However, the fatal error is possible because the query contains an attribute, kSecAttrGeneric, that does not contribute to the uniqueness. If the keychain contains a generic password whose service (kSecAttrService) and account (kSecAttrAccount) attributes match those supplied but whose generic (kSecAttrGeneric) attribute does not, the SecItemCopyMatching calls will return errSecItemNotFound. However, for a generic password item, of the attributes shown here, only the service and account attributes are included in the uniqueness constraint. If you try to add an item where those attributes match an existing item, the add will fail with errSecDuplicateItem even though the value of the generic attribute is different. The take-home point is that that you should study the attributes that contribute to uniqueness and use them in a way that’s aligned with your view of uniqueness. See the Uniqueness section of SecItem: Fundamentals for a link to the relevant documentation. Erroneous Attributes Each keychain item class supports its own specific set of attributes. For information about the attributes supported by a given class, see SecItem: Fundamentals. I regularly see folks use attributes that aren’t supported by the class they’re working with. For example, the kSecAttrApplicationTag attribute is only supported for key items (kSecClassKey). Using it with a certificate item (kSecClassCertificate) will cause, at best, a runtime error and, at worst, mysterious bugs. This is an easy mistake to make because: The ‘parameter block’ nature of the SecItem API means that the compiler won’t complain if you use an erroneous attribute. On macOS, the shim that connects to the file-based keychain ignores unsupported attributes. Imagine you want to store a certificate for a particular user. You might write code like this: let err = SecItemAdd([ kSecClass: kSecClassCertificate, kSecAttrApplicationTag: Data(name.utf8), kSecValueRef: cert, ] as NSDictionary, nil) The goal is to store the user’s name in the kSecAttrApplicationTag attribute so that you can get back their certificate with code like this: let err = SecItemCopyMatching([ kSecClass: kSecClassCertificate, kSecAttrApplicationTag: Data(name.utf8), kSecReturnRef: true, ] as NSDictionary, &copyResult) On iOS, and with the data protection keychain on macOS, both calls will fail with errSecNoSuchAttr. That makes sense, because the kSecAttrApplicationTag attribute is not supported for certificate items. Unfortunately, the macOS shim that connects the SecItem API to the file-based keychain ignores extraneous attributes. This results in some very bad behaviour: SecItemAdd works, ignoring kSecAttrApplicationTag. SecItemCopyMatching ignores kSecAttrApplicationTag, returning the first certificate that it finds. If you only test with a single user, everything seems to work. But, later on, when you try your code with multiple users, you might get back the wrong result depending on the which certificate the SecItemCopyMatching call happens to discover first. Ouch! Context Matters Some properties change behaviour based on the context. The value type properties are the biggest offender here, as discussed in the Value Type Subtleties section of SecItem: Fundamentals. However, there are others. The one that’s bitten me is kSecMatchLimit: In a query and return dictionary its default value is kSecMatchLimitOne. If you don’t supply a value for kSecMatchLimit, SecItemCopyMatching returns at most one item that matches your query. In a pure query dictionary its default value is kSecMatchLimitAll. For example, if you don’t supply a value for kSecMatchLimit, SecItemDelete will delete all items that match your query. This is a lesson that, once learnt, is never forgotten! Note Although this only applies to the data protection keychain. If you’re on macOS and targeting the file-based keychain, kSecMatchLimit always defaults to kSecMatchLimitOne (r. 105800863). Fun times! Digital Identities Aren’t Real A digital identity is the combination of a certificate and the private key that matches the public key within that certificate. The SecItem API has a digital identity keychain item class, namely kSecClassIdentity. However, the keychain does not store digital identities. When you add a digital identity to the keychain, the system stores its components, the certificate and the private key, separately, using kSecClassCertificate and kSecClassKey respectively. This has a number of non-obvious effects: Adding a certificate can ‘add’ a digital identity. If the new certificate happens to match a private key that’s already in the keychain, the keychain treats that pair as a digital identity. Likewise when you add a private key. Similarly, removing a certificate or private key can ‘remove’ a digital identity. Adding a digital identity will either add a private key, or a certificate, or both, depending on what’s already in the keychain. Removing a digital identity removes its certificate. It might also remove the private key, depending on whether that private key is used by a different digital identity. The system forms a digital identity by matching the kSecAttrApplicationLabel (klbl) attribute of the private key with the kSecAttrPublicKeyHash (pkhh) attribute of the certificate. If you add both items to the keychain and the system doesn’t form an identity, check the value of these attributes. For more information the key attributes, see SecItem attributes for keys. Keys Aren’t Stored in the Secure Enclave Apple platforms let you protect a key with the Secure Enclave (SE). The key is then hardware bound. It can only be used by that specific SE [1]. Earlier versions of the Protecting keys with the Secure Enclave article implied that SE-protected keys were stored in the SE itself. This is not true, and it’s caused a lot of confusion. For example, I once asked the keychain team “How much space does the SE have available to store keys?”, a question that’s complete nonsense once you understand how this works. In reality, SE-protected keys are stored in the standard keychain database alongside all your other keychain items. The difference is that the key is wrapped in such a way that only the SE can use it. So, the key is protected by the SE, not stored in the SE. A while back we updated the docs to clarify this point but the confusion persists. [1] Technically it’s that specific iteration of that specific SE. If you erase the device then the key material needed to use the key is erased and so the key becomes permanently useless. This is the sort of thing you’ll find explained in Apple Platform Security. Careful With that Shim, Mac Developer As explained in TN3137 On Mac keychain APIs and implementations, macOS has a shim that connects the SecItem API to either the data protection keychain or the file-based keychain depending on the nature of the request. That shim has limitations. Some of those are architectural but others are simply bugs in the shim. For some great examples, see the Investigating Complex Attributes section below. The best way to avoid problems like this is to target the data protection keychain. If you can’t do that, try to avoid exploring the outer reaches of the SecItem API. If you encounter a case that doesn’t make sense, try that same case with the data protection keychain. If it works there but fails with the file-based keychain, please do file a bug against the shim. It’ll be in good company. Here’s some known issues with the shim: It ignores unsupported attributes. See Erroneous Attributes, above, for more background on that. The shim can fan out to both the data protection and the file-based keychain. In that case it has to make a policy decision about how to handle errors. This results in some unexpected behaviour (r. 143405965). For example, if you call SecItemCopyMatching while the keychain is locked, the data protection keychain will fail with errSecInteractionNotAllowed (-25308). OTOH, it’s possible to query for the presence of items in the file-based keychain even when it’s locked. If you do that and there’s no matching item, the file-based keychain fails with errSecItemNotFound (-25300). When the shim gets these conflicting errors, it chooses to return the latter. Whether this is right or wrong depends on your perspective, but it’s certainly confusing, especially if you’re coming at this from the iOS side. If you call SecItemDelete without specifying a match limit (kSecMatchLimit), the data protection keychain deletes all matching items, whereas the file-based keychain just deletes a single match (r. 105800863). While these issue have all have bug numbers, there’s no guarantee that any of them will be fixed. Fixing bugs like this is tricky because of binary compatibility concerns. Add-only Attributes Some attributes can only be set when you add an item. These attributes are usually associated with the scope of the item. For example, to protect an item with the Secure Enclave, supply the kSecAttrAccessControl attribute to the SecItemAdd call. Once you do that, however, you can’t change the attribute. Calling SecItemUpdate with a new kSecAttrAccessControl won’t work. Lost Keychain Items A common complaint from developers is that a seemingly minor update to their app has caused it to lose all of its keychain items. Usually this is caused by one of two problems: Entitlement changes Query dictionary confusion Access to keychain items is mediated by various entitlements, as described in Sharing access to keychain items among a collection of apps. If the two versions of your app have different entitlements, one version may not be able to ‘see’ items created by the other. Imagine you have an app with an App ID of SKMME9E2Y8.com.example.waffle-varnisher. Version 1 of your app is signed with the keychain-access-groups entitlement set to [ SKMME9E2Y8.groupA, SKMME9E2Y8.groupB ]. That makes its keychain access group list [ SKMME9E2Y8.groupA, SKMME9E2Y8.groupB, SKMME9E2Y8.com.example.waffle-varnisher ]. If this app creates a new keychain item without specifying kSecAttrAccessGroup, the system places the item into SKMME9E2Y8.groupA. If version 2 of your app removes SKMME9E2Y8.groupA from the keychain-access-groups, it’ll no longer be able to see the keychain items created by version 1. You’ll also see this problem if you change your App ID prefix, as described in App ID Prefix Change and Keychain Access. IMPORTANT When checking for this problem, don’t rely on your .entitlements file. There are many steps between it and your app’s actual entitlements. Rather, run codesign to dump the entitlements of your built app: % codesign -d --entitlements - /path/to/your.app Lost Keychain Items, Redux Another common cause of lost keychain items is confusion about query dictionaries, something discussed in detail in this post and SecItem: Fundamentals. If SecItemCopyMatching isn’t returning the expected item, add some test code to get all the items and their attributes. For example, to dump all the generic password items, run code like this: func dumpGenericPasswords() throws { let itemDicts = try secCall { SecItemCopyMatching([ kSecClass: kSecClassGenericPassword, kSecMatchLimit: kSecMatchLimitAll, kSecReturnAttributes: true, ] as NSDictionary, $0) } as! [[String: Any]] print(itemDicts) } Then compare each item’s attributes against the attributes you’re looking for to see why there was no match. Data Protection and Background Execution Keychain items are subject to data protection. Specifically, an item may or may not be accessible depending on whether specific key material is available. For an in-depth discussion of how this works, see Apple Platform Security. Note This section focuses on iOS but you’ll see similar effects on all Apple platforms. On macOS specifically, the contents of this section only apply to the data protection keychain. The keychain supports three data protection levels: kSecAttrAccessibleWhenUnlocked kSecAttrAccessibleAfterFirstUnlock kSecAttrAccessibleAlways Note There are additional data protection levels, all with the ThisDeviceOnly suffix. Understanding those is not necessary to understanding this pitfall. Each data protection level describes the lifetime of the key material needed to work with items protected in that way. Specifically: The key material needed to work with a kSecAttrAccessibleWhenUnlocked item comes and goes as the user locks and unlocks their device. The key material needed to work with a kSecAttrAccessibleAfterFirstUnlock item becomes available when the device is first unlocked and remains available until the device restarts. The default data protection level is kSecAttrAccessibleWhenUnlocked. If you add an item to the keychain and don’t specify a data protection level, this is what you get [1]. To specify a data protection level when you add an item to the keychain, apply the kSecAttrAccessible attribute. Alternatively, embed the access level within a SecAccessControl object and apply that using the kSecAttrAccessControl attribute. IMPORTANT It’s best practice to set these attributes when you add the item and then never update them. See Add-only Attributes, above, for more on that. If you perform an operation whose data protection is incompatible with the currently available key material, that operation fails with errSecInteractionNotAllowed [2]. There are four fundamental keychain operations, discussed in the SecItem: Fundamentals, and each interacts with data protection in a different way: Copy — If you attempt to access a keychain item whose key material is unavailable, SecItemCopyMatching fails with errSecInteractionNotAllowed. This is an obvious result; the whole point of data protection is to enforce this security policy. Add — If you attempt to add a keychain item whose key material is unavailable, SecItemAdd fails with errSecInteractionNotAllowed. This is less obvious. The reason why this fails is that the system needs the key material to protect (by encryption) the keychain item, and it can’t do that if if that key material isn’t available. Update — If you attempt to update a keychain item whose key material is unavailable, SecItemUpdate fails with errSecInteractionNotAllowed. This result is an obvious consequence of the previous result. Delete — Deleting a keychain item, using SecItemDelete, doesn’t require its key material, and thus a delete will succeed when the item is otherwise unavailable. That last point is a significant pitfall. I regularly see keychain code like this: Read an item holding a critical user credential. If that works, use that credential. If it fails, delete the item and start from a ‘factory reset’ state. The problem is that, if your code ends up running in the background unexpectedly, step 1 fails with errSecInteractionNotAllowed and you turn around and delete the user’s credential. Ouch! Note Even if you didn’t write this code, you might have inherited it from a keychain wrapper library. See *Think Before Wrapping, below. There are two paths forward here: If you don’t expect this code to work in the background, check for the errSecInteractionNotAllowed error and non-destructively cancel the operation in that case. If you expect this code to be running in the background, switch to a different data protection level. WARNING For the second path, the most obvious fix is to move from kSecAttrAccessibleWhenUnlocked to kSecAttrAccessibleAfterFirstUnlock. However, this is not a panacea. It’s possible that your app might end up running before first unlock [3]. So, if you choose the second path, you must also make sure to follow the advice for the first path. You can determine whether the device is unlocked using the isProtectedDataAvailable property and its associated notifications. However, it’s best not to use this property as part of your core code, because such preflighting is fundamentally racy. Rather, perform the operation and handle the error gracefully. It might make sense to use isProtectedDataAvailable property as part of debugging, logging, and diagnostic code. [1] For file data protection there’s an entitlement (com.apple.developer.default-data-protection) that controls the default data protection level. There’s no such entitlement for the keychain. That’s actually a good thing! In my experience the file data protection entitlement is an ongoing source of grief. See this thread if you’re curious. [2] This might seem like an odd error but it’s actually pretty reasonable: The operation needs some key material that’s currently unavailable. Only a user action can provide that key material. But the data protection keychain will never prompt the user to unlock their device. Thus you get an error instead. [3] iOS generally avoids running third-party code before first unlock, but there are circumstances where that can happen. The obvious legitimate example of this is a VoIP app, where the user expects their phone to ring even if they haven’t unlocked it since the last restart. There are also other less legitimate examples of this, including historical bugs that caused apps to launch in the background before first unlock. Best Practices With the pitfalls out of the way, let’s talk about best practices. Less Painful Dictionaries I look at a lot of keychain code and it’s amazing how much of it is way more painful than it needs to be. The biggest offender here is the dictionaries. Here are two tips to minimise the pain. First, don’t use CFDictionary. It’s seriously ugly. While the SecItem API is defined in terms of CFDictionary, you don’t have to work with CFDictionary directly. Rather, use NSDictionary and take advantage of the toll-free bridge. For example, consider this CFDictionary code: CFTypeRef keys[4] = { kSecClass, kSecAttrService, kSecMatchLimit, kSecReturnAttributes, }; static const int kTen = 10; CFNumberRef ten = CFNumberCreate(NULL, kCFNumberIntType, &kTen); CFAutorelease(ten); CFTypeRef values[4] = { kSecClassGenericPassword, CFSTR("AYS"), ten, kCFBooleanTrue, }; CFDictionaryRef query = CFDictionaryCreate( NULL, keys, values, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks ); Note This might seem rather extreme but I’ve literally seen code like this, and worse, while helping developers. Contrast this to the equivalent NSDictionary code: NSDictionary * query = @{ (__bridge NSString *) kSecClass: (__bridge NSString *) kSecClassGenericPassword, (__bridge NSString *) kSecAttrService: @"AYS", (__bridge NSString *) kSecMatchLimit: @10, (__bridge NSString *) kSecReturnAttributes: @YES, }; Wow, that’s so much better. Second, if you’re working in Swift, take advantage of its awesome ability to create NSDictionary values from Swift dictionary literals. Here’s the equivalent code in Swift: let query = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecMatchLimit: 10, kSecReturnAttributes: true, ] as NSDictionary Nice! Avoid Reusing Dictionaries I regularly see folks reuse dictionaries for different SecItem calls. For example, they might have code like this: var copyResult: CFTypeRef? = nil let dict = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecAttrAccount: "mrgumby", kSecReturnData: true, ] as NSMutableDictionary var err = SecItemCopyMatching(dict, &copyResult) if err == errSecItemNotFound { dict[kSecValueData] = Data("opendoor".utf8) err = SecItemAdd(dict, nil) } This specific example will work, but it’s easy to spot the logic error. kSecReturnData is a return type property and it makes no sense to pass it to a SecItemAdd call whose second parameter is nil. I’m not sure why folks do this. I think it’s because they think that constructing dictionaries is expensive. Regardless, this pattern can lead to all sorts of weird problems. For example, it’s the leading cause of the issue described in the Queries and the Uniqueness Constraints section, above. My advice is that you use a new dictionary for each call. That prevents state from one call accidentally leaking into a subsequent call. For example, I’d rewrite the above as: var copyResult: CFTypeRef? = nil let query = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecAttrAccount: "mrgumby", kSecReturnData: true, ] as NSMutableDictionary var err = SecItemCopyMatching(query, &copyResult) if err == errSecItemNotFound { let add = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecAttrAccount: "mrgumby", kSecValueData: Data("opendoor".utf8), ] as NSMutableDictionary err = SecItemAdd(add, nil) } It’s a bit longer, but it’s much easier to track the flow. And if you want to eliminate the repetition, use a helper function: func makeDict() -> NSMutableDictionary { [ kSecClass: kSecClassGenericPassword, kSecAttrService: "AYS", kSecAttrAccount: "mrgumby", ] as NSMutableDictionary } var copyResult: CFTypeRef? = nil let query = makeDict() query[kSecReturnData] = true var err = SecItemCopyMatching(query, &copyResult) if err == errSecItemNotFound { let add = makeDict() query[kSecValueData] = Data("opendoor".utf8) err = SecItemAdd(add, nil) } Think Before Wrapping A lot of folks look at the SecItem API and immediately reach for a wrapper library. A keychain wrapper library might seem like a good idea but there are some serious downsides: It adds another dependency to your project. Different subsystems within your project may use different wrappers. The wrapper can obscure the underlying API. Indeed, its entire raison d’être is to obscure the underlying API. This is problematic if things go wrong. I regularly talk to folks with hard-to-debug keychain problems and the conversation goes something like this: Quinn: What attributes do you use in the query dictionary? J R Developer: What’s a query dictionary? Quinn: OK, so what error are you getting back? J R Developer: It throws WrapperKeychainFailedError. That’s not helpful )-: If you do use a wrapper, make sure it has diagnostic support that includes the values passed to and from the SecItem API. Also make sure that, when it fails, it returns an error that includes the underlying keychain error code. These benefits will be particularly useful if you encounter a keychain problem that only shows up in the field. Wrappers must choose whether to be general or specific. A general wrapper may be harder to understand than the equivalent SecItem calls, and it’ll certainly contain a lot of complex code. On the other hand, a specific wrapper may have a model of the keychain that doesn’t align with your requirements. I recommend that you think twice before using a keychain wrapper. Personally I find the SecItem API relatively easy to call, assuming that: I use the techniques shown in Less Painful Dictionaries, above, to avoid having to deal with CFDictionary. I use my secCall(…) helpers to simplify error handling. For the code, see Calling Security Framework from Swift. If you’re not prepared to take the SecItem API neat, consider writing your own wrapper, one that’s tightly focused on the requirements of your project. For example, in my VPN apps I use the wrapper from this post, which does exactly what I need in about 100 lines of code. Prefer to Update Of the four SecItem functions, SecItemUpdate is the most neglected. Rather than calling SecItemUpdate I regularly see folks delete and then re-add the item. This is a shame because SecItemUpdate has some important benefits: It preserves persistent references. If you delete and then re-add the item, you get a new item with a new persistent reference. It’s well aligned with the fundamental database nature of the keychain. It forces you to think about which attributes uniquely identify your item and which items can be updated without changing the item’s identity. Understand These Key Attributes Key items have a number of attributes that are similarly named, and it’s important to keep them straight. I created a cheat sheet for this, namely, SecItem attributes for keys. You wouldn’t believe how often I consult this! Investigating Complex Attributes Some attributes have values where the format is not obvious. For example, the kSecAttrIssuer attributed is documented as: The corresponding value is of type CFData and contains the X.500 issuer name of a certificate. What exactly does that mean? If I want to search the keychain for all certificates issued by a specific certificate authority, what value should I supply? One way to figure this out is to add a certificate to the keychain, read the attributes back, and then dump the kSecAttrIssuer value. For example: let cert: SecCertificate = … let attrs = try secCall { SecItemAdd([ kSecValueRef: cert, kSecReturnAttributes: true, ] as NSDictionary, $0) } as! [String: Any] let issuer = attrs[kSecAttrIssuer as String] as! NSData print((issuer as NSData).debugDescription) // prints: <3110300e 06035504 030c074d 6f757365 4341310b 30090603 55040613 024742> Those bytes represent the contents of a X.509 Name ASN.1 structure with DER encoding. This is without the outer SEQUENCE element, so if you dump it as ASN.1 you’ll get a nice dump of the first SET and then a warning about extra stuff at the end of the file: % xxd issuer.asn1 00000000: 3110 300e 0603 5504 030c 074d 6f75 7365 1.0...U....Mouse 00000010: 4341 310b 3009 0603 5504 0613 0247 42 CA1.0...U....GB % dumpasn1 -p issuer.asn1 SET { SEQUENCE { OBJECT IDENTIFIER commonName (2 5 4 3) UTF8String 'MouseCA' } } Warning: Further data follows ASN.1 data at position 18. Note For details on the Name structure, see section 4.1.2.4 of RFC 5280. Amusingly, if you run the same test against the file-based keychain you’ll… crash. OK, that’s not amusing. It turns out that the code above doesn’t work when targeting the file-based keychain because SecItemAdd doesn’t return a dictionary but rather an array of dictionaries (r. 21111543). Once you get past that, however, you’ll see it print: <301f3110 300e0603 5504030c 074d6f75 73654341 310b3009 06035504 06130247 42> Which is different! Dumping it as ASN.1 shows that it’s the full Name structure, including the outer SEQUENCE element: % xxd issuer-file-based.asn1 00000000: 301f 3110 300e 0603 5504 030c 074d 6f75 0.1.0...U....Mou 00000010: 7365 4341 310b 3009 0603 5504 0613 0247 seCA1.0...U....G 00000020: 42 B % dumpasn1 -p issuer-file-based.asn1 SEQUENCE { SET { SEQUENCE { OBJECT IDENTIFIER commonName (2 5 4 3) UTF8String 'MouseCA' } } SET { SEQUENCE { OBJECT IDENTIFIER countryName (2 5 4 6) PrintableString 'GB' } } } This difference in behaviour between the data protection and file-based keychains is a known bug (r. 26391756) but in this case it’s handy because the file-based keychain behaviour makes it easier to understand the data protection keychain behaviour. Import, Then Add It’s possible to import data directly into the keychain. For example, you might use this code to add a certificate: let certData: Data = … try secCall { SecItemAdd([ kSecClass: kSecClassCertificate, kSecValueData: certData, ] as NSDictionary, nil) } However, it’s better to import the data and then add the resulting credential reference. For example: let certData: Data = … let cert = try secCall { SecCertificateCreateWithData(nil, certData as NSData) } try secCall { SecItemAdd([ kSecValueRef: cert, ] as NSDictionary, nil) } There are two advantages to this: If you get an error, you know whether the problem was with the import step or the add step. It ensures that the resulting keychain item has the correct attributes. This is especially important for keys. These can be packaged in a wide range of formats, so it’s vital to know whether you’re interpreting the key data correctly. I see a lot of code that adds key data directly to the keychain. That’s understandable because, back in the day, this was the only way to import a key on iOS. Fortunately, that’s not been the case since the introduction of SecKeyCreateWithData in iOS 10 and aligned releases. For more information about importing keys, see Importing Cryptographic Keys. App Groups on the Mac Sharing access to keychain items among a collection of apps explains that three entitlements determine your keychain access: keychain-access-groups application-identifier (com.apple.application-identifier on macOS) com.apple.security.application-groups In the discussion of com.apple.security.application-groups it says: Starting in iOS 8, the array of strings given by this entitlement also extends the list of keychain access groups. That’s true, but it’s also potentially misleading. This affordance only works on iOS and its child platforms. It doesn’t work on macOS. That’s because app groups work very differently on macOS than they do on iOS. For all the details, see App Groups: macOS vs iOS: Working Towards Harmony. However, the take-home point is that, when you use the data protection keychain on macOS, your keychain access group list is built from keychain-access-groups and com.apple.application-identifier. Revision History 2025-06-29 Added the Data Protection and Background Execution section. Made other minor editorial changes. 2025-02-03 Added another specific example to the Careful With that Shim, Mac Developer section. 2025-01-29 Added somes specific examples to the Careful With that Shim, Mac Developer section. 2025-01-23 Added the Import, Then Add section. 2024-08-29 Added a discussion of identity formation to the Digital Identities Aren’t Real section. 2024-04-11 Added the App Groups on the Mac section. 2023-10-25 Added the Lost Keychain Items and Lost Keychain Items, Redux sections. 2023-09-22 Made minor editorial changes. 2023-09-12 Fixed various bugs in the revision history. Added the Erroneous Attributes section. 2023-02-22 Fixed the link to the VPNKeychain post. Corrected the name of the Context Matters section. Added the Investigating Complex Attributes section. 2023-01-28 First posted.
0
0
3.7k
Jun ’25
Regarding licensed applet
To apply for NFC & SE Platform entitlement, I need to provide information regarding licensed applets and TSM. However, I currently lack background knowledge in these areas. Could you provide me with an overview or examples of what licensed applets and TSM entail?
0
0
424
Dec ’24
Unable to give permission to app I'm building to access devices on local network on Sequoia - no prompt given
I am developing apps using NWJS framework, which access devices on the local network. I am doing this on Sequoia on Macos (Desktop). I have developed other apps using NWJS before, but on earlier versions of Macos. My issue is, I am unable to give my app permission to app to access devices on local network on one of the apps. Some background: Other apps which I have used which access devices on the local network, on first-time launching, have given a prompt asking me if I want to allow or deny access to local device for the app. However, on first-time launching (and many others after that), It simply says the device cannot be reached, and I never get a prompt asking me if I want to allow or deny access to local device for my app. In its barebones proof-of-concept stage of my app, I have an iframe who's src attribute is the IP address of a device known on the network with that address. I have tried the protocol https://192.168.1.99 and http://192.168.1.99 in the src attribute. This protocol works in another app I have built where upon first-time launch, I was able to get a prompt and give it the needed permission. If I check in System Settings > Privacy and Security > Network, the app doesn't appear where I can toggle a setting. I also am unable to explicitly add my app to the list. ** This worked for one app, but not another: In researching this issue, it was recommended that I add the following keys in info.plist: com.apple.developer.networking.multicast - boolean true NSLocalNetworkUsageDescription - string description NSNearbyInteractionUsageDescription - string description This worked for one of my apps, but not another, which has a nearly identical structure. In fact, other than CFBundleIdentifier, CFBundleDisplayName and CFBundleName, info.plist is identical. Why did this work one time, and how can I get my app to prompt for permission for local network access?
0
1
541
Dec ’24
SFAuthorizationPluginView and invalid password
I am using SFAuthorizationPluginView in my Security agent plugin. My code expects that its willActivate method be called. With normal screensaver unlock, this works fine. However if I enter an invalid password, then enter the correct password, I never get the willActivate call. I have reproduced this with Quinn's LoginUIAuthPlugin from the QAuthPlugins example code. My mechanisms look like this with LoginUIAuthPlugin: mechanisms HyprAuthPlugin:invoke builtin:authenticate,privileged PKINITMechanism:auth,privileged LoginUIAuthPlugin:login CryptoTokenKit:login I would like to be able to get my plugin working properly when the user had previously entered an invalid password.
0
0
382
Dec ’24
Inconsistent "Sign in with Apple" behaviour: Missing Claims in ID Token and App Icon/Name Issues
Context We are experiencing inconsistent behaviour with "Sign in with Apple" across different environments (we have an app for "A" and "B" regions) on our web client in browsers. Specifically, we have observed two key issues: Missing email and email_verified Claims in ID Token In some cases, the ID token received after successful authentication does not contain the email and email_verified claims. Here the docs state that "Alternatively, if the managed Apple ID is in Apple School Manager, the email claim may be empty. Students, for example, often don’t have an email that the school issues.", but this was experienced with a non-student Apple ID. This issue was observed for certain users in the "A" environment, while the same users had no issues in the "B" environment. For one affected user, removing and re-enabling the "Sign in with Apple" integration resolved the issue (https://account.apple.com/account/manage/section/security). However, for another user, the integration could not be removed, preventing this workaround (button was active, but did nothing). In contrast, for some users, authentication works correctly in both environments without missing claims. Inconsistent Display of App Icon and App Name The app icon and app name do not always appear on the Apple login interface. One user observed that the app icon and name were displayed in "A" but not in "B". Another user had the opposite experience, with the app icon and name appearing in "B" but not in "A". A third user did not see the app icon or name in either environment. Questions Why does the app icon and name not always appear on the "Sign in with Apple" login screen? How is it possible that the ID token sometimes lacks email and email_verified claims when using the same Apple ID in different environments?
0
1
445
Mar ’25