How to hardcode CFBundleIdentifier? - objective-c

what else can I say, how do I hardcode the CFBundleIdentifier in the app ?
Thanks

If you want to change CFBundleIdentifier during runtime, you can’t unless you write to the application bundle Info.plist. That’s bad practice because the application bundle might have been moved to a read-only volume, or had its write permissions limited by the system administrator, or digitally signed to avoid tampering. I guess Launch Services wouldn’t recognise this change instantly and the application would have to be restarted. Furthermore, it is also a reason for rejection by the Mac Apple Store.
On the other hand, if you want to detect tampering of CFBundleIdentifier, you can always read its value upon application startup, e.g. in applicationDidFinishLaunching:
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
NSString *bundleId = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleIdentifier"];
if (! [bundleId isEqualToString:#"com.yourcompany.yourapp"])
{
// Ooops, CFBundleIdentifier doesn’t match
}
}
Depending on your requirements, you might want to obfuscate the code above, including the literal strings. However, in general, you won’t be able to stop a determined adversary.

Related

Prevent NSDocument saving in temporary dictionary

I have an app with subclass of NSDocument that has overridden method writeToURL:(NSURL *) ofType:(NSString *) error:(NSError **) which saves data at given NSURL location, but also can save additional file (with appended .my2ext) with debug information. Previously it worked well (I created the app several years ago), but now I see that instead of user selected location the method gets some temporary directory:
file:///var/folders/yv/gwf3_hjs0ps7sb3psh3d0w3m0000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20MyApp%202)/myfilename.myext
Then, as I understand, the framework relocates the main file (at given url), but the additional file gets lost. So, can I somehow obtain the user selected path to save directly into it? Or prevent using temp directories at all?
I've already turned off the SandBox mode, but this didn't help. I also know that I can use "File Package" approach, but my app is created for a few people only, so, there is not interest in good production approach, only in simplicity.
I tried to google any possible solution, but found nothing helpful or just related. Even the documentation says nothing about using temporary directories! So, I decided to override different NSDocument methods. After several experiments I almost lost hope, but then I found that the method
saveToURL: ofType: forSaveOperation: delegate: didSaveSelector: contextInfo: provides real, user selected location. And this finally solved the problem.

implementing multi-process shared DB in macOS using XPC

My Goal is to develop robust, coherent, and persistent DB that can be shared between processes, just list Windows Registry.
On a previous question I advised not to use CFPreferences (and NSUserDefaults) due to the following reason
Current versions of macOS have a great deal of difficulty, and sometimes outright refuse, to update the values in one process with the values set by a second process.
Alternatively, I advised to use the following approach:
To have one process responsible for all of the shared values and the other processes get/set those values via XPC.
The XPC is quite new to me but from what I've read so far, it seems to use GCD queues for each connection, so how can I keep coherency if there are multiple processes asking to access the same DB for R/W operations (how can I enforce single thread to execute items in all queues).
Furthermore, I'd like to make this DB to fulfill ACID requirements, how can I achieve this ?
Here's my suggestion, and the solution I use in my applications.
(1) Create a named XPC service.
If you need to connect with your service from multiple apps, you'll need to name and register your service app with launchd.
(XPC make it pretty easy to create an anonymous service used only by your app, but connecting from other apps gets a little trickier. Start with the Daemons and Services Programming Guide.)
Note that in my solution, I already had a user agent registered with launchd, so this was just a matter of moving on to step (2).
(2) Add XPC message handlers to get and set the values you want to share.
- (void)queryPreferenceForKey:(NSString*)key withReply:(void(^)(id value))reply
{
reply([[NSUserDefaults standardUserDefaults] objectForKey:key]);
}
- (void)setPreferenceValue:(id)value forKey:(NSString*)key withReply:(void(^)(BOOL changed))reply
{
BOOL changed = NO;
id previous = [[DDUserPreferences standardUserDefaults] objectForKey:key];
if (!OBJECTS_EQUAL(previous,value))
{
[[NSUserDefaults standardUserDefaults] setObject:value forKey:key];
changed = YES;
}
reply(changed);
}
(3) There is no step 3.
Basically, that's it. The NSUserDefault class is thread safe, so there are no concurrently issues, and it automatically takes care of serializing the property values and synchronizing them with the app's persistent defaults .plist file.
Note: since this is based on NSUserDefaults, the value objects must be property-list objects (NSString, NSNumber, NSArray, NSDictionary, NSDate, NSData, ...). See Preferences and Settings Programming Guide.

Sandboxed Mac app exhausting security scoped URL resources

I am developing a Mac application that prompts the user for files using the NSOpenPanel. The application is sandboxed (testing on OSX 10.9.4). I noticed that if I open a large amount of files (~3000), the open panel starts to emit errors to the log. This also happens if I try to open less amount of files in chucks for several times.
After the errors start to appear the first time, every time the NSOpenPanel is used again to open files, no matter for how many files, these errors will be generated again (until the application is closed).
The error message looks like this:
TestPanel[98508:303] __41+[NSSavePanel _consumeSandboxExtensions:]_block_invoke: sandbox_consume_fs_extension failed
One line for each file I try to open.
I managed to reproduce this behavior with a simple app: A sandboxed application with a single button invoking the following code:
NSOpenPanel* panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:YES];
[panel setCanChooseDirectories:NO];
[panel setCanChooseFiles:YES];
[panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger result) {
NSLog(#"%lu", [panel.URLs count]);
}];
The errors appear before the code reaches the completion handler.
It seems that I can still get the URLs from the panel in the completion handler but it really pollutes the system log.
EDIT:
Seems that this problem is not directly related to the NSOpenPanel/NSSavePanel panels. A very similar thing happens when using drap/drop with files. Something like this:
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
...
NSPasteboard *pboard = [sender draggingPasteboard];
if ([[pboard types] containsObject:NSURLPboardType]) {
NSArray *urls = [pboard readObjectsForClasses:#[[NSURL class]] options:nil];
}
...
}
This will generate the following log messages when dragging a large amount of files (the "magic" number seems to be somewhere around 2900):
Consume sandbox extension for itemIdentifier (2937) from pasteboard failed!
As with the NSOpenPanel, after the first occurrence of this, every single file dropped will generate the same error in the log.
EDIT 2:
#mahal tertin's reply pointed me to the right direction. The problem is indeed with the number of files and the fact that security scoped URL resources are limited.
However, there seems to be no reasonable solution found. The problem is that when the user clicks "OK" on the NSOpenPanel (or drops the files on a drag&drop aware control), behind the scenes the OS already attempts to create these security scoped URLs and implicitly calls startAccessingSecurityScopedResource for you. So if the user attempts to open more files than the limit, the resources are exhausted and the only option is to close and restart the application.
Calling stopAccessingSecurityScopedResource on the returned URLs seem to free the resources however this solution was discouraged by Apple's representative on the official developers forums (link is behind login).
It seems that the app is at the mercy of the user not to open too many files. And that is not even at once, since there is no approved way to release these resources. You can warn the user in documentation or even with an in-app alert but there is no way to prevent them from messing up the app and forcing a restart.
So if the app runs long enough and the user keeps opening files, the app will eventually become unusable.
Still looking for a reasonable solution for this.
After searching high and low and asking in various places, I am going to close this question and conclude there is no answer or solution to this. I am posting the known information on this for future reference.
All the solutions suggested are just workarounds that may minimize the problem and try to guide the user toward not trying to open too many files. But there nothing that can be done to actually solve this.
Here are the known facts about this issue:
No matter what you do, the user can attempt to open too many files in the NSOpenPanel dialog and exhaust the security scoped URL resources
Once these resources are exhausted, it is not possible to open any more files for reading/writing. The application needs to be closed and reopened
Even if the user doesn't attempt to open too many files at once, the application may still exhaust these resources if it runs long enough and the user opens enough files over time since startAccessingSecurityScopedResource is called automatically for files opened with NSOpenPanel (or the drag/drop mechanism) and nothing ever closes these resources
Calling stopAccessingSecurityScopedResource on all URL retrieved by the open panel will free these resources but this practice is discouraged by Apple, saying it might not be compatible with future solutions
When you receive the list of URLs from NSOpenPanel (or drag/drop), there is no way to tell if all URLs were successfully accessed or if there are URLs that are over the limit and therefore invalid.
Apple is aware of this and may fix it in the future. It is still not fixed in 10.10 and of course, that will not help current applications running on current/previous OSX version.
It seems Apple has really dropped the ball on this one, the Sandbox implementation seems very sloppy and short sighted.
The behavior you experience is because the security scoped resources are limited:
NSURL - (BOOL)startAccessingSecurityScopedResource tells
If sufficient kernel resources are leaked, your app loses its ability
to add file-system locations to its sandbox...
The current limit is roughly what you experienced. See:
What are the current kernel resource limits on security-scoped bookmarks?
To prevent it:
only start accessing those SSBs you need at a given time and subsequently stop accessing them
start access not files but enclosing folders: ask the user not to choose files but a full folder. This will grant you access to the whole tree beneath that directory
on draggingEntered: show a NSOpenPanel with the enclosing directory(ies) to grant access

