How can I extract all root CA certificates from all the keychains on OSX programmatically in pem format?
Keychain programming services should allow this but how?
Any help would be appreciable.
security find-certificate -a -p /System/Library/Keychains/SystemRootCertificates.keychain >certs-roots.pem
security find-certificate -a -p /Library/Keychains/System.keychain >certs-system.pem
security find-certificate -a -p ~/Library/Keychains/login.keychain-db >certs-user.pem
BTW: You can see those paths in Keychain Access when you hover over the Keychains list (top/left).
You can combine system and user pems by using the default certificate source
security find-certificate -a -p >certs.pem
This is super useful for node.js, when you want to use require('https').request on typical corporate internal stuff without having to resort to hacks like accepting any certificate without checking. You don't need to include the system roots since nodejs has you covered already for those.
NODE_EXTRA_CA_CERTS=certs.pem node
Answering my own question:
On OSX you can invoke a NSTask to get response from the security command line utility:
security find-certificate -a -p /System/Library/Keychains/SystemCACertificates.keychain > allcerts.pem
Hey I know I'm late to this but I came across the same problem today and spent many hours figuring out how to do this. I know that the original poster might not need to know this anymore but hopefully this helps someone.
The following is my code to exactly replicate what you've done without using the command line.
+ (NSURL *)createCertsFileInDirectory:(NSURL *)directory {
NSString *outPath = [directory path];
if (!outPath) {
return nil;
}
outPath = [outPath stringByAppendingPathComponent:#"allcerts.pem"];
NSURL * outURL = [NSURL fileURLWithPath:outPath];
SecKeychainRef keychain;
if (SecKeychainOpen("/System/Library/Keychains/SystemCACertificates.keychain", &keychain) != errSecSuccess) {
return nil;
}
CFMutableArrayRef searchList = CFArrayCreateMutable(kCFAllocatorDefault, 1, &kCFTypeArrayCallBacks);
CFArrayAppendValue(searchList, keychain);
CFTypeRef keys[] = { kSecClass, kSecMatchLimit, kSecAttrCanVerify, kSecMatchSearchList };
CFTypeRef values[] = { kSecClassCertificate, kSecMatchLimitAll, kCFBooleanTrue, searchList };
CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFTypeRef results;
OSStatus status = SecItemCopyMatching(dict, &results);
CFArrayRef arr = (CFArrayRef) results;
NSLog(#"total item count = %ld", CFArrayGetCount(arr));
CFRelease(dict);
CFRelease(searchList);
CFRelease(keychain);
if (status != errSecSuccess) {
return nil;
}
CFDataRef certsData;
status = SecItemExport(results, kSecFormatPEMSequence, kSecItemPemArmour, NULL, &certsData);
CFRelease(results);
if (status != errSecSuccess) {
return nil;
}
NSData *topLevelData = (NSData *) CFBridgingRelease(certsData);
if (![topLevelData writeToURL:outURL atomically:YES]) {
return nil;
}
return outURL;
}
Related
I would like to start a lot of NSTasks that require root privilege, I found that I should use Apple's Authorization Kit and I found STPrivilegedTask, which is designed for that.
The thing is, when I start a STPrivilegedTask, it will ask me for the root password each time. Is there a way to enter the root password for the first privileged task, and then for the other tasks, the application will remember the root password, so the user won't have to enter the password again and again and again?
Depending on your use case, you might be happy just replacing the initialization of authorizationRef in STPrivilegedTask -launch with something like...
// create authorization reference
static AuthorizationRef authorizationRef = NULL;
#synchronized(self) {
if (!authorizationRef) {
err = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef);
if (err != errAuthorizationSuccess) { return err; }
}
}
... and then removing the AuthorizationFree:
// free the auth ref
AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults);
That will allow you to launch tasks without the password prompt for five minutes.
If you need more control, create a Command Line Tool in Xcode that executes the desired commands using normal NSTask calls.
When you launch the tool using STPrivilegedTask or AuthorizationExecuteWithPrivileges, the NSTask commands will run with administrator privileges (euid = 0).
SMJobBless is the way to launch a tool going forward — especially if you need to perform privileged operations on a regular basis, without prompting for a password.
It's much harder to do that correctly using AuthorizationExecuteWithPrivileges.
Notes on SMJobBless
SMJobBless prompts for a password & installs the helper.
When you talk to the helper, there will be no password prompt.
Apple's own example on this is fairly convoluted, but there are other examples out there.
Some pages suggest that only your application is allowed to talk to your helper. Apple doesn't say that anywhere. After the helper is installed, I'm pretty sure anyone can talk to it.
You could probably enforce the code signing requirements (PROPERTY LISTS section of this document) yourself, but I couldn't find any examples that do that.
I used AuthorizationExecuteWithPrivileges a couple years ago to install a launchd daemon, but I have yet to tangle with SMJobBless myself.
Hi I found some work around for this issue. Here I am listing all steps, hope help someone facing same issue:
First step on application launch to check if our application has administration privileges.
// run a terminal command for admin privileges available or not
NSString* csPluginDirectoryPath = [NSString stringWithFormat:#"%#/supportFiles",[CommonMethods getResourceFolderPath]];
NSString* tapPathCommand = [NSString stringWithFormat:#"chown root %#/tap.kext", csPluginDirectoryPath];
//if app has not admin privileges then it return error text
// if application has admin privileges it will return empty message
NSString* csErrorMessage = [self ExecuteSudoTerminalCommand:tapPathCommand];
if(csErrorMessage && [csErrorMessage length] > 2)
{
// go for admin privileges
[self launchHelperTool];
}
-(NSString*) ExecuteSudoTerminalCommand:(NSString*) terminalCommands
{
NSString* resultOfScript = #"";
NSString* csScript = [NSString stringWithFormat:#"do shell script \"%#\"",terminalCommands];
NSDictionary* errors = [NSDictionary dictionary];
NSAppleScript* appleScript = [[NSAppleScript alloc] initWithSource:csScript];
NSAppleEventDescriptor* descriptor = [appleScript executeAndReturnError:&errors];
if(descriptor == nil)
{
resultOfScript = [errors objectForKey:#"NSAppleScriptErrorMessage"];
}
else
resultOfScript = [descriptor stringValue];
return resultOfScript;
}
-(void) launchHelperTool
{
helperToolPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:#"/Chameleon VPN"];
NSBundle *thisBundle = [NSBundle mainBundle];
NSString* commonDictionaryPath = [[thisBundle bundleURL] absoluteString];
commonDictionaryPath = [commonDictionaryPath substringToIndex:commonDictionaryPath.length - 1];
commonDictionaryPath = [commonDictionaryPath stringByReplacingOccurrencesOfString:#"file:/" withString:#""];
NSArray *args = [NSArray arrayWithObjects:helperToolPath, commonDictionaryPath, nil];
NSTask *task = [NSTask launchedTaskWithLaunchPath:helperToolPath arguments:args];
[task waitUntilExit];
int status = [task terminationStatus];
if (status == 0)
{
NSLog(#"Task succeeded.");
}
else
{
NSLog(#"Task failed.");
exit(0);
}
}
I'm trying to figure out how to set the default web browser on OS X via command line. I found this forum post, but the solution they are working out there is to open a specific URL with a specific application. I am looking for a way to set the default browser system wide. There's this neat app on GitHub, named Objektiv, which does exactly what I am after, but it's an app. There's a Cocoa method, apparently, to set the NSWorkSpace default browser.
The preference file com.apple.LaunchServices needs to be changed for this, maybe more.
How to set a different default browser via command line?
Thanks for help.
I found this tool. After installing it, you can do this:
defaultbrowser chrome
Here's the entire source-code in case the link 404s in the future. It is licensed under the MIT license with Copyright (c) 2014 Margus Kerma.
#import <Foundation/Foundation.h>
#import <ApplicationServices/ApplicationServices.h>
NSString* app_name_from_bundle_id(NSString *app_bundle_id) {
return [[app_bundle_id componentsSeparatedByString:#"."] lastObject];
}
NSMutableDictionary* get_http_handlers() {
NSArray *handlers =
(__bridge NSArray *) LSCopyAllHandlersForURLScheme(
(__bridge CFStringRef) #"http"
);
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (int i = 0; i < [handlers count]; i++) {
NSString *handler = [handlers objectAtIndex:i];
dict[[app_name_from_bundle_id(handler) lowercaseString]] = handler;
}
return dict;
}
NSString* get_current_http_handler() {
NSString *handler =
(__bridge NSString *) LSCopyDefaultHandlerForURLScheme(
(__bridge CFStringRef) #"http"
);
return app_name_from_bundle_id(handler);
}
void set_default_handler(NSString *url_scheme, NSString *handler) {
LSSetDefaultHandlerForURLScheme(
(__bridge CFStringRef) url_scheme,
(__bridge CFStringRef) handler
);
}
int main(int argc, const char *argv[]) {
const char *target = (argc == 1) ? '\0' : argv[1];
#autoreleasepool {
// Get all HTTP handlers
NSMutableDictionary *handlers = get_http_handlers();
// Get current HTTP handler
NSString *current_handler_name = get_current_http_handler();
if (target == '\0') {
// List all HTTP handlers, marking the current one with a star
for (NSString *key in handlers) {
char *mark = [key isEqual:current_handler_name] ? "* " : " ";
printf("%s%s\n", mark, [key UTF8String]);
}
} else {
NSString *target_handler_name = [NSString stringWithUTF8String:target];
if ([target_handler_name isEqual:current_handler_name]) {
printf("%s is already set as the default HTTP handler\n", target);
} else {
NSString *target_handler = handlers[target_handler_name];
if (target_handler != nil) {
// Set new HTTP handler (HTTP and HTTPS separately)
set_default_handler(#"http", target_handler);
set_default_handler(#"https", target_handler);
} else {
printf("%s is not available as an HTTP handler\n", target);
return 1;
}
}
}
}
return 0;
}
Makefile:
BIN ?= defaultbrowser
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
CC = gcc
CFLAGS = -O2
.PHONY: all install uninstall clean
all:
gcc -o $(BIN) $(CFLAGS) -framework Foundation -framework ApplicationServices src/main.m
install: all
install -d $(BINDIR)
install -m 755 $(BIN) $(BINDIR)
uninstall:
rm -f $(BINDIR)/$(BIN)
clean:
rm -f $(BIN)
I know this doesn't specifically answer your question, but if you only need to do this for Chrome, it has a flag that let's you set itself as the default browser:
$ open -a "Google Chrome" --args --make-default-browser
For newer versions of macOS (Catalina, Big Sur) I re-implemented the default browser tool in Swift (called it defbro). Main reason was to use a "modern" programming language that gives me an easy way to tweak and improve it. I also added json output defbro --json, in case you want to do other stuff with it.
Installation: brew install jwbargsten/misc/defbro
Repo: https://github.com/jwbargsten/defbro
The only way is to set it in the main Settings:
From the Apple menu, choose System Preferences, then click
General.
Click the “Default web browser” pop-up menu and choose
a web browser, like Chrome.
So the OS X Keychain has three pieces of information:
ServiceName (the name of my app)
Username
Password
I obviously always know the ServiceName. Is there a way to find any saved Username(s) for that ServiceName? (Finding the password is easy once you know the Username.)
I would much prefer to use a nice Cocoa wrapper such as EMKeychain to do this. But EMKeychain requires the UserName to get any keychain item!
+ (EMGenericKeychainItem *)genericKeychainItemForService:(NSString *)serviceNameString withUsername:(NSString *)usernameString;
How are you expected to fully utilize saving credentials in the Keychain, if you need the Username to find the credentials? Is the best practice to save the Username in the .plist file or something?
SecKeychainFindGenericPassword only returns a single keychain item. To find all generic passwords for a specific service, you need to run a query on the keychain. There are several ways to do this, based on what version of OS X you target.
If you need to run on 10.5 or below, you'll need to use SecKeychainSearchCreateFromAttributes. It's a rather horrible API. Here is a rough cut of a method that returns a dictionary mapping usernames to passwords.
- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
OSStatus status;
// Construct a query.
const char *utf8Service = [service UTF8String];
SecKeychainAttribute attr = { .tag = kSecServiceItemAttr,
.length = strlen(utf8Service),
.data = (void *)utf8Service };
SecKeychainAttribute attrList = { .count = 1, .attr = &attr };
SecKeychainSearchRef *search = NULL;
status = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attrList, &search);
if (status) {
report(status);
return nil;
}
// Enumerate results.
NSMutableDictionary *result = [NSMutableDictionary dictionary];
while (1) {
SecKeychainItemRef item = NULL;
status = SecKeychainSearchCopyNext(search, &item);
if (status)
break;
// Find 'account' attribute and password value.
UInt32 tag = kSecAccountItemAttr;
UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
SecKeychainAttributeInfo info = { .count = 1, .tag = &tag, .format = &format };
SecKeychainAttributeList *attrList = NULL;
UInt32 length = 0;
void *data = NULL;
status = SecKeychainItemCopyAttributesAndData(item, &info, NULL, &attrList, &length, &data);
if (status) {
CFRelease(item);
continue;
}
NSAssert(attrList->count == 1 && attrList->attr[0].tag == kSecAccountItemAttr, #"SecKeychainItemCopyAttributesAndData is messing with us");
NSString *account = [[[NSString alloc] initWithBytes:attrList->attr[0].data length:attrList->attr[0].length encoding:NSUTF8StringEncoding] autorelease];
NSString *password = [[[NSString alloc] initWithBytes:data length:length encoding:NSUTF8StringEncoding] autorelease];
[result setObject:password forKey:account];
SecKeychainItemFreeAttributesAndData(attrList, data);
CFRelease(item);
}
CFRelease(search);
return result;
}
For 10.6 and later, you can use the somewhat less inconvenient SecItemCopyMatching API:
- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
kSecClassGenericPassword, kSecClass,
(id)kCFBooleanTrue, kSecReturnData,
(id)kCFBooleanTrue, kSecReturnAttributes,
kSecMatchLimitAll, kSecMatchLimit,
service, kSecAttrService,
nil];
NSArray *itemDicts = nil;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)q, (CFTypeRef *)&itemDicts);
if (status) {
report(status);
return nil;
}
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (NSDictionary *itemDict in itemDicts) {
NSData *data = [itemDict objectForKey:kSecValueData];
NSString *password = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
NSString *account = [itemDict objectForKey:kSecAttrAccount];
[result setObject:password forKey:account];
}
[itemDicts release];
return result;
}
For 10.7 or later, you can use my wonderful LKKeychain framework (PLUG!). It doesn't support building attribute-based queries, but you can simply list all passwords and filter out the ones you don't need.
- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
LKKCKeychain *keychain = [LKKCKeychain defaultKeychain];
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (LKKCGenericPassword *item in [keychain genericPasswords]) {
if ([service isEqualToString:item.service]) {
[result setObject:item.password forKey:item.account];
}
}
return result;
}
(I didn't try running, or even compiling any of the above code samples; sorry for any typos.)
You don't need the username. You do with EMKeychain, but that's an artificial distinction that that class imposes; the underlying Keychain Services function does not require a username to find a keychain item.
When using SecKeychainFindGenericPassword directly, pass 0 and NULL for the username parameters. It will return a keychain item that exists on that service.
However, that will return only one item. If the user has multiple keychain items on the same service, you won't know that, or which one you got (the documentation says it returns the “first” matching item, with no specification of what it considers “first”). If you want any and all items for that service, you should create a search and use that.
Generic passwords have a unique key of the service name and the username. Thus, to fetch a single generic keychain entry, you will need to provide both. However, you can iterate over all generic keychain entries for your given service using the SecKeychainFindGenericPassword function.
(Disclaimer: I don't know anything about doing this in EMKeychain.)
I've looked over the Security framework documentation but I can't seem to be able to find a way to get all of the certificates on a given keychain. Are there methods to accomplish this?
After mining the documentation, header files, and source files, I’ve come up with the following code:
#import <Security/Security.h>
- (void)logMessageForStatus:(OSStatus)status
functionName:(NSString *)functionName
{
CFStringRef errorMessage;
errorMessage = SecCopyErrorMessageString(status, NULL);
NSLog(#"error after %#: %#", functionName, (NSString *)errorMessage);
CFRelease(errorMessage);
}
- (void)listCertificates
{
OSStatus status;
SecKeychainSearchRef search = NULL;
// The first argument being NULL indicates the user's current keychain list
status = SecKeychainSearchCreateFromAttributes(NULL,
kSecCertificateItemClass, NULL, &search);
if (status != errSecSuccess) {
[self logMessageForStatus:status
functionName:#"SecKeychainSearchCreateFromAttributes()"];
return;
}
SecKeychainItemRef searchItem = NULL;
while (SecKeychainSearchCopyNext(search, &searchItem) != errSecItemNotFound) {
SecKeychainAttributeList attrList;
CSSM_DATA certData;
attrList.count = 0;
attrList.attr = NULL;
status = SecKeychainItemCopyContent(searchItem, NULL, &attrList,
(UInt32 *)(&certData.Length),
(void **)(&certData.Data));
if (status != errSecSuccess) {
[self logMessageForStatus:status
functionName:#"SecKeychainItemCopyContent()"];
CFRelease(searchItem);
continue;
}
// At this point you should have a valid CSSM_DATA structure
// representing the certificate
SecCertificateRef certificate;
status = SecCertificateCreateFromData(&certData, CSSM_CERT_X_509v3,
CSSM_CERT_ENCODING_BER, &certificate);
if (status != errSecSuccess) {
[self logMessageForStatus:status
functionName:#"SecCertificateCreateFromData()"];
SecKeychainItemFreeContent(&attrList, certData.Data);
CFRelease(searchItem);
continue;
}
// Do whatever you want to do with the certificate
// For instance, print its common name (if there's one)
CFStringRef commonName = NULL;
SecCertificateCopyCommonName(certificate, &commonName);
NSLog(#"common name = %#", (NSString *)commonName);
if (commonName) CFRelease(commonName);
SecKeychainItemFreeContent(&attrList, certData.Data);
CFRelease(searchItem);
}
CFRelease(search);
}
If you target Mac OS 10.6 or later, you can use SecItemCopyMatching to easily query the keychain:
SecKeychainRef keychain = ...
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
kSecClassCertificate, kSecClass,
[NSArray arrayWithObject:(id)keychain], kSecMatchSearchList,
kCFBooleanTrue, kSecReturnRef,
kSecMatchLimitAll, kSecMatchLimit,
nil];
NSArray *items = nil;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&items);
if (status) {
if (status != errSecItemNotFound)
LKKCReportError(status, #"Can't search keychain");
return nil;
}
return [items autorelease]; // items contains all SecCertificateRefs in keychain
Note that you must not use this to implement certificate validation — the presence of a CA certificate in a keychain does not indicate that it is trusted to sign certificates for any particular policy. See the Certificate, Key, and Trust Programming Guide to learn how to do certificate validation with the Keychain.
http://developer.apple.com/library/mac/#documentation/Security/Conceptual/CertKeyTrustProgGuide/03tasks/tasks.html#//apple_ref/doc/uid/TP40001358-CH205-SW1
Couldn't find that info using DiskArbitration or FSGetVolumeInfo/GetVolumeParms...
I know that hdiutil uses a private framework called DiskImages framework, but I wouldn't want to run an external utility each time I want this info... wheres the API for this ?
July 2015 Update
This update was prompted by Stan James' new question.
You can obtain this information using the DiskArbitration framework. To use the example below, you must link against and #import it.
#import <DiskArbitration/DiskArbitration.h>
...
- (BOOL)isDMGVolumeAtURL:(NSURL *)url
{
BOOL isDMG = NO;
if (url.isFileURL) {
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
if (session != nil) {
DADiskRef disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, (__bridge CFURLRef)url);
if (disk != nil) {
NSDictionary * desc = CFBridgingRelease(DADiskCopyDescription(disk));
NSString * model = desc[(NSString *)kDADiskDescriptionDeviceModelKey];
isDMG = ([model isEqualToString:#"Disk Image"]);
CFRelease(disk);
}
CFRelease(session);
}
}
return isDMG;
}
Usage:
BOOL isDMG = [someObject isDMGVolumeAtURL:[NSURL fileURLWithPath:#"/Volumes/Some Volume"]];
I hope this helps.