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

All subtopics
Posts under Privacy & Security topic

Post

Replies

Boosts

Views

Activity

DeviceCheck - Device Validation Endpoint not working
We have been having very high response times in device check device validation service (https://developer.apple.com/documentation/devicecheck/accessing-and-modifying-per-device-data#Create-the-payload-for-a-device-validation-request) since 17 July at 19:10hs GMT. The service information page says the service was running in green status but that isn't the case and we currenly have stop consuming it. Is it being looked at? Are you aware of this issue? Can you give us an estimate of when it should be working correctly?
1
0
807
Jul ’25
Zero Trust - macOS Tahoe 26.0 (
Hi all, I've on high alert after hearing about the security concerns with npm. Full disclosure, I'm new to computer and network architecture, however, as someone who is on high alert for aplications exfiltrating data or poisioning my on-device machine learning models — I've seen some things I can't fully explain and I'm hoping the community can help. I ran the code odutil show all and I was wondering why certain node names are hidden in my system and when I use the directory utility, I can't use my computer login and password to authenticate to see the users? Am I being locked out of seeing my own system? I'm trying to dig to see if a root kit was installed on my device. Does anyone know what the users and groups in the directory utility are? Who is "nobody" and who is "Unknown user"? I'll probably have a lot more questions about this suspicious files I've seen on my device. Does anyone else's device download machine learning model payloads from the internet without notifying the user (even through a firewall, no startup applications?). I've also tried deleting applications I no longer need anymore and my "system" makes them re-appear.... what?
0
0
497
Sep ’25
Clarity App Attestation Errors
I'm currently reviewing the various DCError cases defined in Apple’s DeviceCheck framework (reference: https://developer.apple.com/documentation/devicecheck/dcerror-swift.struct). To better understand how to handle these in production, I’m looking for a clear breakdown of: Which specific DCError values can occur during service.generateKey, service.attestKey, and service.generateAssertion The realworld scenarios or conditions that typically cause each error for each method. If anyone has insight on how these errors arise and what conditions trigger them, I’d appreciate your input.
1
0
232
1w
How to undisplay `Private Access` in `Contacts Access` when i use `CNContactPickerViewController`?
In iOS 18, i use CNContactPickerViewController to access to Contacts (i know it is one-time access). After first pick up one contact, the Setting > Apps > my app > Contacts shows Private Access without any option to close it. Is there any way to close it and undisplay it ? I tried to uninstall and reinstall my app, but it didn't work.
3
0
376
Apr ’25
Sign in with Apple in a broken state (for my account)
I have a user (myself, during development) who originally signed in with Apple successfully. I attempted to revoke access via Settings > Apple ID > Sign-In & Security > Sign in with Apple, but the app appears stuck in the list and cannot be fully removed. Now when attempting to sign in again, the identity token contains the correct sub but email is undefined. According to Apple's documentation, "Apple provides the user's email address in the identity token on all subsequent API responses." I've tried programmatically revoking via the /auth/revoke endpoint (received 200 OK), and I've implemented the server-to-server notification endpoint to handle consent-revoked events, but subsequent sign-in attempts still return no email. The same Apple ID works fine with other apps. Is there a way to fully reset the credential state for a specific app, or is this a known issue with partially-revoked authorizations?
1
0
434
3w
Platform SSO: Biometric Prompt Behavior with userSecureEnclaveKey
I have a question regarding Platform SSO and the use of Secure Enclave–backed keys with biometric policies. If we configure userSecureEnclaveKeyBiometricPolicy with userSecureEnclaveKey, my understanding is that the Secure Enclave key is protected by biometric authentication (e.g., Face ID / Touch ID). In this setup, during a login request that also refreshes the id_token and refresh_token, the assertion is signed using the userSecureEnclaveKey. My question is: Will this signing operation trigger a biometric prompt every time the assertion is generated (i.e., during login/token refresh) ?
0
0
63
1w
[Resolved] Sign in with Apple Service Outage: Thursday, June 12, 2025
On Thursday, June 12, 2025, Sign in with Apple was impacted by an incorrect subdomain defined in its /.well-known/openid-configuration file. The JSON returned incorrectly provided https://account.apple.com instead of the expected https://appleid.apple.com. For Sign in with Apple, the value for the issuer (iss) claim in the user's identity token is https://appleid.apple.com. Additionally, if your clients use the Sign in with Apple REST API, the following endpoints should be used for each request: https://appleid.apple.com/auth/authorize https://appleid.apple.com/auth/token https://appleid.apple.com/auth/revoke https://appleid.apple.com/auth/keys This issue with the /.well-known/openid-configuration file was resolved the same day. Use the URL below to confirm the expected subdomain is provided, as needed: https://appleid.apple.com/.well-known/openid-configuration Cheers, Paris X Pinkney |  WWDR | DTS Engineer
0
0
282
Jun ’25
App Attest server unreachable – DNS or firewall issue suspected
Hello, We are working on integrating app integrity verification into our service application, following Apple's App Attest and DeviceCheck guide. Our server issues a challenge to the client, which then sends the challenge, attestation, and keyId in CBOR format to Apple's App Attest server for verification. However, we are unable to reach both https://attest.apple.com and https://attest.development.apple.com due to network issues. These attempts have been made from both our internal corporate network and mobile hotspot environments. Despite adjusting DNS settings and other configurations, the issue persists. Are there alternative methods or solutions to address this problem? Any recommended network configurations or guidelines to successfully connect to Apple's App Attest servers would be greatly appreciated. Thank you.
0
0
187
May ’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.9k
Jun ’25
App Sandbox Resources
General: Forums subtopic: Privacy & Security > General Forums tag: App Sandbox App Sandbox documentation App Sandbox Design Guide documentation — This is no longer available from Apple. There’s still some info in there that isn’t covered by the current docs but, with the latest updates, it’s pretty minimal (r. 110052019). Still, if you’re curious, you can consult an old copy [1]. App Sandbox Temporary Exception Entitlements archived documentation — To better understand the role of temporary exception entitlements, see this post. Embedding a command-line tool in a sandboxed app documentation Discovering and diagnosing App Sandbox violations (replaces the Viewing Sandbox Violation Reports forums post) Resolving App Sandbox Inheritance Problems forums post The Case for Sandboxing a Directly Distributed App forums post Implementing Script Attachment in a Sandboxed App forums post Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" [1] For example, this one archived by the Wayback Machine.
0
0
2.9k
Jul ’25
Apple Account Security and Passkeys
hello, I'm writing to seek clarification on Apple account security, particularly regarding potential risks of compromise, implemented safeguards, and residual risks with corresponding mitigation strategies. We would appreciate your insights on the following specific points: iCloud Keychain Access: Is an Apple ID login strictly required to access iCloud Keychain? We understand that a compromise of iCloud Keychain is unlikely unless a malicious actor successfully takes over the legitimate user's Apple ID. Is this understanding correct? Passkey Theft Methods and Protections: What are the conceivable methods a malicious actor might employ to steal a legitimate user's passkey, and how are these attempts protected against? Impact of Apple ID Compromise on Passkeys: If a malicious actor successfully compromises a legitimate user's Apple ID, is it accurate to assume that the legitimate user's passkeys would then synchronize to the attacker's device, potentially allowing them to log in using their own biometrics? Authorization Flow on Legitimate User's Device: Could you please detail the authorization flow that occurs on the legitimate user's device? We are particularly interested in the types of authentication involved and the conditions under which they are triggered. Detection and Additional Authentication for Unauthorized Login: How are attempts to log in to an Apple ID from an unrecognized device or browser detected, and what additional authentication steps are implemented in such scenarios? Thank you for your time and assistance in addressing these important security questions.
0
0
134
Feb ’26
Mac App Store app triggers "cannot verify free of malware" alert when opening as default app
My app Mocawave is a music player distributed through the Mac App Store. It declares specific audio document types (public.mp3, com.microsoft.waveform-audio, public.mpeg-4-audio, public.aac-audio) in its CFBundleDocumentTypes with a Viewer role. When a user sets Mocawave as the default app for audio files and double-clicks an MP3 downloaded from the internet (which has the com.apple.quarantine extended attribute), macOS displays the alert: "Apple could not verify [filename] is free of malware that may harm your Mac or compromise your privacy." This does not happen when: Opening the same file via NSOpenPanel from within the app Opening the same file with Apple's Music.app or QuickTime Player The app is: Distributed through the Mac App Store Sandboxed (com.apple.security.app-sandbox) Uses com.apple.security.files.user-selected.read-write entitlement The file being opened is a regular audio file (MP3), not an executable. Since the app is sandboxed and distributed through the App Store, I expected it to have sufficient trust to open quarantined data files without triggering Gatekeeper warnings — similar to how Music.app and QuickTime handle them. Questions: Is there a specific entitlement or Info.plist configuration that allows a sandboxed Mac App Store app to open quarantined audio files without this alert? Is this expected behavior for third-party App Store apps, or could this indicate a misconfiguration on my end? Environment: macOS 15 (Sequoia), app built with Swift/SwiftUI, targeting macOS 13+.
2
0
188
Feb ’26
Password AutoFill does not pick up saved password in developer mode
Without developer mode, I was able to get Password AutoFill to work in my SwiftUI app with my local Vapor server using ngrok and adding the Associated Domains capability with the value webcredentials:....ngrok-free.app and the respective apple-app-site-association file on my local server in /.well-known/. (works on device, but not in the simulator). However, if I use the developer mode (webcredentials:....ngrok-free.app?mode=developer) it only works halfway when running from Xcode: I get asked to save the password, but the saved passwords are not picked up, when I try to login again. Neither on device, nor in the simulator. If I remove the ?mode=developer it seems to work as expected. Is this by design, or am I missing something? var body: some View { ... Section(header: Text("Email")) { TextField("Email", text: $viewModel.credentials.username) .textContentType(.username) .autocapitalization(.none) .keyboardType(.emailAddress) } Section(header: Text("Passwort")) { SecureField("Passwort", text: $viewModel.credentials.password) .textContentType(.password) } ... }
0
0
241
May ’25
App ID Prefix Change and Keychain Access
DTS regularly receives questions about how to preserve keychain items across an App ID change, and so I thought I’d post a comprehensive answer here for the benefit of all. If you have any questions or comments, please start a new thread here on the forums. Put it in the Privacy & Security > General subtopic and tag it with Security. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" App ID Prefix Change and Keychain Access The list of keychain access groups your app can access is determined by three entitlements. For the details, see Sharing Access to Keychain Items Among a Collection of Apps. If your app changes its App ID prefix, this list changes and you’re likely to lose access to existing keychain items. This situation crops up under two circumstances: When you migrate your app from using a unique App ID prefix to using your Team ID as its App ID prefix. When you transfer your app to another team. In both cases you have to plan carefully for this change. If you only learn about the problem after you’ve made the change, consider undoing the change to give you time to come up with a plan before continuing. Note On macOS, the information in this post only applies to the data protection keychain. For more information about the subtleties of the keychain on macOS, see On Mac Keychains. For more about App ID prefix changes, see Technote 2311 Managing Multiple App ID Prefixes and QA1726 Resolving the Potential Loss of Keychain Access warning. Migrate From a Unique App ID Prefix to Your Team ID Historically each app was assigned its own App ID prefix. This is no longer the case. Best practice is for apps to use their Team ID as their App ID prefix. This enables multiple neat features, including keychain item sharing and pasteboard sharing. If you have an app that uses a unique App ID prefix, consider migrating it to use your Team ID. This is a good thing in general, as long as you manage the migration process carefully. Your app’s keychain access group list is built from three entitlements: keychain-access-groups — For more on this, see Keychain Access Groups Entitlement. application-identifier (com.apple.application-identifier on macOS) com.apple.security.application-groups — For more on this, see App Groups Entitlement. Keycahin access groups from the third bullet are call app group identified keychain access groups, or AGI keychain access groups for short. IMPORTANT A macOS app can only use an AGI keychain access group if all of its entitlement claims are validated by a provisioning profile. See App Groups: macOS vs iOS: Working Towards Harmony for more about this concept. Keychain access groups from the first two bullets depend on the App ID prefix. If that changes, you lose access to any keychain items in those groups. WARNING Think carefully before using the keychain to store secrets that are the only way to access irreplaceable user data. While the keychain is very reliable, there are situations where a keychain item can be lost and it’s bad if it takes the user’s data with it. In some cases losing access to keychain items is not a big deal. For example, if your app uses the keychain to manage a single login credential, losing that is likely to be acceptable. The user can recover by logging in again. In other cases losing access to keychain items is unacceptable. For example, your app might manage access to dozens of different servers, each with unique login credentials. Your users will be grumpy if you require them to log in to all those servers again. In such situations you must carefully plan your migration. The key thing to understand is that an app group is tied to your team, not your App ID prefix, and thus your app retains access to AGI keychain access groups across an App ID prefix change. This suggests the following approach: Release a version of your app that moves keychain items from other keychain access groups to an AGI keychain access group. Give your users time to update to this new version, run it, and so move their keychain items. When you’re confident that the bulk of your users have done this, change your App ID prefix. The approach has one obvious caveat: It’s hard to judge how long to wait at step 2. Transfer Your App to Another Team Historically there was no supported way to maintain access to keychain items across an app transfer. That’s no longer the case, but you must still plan the transfer carefully. The overall approach is: Identify an app group ID to transfer. This could be an existing app group ID, but in many cases you’ll want to register a new app group ID solely for this purpose. Use the old team (the transferor) to release a version of your app that moves keychain items from other keychain access groups to the AGI keychain access group for this app group ID. Give your users time to update to this new version, run it, and so move their keychain items. When you’re confident that the bulk of your users have done this, initiate the app transfer. Once that’s complete, transfer the app group ID you selected in step 1. See App Store Connect Help > Transfer an app > Overview of app transfer > Apps using App Groups. Publish an update to your app from the new team (the transferee). When a user installs this version, it will have access to your app group, and hence your keychain items. WARNING Once you transfer the app group, the old team won’t be able to publish a new version of any app that uses this app group. That makes step 1 in the process critical. If you have an existing app group that’s used solely by the app being transferred — for example, an app group that you use to share state between the app and its app extensions — then choosing that app group ID makes sense. On the other hand, choosing the ID of an app group that’s share between this app and some unrelated app, one that’s not being transferred, would be bad, because any updates to that other app will lose access to the app group. There are some other significant caveats: The process doesn’t work for Mac apps because Mac apps that have ever used an app group can’t be transferred. See App Store Connect Help > Transfer an app > App transfer criteria. If and when that changes, you’ll need to choose an iOS-style app group ID for your AGI keychain access group. For more about the difference between iOS- and macOS-style app group IDs, see App Groups: macOS vs iOS: Working Towards Harmony. The current transfer process of app groups exposes a small window where some other team can ‘steal’ your app group ID. We have a bug on file to improve that process (r. 171616887). The process works best when transferring between two teams that are both under the control of the same entity. If that’s not the case, take steps to ensure that the old team transfers the app group in step 5. When you submit the app from the new team (step 6), App Store Connect will warn you about a potential loss of keychain access. That warning is talking about keychain items in normal keychain access groups. Items in an AGI keychain access group will still be accessible as long as you transfer the app group. Alternative Approaches for App Transfer In addition to the technique described in the previous section, there are a some alternative approaches you should at consider: Do nothing Do not transfer your app Get creative Do Nothing In this case the user loses all the secrets that your app stored in the keychain. This may be acceptable for certain apps. For example, if your app uses the keychain to manage a single login credential, losing that is likely to be acceptable. The user can recover by logging in again. Do Not Transfer Another option is to not transfer your app. Instead, ship a new version of the app from the new team and have the old app recommend that the user upgrade. There are a number of advantages to this approach. The first is that there’s absolutely no risk of losing any user data. The two apps are completely independent. The second advantage is that the user can install both apps on their device at the same time. This opens up a variety of potential migration paths. For example, you might ship an update to the old app with an export feature that saves the user’s state, including their secrets, to a suitably encrypted file, and then match that with an import facility on the new app. Finally, this approach offers flexible timing. The user can complete their migration at their leisure. However, there are a bunch of clouds to go with these silver linings: Your users might never migrate to the new app. If this is a paid app, or an app with in-app purchase, the user will have to buy things again. You lose the original app’s history, ratings, reviews, and so on. Get Creative Finally, you could attempt something creative. For example, you might: Publish a new version of the app that supports exporting the user’s state, including the secrets. Tell your users to do this, with a deadline. Transfer the app and then, when the deadline expires, publish the new version with an import feature. Frankly, this isn’t very practical. The problem is with step 2: There’s no good way to get all your users to do the export, and if they don’t do it before the deadline there’s no way to do it after. Revision History 2026-03-31 Rewrote the Transfer Your App to Another Team section to describe a new approach for preserving access to keychain items across app transfers. Moved the previous discussion into a new Alternative Approaches for App Transfer section. Clarified that a macOS program can now use an app group as a keychain access group as long as its entitlements are validated. Made numerous editorial changes. 2022-05-17 First posted.
0
0
8.5k
1d
invalid_client
Would really appreciate support with this invalid_client issue: I have a web app and have aligned the JWT Header and Payload JWT Header { "alg": "ES256", "kid": "ABC123DEFG" } JWT Payload { "iss": "DEF123GHIJ", "iat": 1234567890, "exp": 1234567890, "aud": "https://appleid.apple.com", "sub": "com.yourapp.service" The domains and callback are aligned and correct I've even created a new p8 and updated the Key_ID Sending Credentials to Apple (Token Request) Content-Type: application/x-www-form-urlencoded However, still no luck. Can anyone assist with identifying the possible error? Many thanks
1
0
550
Sep ’25
Trusted Execution Resources
Trusted execution is a generic name for a Gatekeeper and other technologies that aim to protect users from malicious code. General: Forums topic: Code Signing Forums tag: Gatekeeper Developer > Signing Mac Software with Developer ID Apple Platform Security support document Safely open apps on your Mac support article Hardened Runtime document WWDC 2022 Session 10096 What’s new in privacy covers some important Gatekeeper changes in macOS 13 (starting at 04: 32), most notably app bundle protection WWDC 2023 Session 10053 What’s new in privacy covers an important change in macOS 14 (starting at 17:46), namely, app container protection WWDC 2024 Session 10123 What’s new in privacy covers an important change in macOS 15 (starting at 12:23), namely, app group container protection Updates to runtime protection in macOS Sequoia news post Testing a Notarised Product forums post Resolving Trusted Execution Problems forums post App Translocation Notes (aka Gatekeeper path randomisation) forums post Most trusted execution problems are caused by code signing or notarisation issues. See Code Signing Resources and Notarisation Resources. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
0
0
3.4k
Jan ’26
Backup Eligibility and Backup State has set to true for support hybrid transport with legacy authenticators
My application is supporting hybrid transport on FIDO2 webAuthn specs to create credential and assertion. And it support legacy passkeys which only mean to save to 1 device and not eligible to backup. However In my case, if i set the Backup Eligibility and Backup State flag to false, it fails on the completion of the registrationRequest to save the passkey credential within credential extension, the status is false instead of true. self.extension.completeRegistrationRequest(using: passkeyRegistrationCredential) The attestation and assertion flow only works when both flags set to true. Can advice why its must have to set both to true in this case?
1
0
199
Jan ’26
"Unknown" error on Sign in with Apple only for US users
Hey folks, I'm seeing an issue where my iOS app is getting an "unknown" error when US users try to sign in with Apple. It works fine for users in other countries like the UK, Singapore, and Taiwan. Could it be related to my developer account not being based in the US? Or have I missed something in my settings? Thanks in advance!
Replies
0
Boosts
0
Views
178
Activity
Aug ’25
DeviceCheck - Device Validation Endpoint not working
We have been having very high response times in device check device validation service (https://developer.apple.com/documentation/devicecheck/accessing-and-modifying-per-device-data#Create-the-payload-for-a-device-validation-request) since 17 July at 19:10hs GMT. The service information page says the service was running in green status but that isn't the case and we currenly have stop consuming it. Is it being looked at? Are you aware of this issue? Can you give us an estimate of when it should be working correctly?
Replies
1
Boosts
0
Views
807
Activity
Jul ’25
Zero Trust - macOS Tahoe 26.0 (
Hi all, I've on high alert after hearing about the security concerns with npm. Full disclosure, I'm new to computer and network architecture, however, as someone who is on high alert for aplications exfiltrating data or poisioning my on-device machine learning models — I've seen some things I can't fully explain and I'm hoping the community can help. I ran the code odutil show all and I was wondering why certain node names are hidden in my system and when I use the directory utility, I can't use my computer login and password to authenticate to see the users? Am I being locked out of seeing my own system? I'm trying to dig to see if a root kit was installed on my device. Does anyone know what the users and groups in the directory utility are? Who is "nobody" and who is "Unknown user"? I'll probably have a lot more questions about this suspicious files I've seen on my device. Does anyone else's device download machine learning model payloads from the internet without notifying the user (even through a firewall, no startup applications?). I've also tried deleting applications I no longer need anymore and my "system" makes them re-appear.... what?
Replies
0
Boosts
0
Views
497
Activity
Sep ’25
Clarity App Attestation Errors
I'm currently reviewing the various DCError cases defined in Apple’s DeviceCheck framework (reference: https://developer.apple.com/documentation/devicecheck/dcerror-swift.struct). To better understand how to handle these in production, I’m looking for a clear breakdown of: Which specific DCError values can occur during service.generateKey, service.attestKey, and service.generateAssertion The realworld scenarios or conditions that typically cause each error for each method. If anyone has insight on how these errors arise and what conditions trigger them, I’d appreciate your input.
Replies
1
Boosts
0
Views
232
Activity
1w
How to undisplay `Private Access` in `Contacts Access` when i use `CNContactPickerViewController`?
In iOS 18, i use CNContactPickerViewController to access to Contacts (i know it is one-time access). After first pick up one contact, the Setting > Apps > my app > Contacts shows Private Access without any option to close it. Is there any way to close it and undisplay it ? I tried to uninstall and reinstall my app, but it didn't work.
Replies
3
Boosts
0
Views
376
Activity
Apr ’25
Sign in with Apple in a broken state (for my account)
I have a user (myself, during development) who originally signed in with Apple successfully. I attempted to revoke access via Settings > Apple ID > Sign-In & Security > Sign in with Apple, but the app appears stuck in the list and cannot be fully removed. Now when attempting to sign in again, the identity token contains the correct sub but email is undefined. According to Apple's documentation, "Apple provides the user's email address in the identity token on all subsequent API responses." I've tried programmatically revoking via the /auth/revoke endpoint (received 200 OK), and I've implemented the server-to-server notification endpoint to handle consent-revoked events, but subsequent sign-in attempts still return no email. The same Apple ID works fine with other apps. Is there a way to fully reset the credential state for a specific app, or is this a known issue with partially-revoked authorizations?
Replies
1
Boosts
0
Views
434
Activity
3w
How to verify Apple signed firmware, hardware, and OS authenticity in an SDK?
I am working on a SDK which helps identify the device authenticity. I am in need of something which can confirm the firmware/Hardware/OS is signed by Apple and is authentic. There will be no tempering to device?
Replies
1
Boosts
0
Views
139
Activity
May ’25
Platform SSO: Biometric Prompt Behavior with userSecureEnclaveKey
I have a question regarding Platform SSO and the use of Secure Enclave–backed keys with biometric policies. If we configure userSecureEnclaveKeyBiometricPolicy with userSecureEnclaveKey, my understanding is that the Secure Enclave key is protected by biometric authentication (e.g., Face ID / Touch ID). In this setup, during a login request that also refreshes the id_token and refresh_token, the assertion is signed using the userSecureEnclaveKey. My question is: Will this signing operation trigger a biometric prompt every time the assertion is generated (i.e., during login/token refresh) ?
Replies
0
Boosts
0
Views
63
Activity
1w
[Resolved] Sign in with Apple Service Outage: Thursday, June 12, 2025
On Thursday, June 12, 2025, Sign in with Apple was impacted by an incorrect subdomain defined in its /.well-known/openid-configuration file. The JSON returned incorrectly provided https://account.apple.com instead of the expected https://appleid.apple.com. For Sign in with Apple, the value for the issuer (iss) claim in the user's identity token is https://appleid.apple.com. Additionally, if your clients use the Sign in with Apple REST API, the following endpoints should be used for each request: https://appleid.apple.com/auth/authorize https://appleid.apple.com/auth/token https://appleid.apple.com/auth/revoke https://appleid.apple.com/auth/keys This issue with the /.well-known/openid-configuration file was resolved the same day. Use the URL below to confirm the expected subdomain is provided, as needed: https://appleid.apple.com/.well-known/openid-configuration Cheers, Paris X Pinkney |  WWDR | DTS Engineer
Replies
0
Boosts
0
Views
282
Activity
Jun ’25
App Attest server unreachable – DNS or firewall issue suspected
Hello, We are working on integrating app integrity verification into our service application, following Apple's App Attest and DeviceCheck guide. Our server issues a challenge to the client, which then sends the challenge, attestation, and keyId in CBOR format to Apple's App Attest server for verification. However, we are unable to reach both https://attest.apple.com and https://attest.development.apple.com due to network issues. These attempts have been made from both our internal corporate network and mobile hotspot environments. Despite adjusting DNS settings and other configurations, the issue persists. Are there alternative methods or solutions to address this problem? Any recommended network configurations or guidelines to successfully connect to Apple's App Attest servers would be greatly appreciated. Thank you.
Replies
0
Boosts
0
Views
187
Activity
May ’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.
Replies
0
Boosts
0
Views
3.9k
Activity
Jun ’25
App Sandbox Resources
General: Forums subtopic: Privacy & Security > General Forums tag: App Sandbox App Sandbox documentation App Sandbox Design Guide documentation — This is no longer available from Apple. There’s still some info in there that isn’t covered by the current docs but, with the latest updates, it’s pretty minimal (r. 110052019). Still, if you’re curious, you can consult an old copy [1]. App Sandbox Temporary Exception Entitlements archived documentation — To better understand the role of temporary exception entitlements, see this post. Embedding a command-line tool in a sandboxed app documentation Discovering and diagnosing App Sandbox violations (replaces the Viewing Sandbox Violation Reports forums post) Resolving App Sandbox Inheritance Problems forums post The Case for Sandboxing a Directly Distributed App forums post Implementing Script Attachment in a Sandboxed App forums post Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" [1] For example, this one archived by the Wayback Machine.
Replies
0
Boosts
0
Views
2.9k
Activity
Jul ’25
Apple Account Security and Passkeys
hello, I'm writing to seek clarification on Apple account security, particularly regarding potential risks of compromise, implemented safeguards, and residual risks with corresponding mitigation strategies. We would appreciate your insights on the following specific points: iCloud Keychain Access: Is an Apple ID login strictly required to access iCloud Keychain? We understand that a compromise of iCloud Keychain is unlikely unless a malicious actor successfully takes over the legitimate user's Apple ID. Is this understanding correct? Passkey Theft Methods and Protections: What are the conceivable methods a malicious actor might employ to steal a legitimate user's passkey, and how are these attempts protected against? Impact of Apple ID Compromise on Passkeys: If a malicious actor successfully compromises a legitimate user's Apple ID, is it accurate to assume that the legitimate user's passkeys would then synchronize to the attacker's device, potentially allowing them to log in using their own biometrics? Authorization Flow on Legitimate User's Device: Could you please detail the authorization flow that occurs on the legitimate user's device? We are particularly interested in the types of authentication involved and the conditions under which they are triggered. Detection and Additional Authentication for Unauthorized Login: How are attempts to log in to an Apple ID from an unrecognized device or browser detected, and what additional authentication steps are implemented in such scenarios? Thank you for your time and assistance in addressing these important security questions.
Replies
0
Boosts
0
Views
134
Activity
Feb ’26
Mac App Store app triggers "cannot verify free of malware" alert when opening as default app
My app Mocawave is a music player distributed through the Mac App Store. It declares specific audio document types (public.mp3, com.microsoft.waveform-audio, public.mpeg-4-audio, public.aac-audio) in its CFBundleDocumentTypes with a Viewer role. When a user sets Mocawave as the default app for audio files and double-clicks an MP3 downloaded from the internet (which has the com.apple.quarantine extended attribute), macOS displays the alert: "Apple could not verify [filename] is free of malware that may harm your Mac or compromise your privacy." This does not happen when: Opening the same file via NSOpenPanel from within the app Opening the same file with Apple's Music.app or QuickTime Player The app is: Distributed through the Mac App Store Sandboxed (com.apple.security.app-sandbox) Uses com.apple.security.files.user-selected.read-write entitlement The file being opened is a regular audio file (MP3), not an executable. Since the app is sandboxed and distributed through the App Store, I expected it to have sufficient trust to open quarantined data files without triggering Gatekeeper warnings — similar to how Music.app and QuickTime handle them. Questions: Is there a specific entitlement or Info.plist configuration that allows a sandboxed Mac App Store app to open quarantined audio files without this alert? Is this expected behavior for third-party App Store apps, or could this indicate a misconfiguration on my end? Environment: macOS 15 (Sequoia), app built with Swift/SwiftUI, targeting macOS 13+.
Replies
2
Boosts
0
Views
188
Activity
Feb ’26
Password AutoFill does not pick up saved password in developer mode
Without developer mode, I was able to get Password AutoFill to work in my SwiftUI app with my local Vapor server using ngrok and adding the Associated Domains capability with the value webcredentials:....ngrok-free.app and the respective apple-app-site-association file on my local server in /.well-known/. (works on device, but not in the simulator). However, if I use the developer mode (webcredentials:....ngrok-free.app?mode=developer) it only works halfway when running from Xcode: I get asked to save the password, but the saved passwords are not picked up, when I try to login again. Neither on device, nor in the simulator. If I remove the ?mode=developer it seems to work as expected. Is this by design, or am I missing something? var body: some View { ... Section(header: Text("Email")) { TextField("Email", text: $viewModel.credentials.username) .textContentType(.username) .autocapitalization(.none) .keyboardType(.emailAddress) } Section(header: Text("Passwort")) { SecureField("Passwort", text: $viewModel.credentials.password) .textContentType(.password) } ... }
Replies
0
Boosts
0
Views
241
Activity
May ’25
App ID Prefix Change and Keychain Access
DTS regularly receives questions about how to preserve keychain items across an App ID change, and so I thought I’d post a comprehensive answer here for the benefit of all. If you have any questions or comments, please start a new thread here on the forums. Put it in the Privacy & Security > General subtopic and tag it with Security. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" App ID Prefix Change and Keychain Access The list of keychain access groups your app can access is determined by three entitlements. For the details, see Sharing Access to Keychain Items Among a Collection of Apps. If your app changes its App ID prefix, this list changes and you’re likely to lose access to existing keychain items. This situation crops up under two circumstances: When you migrate your app from using a unique App ID prefix to using your Team ID as its App ID prefix. When you transfer your app to another team. In both cases you have to plan carefully for this change. If you only learn about the problem after you’ve made the change, consider undoing the change to give you time to come up with a plan before continuing. Note On macOS, the information in this post only applies to the data protection keychain. For more information about the subtleties of the keychain on macOS, see On Mac Keychains. For more about App ID prefix changes, see Technote 2311 Managing Multiple App ID Prefixes and QA1726 Resolving the Potential Loss of Keychain Access warning. Migrate From a Unique App ID Prefix to Your Team ID Historically each app was assigned its own App ID prefix. This is no longer the case. Best practice is for apps to use their Team ID as their App ID prefix. This enables multiple neat features, including keychain item sharing and pasteboard sharing. If you have an app that uses a unique App ID prefix, consider migrating it to use your Team ID. This is a good thing in general, as long as you manage the migration process carefully. Your app’s keychain access group list is built from three entitlements: keychain-access-groups — For more on this, see Keychain Access Groups Entitlement. application-identifier (com.apple.application-identifier on macOS) com.apple.security.application-groups — For more on this, see App Groups Entitlement. Keycahin access groups from the third bullet are call app group identified keychain access groups, or AGI keychain access groups for short. IMPORTANT A macOS app can only use an AGI keychain access group if all of its entitlement claims are validated by a provisioning profile. See App Groups: macOS vs iOS: Working Towards Harmony for more about this concept. Keychain access groups from the first two bullets depend on the App ID prefix. If that changes, you lose access to any keychain items in those groups. WARNING Think carefully before using the keychain to store secrets that are the only way to access irreplaceable user data. While the keychain is very reliable, there are situations where a keychain item can be lost and it’s bad if it takes the user’s data with it. In some cases losing access to keychain items is not a big deal. For example, if your app uses the keychain to manage a single login credential, losing that is likely to be acceptable. The user can recover by logging in again. In other cases losing access to keychain items is unacceptable. For example, your app might manage access to dozens of different servers, each with unique login credentials. Your users will be grumpy if you require them to log in to all those servers again. In such situations you must carefully plan your migration. The key thing to understand is that an app group is tied to your team, not your App ID prefix, and thus your app retains access to AGI keychain access groups across an App ID prefix change. This suggests the following approach: Release a version of your app that moves keychain items from other keychain access groups to an AGI keychain access group. Give your users time to update to this new version, run it, and so move their keychain items. When you’re confident that the bulk of your users have done this, change your App ID prefix. The approach has one obvious caveat: It’s hard to judge how long to wait at step 2. Transfer Your App to Another Team Historically there was no supported way to maintain access to keychain items across an app transfer. That’s no longer the case, but you must still plan the transfer carefully. The overall approach is: Identify an app group ID to transfer. This could be an existing app group ID, but in many cases you’ll want to register a new app group ID solely for this purpose. Use the old team (the transferor) to release a version of your app that moves keychain items from other keychain access groups to the AGI keychain access group for this app group ID. Give your users time to update to this new version, run it, and so move their keychain items. When you’re confident that the bulk of your users have done this, initiate the app transfer. Once that’s complete, transfer the app group ID you selected in step 1. See App Store Connect Help > Transfer an app > Overview of app transfer > Apps using App Groups. Publish an update to your app from the new team (the transferee). When a user installs this version, it will have access to your app group, and hence your keychain items. WARNING Once you transfer the app group, the old team won’t be able to publish a new version of any app that uses this app group. That makes step 1 in the process critical. If you have an existing app group that’s used solely by the app being transferred — for example, an app group that you use to share state between the app and its app extensions — then choosing that app group ID makes sense. On the other hand, choosing the ID of an app group that’s share between this app and some unrelated app, one that’s not being transferred, would be bad, because any updates to that other app will lose access to the app group. There are some other significant caveats: The process doesn’t work for Mac apps because Mac apps that have ever used an app group can’t be transferred. See App Store Connect Help > Transfer an app > App transfer criteria. If and when that changes, you’ll need to choose an iOS-style app group ID for your AGI keychain access group. For more about the difference between iOS- and macOS-style app group IDs, see App Groups: macOS vs iOS: Working Towards Harmony. The current transfer process of app groups exposes a small window where some other team can ‘steal’ your app group ID. We have a bug on file to improve that process (r. 171616887). The process works best when transferring between two teams that are both under the control of the same entity. If that’s not the case, take steps to ensure that the old team transfers the app group in step 5. When you submit the app from the new team (step 6), App Store Connect will warn you about a potential loss of keychain access. That warning is talking about keychain items in normal keychain access groups. Items in an AGI keychain access group will still be accessible as long as you transfer the app group. Alternative Approaches for App Transfer In addition to the technique described in the previous section, there are a some alternative approaches you should at consider: Do nothing Do not transfer your app Get creative Do Nothing In this case the user loses all the secrets that your app stored in the keychain. This may be acceptable for certain apps. For example, if your app uses the keychain to manage a single login credential, losing that is likely to be acceptable. The user can recover by logging in again. Do Not Transfer Another option is to not transfer your app. Instead, ship a new version of the app from the new team and have the old app recommend that the user upgrade. There are a number of advantages to this approach. The first is that there’s absolutely no risk of losing any user data. The two apps are completely independent. The second advantage is that the user can install both apps on their device at the same time. This opens up a variety of potential migration paths. For example, you might ship an update to the old app with an export feature that saves the user’s state, including their secrets, to a suitably encrypted file, and then match that with an import facility on the new app. Finally, this approach offers flexible timing. The user can complete their migration at their leisure. However, there are a bunch of clouds to go with these silver linings: Your users might never migrate to the new app. If this is a paid app, or an app with in-app purchase, the user will have to buy things again. You lose the original app’s history, ratings, reviews, and so on. Get Creative Finally, you could attempt something creative. For example, you might: Publish a new version of the app that supports exporting the user’s state, including the secrets. Tell your users to do this, with a deadline. Transfer the app and then, when the deadline expires, publish the new version with an import feature. Frankly, this isn’t very practical. The problem is with step 2: There’s no good way to get all your users to do the export, and if they don’t do it before the deadline there’s no way to do it after. Revision History 2026-03-31 Rewrote the Transfer Your App to Another Team section to describe a new approach for preserving access to keychain items across app transfers. Moved the previous discussion into a new Alternative Approaches for App Transfer section. Clarified that a macOS program can now use an app group as a keychain access group as long as its entitlements are validated. Made numerous editorial changes. 2022-05-17 First posted.
Replies
0
Boosts
0
Views
8.5k
Activity
1d
invalid_client
Would really appreciate support with this invalid_client issue: I have a web app and have aligned the JWT Header and Payload JWT Header { "alg": "ES256", "kid": "ABC123DEFG" } JWT Payload { "iss": "DEF123GHIJ", "iat": 1234567890, "exp": 1234567890, "aud": "https://appleid.apple.com", "sub": "com.yourapp.service" The domains and callback are aligned and correct I've even created a new p8 and updated the Key_ID Sending Credentials to Apple (Token Request) Content-Type: application/x-www-form-urlencoded However, still no luck. Can anyone assist with identifying the possible error? Many thanks
Replies
1
Boosts
0
Views
550
Activity
Sep ’25
Trusted Execution Resources
Trusted execution is a generic name for a Gatekeeper and other technologies that aim to protect users from malicious code. General: Forums topic: Code Signing Forums tag: Gatekeeper Developer > Signing Mac Software with Developer ID Apple Platform Security support document Safely open apps on your Mac support article Hardened Runtime document WWDC 2022 Session 10096 What’s new in privacy covers some important Gatekeeper changes in macOS 13 (starting at 04: 32), most notably app bundle protection WWDC 2023 Session 10053 What’s new in privacy covers an important change in macOS 14 (starting at 17:46), namely, app container protection WWDC 2024 Session 10123 What’s new in privacy covers an important change in macOS 15 (starting at 12:23), namely, app group container protection Updates to runtime protection in macOS Sequoia news post Testing a Notarised Product forums post Resolving Trusted Execution Problems forums post App Translocation Notes (aka Gatekeeper path randomisation) forums post Most trusted execution problems are caused by code signing or notarisation issues. See Code Signing Resources and Notarisation Resources. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
Replies
0
Boosts
0
Views
3.4k
Activity
Jan ’26
What classifies a number in imessages as a known number? In iOS 26 what makes a number filtered out of the main inbox?
With the new ios 26 update, certain numbers will be filtered into other inboxes within imessage. What numbers are classified as "known", and will not be moved into these filters. Do they need to be a contact in your phone, or if a business texts you how will that be filtered?
Replies
0
Boosts
0
Views
640
Activity
Jul ’25
Backup Eligibility and Backup State has set to true for support hybrid transport with legacy authenticators
My application is supporting hybrid transport on FIDO2 webAuthn specs to create credential and assertion. And it support legacy passkeys which only mean to save to 1 device and not eligible to backup. However In my case, if i set the Backup Eligibility and Backup State flag to false, it fails on the completion of the registrationRequest to save the passkey credential within credential extension, the status is false instead of true. self.extension.completeRegistrationRequest(using: passkeyRegistrationCredential) The attestation and assertion flow only works when both flags set to true. Can advice why its must have to set both to true in this case?
Replies
1
Boosts
0
Views
199
Activity
Jan ’26