SecItemCopyMatching - Finding specific kSecClassIdentity by name - macos

I'm trying to find a specific entry in my Mac OS X keychain, based on it's name (kSecAttrLabel), but it looks like SecItemCopyMatching is broken and applies no filtering whatsoever when looking for items of type: kSecClassIdentity.
This piece of code will return all identities found in all keychains, despite the kSecAttrLabel: #"MyIdentity" parameter:
NSDictionary *query = #{ (__bridge id)kSecClass: (__bridge NSString*)kSecClassIdentity,
(__bridge id)kSecAttrLabel: #"MyIdentity",
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll,
(__bridge id)kSecReturnAttributes: #YES,
(__bridge id)kSecReturnRef: #YES };
OSStatus status;
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&privateKey);
Granted, I can then find the one identity I'm looking for by filtering manually the returned array, however, beside the fact that, IMHO, it should just work, I also would like to remove this identity from my keychain using SecItemDelete(), which takes a query as parameter, just like SecItemCopyMatching.
If filtering doesn't work for SecItemCopyMatching, then it is likely that it won't work for SecItemDelete and this mean I will simply erase the content of my keychain if I try to call SecItemDelete with this query.
What am I doing wrong?

I think I have just found a solution. It was suggested on on another forum that
Using labels on an identity is tricky because identities are not stored in the keychain as an atomic item but are store as a separate private key and certificate, and those items use labels in different ways.
This made me realise that a solution is to search for a certificate by label using SecItemCopyMatching, and then create the identity using SecIdentityCreateWithCertificate. The latter call should find the matching private key in the Keychain. Here is the full code (in C++) that seems to work for me on macOS Mojave:
SecIdentityRef identity = nullptr;
const char* certificateName = ...;
const void* keys[] = {
kSecClass,
kSecMatchLimit,
kSecReturnRef,
kSecAttrLabel
};
const void* values[] = {
kSecClassCertificate,
kSecMatchLimitOne,
kCFBooleanTrue,
CFStringCreateWithCString(nullptr, certificateName, kCFStringEncodingUTF8)
};
CFDictionaryRef query = CFDictionaryCreate(nullptr, keys, values, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFTypeRef result;
OSStatus status = SecItemCopyMatching(query, &result);
CFRelease(query);
CFRelease(values[3]);
if (status) {
// error
}
else {
SecCertificateRef certificate = (SecCertificateRef)result;
status = SecIdentityCreateWithCertificate(nullptr, certificate, &identity);
CFRelease(certificate);
if (status) {
// error
}
}

I had a similar problem, but I was using kSecAttrApplicationTag rather than kSecAttrLabel. I am not an objective c nor an IOS security expert by any means. It turns out the method I was using to create the lookup tag was incorrect. Here is what worked for me:
- (void) getOrCreateKey: (NSNumber*)bits publicIdentifier:(NSString*)publicID
// no! This appears in several samples, but did not work for me on iOS
// NSData * publicLookupTag = [NSData dataWithBytes:publicId length:strlen((const char *)publicId)];
//
// yes!
NSData * publicLookupTag = [NSData dataWithBytes:[publicId UTF8String] length:publicId.length];
NSMutableDictionary *queryPublicKey = [[NSMutableDictionary alloc] init];
[queryPublicKey setObject:(id)kSecClassKey forKey:(id)kSecClass];
[queryPublicKey setObject:publicLookupTag forKey:(id)kSecAttrApplicationTag];
[queryPublicKey setObject:(id)kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnRef];
OSStatus lookupStatus = noErr;
lookupStatus = SecItemCopyMatching((CFDictionaryRef)queryPublicKey, (CFTypeRef *)&publicLookupKey);

Related

How can I get the a certificate's validity period using Apple's Security framework?

I am parsing certificates inside macOS app's code signature and I want to get the expiration date (aka "validity period"). According to Apple's documentation the certificate contains a validity period but there is no function mentioned for retrieving it.
I am currently manually digging into the certificate and grabbing the value manually using SecCertificateCopyValues() from the dictionary but this does not seem to be the proper approach.
How can I get a SecCertificateRef's validity period (NSDate) in CoreFoundation or Foundation (Objective-C) using Apple's Security framework (not OpenSSL)?
Thank you.
For those who are interested in my approach I'll leave my snippet here. Cheers!
#import <Foundation/Foundation.h>
id getX509ValueforKey(SecCertificateRef certificate, CFStringRef kSecPropertyKey) {
id value;
CFDictionaryRef valuesDict = SecCertificateCopyValues(certificate, (__bridge CFArrayRef)#[(__bridge id)kSecPropertyKey], NULL);
if (valuesDict) {
CFDictionaryRef invalidityDateDictionaryRef = CFDictionaryGetValue(valuesDict, kSecPropertyKey);
if (invalidityDateDictionaryRef) {
CFTypeRef invalidityRef = CFDictionaryGetValue(invalidityDateDictionaryRef, kSecPropertyKeyValue);
if (invalidityRef)
value = CFBridgingRelease(invalidityRef);
}
CFRelease(valuesDict);
}
return value;
}
int main(int argc, const char * argv[]) {
SecCertificateRef certificateRef = NULL;
NSDate *certExpiryDate = getX509ValueforKey(certificateRef, kSecOIDInvalidityDate);
NSLog(#"certExpiryDate: %#", certExpiryDate);
return noErr;
}

Unmounting Drive/volume without ejecting

I am want to unmount a disk WITHOUT EJECTING. To do that I tried following code
{
NSString *path;
CFStringRef *volumeName=(__bridge CFStringRef)path;
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
CFURLRef pathRef = CFURLCreateWithString(NULL, CFSTR("/volumes/Untitled"), NULL);
DADiskRef disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, pathRef);
DADiskUnmount(disk, kDADiskUnmountOptionForce, NULL, NULL);
}
This code is from this question, Thanks to #zeFree
Its working but I want dynamic path to the volume where as in the code its static. I tried changing NSString to CFStringRef and then tried to use at place of path("/volumes/Untitled") mention but its still same.
Any suggestion is welcome.
First of all, you are strongly discouraged from using kDADiskUnmountOptionForce.
This is a method to unmount a volume at given URL with basic error handling and memory management.
- (BOOL)unmountVolumeAtURL:(NSURL *)url
BOOL returnValue = NO;
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
if (session) {
DADiskRef disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, (__bridge CFURLRef)url);
if (disk) {
DADiskUnmount(disk, kDADiskUnmountOptionDefault, NULL , NULL);
returnValue = YES;
CFRelease(disk);
} else {
NSLog(#"Could't create disk reference from %#", url.path);
}
} else {
NSLog(#"Could't create DiskArbritation session");
}
if (session) CFRelease(session);
return returnValue;
}
The error handling could be still improved by providing a callback handler in the DADiskUnmount function.

Getting the position of my application's dock icon using Cocoa's Accessibility API

How can I get the position of my application's dock icon using the Accessibility API?
Found it! Using this forum post as reference, I was able to shape the given sample code to what I needed:
- (NSArray *)subelementsFromElement:(AXUIElementRef)element forAttribute:(NSString *)attribute
{
NSArray *subElements = nil;
CFIndex count = 0;
AXError result;
result = AXUIElementGetAttributeValueCount(element, (CFStringRef)attribute, &count);
if (result != kAXErrorSuccess) return nil;
result = AXUIElementCopyAttributeValues(element, (CFStringRef)attribute, 0, count, (CFArrayRef *)&subElements);
if (result != kAXErrorSuccess) return nil;
return [subElements autorelease];
}
- (AXUIElementRef)appDockIconByName:(NSString *)appName
{
AXUIElementRef appElement = NULL;
appElement = AXUIElementCreateApplication([[[NSRunningApplication runningApplicationsWithBundleIdentifier:#"com.apple.dock"] lastObject] processIdentifier]);
if (appElement != NULL)
{
AXUIElementRef firstChild = (__bridge AXUIElementRef)[[self subelementsFromElement:appElement forAttribute:#"AXChildren"] objectAtIndex:0];
NSArray *children = [self subelementsFromElement:firstChild forAttribute:#"AXChildren"];
NSEnumerator *e = [children objectEnumerator];
AXUIElementRef axElement;
while (axElement = (__bridge AXUIElementRef)[e nextObject])
{
CFTypeRef value;
id titleValue;
AXError result = AXUIElementCopyAttributeValue(axElement, kAXTitleAttribute, &value);
if (result == kAXErrorSuccess)
{
if (AXValueGetType(value) != kAXValueIllegalType)
titleValue = [NSValue valueWithPointer:value];
else
titleValue = (__bridge id)value; // assume toll-free bridging
if ([titleValue isEqual:appName]) {
return axElement;
}
}
}
}
return nil;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
AXUIElementRef dockIcon = [self appDockIconByName:#"MYAPPNAME"];
if (dockIcon) {
CFTypeRef value;
CGPoint iconPosition;
AXError result = AXUIElementCopyAttributeValue(dockIcon, kAXPositionAttribute, &value);
if (result == kAXErrorSuccess)
{
if (AXValueGetValue(value, kAXValueCGPointType, &iconPosition)) {
NSLog(#"position: (%f, %f)", iconPosition.x, iconPosition.y);
}
}
}
}
As for Mac OS El Capitan, looks like you aren't supposed to get the position of the icon using Accessibility API. The matter is that the icon isn't located in accessibility objects hierarchy of the app—it can be found in the hierarchy of the system Dock application. A sandboxed app isn't supposed to access the accessibility objects of other apps.
The code in approved answer doesn't yield any warnings of the sandboxd daemon in the console, looks like it doesn't violate any rules. It creates the top-level accessibility object with the function AXUIElementCreateApplication. The documentation states, that it:
Creates and returns the top-level accessibility object for the
application with the specified process ID.
Unfortunately, this top-level object is not the ancestor of the Dock icon.
I've tried to run the code, and it calculates the position of the first app's main menu item (which has the same title as the app itself). The comparison takes place in this line:
if ([titleValue isEqual:appName]) {
So the output was always the same for my app:
position: (45.000000, 0.000000)
An attempt to access the other app's accessibility object yielded a warning in console. I guess another way to calculate the position of the icon has to be found.

Is it possible to use FSEvents to get notifications that a folder has been moved?

I'm using the FSEvents API to get notifications of changes in a local directory that I'm tracking.
Is it possible to get a notification that the watched directory has been moved to another location on disk, using FSEvents or anything else?
Update:
Here is the code I have so far, I'm now trying to use the kFSEventStreamCreateFlagWatchRoot flag with FSEventStreamCreate to get the root changed notification, so far without success.
- (void)registerForFileSystemNotifications {
NSString *watchedDirectoryPath = [[NSUserDefaults standardUserDefaults] valueForKey:kMyWatchedDirectoryPathKey];
self.watchedDirectoryFileDescriptor = open([watchedDirectoryPath cStringUsingEncoding:NSUTF8StringEncoding], O_RDONLY);
NSArray *paths = [NSArray arrayWithObject:watchedDirectoryPath];
void *appController = (void *)self;
FSEventStreamContext context = {0, appController, NULL, NULL, NULL};
FSEventStreamRef streamRef = FSEventStreamCreate(NULL,
&fsevents_callback,
&context,
(CFArrayRef) paths,
kFSEventStreamEventIdSinceNow,
(CFTimeInterval)2.0,
kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagWatchRoot);
FSEventStreamScheduleWithRunLoop(streamRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
FSEventStreamStart(streamRef);
}
void fsevents_callback(ConstFSEventStreamRef streamRef,
void *userData,
size_t numumberOfEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]) {
MyAppController *appController = (MyAppController *)userData;
char *newPath = calloc(4096, sizeof(char));
int pathIntPointer = (int)newPath;
int length = fcntl(appController.watchedDirectoryFileDescriptor, F_GETPATH, pathIntPointer);
NSString *newPathString = [[NSString alloc] initWithBytes:newPath length:(NSUInteger)length encoding:NSUTF8StringEncoding];
NSLog(#"newPathString: %#", newPathString); // empty
}
Yes. Pass kFSEventStreamCreateFlagWatchRoot as the last argument to FSEventStreamCreate, and you'll be notified if the directory's moved or renamed. From the docs:
Request notifications of changes along the path to the path(s) you're watching. For example, with this flag, if you watch "/foo/bar" and it is renamed to "/foo/bar.old", you would receive a RootChanged event. The same is true if the directory "/foo" were renamed. The event you receive is a special event: the path for the event is the original path you specified, the flag kFSEventStreamEventFlagRootChanged is set and event ID is zero. RootChanged events are useful to indicate that you should rescan a particular hierarchy because it changed completely (as opposed to the things inside of it changing). If you want to track the current location of a directory, it is best to open the directory before creating the stream so that you have a file descriptor for it and can issue an F_GETPATH fcntl() to find the current path.
Edit: adding fcntl example
That cocoadev example suggests the author's a bit inexperienced with pointers. The pathIntPointer is not only unnecessary, it's also the cause of your problem. Error checking of the return code from fnctl would have caught it. Here's a revised version of your callback:
void fsevents_callback(ConstFSEventStreamRef streamRef,
void *userData,
size_t numumberOfEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]) {
MyAppController *appController = (MyAppController *)userData;
char newPath[ MAXPATHLEN ];
int rc;
rc = fcntl( appController.watchedDirectoryFileDescriptor, F_GETPATH, newPath );
if ( rc == -1 ) {
perror( "fnctl F_GETPATH" );
return;
}
NSString *newPathString = [[NSString alloc] initWithUTF8String: newPath ];
NSLog(#"newPathString: %#", newPathString);
[ newPathString release ];
}

Register as Login Item with Cocoa?

Google gave me: http://developer.apple.com/samplecode/LoginItemsAE/index.html
And I figured there must be a better way than using AppleScript Events.
So I downloaded the Growl sources. They use the exact sources from that Apple developer article.
Is there a better way?
(I refer to Login Items in Accounts in System Preferences, ie. making my program start when the user Logs in, programmatically)
There's an API that's new in Leopard called LSSharedFileList. One of the things it lets you do is view and edit the Login Items list (called Session Login Items in that API).
BTW, I'm the lead developer of Growl. We haven't switched away from AE yet because we still require Tiger, but I'm thinking of dropping that for 1.2 (haven't talked it over with the other developers yet). When we do drop Tiger, we'll drop LoginItemsAE as well, and switch to the Shared File List API.
EDIT from the year 2012: Since 2009, when I originally wrote this answer, Growl has switched to LSSharedFileList and I've left the project.
I stumbled across Ben Clark-Robinson's LaunchAtLoginController. A very elegant solution to a very common problem.
This works on xcode 5.
- (BOOL)isLaunchAtStartup {
// See if the app is currently in LoginItems.
LSSharedFileListItemRef itemRef = [self itemRefInLoginItems];
// Store away that boolean.
BOOL isInList = itemRef != nil;
// Release the reference if it exists.
if (itemRef != nil) CFRelease(itemRef);
return isInList;
}
- (void)toggleLaunchAtStartup {
// Toggle the state.
BOOL shouldBeToggled = ![self isLaunchAtStartup];
// Get the LoginItems list.
LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItemsRef == nil) return;
if (shouldBeToggled) {
// Add the app to the LoginItems list.
CFURLRef appUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, appUrl, NULL, NULL);
if (itemRef) CFRelease(itemRef);
}
else {
// Remove the app from the LoginItems list.
LSSharedFileListItemRef itemRef = [self itemRefInLoginItems];
LSSharedFileListItemRemove(loginItemsRef,itemRef);
if (itemRef != nil) CFRelease(itemRef);
}
CFRelease(loginItemsRef);
}
- (LSSharedFileListItemRef)itemRefInLoginItems {
LSSharedFileListItemRef res = nil;
// Get the app's URL.
NSURL *bundleURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
// Get the LoginItems list.
LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItemsRef == nil) return nil;
// Iterate over the LoginItems.
NSArray *loginItems = (__bridge NSArray *)LSSharedFileListCopySnapshot(loginItemsRef, nil);
for (id item in loginItems) {
LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)(item);
CFURLRef itemURLRef;
if (LSSharedFileListItemResolve(itemRef, 0, &itemURLRef, NULL) == noErr) {
// Again, use toll-free bridging.
NSURL *itemURL = (__bridge NSURL *)itemURLRef;
if ([itemURL isEqual:bundleURL]) {
res = itemRef;
break;
}
}
}
// Retain the LoginItem reference.
if (res != nil) CFRetain(res);
CFRelease(loginItemsRef);
CFRelease((__bridge CFTypeRef)(loginItems));
return res;
}
I do this in an app I'm writing:
Check out UKLoginItemRegistry for an easy way to do this pragmatically. Afaik, there is no way in Tiger to do this without Apple Events; in Leopard there's a better way, but if you use UKLoginItemRegistry it really is no problem. Here's the complete code for implementing an "Open at Logon" menu item
+ (bool)isAppSetToRunAtLogon {
int ret = [UKLoginItemRegistry indexForLoginItemWithPath:[[NSBundle mainBundle] bundlePath]];
NSLog(#"login item index = %i", ret);
return (ret >= 0);
}
- (IBAction)toggleOpenAtLogon:(id)sender {
if ([PopupController isAppSetToRunAtLogon]) {
[UKLoginItemRegistry removeLoginItemWithPath:[[NSBundle mainBundle] bundlePath]];
} else {
[UKLoginItemRegistry addLoginItemWithPath:[[NSBundle mainBundle] bundlePath] hideIt: NO];
}
}
I've refactored some of the answers here to provide a category on NSApplication that provides a launchAtLogin property.
https://gist.github.com/joerick/73670eba228c177bceb3
SMLoginItemSetEnabled is another modern option, see Modern Login Items article by Cory Bohon where he explains that you have to create a helper application whose sole purpose is to launch the main application. There's also a full step by step explanation in SMLoginItemSetEnabled - Start at Login with App Sandboxed on Stack Overflow.
Check here an open source example: https://github.com/invariant/rhpnotifier (LoginItem.m, LoginItem.h)

Resources