Security

RSS for tag

Secure the data your app manages and control access to your app using the Security framework.

Security Documentation

Pinned Posts

Posts under Security tag

264 Posts
Sort by:
Post not yet marked as solved
0 Replies
18 Views
I'm using this code to get the path of an executable from the audit token provided in NEFilterDataProvider.handleNewFlow(_:), forwarded from the Network Extension to the main app via IPC: private func securePathFromAuditToken(_ auditToken: Data) throws -> String { let secFlags = SecCSFlags() var secCode: SecCode? var status = SecCodeCopyGuestWithAttributes(nil, [kSecGuestAttributeAudit: auditToken] as CFDictionary, secFlags, &secCode) guard let secCode = secCode else { throw NSError(domain: NSOSStatusErrorDomain, code: Int(status)) } var secStaticCode: SecStaticCode? status = SecCodeCopyStaticCode(secCode, secFlags, &secStaticCode) guard let secStaticCode = secStaticCode else { throw NSError(domain: NSOSStatusErrorDomain, code: Int(status)) } var url: CFURL? status = SecCodeCopyPath(secStaticCode, secFlags, &url) guard let url = url as URL? else { throw NSError(domain: NSOSStatusErrorDomain, code: Int(status)) } return url.path } This code sometimes returns paths like /System/Library/PrivateFrameworks/HelpData.framework/Versions/A/Resources/helpd or /Library/Developer/CoreSimulator/Volumes/iOS_21A328/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 17.0.simruntime/Contents/Resources/RuntimeRoot/usr/libexec/mobileassetd. But sometimes the SecCodeCopyGuestWithAttributes fails with status 100001 which is defined in MacErrors.h as kPOSIXErrorEPERM = 100001, /* Operation not permitted */. In these cases I resort to this code, which I have read is not as secure: private func insecurePathFromAuditToken(_ auditToken: Data) throws -> String? { if auditToken.count == MemoryLayout<audit_token_t>.size { let pid = auditToken.withUnsafeBytes { buffer in audit_token_to_pid(buffer.baseAddress!.assumingMemoryBound(to: audit_token_t.self).pointee) } let pathbuf = UnsafeMutablePointer<Int8>.allocate(capacity: Int(PROC_PIDPATHINFO_SIZE)) defer { pathbuf.deallocate() } let ret = proc_pidpath(pid, pathbuf, UInt32(PROC_PIDPATHINFO_SIZE)) if ret <= 0 { throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) } return String(cString: pathbuf) } return nil } This insecure code then returns paths like /usr/libexec/trustd, /usr/libexec/rapportd, /usr/libexec/nsurlsessiond and /usr/libexec/timed. From what I can see, SecCodeCopyGuestWithAttributes fails for all processes in /usr/libexec. Some of these processes have executables with the same name placed in another directory, like /Library/Developer/CoreSimulator/Volumes/iOS_21A328/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 17.0.simruntime/Contents/Resources/RuntimeRoot/usr/libexec/mobileassetd for which it succeeds, while for /usr/libexec/mobileassetd it fails. Occasionally, both the secure and the insecure methods fail and in these cases the secure one returns status code 100003, which is defined as kPOSIXErrorESRCH = 100003, /* No such process */. When can this happen? This seems to happen with both NEFilterFlow.sourceAppAuditToken and sourceProcessAuditToken. What is the problem?
Posted
by Nickkk.
Last updated
.
Post not yet marked as solved
3 Replies
73 Views
I need to store some data of my application in system keychain which should to accessible to all the users in the system. Here is the below sample code : // Create a SecAccessControlRef for a keychain item with access control SecAccessControlRef accessControl = SecAccessControlCreateWithFlags( kCFAllocatorDefault, kSecAttrAccessibleWhenUnlocked, kSecAccessControlUserPresence, NULL ); // Define a query dictionary for a keychain item NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService: @"MyService", (__bridge id)kSecAttrAccount: @"MyAccount", (__bridge id)kSecValueData: [@"MyPassword" dataUsingEncoding:NSUTF8StringEncoding], (__bridge id)kSecAttrAccessControl: (__bridge_transfer id)accessControl, }; // Add the keychain item to the default keychain (login keychain) OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, NULL); if (status != errSecSuccess) { NSLog(@"Error adding keychain item: %d", (int)status); } I tried using SecKeychainOpen to access the system keychain but SecKeychainOpen is deprecated and I could not find any equivalent latest API to support that. SecKeychainRef systemKeychain; OSStatus status = SecKeychainOpen("/Library/Keychains/System.keychain", &systemKeychain); if (status != errSecSuccess) { NSLog(@"Error opening system keychain: %d", status); } else { SecAccessControlRef accessControl = SecAccessControlCreateWithFlags( kCFAllocatorDefault, kSecAttrAccessibleWhenUnlocked, kSecAccessControlUserPresence, NULL ); NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService: @"MyService", (__bridge id)kSecAttrAccount: @"MyAccount", (__bridge id)kSecValueData: [@"MyPassword" dataUsingEncoding:NSUTF8StringEncoding], (__bridge id)kSecUseKeychain: (__bridge id)systemKeychain, (__bridge id)kSecAttrAccessControl: (__bridge_transfer id)accessControl, }; // Add the keychain item to the system keychain status = SecItemAdd((__bridge CFDictionaryRef)query, NULL); if (status != errSecSuccess) { NSLog(@"Error adding keychain item to system keychain: %d", (int)status); } if (systemKeychain) { CFRelease(systemKeychain); } } ANY suggestions will be helpful, Please help!
Posted Last updated
.
Post not yet marked as solved
1 Replies
35 Views
I have created certificates to test development locally with HTTPS. You used to be able to drag-&amp;-drop a certificate on the simulator, but this does not appear to work anymore. You can drag one onto the simulator and get the + drop symbol, but attempting to go into "VPN &amp; device management" to trust it under General settings just shows a blank screen that bounces you out immediately. Now what?
Posted Last updated
.
Post not yet marked as solved
1 Replies
68 Views
So nowadays my MacOS software uses an installer (http://s.sudre.free.fr/Software/Packages/about.html Packages) and it is able to correctly install the application. My client needs to have a quiet installer because he needs to install the app in a large number of computers so using the interactive mode is not good. I am trying to create a script to do that but I am facing the Read-only file system error. Of course my script runs as root. I tried creating a simple Swift code to check, I got the same. So my question: why an installer such Packages can create the folder into the /System/Applications and copy the required files as it runs at the same level of the script and my test program and them don't have the same permission?
Posted Last updated
.
Post not yet marked as solved
3 Replies
92 Views
I have items written to the keychain by an XPC service. On disk, it's just a plain binary. There are times when this service shows a system authentication dialog via LAContext. By default, the application icon is a tiny version of a terminal. We found that if we wrap the binary in a bundle, we can include an icon and it will show up in the authentication dialog. The problem is that this new bundle seems to be different, as updates to an existing keychain item (created by the old, standalone binary) fail with errSecInvalidOwnerEdit. The bundle ID of the embedded binary has not changed. How does the system decide who is the owner of a keychain item and is there any way to do a migration like this without affecting item ownership?
Posted
by sjmadsen.
Last updated
.
Post not yet marked as solved
1 Replies
64 Views
Hi Apple team, Our app stores a private key in keychain services (kSecClassGenericPassword) via expo-secure-store. We need urgent help in recovering an item stored in the keychain AFTER an iCloud Restore has happened. The private key is specifically stored with the kSecAttrAccessible trait of kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. STEPS TO REPRODUCE (iPhone 14 Pro, iOS 16.7) Write an item to the keychain via expo-secure-store using the described attributes (on an app that's dispatched via Testflight) await SecureStore.setItemAsync("private_key", private_key, { requireAuthentication: true, authenticationPrompt: "Unlock your private key", keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY, }), Create an iCloud Backup for the device. Factory Reset the iPhone (Erase all content, apps, and settings) Restore the iOS backup from earlier and then attempt to retrieve the keychain item. Is there any way at all of retrieving this keychain item after an iCloud backup has been restored? Note. Our app has only been deployed via Testflight and there is no store listing. After restoring the device from an iCloud backup, our app icon has a small "download"/cloud icon beside it. When tapped, we get an error saying "Unable to install " because the app is not listed in the App Store. Is it possible that being on TestFlight is causing the keychain items to be wiped?
Posted
by Deagler.
Last updated
.
Post not yet marked as solved
1 Replies
92 Views
Since upgrading to Sonoma, I get two pop-ups saying that "NETserver" and "USBserver" would like to access data from other apps and asking for permission. How do I get rid of seeing these pop-ups after every reboot? I have no apps specifically called "NETserver" or "USBserver".
Posted
by geyien.
Last updated
.
Post not yet marked as solved
11 Replies
416 Views
On Sonoma beta 7, if system.login.screensaver is updated to use “authenticate-session-owner-or-admin”, and then Lock Screen is not hiding the macOS Desktop. Step1. Update system.login.screensaver authorizationdb rule to use “authenticate-session-owner-or-admin”( to get old SFAutorizationPluginView at Lock Screen ). Step 2. Once the rule is in place after logout and login, now click on Apple icon and select “Lock Screen”. Even after selecting Lock Screen, complete macOS Desktop is visible with no control for the user to unlock the screen. To gain access we have to restart the MAC.
Posted Last updated
.
Post marked as solved
2 Replies
1.3k Views
We've enabled ATS restrictions in our app, and everything works fine, except sometimes, randomly, the CDN download resource fails. In most cases, it happens to users who on iOS 14.* and WiFI (VPN helps solve the problem :thinking_face:) Logs: (ExampleClientErrorLogServlet) :: Client error: {"arguments":["test_resource","Caught Error Domain%3DNSURLErrorDomain Code%3D-1200 \"An SSL error has occurred and a secure connection to the server cannot be made.\" UserInfo%3D{NSErrorFailingURLStringKey%3Dhttps://my-url/reource.bin, NSLocalizedRecoverySuggestion%3DWould you like to connect to the server anyway?, _kCFStreamErrorDomainKey%3D3, _NSURLErrorFailingURLSessionTaskErrorKey%3DLocalDownloadTask &lt;A50DCF0E-38F3-4454-A78A-B4552336561E&gt;.&lt;1&gt;, _NSURLErrorRelatedURLSessionTaskErrorKey%3D(\n \"LocalDownloadTask &lt;A50DCF0E-38F3-4454-A78A-B4552336561E&gt;.&lt;1&gt;\"\n), NSLocalizedDescription%3DAn SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey%3Dhttps://my-url/reource.bin, NSUnderlyingError%3D0x2882e1050 {Error Domain%3DkCFErrorDomainCFNetwork Code%3D-1200 \"(null)\" UserInfo%3D{_kCFStreamPropertySSLClientCertificateState%3D0, _kCFNetworkCFStreamSSLErrorOriginalValue%3D-9816, _kCFStreamErrorDomainKey%3D3, _kCFStreamErrorCodeKey%3D-9816, _NSURLErrorNWPathKey%3Dsatisfied (Path is satisfied), viable, interface: en0, ipv4, dns}}, _kCFStreamErrorCodeKey%3D-9816}"],"format":"Downloading {} file failed: {}","platform":"ios","version":"2.87.1"} 26.07.2022 01:39:55 [DEBUG][9] :: platform: ios, version: 2.87.1. Downloading test_resource file failed: Caught Error Domain%3DNSURLErrorDomain Code%3D-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo%3D{NSErrorFailingURLStringKey%3Dhttps://my-url/reource.bin, NSLocalizedRecoverySuggestion%3DWould you like to connect to the server anyway?, _kCFStreamErrorDomainKey%3D3, _NSURLErrorFailingURLSessionTaskErrorKey%3DLocalDownloadTask &lt;A50DCF0E-38F3-4454-A78A-B4552336561E&gt;.&lt;1&gt;, _NSURLErrorRelatedURLSessionTaskErrorKey%3D( ), NSLocalizedDescription%3DAn SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey%3Dhttps://my-url/reource.bin, NSUnderlyingError%3D0x2882e1050 {Error Domain%3DkCFErrorDomainCFNetwork Code%3D-1200 "(null)" UserInfo%3D{_kCFStreamPropertySSLClientCertificateState%3D0, _kCFNetworkCFStreamSSLErrorOriginalValue%3D-9816, _kCFStreamErrorDomainKey%3D3, _kCFStreamErrorCodeKey%3D-9816, _NSURLErrorNWPathKey%3Dsatisfied (Path is satisfied), viable, interface: en0, ipv4, dns}}, _kCFStreamErrorCodeKey%3D-9816} _kCFNetworkCFStreamSSLErrorOriginalValue=-9816 _kCFStreamErrorDomainKey=3 _kCFStreamErrorCodeKey=-9816 We've tried nscurl --ats-diagnostics on the URL: Configuring ATS Info.plist keys and displaying the result of HTTPS loads to https:/url-path. A test will "PASS" if URLSession:task:didCompleteWithError: returns a nil error. ============================================================== Default ATS Secure Connection --- ATS Default Connection ATS Dictionary: {} Result : PASS --- ============================================================== Allowing Arbitrary Loads --- Allow All Loads ATS Dictionary: {     NSAllowsArbitraryLoads = true; } Result : PASS --- ================================================================================ Configuring TLS exceptions for url --- TLSv1.3 ATS Dictionary: {     NSExceptionDomains =     {         "url" =         {             NSExceptionMinimumTLSVersion = "TLSv1.3";         };     }; } Result : FAIL Error : Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSErrorFailingURLStringKey=url, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask &lt;250D7C7A-A090-41F1-8FED-E73FCB511F41&gt;.&lt;1&gt;, _NSURLErrorRelatedURLSessionTaskErrorKey=(     "LocalDataTask &lt;250D7C7A-A090-41F1-8FED-E73FCB511F41&gt;.&lt;1&gt;" ), NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=url, NSUnderlyingError=0x6000021318f0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, _kCFNetworkCFStreamSSLErrorOriginalValue=-9836, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9836, _NSURLErrorNWPathKey=satisfied (Path is satisfied), viable, interface: lo0}}, _kCFStreamErrorCodeKey=-9836} --- ====================================== nsurl --ats-diagnostic show me another error code -9836 and like I know TLSv1.3 not necessary yet Maybe someone can give some suggestions, any help !! :pray: Thx!
Posted
by 1semeDev.
Last updated
.
Post not yet marked as solved
1 Replies
109 Views
I am trying to establish a TLS 1.3 connection to a server that only accepts the SECP256R1 and FFDHE2048 TLS key share groups using the following code but the server is failing the TLS handshake because my client is not using a supported key exchange group. How do I specify which TLS key exchange group my client should use during the handshake? let tlsOptions = NWProtocolTLS.Options() if let secIdentity = getSecIdentity(), let identity = sec_identity_create(secIdentity) { sec_protocol_options_set_min_tls_protocol_version( tlsOptions.securityProtocolOptions, .TLSv13) sec_protocol_options_set_local_identity( tlsOptions.securityProtocolOptions, identity) } let tlsParams = NWParameters(tls: tlsOptions, tcp: .init()) let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(host), port: NWEndpoint.Port(port)) let nwConnection = NWConnection(to: endpoint, using: tlsParams) nwConnection.stateUpdateHandler = stateDidChange(to:) nwConnection.start(queue: queue) Thanks!
Posted
by mohamedGr.
Last updated
.
Post marked as Apple Recommended
3.8k Views
I am currently not able to change the ulimit on my machine. As of the newest MacOs releases (11.7.9, 12.6.8, and 13.5) I am no longer able to increase the ulimit of my computer using the strategies outlined here: https://wilsonmar.github.io/maximum-limits/ https://apple.stackexchange.com/questions/453050/how-to-increase-global-maxfiles-ulimit-on-osx-13-1-ventura?newreg=44fe471004094ccdb3ba51c1c3f9f84a Running sudo launchctl limit maxfiles 65536 200000 returns Could not set resource limits: 150: Operation not permitted while System Integrity Protection is engaged. This is relevant for me as I am using Vite which is currently broken and blocks me from developing locally. It is mentioned in their troubleshooting page (https://vitejs.dev/guide/troubleshooting.html#requests-are-stalled-forever) that Vite causes a large number of open files and how to increase the limit. There are similar comments in the Ruby Vite troubleshooting page (https://vite-ruby.netlify.app/guide/troubleshooting.html#requests-to-vite-sporadically-return-a-500-error-response). I have added a comment in the Vite discussion board about this issue. There is a discussion the Apple Stack Exchange that reports this problem but no one has provided a solution yet (https://apple.stackexchange.com/questions/462489/how-to-increase-global-max-opened-files-limit-on-osx-13-5-ventura)
Posted
by axeman12.
Last updated
.
Post not yet marked as solved
1 Replies
67 Views
Basically what it says in the title. Is it expected that /var/db/auth.db is recreated when updating to a newer macOS major version? This effectively removes the installed authorization plug-ins from the login flow, decreasing the intended security. And it's not a good end-user experience either to have to reinstall the plugin after updating - nothing such can be noticed with regular applications.
Posted Last updated
.
Post not yet marked as solved
1 Replies
118 Views
When I'm trying to create SecKey using SecKeyCreateWithData it returns nil in iOS 17 with following error: Optional(Swift.Unmanaged<__C.CFErrorRef>(_value: Error Domain=NSOSStatusErrorDomain Code=-50 "EC private key creation from data failed" UserInfo={numberOfErrorsDeep=0, NSDescription=EC private key creation from data failed})) guard let secKey = SecKeyCreateWithData(privateKey as CFData, attributes as CFDictionary, &error) else { print(error.debugDescription) throw EosioError(.keyManagementError, reason: error.debugDescription) } It still working fine on iOS 16.4
Posted Last updated
.
Post not yet marked as solved
13 Replies
1.2k Views
At present, we have been receiving numerous reports from customers who integrate our SDK who have been encountering the failures (errSecItemNotFound) while trying to retrieve a key using SecItemCopyMatching. We are raising this query we are still in the midst of properly reproducing this issue though it has been reported to occur in several devices during the OS upgrade to the iOS 17 betas. This issue is still occurring in the latest iOS 17 beta 7. This issue was not present in previous iOS version. At present, we are of the conclusion that this issue is occurring randomly amongst devices that upgraded to the iOS 17 betas and it is not limited to older devices. What we believe is occurring is that: A key is created and stored into Keychain using SecItemAdd. The same key is queried at a later timepoint but encounters the error errSecItemNotFound. Our SDK then attempts to regenerate a new key for the same label and attribute to store it using SecItemAdd, but the system then reports errSecDuplicateItem at the key already exists. The workaround here includes a manual deletion of the said key. This issue seems to occur only during an OS upgrade to the iOS17 betas with the likelihood that the key was already present in Keychain prior to the upgrade. I share below the snippet relating to how this said key is generated, stored and retrieved. // Initial key is added // A random data of 32 bytes length is generated CFDataRef dataRef = <32bytes of data>; *attr = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); BREAK_IF_NULL(*attr); CFDictionarySetValue(*attr, kSecClass, kSecClassGenericPassword); CFStringRef aKey = CFStringCreateWithCString(kCFAllocatorDefault, "KEY_ACCOUNT", kCFStringEncodingUTF8); CFDictionaryAddValue(*attr, kSecAttrAccount, aKey); CFDictionarySetValue(*attr, kSecReturnData, kCFBooleanFalse); CFDictionarySetValue(*attr, kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlock); label = CFStringCreateWithFormat(NULL, NULL, CFSTR("A_LABEL")); CFDictionarySetValue(attr, kSecAttrService, label); CFDictionarySetValue(attr, kSecValueData, dataRef); SecItemAdd(attr); // Query for retrieval of key label = CFStringCreateWithFormat(NULL, NULL, CFSTR("A_LABEL")); CFDictionarySetValue(attributes, kSecAttrService, label); CFDictionarySetValue(attributes, kSecReturnData, kCFBooleanTrue); CFDictionarySetValue(attributes, kSecMatchLimit, limit); CFDictionarySetValue(attributes, kSecReturnAttributes, returnAttributes); osStatus = SecItemCopyMatching(attributes, result); if (errSecItemNotFound == osStatus) { } Please do let me know if more information could be useful. At present, we have ensured that the key generated are well-within the size limits and is stored simply as a kSecClassGenericPassword with limited access control to the key. Additionally, the query used was intended to be generalised to avoid encountering such occurrences.
Posted Last updated
.
Post not yet marked as solved
0 Replies
83 Views
I am having trouble creating a CSR to renew a SecIdentity whose private SecKey is stored in slot 9d of a smartcard. For slot 9a, I am able to accomplish this by way of SecKeyCreateSignature using CertificateSigningRequest from a gently-modified fork of swift-certificates/swift-crypto to sort out all the details. But for the SecKey associated with slot 9d, the Security framework instantly returns an "algorithm not supported by the key" error when I call SecKeyCreateSignature, without even prompting for a PIN. I believe the difference is that kSecAttrCanSign is true for slot 9a but false for slot 9d. The value makes some sense for day-to-day usage because this identity is usually not used for signing, but if we are to occasionally sign a CSR for this key an exception would need to be made. Is there any way to basically force this exception with the Security framework? Again the actual private key material is not available so the only access as far as I'm aware is via the enumerated SecKey reference. Is there any way to SecKeyCreateWithData a secondary reference to the same underlying (but unexportable!) key but with allowed-usage attributes of my own choosing?
Posted
by natevw.
Last updated
.
Post not yet marked as solved
0 Replies
771 Views
If you’re on macOS and targeting the file-based keychain, kSecMatchLimitAll always defaults to kSecMatchLimitOne 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 who’s 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. 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. 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. 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. Revision History 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.
Posted
by eskimo.
Last updated
.
Post not yet marked as solved
2 Replies
105 Views
I would like to be able to store a secret in the keychain and make it accessible to a process running as root without any prompting of the user. So far I've only got this working if I opt for "Allow all applications to access this item." But if I do that, any process whether root or not has access to the item. On the other hand, if I choose "confirm before allowing access," the user is prompted for a root password. Unfortunately, "Always allow access by these applications" isn't an option because I need the secret in the context of an Authorization Plugin and so it's not a "normal" application that I can list. I'm wondering if maybe there's a way to set some sort of ACL from the command line. I've looked at the man page for security and not seen any obvious solution. My goal is to have my Authorization Plugin process "authenticate" itself to an XPC Service using a shared secret that both read from the keychain. If I can't get the keychain to work, I may do the same thing by using a shared secret in a file readable only by root. Any suggestions welcome. Thanks, Francis
Posted
by fxk510.
Last updated
.
Post not yet marked as solved
2 Replies
92 Views
I am putting together a demo app that will pick up keychain items shared via Keychain Access Groups (or Shared Items Groups, as described here). Sadly, I'm getting the infamous -34018 A required entitlement isn't present error when trying to load successfully saved items from main app A into my demo app B via SecItemCopyMatching. From everything I can tell -- after looking at Quinn's excellent Troubleshooting -34018 Keychain Errors post -- after dumping out the entitlements plist, I see the main app A and my new demo app B both have different ApplicationIdentifierPrefix'es compared to the team identifier. ApplicationIdentifierPrefix and TeamIdentifierPrefix are supposed be the same thing. I believe (from this ancient StackOverflow answer) it might be due to the fact I'm using my developer certificate (the one associated with my current team) along with Xcode's automatically managed signing may still be accidentally generating random application identifiers. Is it possible to do keychain item sharing with apps that were built/installed with developer provisioning profiles?
Posted Last updated
.
Post not yet marked as solved
1 Replies
83 Views
CDVAssetLibraryFilesystem.m, CDVDevice.m and srp.h and many pods Issue : Binary makes use of malloc function CWE: CWE-789: UncontrolledMemory AllocationOWASP Top 10: M7: Client CodeQualityOWASP MASVS: MSTG-CODE-8
Posted Last updated
.