This happens on iOS 13.3 (iPhone 11)
I developed a react native iOS app which saves/loads data into/from keychain via https://github.com/mCodex/react-native-sensitive-info/blob/master/ios/RNSensitiveInfo/RNSensitiveInfo.m
My read/write keychain options is below, it never changes and works well since day1
const defaultOptions: RNSensitiveInfoOptions = {
touchID: true,
kSecAccessControl: 'kSecAccessControlBiometryAny',
kSecUseOperationPrompt: 'We will store data securely on your device.',
sharedPreferencesName: 'sensitive',
keychainService: 'sensitive',
};
Recently one of my clients complain about the data cannot be retrieved from keychain because of errSecNotAvailable = -25291, /* No keychain is available. You may need to restart your computer. */
I thought there might be something wrong happened to keychain, so I tried set a single key/value item with the same option (so the kSecAttrService and kSecClass are same with business related items): 'foo' -> 'bar'. And I can load this value out via the same option.
I did more experiences:
if I read a key doesn't exist, the keychain API will return error errSecItemNotFound -25300
if I read a business related key, the keychain API return error errSecNotAvailable -25291
I jailbreak this iPhone and use https://github.com/ptoomey3/Keychain-Dumper to dump the items but it prompt (although the phone is unlock):
[INFO] No Generic Password Keychain items found.
[HINT] You should unlock your device!
I copied Keychain-2.db from device to local computer and using sqlite DB to query the generic password items, looks like they are there (but as they are encrypted, i am not sure 100%):
I am expecting someone could suggest:
why this issue could happen?
is there any possibility I can restore the keychain data?
Related
In Apple Push Notification service (APNs), the server-side developer must choose the environment type (sandbox or production) as the HTTP/2 URL (api.sandbox.push.apple.com or api.push.apple.com). [1]
On the other hand, in Firebase Cloud Messaging (FCM) over APNs, there seems no explicit interface to specify the environment type. [2]
So I guess FCM somehow decide the environment type internally, but I have no idea about how it detects the environment type.
Does anyone have knowledge about it? Any insight would be helpful. Thanks!
Some pre-requisite information
The sandbox environment is always used when the app is built and ran through Xcode in debug, release or profile configurations, or if the app is distributed through development method.
The production environment is used when the app is distributed through App Store Connect (App Store and TestFlight), Ad Hoc and Enterprise methods. See "Distributing Your App for Beta Testing and Releases" for more information.
To find out what distribution mode/ APNs environment is used, you have to read the provisioning profile. On iOS, watchOS and tvOS, it is embedded.mobileprovision, and in macOS or Catalyst, it is embedded.provisionprofile. You cannot read App.entitlements, because that file isn't always available. Instead, embedded.mobileprovision contains a dictionary (in XML format). This is an example of this file I extracted from a test app. It contains, among other things:
<key>Entitlements</key>
<dict>
<key>aps-environment</key>
<string>development</string>
...
If you generate one yourself (archive the Xcode project), you can view the package contents of the xcarchive (/Users/username/Library/Developer/Xcode/Archives/2021-08-28/projectName\ 28-08-2021,\ 08.17.xcarchive/Products/Applications/projectName.app/embedded.mobileprovision), and it is shown nicely in the finder preview.
There is also a comment in the Firebase iOS SDK:
* #param type The type of APNs token. Debug builds should use
* FIRMessagingAPNSTokenTypeSandbox. Alternatively, you can supply
* FIRMessagingAPNSTokenTypeUnknown to have the type automatically
* detected based on your provisioning profile.
Firebase's solution
You could read FIRMessagingTokenManager.m, or read my analysis across different files:
In Firebase iOS SDK, If you pass don't pass a type (sandbox/ production) or explicitly pass FIRMessagingAPNSTokenTypeUnknown, this code runs:
if (type == FIRMessagingAPNSTokenTypeUnknown) {
isSandboxApp = FIRMessagingIsSandboxApp();
}
which is
BOOL FIRMessagingIsSandboxApp(void) {
static BOOL isSandboxApp = YES;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
isSandboxApp = !FIRMessagingIsProductionApp();
});
return isSandboxApp;
}
FIRMessagingIsProductionApp is a method that is 119 lines long. What does it do? It almost always defaults to a production app, theres a lot of Firebase specific configuration logic and checking if production if running on iOS simulator, if app is delivered AppStore or TestFlight
Fundamentally it checks embedded.provisionprofile or embedded.mobileprovision (that is how plistMap is generated):
// plistMap is loaded from the provisioning profile in a multi step process.
NSString *apsEnvironment = [plistMap valueForKeyPath:kEntitlementsAPSEnvironmentKey];
if ([apsEnvironment isEqualToString:kAPSEnvironmentDevelopmentValue]) {
return NO;
}
where they reference the following keys inside the provisioning profile:
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
static NSString *const kEntitlementsAPSEnvironmentKey = #"Entitlements.aps-environment";
#else
static NSString *const kEntitlementsAPSEnvironmentKey =
#"Entitlements.com.apple.developer.aps-environment";
#endif
static NSString *const kAPSEnvironmentDevelopmentValue = #"development";
If you're interested in seeing how the provisioning profile is read, read the source file.
Create full path to provisioning profile
Load data from file path
Clean the data (the provisioning profile contains 0 which "halts" the ASCII parser, or value larger than 127, which is invalid.)
Convert Data to String
Scan the string using NSScanner. They do this because the provisioning profile contains a lot more non-xml/ non-plist structure. Look at the example file.
Convert this string back into data.
Convert this data into a dictionary using NSPropertyListSerialization
Look for ProvisionedDevices key, if so, it's a dev profile.
Get environment from dictionary with kEntitlementsAPSEnvironmentKey
How does firebase servers know which endpoint to use?
Finally, once the Firebase iOS SDK knows that a device (and APNs device token) is running in production/ development, it can tell the APNs provider (their server which connects with APNs) to use the correct endpoint, either api.push.apple.com:443 and api.sandbox.push.apple.com:443 aka. api.development.push.apple.com:443 (its just a CNAME pointing to sandbox). This isProduction or isSandbox boolean will probably live with the APNs device token in a Firebase database.
I found the answer on the doc on FIRInstanceIDAPNSTokenType
http://cocoadocs.org/docsets/FirebaseInstanceID/1.0.6/Constants/FIRInstanceIDAPNSTokenType.html
The APNS token type for the app. If the token type is set to UNKNOWN InstanceID will implicitly try to figure out what the actual token type is from the provisioning profile.
Therefore, the answer is "the actual token type is determined by the provisioning profile", maybe by "aps-environment" key.
The FCM token we use for sending the notifications is generated by the SDK using multiple parameters. It contains information about the actual APNS device token, Project ID, Project URL endpoint on Firebase, App Bundle Identifier, etc (You get an invalid token error if you try to use some other token generated from different Firebase configuration). It stands to reason that the FCM token also takes into account the Environment used for compiling the application (DEBUG/RELEASE). Using this information, Firebase should be able to use the corresponding APNS gateways.
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!
The issue is with ios device when user uninstall and reinstall the app it will generate new UUID which is not unique.
On an android device we can get a unique device UUID.
Here i got the trick for it.It worked for me.
Use Keychain plugin
The Keychain plugin allows you to securely store data in the iOS
Keychain.
You can set, get, and remove values in the Keychain using this plugin.
Items stored in the Keychain survive uninstalls and reinstalls of your
app. The data is not available to any other app, unless it's signed
with the same provisioning profile. When a user backs up iOS data, the
keychain data is backed up, but the secrets in the keychain remain
encrypted in the backup.
The Keychain stores data as key-value pairs. You need to set and get
these pairs using the same servicename. If you set an existing key
with a new value, the old value will be overwritten
Phonegap Build user just use this plugin into config.xml file
<plugin name="cordova-keychain" source="npm" />
How to use it in App?
// prepare some variables
var servicename = 'MyAppUUID';
var key = 'MyApp';
var value = 'Your UUID'; // Set your Device Id here.
// prepare the callback functions
function onSuccess (msg) {alert(msg)};
function onError (msg) {alert(msg)};
// store your password in the Keychain
new Keychain().setForKey(onSuccess, onError, key, servicename, value);
// get your password from the Keychain
new Keychain().getForKey(onSuccess, onError, key, servicename);
// remove your password from the Keychain
new Keychain().removeForKey(onSuccess, onError, key, servicename);
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
Currently i have an application which has a "Remember Me" option for storing User ID.So to store this currently i am using Keychain APIs.
But i have a doubt if by chance device is stolen and somebody jailbreak the device. Can he able to get all these data from keychain?
How to prevent this ?
The most important thing when using the KeyChain is to not use kSecAttrAccessibleAlways or kSecAttrAccessibleAlwaysThisDeviceOnly because then data is not encrypted securely (see Apple's documentation). Not using these adds a layer of security to KeyChain data, but still, a strong passcode would be required by the user to protect his data. If the user has no passcode on the device, the data is unprotected. If the user has a 4-digit passcode (the standard), the data is protected very weakly and can be brute forced in minutes.
If you require protection from jailbreak (and other attacks), your best option is to not use the KeyChain, but create an encrypted sensitive data store of your own and require the user to have a secure passcode. Store the data encrypted using a key generated from that passcode.
This could inconvenience your users, so if you wish to provide a grace period between requiring passcode, think of a way to provide a session cookie to the app which is invalidated after a set period of time.
To be extra safe I'd add another layer of security on top of everything and make a simple check if the device is jailbroken. If that's the case I'd delete the current KeyChain \ sensitive data.
Something like that:
NSString *filePath = #"/Applications/Cydia.app";
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
//Device is jailbroken --> delete KeyChain
}
Or even better:
FILE *f = fopen("/bin/bash", "r");
BOOL isbash = NO;
if (f != NULL)
{
//Device is jailbroken --> delete KeyChain
isbash = YES;
}
fclose(f);
Here is the best way for checking if Device jailbroken
Code that checks
bool forked = fork();
if (forked) {
// Device is jailbroken
}
Check this link Keychain Items, where you can enumerate all keychain items.
You can also use Protection Attributes for securing info.
Apple Docs
Good Read