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

Post

Replies

Boosts

Views

Activity

Investigating hard-to-reproduce keychain problems
Keychain is a bit of a ‘call driver’ for DTS. I’ve sent instructions like this to many developers over the years. Today I decided to write it down for everyone’s benefit. If you have questions or comments, put them in a new thread here on DevForums. Tag it with Security so that I see it. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" Investigating hard-to-reproduce keychain problems SecItem is the primary API for the keychain on all Apple platforms. If you’re getting started with that API, I have two DevForums posts that you’ll find useful: SecItem: Fundamentals SecItem: Pitfalls and Best Practices Also, if you’re on macOS, make sure to read TN3137 On Mac keychain APIs and implementations. Every now and again folks encounter a hard-to-reproduce keychain problem. For example, you might have code that works most of the time but fails for some users in the field. This failure might be seen regularly by some users, or it might happen sporadically across all your users. Problems like this are often caused by a bug in the system itself, but the SecItem API is sufficiently tricky that I’ve seen cases of this where the actual problem was a bug in the developer’s code. This post outlines a process for investigating such problems. I’ve helped a number of developers use it to good effect, and I figured I should share it for the enjoyment of all (hey hey!). This process depends on the system log. If you’re not best friends with the system log, you should be! See Your Friend the System Log. This process is a special case of the process I describe in Using a Sysdiagnose Log to Debug a Hard-to-Reproduce Problem. Read that before continuing. Unwrap the wrapper Many developers use a wrapper around the SecItem API. Such wrappers make it hard to investigate problems like this. For example, you might call a wrapper routine that returns a password string or nil if there’s a failure. That means you can’t distinguish between an expected nil, where the password hasn’t been saved yet, or a weird error. If you need to debug a hard-to-reproduce keychain problem, look through the wrapper to find the calls to Apple’s SecItem APIs. It’s fine to leave your wrapper in place, but do this debugging at the SecItem level. Add before and after log points The basic strategy here is: Add a log point before the call to the SecItem API, including the parameters that you’ll pass in. Add a log point after the call to the SecItem API, including the returned error code and any response you got. This is trickier than it might seem due to the way that the SecItem API is structured. Consider this example: func copyAccountPassword(_ userName: String) -> String? { var copyResult: CFTypeRef? = nil let err = SecItemCopyMatching([ kSecClass: kSecClassGenericPassword, kSecAttrService: "WaffleVarnish", kSecAttrAccount: userName, kSecReturnData: true, ] as NSDictionary, &copyResult) guard err == errSecSuccess, let result = String(data: copyResult! as! Data, encoding: .utf8) else { return nil } return result } There are a bunch of issues here: The query dictionary is created inline, so it’s not easy to log it. The query dictionary contains the kSecAttrAccount property, which is likely to hold private data. The query can fail, leaving copyResult set to nil. The query can work but return invalid data. In this case the function will return nil but there’s no error. The password is obviously private data. WARNING Be careful when logging keychain data. The whole point of the keychain is to protect the user’s secrets. It would be bad to then go and log those secrets to the system log. Here’s an updated version of that routine: func copyAccountPasswordWithLogging(_ userName: String) -> String? { let query = [ kSecClass: kSecClassGenericPassword, kSecAttrService: "WaffleVarnish", kSecAttrAccount: userName, kSecReturnData: true, ] as NSDictionary var copyResult: CFTypeRef? = nil log.log("will copy account password, query: \(query)") let err = SecItemCopyMatching(query, &copyResult) guard err == errSecSuccess else { log.log("did not copy account password, err: \(err)") return nil } guard let result = String(data: copyResult! as! Data, encoding: .utf8) else { log.log("did not copy account password, malformed") return nil } log.log("did copy account password") return result } This example assumes that log is a value of type Logger. Redact private data This new code isn’t perfect: The query value is considered private data and not recorded in the log. It logs nothing about the resulting password. Addressing the second problem is a challenge. You could do something creative like log a salted hash of the password but, honestly, I think it’s best to err on the side of caution here. You might try to fix the first problem with code like this: !!! DO NOT DO THIS !!! log.log("will copy account password, query: \(query, privacy: .public)") !!! DO NOT DO THIS !!! However, that’s problematic because it logs the account value (kSecAttrAccount) when it should be redacted. What you do about this depends on the scope of your deployment: If you’re targeting an internal test harness, you might choose to leave it as is. The machines on the test harness don’t have any truly private data. If this code is going to be used by actual humans, you must further redact your logging. For example, you might write a helper like this: func redactedQuery(_ query: NSDictionary) -> String { let query = query.mutableCopy() as! NSMutableDictionary if query.object(forKey: kSecAttrAccount as NSString) != nil { query.setObject("REDACTED", forKey: kSecAttrAccount as NSString) } return "\(query)" } You’ll have to customise this code for your specific use case. For example, your code might put information that’s not private into kSecAttrAccount — in many of my projects, I use a fixed string for this — and so redacting that might be pointless. OTOH, your code might put private information into other properties. If you call a SecItem API that returns a dictionary, you’ll need a similar redactedResponse(_:) routine for that response. Test your logging The next step is to test your logging. Make sure that the stuff you want logged is logged and the stuff you don’t want logged is not. Test outside of Xcode, because Xcode automatically captures private data. Look in the log When you get a sysdiagnose log back from a user having this problem, unpack and open the system log snapshot. Find all the log entry pairs created by your before and after log points. Then look for the most recent one that illustrates the problem you’re investigating. To learn more about the cause of this problem, look for other log entries of interest between those two points. When looking at keychain-related log entries, keep in mind that most of the Security framework is open source. If you’re curious what a log entry means, search the source to see the context. Note Darwin open source won’t always exactly match the source in the corresponding OS version. Also, the Security framework is only part of the story here, and some other subsystems that are relevant to the keychain aren’t open source. However, my experience is that looking at the source is super useful. If you find that log entries are missing, remember that the system log purges older log entries to make room for newer ones. That’s why it’s important to take your sysdiagnose as soon as possible after encountering the problem. If you escalate this problem to Apple — via a bug report, a DTS tech support incident, or whatever — make sure to include: Your sysdiagnose log The exact timestamps of your before and after log entries We may not need this level of information, but in many cases it really helps.
0
0
580
Mar ’24
Is there a reason why the colors are displayed differently in the mail related to Privacy Manifest?
This is the mail I received from Apple while testing. If you look at it, it shows that there are two items that are problematic. As you can see, "ITMS-91053: Missing API decimation" shows the same problem in both, but the colors are displayed differently. (Purple, Gray) There were four problems in the mail I received while testing more, but all of them received the same color. Is this just a mail error?? Or does the color have a meaning?
1
0
1.1k
Mar ’24
Apple Sing In on VisionOS
Hello. On my game I have Apple Sing In option that is required to keep user high score, show name in game, and for other app functionality. Game is made on Unity. Now when I'm trying to port the game to Vision Pro, Im getting error that Authentication is not supported on this platform. It may be the plugin issue that I'm using(not sure yet). But I also didn't find any documentation for native code ( so I could make plugin that unity game could use). Question: does VisionOS support Apple Sing In in the applications? if yes please give me some resources. if no - is there any plans to add that functionality? Thanks.
1
0
554
Mar ’24
How do I check if a version of an sdk I am using in my app uses a privacy impacting sdk?
I am assuming that even if the app i am using is not listed in the ios list of privacy impacting sdks, if they use a privacy impacting sdk in their sdk, then my app will be required to get the privacy manifest for that privacy impacting sdk: the rule must (logically!) be transitive. So far apple has not sent any email about the app needing to provide that for any of our sdks. but i am worried that maybe apple has not done the check for us yet, and by the time they do , we will be near deadline to submit an app.
1
0
723
Mar ’24
Create a SecKey from .p8 file
Hi, I am trying to create a secKey from a .p8 file which I will then use to sign some data. I'm targeting an iOS application. I understand I cannot use the p8 key directly with iOS APIs and will have to unwrap the p8 file before I can feed it to SecKeyCreateWithData I'm unsure how to go about doing it though and any help will be appreciated. For context it is the APNS p8 file.
1
0
482
Mar ’24
"Document storage" in privacy settings
Does anybody know what the "Document Storage" entry in the Privacy settings for an app means? I recently discovered that the Privacy Settings of my own app nowadays has a "Document Storage" entry, with (for me) the possible choices: "iCloud Drive", "On My Phone", and "Dropbox". I don't know with which version of iOS these appeared. When "iCloud Drive" is selected (the default), then the explanatory text below it says "Automatically upload and store your documents in iCloud Drive" My app has no explicit support for iCloud Drive or iCloud in general, and no support for Dropbox. Some of its files are stored in the Documents folder of the app, which is publicly accessible (through the Files app, e.g.) My users assume that enabling the option will automatically copy those files to iCloud Drive, but that does not seem to be happening. I have searched half a day for any documentation around this from Apple, but found nothing. So: does anybody know what that setting does? And: if it does not do anything, then how can I can make sure it does NOT appear, to not confuse my users?
0
0
483
Mar ’24
Do App extensions require privacy manifests?
Near the bottom, Describing data use in privacy manifests, says: App extensions don’t include privacy information files. The operating system and App Store Connect use the privacy information file in the extension’s host app bundle, in combination with those from third-party SDKs your app links to. Yet the warnings email we see lists the app's extensions as missing manifests. Are we reading the documentation incorrectly? Getting this clarified helps us justify approvals for the additional work.
2
2
1.4k
Mar ’24
Lock Screen on Token Removal Does Not Work on MacOS14
Issue: The screen saver is not shown, and the user is not locked after removing a smart card with a logged in user. I have tried setting tokenRemovalAction to 1, along with various other com.apple.security.smartcard defaults, and I have also tried setting "turn on screen saver when login token removed." None of this makes the screen locked on card removal. Is this an issue with MacOS14 or is there a different setting/value that has to be set for this to work correctly?
2
0
646
Mar ’24
Using AppleId appleIdToken in two different sub-systems
Hello Apple ID support, When a user successfully login with Apple, the apple OAuth will produce a appleIdToken. From my understanding this token is best to not leave the user device. I have two sub-system that can take a appleIdToken and manages the token-refresh separately. In short: Apple -> appleIdToken sub-SystemA(appleIdToken) and sub-systemB(appleIdToken) sub-SystemA and sub-systemB has two separate token management/refresh The question: Is this allowed by the Apple identify server? Is the usecase of supplying appleIdToken to sub-SystemA and sub-systemB valid?
2
0
484
Mar ’24
Adding privacy manifest blocks BLE communication at background when there are multiple reader
Dear Developer Community, I recently implemented privacy manifest changes in accordance with Apple guidelines. However, have encountered unexpected issues with BLE communication while our app was running in the background when there are multiple reader. During local testing in both debug and release modes within Xcode, have not experienced any problems with BLE communication, even with multiple readers. However, upon uploading the build to TestFlight for testing, i found that communication was being blocked when multiple readers are there. This behavior was quite perplexing. Upon further investigation, I decided to revert the privacy manifest changes and retested via TestFlight. Surprisingly, we did not encounter any issues with BLE communication. I am reaching out to this forum to inquire whether anyone else has encountered similar issues with BLE communication. Additionally, I have submitted a report via Feedback Assistant to seek assistance from Apple. I am particularly interested in understanding if any core logic related to BLE is affected by the privacy manifest changes. As Apple has mandated the inclusion of the privacy manifest for App Store submissions starting from Spring 2024, any insights or assistance on this matter would be greatly appreciated.
2
0
529
Mar ’24
Converting Secure Enclave protected SecKey to SecureEnclave.P256.Signing.PrivateKey
Is it possible? The original key was generated and stored in the Keychain using the following code: func generateSecureEnclaveProtectedSecKey(withTag tag: Data) throws -> SecKey { var error: Unmanaged<CFError>? let accessControl = SecAccessControlCreateWithFlags( kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, [.privateKeyUsage], &error )! let attributes = [ kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeySizeInBits as String: 256, kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave, kSecPrivateKeyAttrs as String: [ kSecAttrCanSign as String: true, kSecAttrIsPermanent as String: true, kSecAttrApplicationTag as String: tag, kSecAttrAccessControl as String: accessControl, ] as [String: Any], ] as [String: Any] let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error)! return privateKey } Then I wanted to use the strongly typed interface of CryptoKit, so I naively tried to get a hold of the existing key as follows (querying for kSecReturnPersistentRef and not kSecReturnRef): func getSecureEnclaveProtectedCryptoKitKey(fromSecureEnclaveProtectedSecKeyWithTag tag: Data) throws -> SecureEnclave.P256.Signing.PrivateKey { let query: [String: Any] = [ kSecClass as String: kSecClassKey, kSecAttrApplicationTag as String: tag, kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecReturnPersistentRef as String: true, ] var item: CFTypeRef? let status = SecItemCopyMatching(query as CFDictionary, &item) let keyData = item as! CFData return try SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: keyData as Data) } But that resulted in: Error Domain=CryptoTokenKit Code=-3 "corrupted objectID detected" UserInfo={NSLocalizedDescription=corrupted objectID detected} Since this is a Secure Enclave protected key, it is not possible to use SecKeyCopyExternalRepresentation (or query for kSecReturnData), but perhaps there is another way to convert a SecKey object to a SecureEnclave.P256.Signing.PrivateKey? The other way around seem to be possible using the answers to this blog post: https://developer.apple.com/forums/thread/728314
2
0
817
Mar ’24
Live Activity Stops Updating after iPhone Lock
My background audio app stops updating its Live Activity after the iPhone locks, and doesn't resume updating the activity after tapping the screen or even after FaceID unlocks the device (without opening the lock screen). My live activity requests a ContentState update & iOS updates the content for the activity as below: Task{ log.debug("LiveActivityManager.updateLiveActivity() with new ContentState") await liveActivity.update( ActivityContent(state:contentState, staleDate:nil) ) } Below what my log looks like: <<<<SWIPE LOCK SCREEN DOWN>>>> DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState iOS: Updating content for activity 0A519263-1E46-4BB6-BA4F-F3DDBC081AB4 DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState iOS: Updating content for activity 0A519263-1E46-4BB6-BA4F-F3DDBC081AB4 <<<<PRESS LOCK BUTTON->Lock iPhone>>>> INFO: --------protectedDataWillBecomeUnavailableNotification-------- DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState iOS: Updating content for activity 0A519263-1E46-4BB6-BA4F-F3DDBC081AB4 DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState <<<<LOOK AT & TAP LOCK SCREEN->Unlock iPhone without swiping up>>>> INFO: --------protectedDataDidBecomeAvailableNotification----------- DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState DEBUG: LiveActivityManager.updateLiveActivity() with new ContentState As shown in the log, normally iOS updates the content for my activity after my liveActivity.update request. This works fine in the Dynamic Island and when after switching apps and swiping down to see the lock screen without locking the phone. However, once I lock the phone, iOS stops updating the Live Activity content, and doesn't resume updates until after the app regains the foreground at least once. Has anyone else encountered this behavior? Is this a setting that I'm missing, or a bug?
11
1
2.3k
Mar ’24
Secure Enclave, key generation failure
I am new to iOS development, and recently I was trying to build an application, which will create a key inside the secure element, and after - I will sing something with it. While developing I've encountered an issue: the key generation fails if there is a flag .biometryAny or .biometryCurrentSet The authentication itself is triggered, but the function still throws a mistake. My setup - Xcode iPhone15 simulator, FaceID enrolled and the animation of it is working. Ive created the same post on overflow, in case somebody will have the same issues: https://stackoverflow.com/questions/78175858/secure-enclave-key-generation-failure I've tried deleting the flag, while keeping the manual authorisation, and this approach works, but I still would like have maximum security. THIS WORKS: func authenticateUser(completion: @escaping (Bool, Error?) -> Void) { let context = LAContext() var error: NSError? if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { let reason = "Biometric authentication is needed to access your secure data." context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in DispatchQueue.main.async { completion(success, authenticationError) } } } else { // Biometry is not available or not enrolled. DispatchQueue.main.async { completion(false, error) } } } @objc func encryptAction() { authenticateUser { [weak self] (success, error) in guard success else { self?.outputLabel.text = "Authentication failed: \(error?.localizedDescription ?? "Unknown error")" return } guard let randomNumber = self?.inputTextField.text, !randomNumber.isEmpty, let dataToSign = randomNumber.data(using: .utf8), let privateKey = self?.generatePrivateKey() else { self?.outputLabel.text = "Error: Could not generate private key." return } if let signature = self?.signData(privateKey: privateKey, data: dataToSign) { self?.outputLabel.text = "Signature: \(signature.base64EncodedString())" } else { self?.outputLabel.text = "Error: Could not sign data." } } } func generatePrivateKey() -> SecKey? { // 1. Create Keys Access Control guard let accessControl = SecAccessControlCreateWithFlags( nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, [.privateKeyUsage], nil) else { fatalError("cannot set access control") } // 2. Create Key Attributes guard let tag = "com.example.keys.mykey".data(using: .utf8) else { fatalError("cannot set tag") } let attributes: [String: Any] = [ kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeySizeInBits as String: 256, kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave, kSecPrivateKeyAttrs as String: [ kSecAttrIsPermanent as String: true, kSecAttrApplicationTag as String: tag, kSecAttrAccessControl as String: accessControl ] ] // 3. Generate Key Pairs var error: Unmanaged<CFError>? guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { if let error = error?.takeRetainedValue() { print("Error creating a key: \(error)") } return nil } return privateKey } func signData(privateKey: SecKey, data: Data) -> Data? { let digest = sha256(data: data) var error: Unmanaged<CFError>? guard let signature = SecKeyCreateSignature(privateKey, .ecdsaSignatureMessageX962SHA256, digest as CFData, &error) as Data? else { print(error!.takeRetainedValue() as Error) return nil } return signature } } THIS DOESN'T guard let accessControl = SecAccessControlCreateWithFlags( nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, [.privateKeyUsage, .biometryCurrentSet], nil) else { info.something file is updated and there is a privacy FaceID field included. the error is triggered at this part: var error: Unmanaged<CFError>? guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { if let error = error?.takeRetainedValue() { print("Error creating a key: \(error)") } return nil } The error itself: Error creating a key: Error Domain=NSOSStatusErrorDomain Code=-25293 "Key generation failed, error -25293" UserInfo={numberOfErrorsDeep=0, NSDescription=Key generation failed, error -25293}
3
0
1.2k
Mar ’24