I have a iOS application that I'm developing and the application must be bundled with a certificate in order to use the application since it makes use of webview and the site can not be accessed without the certificate.
I currently have a working solution but I noticed that when unzipping the .ipa file and looking into the code, I can see the password for the certificate very clearly as you can see from this line from the compiled code:
lastSampleTimeMainjsbundlecertificatep12PASSWORDHEREGCDAsyncSocketErrorDomain
A short code snippet of how I am retrieving the certificate:
[[NSBundle mainBundle] pathForResource:#"certificate" ofType:#"p12"];
NSData *p12Data = [[NSData alloc] initWithContentsOfFile:p12Path];
CFStringRef password = CFSTR("somePassword");
Is there any safe way to handle this type of scenario? Or will the password always be retrievable if it falls into the hands of someone who knows what he's doing.
Use the Keychain API.
https://developer.apple.com/documentation/security/keychain_services
That's the standard way to protect API_KEYS, Certs., Even Username and Passwords.
It's simple and compatible with Objective-C
EDIT: For Certificates: https://developer.apple.com/documentation/security/certificate_key_and_trust_services/certificates/storing_a_certificate_in_the_keychain
A simple solution might be to just obfuscate the string, creating the password string from a byte-array, but that's still not very safe.
Some explanation here.. also to consider, loading the password to a string should be avoided, as it could be read from the heap at runtime:
https://stackoverflow.com/a/8881376/20283130
Related
I inherited a Xcode setup that builds an iOS app that uses Automatic Signing for the development builds. I now have the task to build some CI setup for this project, but without changing the actual Xcode project. This means I can't switch to manual signing for now.
As the project is building fine locally, I didn't expect this to be a big problem, but it turns out Automatic Signing (obviously, in hindsight) needs your Xcode to be signed into the Apple ID (Xcode => Preferences => Accounts) that should be used for automatically creating certificates.
Is there a way to add an Apple ID to Xcode via the command line?
This is what I already did:
I looked around already, but could not find any obvious answers via Google. All the questions and answers here on StackOverflow always mention "Just quickly open Xcode and enter your credentials" which unfortunately does not work on our CI setup.
I found this Jenkins "Xcode Plugin" that lets you import a .developerprofile that you can export from Xcode. But my Java is really rusty and I couldn't fully understand if this "only" imports profiles and identities, or also the list of accounts.
Playing around with an .developerprofile myself, it seems to include the account information (and all the certificates etc.) in a .zip file, so you can extract the files. That also includes a accounts.keychain and accounts.plist, but those are both encrypted with the password - which I don't know how to use to get to the real data to investigate there further.
I also tried to find out where Xcode originally saves the information if you add a new Apple ID: It seems to put the account names and passwords, and some token, into your "login" (com.apple.gs.xcode.auth.com.apple.account.AppleIDAuthentication.token) and "iCloud" keychain (Xcode-AlternateDSID and Xcode-Token). I also couldn't recreate the existing entries in Keychain access manually, as "Access Control" -> "Access group for this item:" was always different when creating an application password manually. Copying the items into a new keychain to be exported also didn't work, as the iCloud keychain doesn't let me copy stuff over to a new one (even after disabling keychain sync in iCloud, so the keychain is named "local items").
First off, I'm not sure that what you are trying to do is a good idea.
Keep in mind that if you are going to set up Xcode to automatically request iOS developer certificates on every build, and that build executes on different machines (say, hosted CI such as Travis or Azure Pipelines), your iOS developer certificate will be revoked and regenerated every time.
A much better option (in my opinion) is to export your iOS development certificates and provisioning profiles via your developer profile, and import the development certificate and provisioning profile in your build environment. Then, if needed, update your Xcode project to use the certificate and profile you've just imported.
I think Fastlane can already do pretty much all that. If you're looking for inspiration, Azure Pipelines has similar functionality. The have a task which installs a provisioning profile, one which installs a certificate and one which builds an Xcode project and allows you to override the certificate and provisioning profiles used when signing your product.
Having said that, the accounts.plist and the accounts.keychain probably contain the information you are looking for. Both files are encrypted using AES encryption.
The key used to encrypt the file is derived from the password using PBKDF2 (Password-Based Key Derivation Function 2), using these parameters:
Hashing function: SHA256
Password: Your password
Salt: The byte representation of your password, using UTF8-encoding
Number of hashing iterations: 33333
Key length: 10
The 'magic numbers' are the default values used by Apple's SecKeyDeriveFromPassword function, as described here and implemented here
Once you have your encryption key, you can decrypt the file. You'll need an initialization vector (IV), which again is the default value used by Apple - a 16-byte array consisting entirely of zeros.
In C, you should be able to use use code like this to generate the encryption key:
CFStringRef password = CFSTR("verysecretstuff");
const char* saltBytes = CFStringGetCStringPtr(password, kCFStringEncodingUTF8);
CFDataRef salt = CFDataCreate(NULL, saltBytes, CFStringGetLength(password));
int keySizeInBits = kSecAES128;
CFNumberRef keySize = CFNumberCreate(NULL, kCFNumberIntType, &keySizeInBits);
int rounds = 33333;
CFNumberRef numberOfRounds = CFNumberCreate(NULL, kCFNumberIntType, &rounds);
CFMutableDictionaryRef parameters = CFDictionaryCreateMutable(NULL, 3, NULL, NULL);
CFDictionaryAddValue(parameters, kSecAttrKeyType, kSecAttrKeyTypeAES);
CFDictionaryAddValue(parameters, kSecAttrKeySizeInBits, keySize);
CFDictionaryAddValue(parameters, kSecAttrPRF, kSecAttrPRFHmacAlgSHA256);
CFDictionaryAddValue(parameters, kSecAttrRounds, numberOfRounds);
CFDictionaryAddValue(parameters, kSecAttrSalt, salt);
CFErrorRef error = NULL;
SecKeyRef key = SecKeyDeriveFromPassword(password, parameters, &error);
To decrypt the data, use:
const UInt *bytes = NULL; // Encrypted data
CFDataRef data = CFDataCreate(NULL, bytes, length);
CFErrorRef error = NULL;
SecTransformRef transform = SecDecryptTransformCreate(key, &error);
if ( transform == NULL )
{
CFShow(error);
CFRelease(error);
}
SecTransformSetAttribute(transform, kSecEncryptionMode, kSecModeCBCKey, &error);
SecTransformSetAttribute(transform, kSecPaddingKey, kSecPaddingPKCS7Key, &error);
SecTransformSetAttribute(transform, kSecTransformInputAttributeName, data, &error);
CFDataRef result = SecTransformExecute(transform, &error);
CFShow(result);
CFRelease(result);
CFRelease(data);
CFRelease(transform);
Hope it helps!
I'm trying to determine the best way for me to store, and access an Access token in use of an iOS app.
I'm getting myself familiar with Core data, and while this has proved beneficial for some parts of the app's data, i'm not sure core data is best for what i need for the token.
Most views rely on some simple network request to our API. The access token is used with these requests to authenticate and authorize the data content requested.
To explain better here, an example of how it would be used is within the AppDelegate.m. on applicaion:hasFinishedLoadingWithOptions i preform a simple check to confirm the token with the API, on success the view is changed to the 'main menu', and on failure, view is changed to the login view.
This token needs to be accessed in most views as at any point the API voids the token, the user will have to re-login to attain a new token (not regularly).
My intial thought is to impement a token.h which i can call in ViewControllers where needed, however as this is my first large iOS app i would feel better with some constructive critism, tips or hints about how best to access this token, 'globally' as if it were a static value
You can store it in keychain like this
Apple has written an Objective-C wrapper that you can use to simplify working with the keychain, the files in the wrapper are KeychainItemWrapper.h and KeychainItemWrapper.m, both are included in the attached Xcode project.
UITextField *accesstoken = [[UITextField alloc] initWithFrame:CGRectMake(40, 30, 240, 30)];
[accesstoken setBorderStyle:UITextBorderStyleRoundedRect];
KeychainItemWrapper *keychain = [[KeychainItemWrapper alloc] initWithIdentifier:#"TestAppLoginData" accessGroup:nil];
Writing to the Keychain-
// Store AcessToken to keychain
Nsstring *strAcessToken=#"abcd"
[keychain setObject:strAcessToken forKey:(id)kSecAttrAccount];
Reading from the Keychain-
// Get AcessToken from keychain (if it exists)
[accesstoken setText:[keychain objectForKey:(id)kSecAttrAccount]];
Notes:
Writing and retrieving values from the keychain doesn’t seem to work in the simulator.
Uninstalling an application will not remove the items from the keychain. If you run the demo application on your device, and uninstall the app, the username and password will remain in the keychain (a firmware reset will, of course, remove the keychain values).
Although is question is highly drawn to have only opinionated answer I will give my point of view on the subject.
You need an API client, which hold the acces token, every time an new view controller is create it should get a reference to this API client. Or if you are not comfortable with pass it every time you might implement the API client a singleton.
All server API class should be via this API client, which should handle the network and API calls.
AFNetworking is great example of how this can be done.
The access token it self should be stored in the keychain, because this is the place to stored these kind of thing.
You can store your token in the NSUserDefaults
+(void)saveToken:(NSString *)token{
[[NSUserDefaults standardUserDefaults] setObject:token forKey:#"token"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
+(NSString *)getToken{
return [[NSUserDefaults standardUserDefaults] objectForKey:#"token"];
}
I am developing a location based Q&A SDK for mobile devices.
When a question is asked about a specific location, the server side targets the most relevant user and sends the question to that user. If the user fails to answer, the question is sent to the second best user, and so on.
The problem is that my SDK might be installed on more than one application on the device, meaning that the user can get a question more than once.
Is there a way to detect whether my SDK is installed on more than one app? I thought that sending the UDID to the server might work, but iOS UDIDs differ between applications.
You can use UIPasteboard to share data between applications on the device.
The UIPasteboard class enables an app to share data within the app and with another app. To share data with any other app, you can use system-wide pasteboards; to share data with another app that has the same team ID as your app, you can use app-specific pasteboards.
In your SDK, do something like this:
#interface SDKDetector : NSObject
#end
#implementation SDKDetector
+ (void)load
{
int numberOfApps = (int)[self numberOfAppsInDeviceUsingSDK];
NSLog(#"Number of apps using sdk:%d", numberOfApps);
}
+ (NSInteger)numberOfAppsInDeviceUsingSDK
{
static NSString *pasteboardType = #"mySDKPasteboardUniqueKey";
NSData *value = [[UIPasteboard generalPasteboard] valueForPasteboardType:pasteboardType];
NSMutableArray *storedData = [[NSKeyedUnarchiver unarchiveObjectWithData:value] mutableCopy];
if (!storedData) {
storedData = [NSMutableArray new];
}
NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier];
if (![storedData containsObject:bundleId]) {
[storedData addObject:[[NSBundle mainBundle] bundleIdentifier]];
}
value = [NSKeyedArchiver archivedDataWithRootObject:storedData];
[[UIPasteboard generalPasteboard] setData:value forPasteboardType:pasteboardType];
return [storedData count];
}
#end
If you only want to provide an SDK, it is not possible. Apple has added security steps to prevent that for user privacy. Keychain sharing will not work, because apps must share the same bundle seed ID (see here for more info).
If you want to provide an app along with your SDK, then you could do something like Facebook does, where app sends a "helo" message, Facebook asks user and finally Facebook sends "ehlo" message.
Your App -> "I would like to use the SDK; please give me token" -> SDK Controller App -> (Remember which apps have requested use) -> "OK, you can use the SDK; here is a token: #123" -> Your App
The SDK controller app can now send the server the list of apps.
I think you can group the apps on the same device by IP address as they will use the same address to connect to your server.
So the IP address will represent the device and the API key will represent the app that uses the SDK.
Can you try using
advertisingIdentifier
Not sure whether it serves your purpose. It is explained in here ASIdentifierManager class reference : Apple doc
I think its possible using keychain, you can have an unique keychain key in which you can save anything you want, and can be accessed by other apps if available. So for your SDK, lets say if there is one app, it will register some value in keychain with a unique key which is private to your SDK only if the key doesn't exist, and if it exist you get to know, since you can save any value in keychain, you can try multiple options and combinations which suits you.
You can use KeychainItemWrapper for the implementations.
Elaboration
Lets say we have an method.
[MySDK register];
Which can be used anywhere, say in AppDelegate. The register method will generate a token for the app, for the device, which we will save in the keychain using an unique key we have defined in the SDK, say in com.mysdk.key. And while saving in keychain the SDK can actually do a registration.
We consider the above method is implemented in multiple apps.
Now we have scenario.
User installs an App-A which uses the SDK, the register method will call and create a token and will save in keychain for the first time.
Now user installs another App-B which also uses the SDK, the same register method will call, but now it will check for the key com.mysdk.key in keychain, if exist it will just update the count for the token, which meant for the device.
Note
Keychain not meant to save only unique identifier, you can save other informations too.
Update
Check demo projects https://db.tt/7xpKrgMp
The wrapper I have used in the projects is same as SDK in your case which is same in both the projects.
Cheers.
In iOS MCSession, you can initialize the section using the following method
initWithPeer:securityIdentity:encryptionPreference:
Most online tutorials the authors put nil for securityIdentity.
However, I wonder whether any damage (hacked) if leaving nil for the real app.
If so, how to generate a SecIdentityRef for this?
I found the following articles/discussions about Security in iOS, but still have trouble to connect SecIdentityRef with MCSession.
Thank you for precious time on my question, and any comment will be helpful.
Securing and Encrypting Data on iOS:
http://code.tutsplus.com/tutorials/securing-and-encrypting-data-on-ios--mobile-21263
How to establish a SecIdentityRef in an iPhone keychain ? (Without a .p12):
How to establish a SecIdentityRef in an iPhone keychain ? (Without a .p12)
Generate key pair on iphone and print to log as NSString:
Generate key pair on iphone and print to log as NSString
I am implementing Music Player application in iOS platform. Here I am storing and retrieving the media contents using Documents directory. So I can't able to access the 'Documents Directory' when iPhone is getting locked with passcode. I referred Apple developer library, they said the Encryption & Decryption is comes under the accessing the file contents from documents directory. So if device is getting locked the Decryption key is Destroyed. Here I have attached the Screen shot of Problem Description. I was stuck with this problem last 2 months. Pls give me any other ways to access the Documents directory at locked state. Not Only Media , Any other content*.
Thanks in Advance.
Apple Developer Site:
https://developer.apple.com/library/mac/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html
You need to specify the file protection level as an option when you write your files. Example:
[someData writeToFile:filePath options:NSDataWritingFileProtectionNone error:&error];
In this, someData is an NSData representation of the data you want to write to disk, filePath is the string path where you want to save the file.
Read the documentation on NSDataWritingOptions in the NSData Class Reference for more encryption options.