As part of the process of creating a VPN connection programatically in OSX, using Cocoa, I need to store the PPP password in the System keychain. When I try to do this using the keychain API, I get the following error as a result of calling SecKeychainAddGenericPassword:
"Could not write to the file. It may have been opened with insufficient access privileges."
Here is the code I am using:
- (void)storePasswordInKeychain
{
SecKeychainRef keychain = nil;
err = SecKeychainCopyDomainDefault(kSecPreferencesDomainSystem, &keychain);
if (err != errSecSuccess) {
NSLog(#"Error getting system keychain: %#", SecCopyErrorMessageString(err, NULL));
} else {
NSLog(#"Succeeded opening keychain: %#", SecCopyErrorMessageString(err, NULL));
SecKeychainItemRef item = nil;
err = SecKeychainUnlock(keychain, 0, NULL, FALSE);
NSLog(#"Keychain unlocked: %#", SecCopyErrorMessageString(err, NULL));
err = SecKeychainAddGenericPassword (keychain,
3, "VPN",
8, "username",
8, "password",
&item);
NSLog(#"Result of storing password: %#", SecCopyErrorMessageString(err, NULL));
}
}
The discussion How to write to the System.keychain? makes it seem like I need to make a command line call to /usr/bin/security from within my program, but the point of the Keychain API seems to be to avoid that kind of hackery.
Can anybody point me in the right direction for storing a new password in the System keychain? Thanks.
You need root privilege when you write something to system keychain.
For xcode debugging, you just "EditScheme"(From Menu by "Product->EditScheme...->Run->Info->Debug process as->root"). Well,my xcode version is 6.1, maybe there is some difference in different xcode version.
Or just use command line by sudo your app.
Hope this helps.
It is true that the credentials need to go into the system keychain and not the user keychain. You will not need the SMJobBless to do that.
After you unlocked the keychain, create an SecAccessRef like so:
SecAccessRef access = nil;
status = SecAccessCreate(CFSTR("Some VPN Test"), (__bridge CFArrayRef)(self.trustedApps), &access);
Then build your keychain item
SecKeychainAttribute attrs[] = {
{kSecLabelItemAttr, (int)strlen(labelUTF8), (char *)labelUTF8},
{kSecAccountItemAttr, (int)strlen(accountUTF8), (char *)accountUTF8},
{kSecServiceItemAttr, (int)strlen(serviceUTF8), (char *)serviceUTF8},
{kSecDescriptionItemAttr, (int)strlen(descriptionUTF8), (char *)descriptionUTF8},
};
And finally store it to the keychain:
SecKeychainAttributeList attributes = {sizeof(attrs) / sizeof(attrs[0]), attrs};
status = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &attributes, (int)strlen(passwordUTF8), passwordUTF8, keychain, access, &item);
There is a project on Github which does just that. Have a look at the VPNKeychain.m file to see the whole implementation. https://github.com/halo/macosvpn
Surely a VPN username & password belong to a particular user, do you really mean the system keychain and not the user keychain?
Try dropping the calls to SecKeychainCopyDomainDefault & SecKeychainUnlock and just pass NULL as the first argument to SecKeychainAddGenericPassword - this should add the item to the default keychain.
Apple's current recommendation for privileged access is to use the Service Management API. They have a sample project: SMJobBless. I'm not sure if the sandbox will allow system keychain access if you are targeting the Mac App Store.
The other question you point to seems to be recommending the Authorization Services API which Service Management replaced (in 10.6) and which is explicitly stated as not being allowed at all when sandboxed.
Related
from OS X 10.11 and iOS 9.0 Apple provided this framework Contacts:
https://developer.apple.com/library/prerelease/mac/documentation/Contacts/Reference/Contacts_Framework/index.html#//apple_ref/doc/uid/TP40015328
and I would like to access contacts from Mac, but I cannot do anything, because the app does not have access to contacts:
switch CNContactStore.authorizationStatusForEntityType(.Contacts) {
case .Authorized:
print("Authorized")
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "contactsAllowed")
case .Denied:
print("Denied")
NSUserDefaults.standardUserDefaults().setBool(false, forKey: "contactsAllowed")
case .NotDetermined:
print("Not Determined")
case .Restricted:
print("Restricted")
}
The app never prompts to allow access to contacts, neither can I select it in System Preferences (it does not show up).
Has anyone any idea how to acccess them on Mac (on iOS it works well)?
Any call to instance of CNContactStore should trigger request. If not try
Reboot (actually this helped me; I was granted access)
Turn on sandboxing
Resetprivacy settings "sudo tccutil reset AddressBook"
Call it on main thread
Instance of CNContactStore call:
[[CNContactStore alloc] init];
Privacy
Users can grant or deny access to contact data on a per-application
basis. Any call to CNContactStore will block the application while the
user is being asked to grant or deny access. Note that the user is
prompted only the first time access is requested; any subsequent
CNContactStore calls use the existing permissions. To avoid having
your app’s UI main thread block for this access, you can use either
the asynchronous method [CNContactStore
requestAccessForEntityType:CNEntityTypeContacts completionHandler:] or
dispatch your CNContactStore usage to a background thread.
Are you trying this from the playground? This won't work.
Try this method in your project:
func getContactImage(name:String) -> NSImage?
{
let store = CNContactStore()
do
{
let contacts = try store.unifiedContactsMatchingPredicate(CNContact.predicateForContactsMatchingName(name), keysToFetch:[CNContactImageDataKey])
if contacts.count > 0
{
if let image = contact[0].imageData
{
return NSImage.init(data: image)
}
}
}
catch
{
}
return nil
}
Anyway your question led me on the path to find a way to get the contacts image, so thanks for that.
I try to use SMJobBless function to authenticate for my application can do write on /Library/Fonts but not working,
if (![self blessHelperWithLabel:#"com.apple.bsd.SMJobBlessHelper" error:&error]) {
NSLog(#"Something went wrong! %# / %d", [error domain], (int) [error code]);
} else {
//Access to this point.
/* At this point, the job is available. However, this is a very
* simple sample, and there is no IPC infrastructure set up to
* make it launch-on-demand. You would normally achieve this by
* using XPC (via a MachServices dictionary in your launchd.plist).
*/
NSLog(#"Job is available!");
bool result = false;
result = [[NSFileManager defaultManager] isWritableFileAtPath:#"/Library/Fonts"];
[self->_textField setHidden:false];
}
My application printed "Job is available" but when i check authorities write on /Library/Fonts, result is false
Please tell me reason and resolve it.
The idea of SMJobBless is that privileged functionality is separated from the main application and run in a helper application.
Therefore, in the case of your example code, you're just using the helper app to test authentication, when in actuality, you should be doing the privileged task of checking if the path is writable from the helper app, as the helper app is provided with the privileged access.
Then if you're going to write to the fonts folder, the privileged helper app should do that, not your main application.
I've been playing with Accounts framework and during my tests I've denied the app access to Accounts. Now the following code returns Denied each time I run it.
[[self accountStore] requestAccessToAccountsWithType:twitterType options:nil completion:^(BOOL granted, NSError *error) {
if (granted) {
accounts = [[self accountStore] accountsWithAccountType:twitterType];
} else if (error != nil) {
NSLog(#"Failed to list Twitter accounts %#", error);
} else {
NSLog(#"Access denied");
}
}];
What sucks is I have no clue where to reset it and googling doesn't help. I've seen how to fix it on iOS but I'm running a Mac app and the solution doesn't apply.
The Account Framework API documentation is not helpful at all.
Palm face.
Figured it out, I've found a file with my app id called "~/Library//Application Support/com.apple.TCC/TCC.db"
It's a sqlite3 file, so run sqlite3 "~/Library//Application Support/com.apple.TCC/TCC.db" then search for your app in "access" table, delete the row and should you have access again.
If that doesn't help there's also a file called ~/Library/Accounts/Accounts3.sqlite, there ZAUTHORIZATION table which includes your app.
The proposed solution no longer works, because of high-profile hacks made by big companies and others. This solution has never been supported and is an invitation to hackers. The right way to do this is using the tool provided by Apple: tccutil.
Thus, do the following:
Open Terminal.
Run tccutil reset <service> [bundle_id], in which <service> can be AddressBook or Calendar or All with the optional ID of the bundle you want to reset.
Example:
tccutil reset All com.apple.Terminal
I'm trying to create an installer for OS X. To be able to write to directories owned by root (/Applications, /Library...) I'm using Authorization Services from the Security framework. The following code works fine with an admin user but fails with a standard user. Creation of the rights works without error message but the process still runs with lower privileges (no file is written at the mentioned position).
Of course I'm using the admin user's loginname/password for authentification.
AuthorizationRef authRef;
char rightName[] = "system.install.root.user";
AuthorizationItem kActionRight = { rightName, 0, 0, 0 };
AuthorizationRights rights = {1, &kActionRight};
AuthorizationFlags flags=kAuthorizationFlagExtendRights|kAuthorizationFlagInteractionAllowed;
OSStatus err = AuthorizationCreate(&rights, 0, flags, &authRef);
// [ write to a file in /Applications; doesn't work with standard users ]
if (authRef != 0)
AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
I know there are examples that use AuthorizationExecuteWithPrivileges(), but as this is deprecated in OS X 10.7 I'd rather avoid using it. Any idea what I'm doing wrong or what I need to do to obtain root privileges?
Thanks for any help,
Chris
Maybe you need use PackageMaker instead of writing your own installer. This save your time... And dependent of your requirement you can use HelperTools that will be automatically copy binaries to the /Library...
I'm having trouble with a Mac App Store submission. I'm using the method below to add my App to the login items if the user toggles the checkbox in the preferences.
-(void) addAppAsLoginItem{
NSString * appPath = [[NSBundle mainBundle] bundlePath];
CFURLRef url = (CFURLRef)[NSURL fileURLWithPath:appPath];
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItems) {
//Insert an item to the list.
LSSharedFileListItemRef item = LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemLast, NULL, NULL, url, NULL, NULL);
if (item){
CFRelease(item);
}
}
CFRelease(loginItems);
}
I‘ve already submitted many updates successfully without any problems, but now my App got rejected, rejected because I'm accessing the file system:
2.30
The application accesses the following location:
'~/Library/Preferences/loginwindow.plist'
This file is used to set an application to launch at login. It should
not be modified until the user has enabled such an option within the
application. This option should not be enabled by default; the user
must take the action of enabling it.
So now I'm confused, because this method was there since the first release and was never a problem. The file is only read or modified when the user toggles the corresponding checkbox in the preferences.
So how should I add my App to the startup login items without getting rejected again?
Look at SMLoginItemSetEnabled and the docs here.
As you're an apple developer you should also look again at the dev forums for discussions on this topic - they are there.
HTH
As I read it: You may not enable it as a login item by default - without asking the user.
I submitted the app again and it got approved within 5 hours. I didn't receive any response from the App Store team to my support request though…