Problem writing plist in local vs. admin mode

In my application, I am storing user preferences (which are applicable for all users) into a plist file,
which is attached as a form of bundle.
The problem is, it runs fine in admin mode, but when I run the application, it's not writing the file. Do I need to set some attribute to write to the plist in local mode? Or is it not possible at all?
My code for writing the file is below:
-(void)SavePrefrence:(NSString *)fileName PrefrenceOption:(NSMutableDictionary *)pDict{
NSString *filePath = [[[NSBundle mainBundle] pathForResource:fileName ofType:#"plist"] retain];
NSDictionary *pTemp = [[NSDictionary alloc]initWithDictionary:pDict];
bool bRet = [pTemp writeToFile:filePath atomically:YES];
if(bRet==YES){
NSLog(#"File Saved ");
}
else {
NSLog(#"File not saved ");
}
}
This is the code which calls it:
-(void)SaveListSettings:(NSMutableDictionary *)pListSettings{
[ self SavePrefrence:#“MyList" PrefrenceOption:pListSettings];
if(pListInfo)
[pListInfo release];
[self LoadListProfile];
}
The application bundle will, by default, only be writable by the owner (if it's installed by dragging into the Applications folder, this'll be whoever installed it; if it's installed by a .pkg, it should probably be root). The way you're doing this, if I follow it properly, requires a user to have write access to the app's Contents/Resources folder, which is a really bad idea (not that it's unheard of -- see the University of Utah's documentation about "Poorly-Made Applications" for examples). Actually, saving preferences inside the application is a bad idea anyway; that's what the various Library/Preferences folders are for (~/Library/Preferences for personal settings, /Library/Preferences for system-wide settings. But, of course, /Library/Preferences is only writable by admins (for very good reasons). I don't really know of a good way to handle this, as system-wide settings modifiable by non-admins is not exactly normal.
You could use an installer which asks for an admin password and then create "/Library/Application Support/MyApp" and then either make this world writable, or make a sub-folder inside it which is world-writeable. Now MyApp running under a non-admin account can still write to this shared folder.
If you don't want to make the folder world-writeable then include a helper app to the bundle to do the writing and make the helper setuid root by using an installer which asks for an admin password...
BTW: Both of those options will fail Mac App Store rules. Maybe you can use '/Users/Shared', but I don't know if it is allowed by MAS, and anyway it is far from standard. Which would leave you with storing it on a web server...

How do I store data securely with objective C? (Mac/Cocoa Dev)

I'm trying to create a Trial part of my cocoa application. I have the licensing all set up (including keys) etc.
But I was wondering how I could store e.g the first the time the user ran the program in a secure place, where the user can't easily find it and/or edit it.
I was having a fiddle with NSUserDefaults standardUserDefaults, but the user can easily find and edit that data in Library > Preferences.
I'd argue against making it super-secure. We once had a server activation but completely went away from it. Here are some of the reasons:
Even the most "secure" storage method can be broken
Even for a niche product a crack might be developed, no way around, no matter how secure your method
There are few, very expensive, very secure methods. All others have been cracked
It goes against fair and honest users, making it harder for them to fix things causing problems
If a user does crack your software or circumvents your security, he'll probably also never have bought it
80% of the users don't even know what a preference file is
95% of all users don't think of the possibility to delete it in order to extend the trial
A simple way to reset trial periods massively eases your support for users you want to give a second trial for whatever reason
Trusting users is a good selling point
Power users tend to turn against software with too much protection
I'm pretty sure that there are some, but extremely few, exceptions from these thoughts. I'd say: don't waste your time on registration security but give
You can't use a file on the filesystem. Anyone who would be looking to fiddle/crack it will be smart enough to know how to track file access through basic, standard OSX features. So a file being added is out. Not only that but it is bad behavior to create files you don't remove when the app is uninstalled. People shouldn't have to have resources consumed after deleting your trial app.
As noted above, messing about in your bundle is a bad idea as well. This leaves you with three fundamental options.
1) Don't worry about it too much. Use a basic expiration system that uses the standard locations and methods. You can still use encryption in the data you store, but know that that too will be broken. Accept that copyright violation will occur unless your app is wholly unpopular.
2) Use a network call and do the validation on the server. This would require the app always be able to reach your service to be run. This is not a good idea in general. What if your servers are down? What if they are off-line? What if a network issue between you and them happens? All of those scenarios are going to happen. When they do you will likely lose customers unless your app requires a connection to your servers already to operate (like say Twitter or Facebook does).
3) Be a "bad citizen" by mucking around with the application bundle or leaving orphan files around. If you do this latter, at least make sure they are clearly named so that they relate to your application obviously.
Ultimately the key thing to remember is that you have no security on a user's machine. It is theirs. That means they have physical access which pretty much nullifies any attempt to prevent them from digging. You can also look at it this way: the more technically minded your market is, the less likely you are going to be smarter than all of us and your "security" will be cracked. If you are designing for a non-technical audience then you can figure that generally speaking they won't be bothering with cracking it, or looking for one.
You can spend your resources on making the app better, or giving yourself a better feeling about people not using it past the trial period. One of those can increase your sales, and one will not.
[edit]
Also I should point out that one of the most common (if not the most common) means of cracking these things is to modify the binary. So by breaking code signing via bundle mucking you would actually open yourself to that method because you would have broken one of the better protections you have. Most cracks involve binaries being modified such that the routine that does the checks always returns a successful authentication.
Allan Odgaard has a pretty decent writeup on how to use OpenSSL to generate/store license keys for Cocoa software. Might be worth a read.
If you must, a simple and common way is to use a key that is embedded in the application which encrypts a file on the disk that contains the sensitive data. The challenge is how to make the key secure.
Look into the Common Crypto digest library.
This will protect from almost all casual users. Though hackers can, with enough motiviation, figure out a way to circumvent.
Try storing a file that's name begins with a period in some folder and then setting the file's hidden flag.
a good place to put the hidden file would be an obscurely named file in the base of the users folder (~/), there are many obscure hidden files there so it is hard to know which one's you can and can't delete. example path: ~/.xdarwinprofile or something equally official sounding.
here is some code that should work to hide the file:
#include <assert.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <sys/attr.h>
#include <sys/errno.h>
#include <unistd.h>
#include <sys/vnode.h>
typedef struct attrlist attrlist_t;
struct FInfoAttrBuf {
u_int32_t length;
fsobj_type_t objType;
union {
char rawBytes[32];
struct {
FileInfo info;
ExtendedFileInfo extInfo;
} file;
struct {
FolderInfo info;
ExtendedFolderInfo extInfo;
} folder;
} finderInfo;
};
typedef struct FInfoAttrBuf FInfoAttrBuf;
- (int)SetFileInvisibility:(NSString *)filePath state:(BOOL)isInvisible) {
attrlist_t attrList;
FInfoAttrBuf attrBuf;
char *path = [filePath cStringUsingEncoding: NSUTF8StringEncoding];
memset(&attrList, 0, sizeof(attrList));
attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
attrList.commonattr = ATTR_CMN_OBJTYPE | ATTR_CMN_FNDRINFO;
int err = getattrlist(path, &attrList, &attrBuf, sizeof(attrBuf), 0);
if (err != 0)
return errno;
// attrBuf.objType = (VREG | VDIR), inconsequential for invisibility
UInt16 flags = CFSwapInt16BigToHost(attrBuf.finderInfo.file.info.finderFlags);
if (isInvisible)
flags |= kIsInvisible;
else
flags &= (~kIsInvisible);
attrBuf.finderInfo.file.info.finderFlags = CFSwapInt16HostToBig(flags);
attrList.commonattr = ATTR_CMN_FNDRINFO;
err = setattrlist(path, &attrList, attrBuf.finderInfo.rawBytes, sizeof(attrBuf.finderInfo.rawBytes), 0);
return err;
}
I modified this code from the answer to this question, you may find more helpful information there:
How to make a file invisible in Finder using objective-c
I have not tested this code but it should work. In fact, it is possible the code is unnecessary and just saving the file with a dot in front of the filename will work.
If you have administrator privileges you can execute a sudo chmod on the file and set it to read only if you want, but you shouldn't make your app ask the user for their password.
This solution worked for me very well. Try this: https://github.com/nielsmouthaan/SecureNSUserDefaults. It will store encrypted bool/string/float/integer in your UserDefaults file. Hopefully this is what you want. Make sure you download and add CocoaSecurity (See the SecureNSUserDefaults GitHub page for the download link) to your project. CocoaSecurity is a required element of SecureNSUSerDefaults, so you do not need to import it into any of your files. You must also download Base64, which is a required element of CocoaSecurity. You also need to add Base64 to your project, but do not need to import it into any of your files.
USAGE
Import the header file anywhere you want to use the encryption method.
#import <SecureNSUserDefaults/NSUserDefaults+SecureAdditions.h>
Then, set an encryption key, probably in your awakeFromNib method:
[[NSUserDefaults standardUserDefaults] setSecret:#"your_secret_goes_here"];
I recommend generating a random string of numbers and letters.
Then, you must store the information in your UserDefaults file.
[[NSUserDefaults standardUserDefaults]
setSecretObject:#"your_secret_object"
forKey:#"the_key_your_object_will be_stored_under"];
To retrieve the string, use this method:
NSString *retrievedData = [[NSUserDefaults standardUserDefaults]
secretStringForKey:#"the_key_your_object_will be_stored_under"];
I hope this helps!