Note this question is was asked in 2001. Things have changed.
I have an iOS device that needs to access a Junos VPN. The opaque instructions from the Junos admin say that I have to retrieve a certificate that has been provisioned to the device using the Apple IPCU. I know that the cert is on the device (I can see it in Settings) and I can access the VPN though Mail, Safari and the Junos App.
The Apple docs state that each app has its own keychain and yet all three of these apps can see the cert. The fact that Jusos can access a cert provisioned by IPCU implies that any app can access this certificate. However when I try to locate it:
CFTypeRef certificateRef = NULL; // will hold a ref to the cert we're trying to retrieve
const char *certLabelString = "myCertificateName"; // c string of the certificate we're searching for.
CFStringRef certLabel = CFStringCreateWithCString( NULL, certLabelString, kCFStringEncodingUTF8); // the search we need - a string match for a UTF8 String.
const void *keys[] = { kSecClass, kSecAttrLabel, kSecReturnRef };
const void *values[] = { kSecClassCertificate, certLabel, kCFBooleanTrue };
CFDictionaryRef dict = CFDictionaryCreate(NULL, keys, values, 3, NULL, NULL); // set up a search to retrieve this certificate.
OSStatus status = SecItemCopyMatching(dict, &certificateRef); // Search the keychain, returning in dict
if(status != errSecSuccess)
NSLog(#"keychain find returned %ld", status);
if(dict)
CFRelease(dict);
It fails. My questions:
Is this code correct? Actually I know
it isn't because
SecItemCopyMatching returns
errSecItemNotFound
What value should I use for
certLabelString - I am assuming the
human readable name shown in
Settings.
In Settings, the cert looks like this (sadly obfuscated to death) the search text I specify is exactly the text shown in settings.
Cross posted to Apple developer forums
So the answer (on the Apple forums) is that mail.app and Safari.app share the Apple keychain identifier and this is the only keychain that you can push certificates to using the Apple MDM tool. Anyone else who comes up against this should file a defect in order to encourage Apple to do the right thing.
Since middle of 2015, there is now the Safari Services framework (next to WKWebView and UIWebView, we now have a SFSafariViewController). SFSafariViewController has the ability to access the apple keychain and therefore can use all identities :) Very nice.
https://developer.apple.com/videos/play/wwdc2015/504/
https://developer.apple.com/library/ios/documentation/SafariServices/Reference/SafariServicesFramework_Ref/index.html#//apple_ref/doc/uid/TP40016218
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.
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?
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 have already done this.
1. Add IOS certificate in Apple Developer page.
2. Add identifiers App IDs with Network Extension enabled in Apple Developer page.
3. Add provisioning profile in Apple Developer page.
4. Create the Xcode project and type the correct bundle ID which made on the apple developer page. And sign right team.
5. Add capabilities and library (Network Extension framework).
6. After 5, Entitlement file is generated automatically in my Xcode project.
7. At last time I wrote the code like this.
NSLog(#"List Scan START");
NSMutableDictionary* options = [[NSMutableDictionary alloc] init];
[options setObject:#"Try Here" forKey:kNEHotspotHelperOptionDisplayName];
dispatch_queue_t queue = dispatch_queue_create("com.miro.wifilist", 0);
BOOL isAvailable = [NEHotspotHelper registerWithOptions:options queue:queue handler: ^(NEHotspotHelperCommand * cmd) {
if (cmd.commandType == kNEHotspotHelperCommandTypeEvaluate || cmd.commandType == kNEHotspotHelperCommandTypeFilterScanList ) {
for (NEHotspotNetwork* network in cmd.networkList) {
NSLog(#"%#", network.SSID);
}
} else {
NSLog(#"there is no available wifi");
}
}];
if (isAvailable) {
NSLog(#"true");
} else {
NSLog(#"false");
}
NSLog(#"List scan END");
return #"";
But I can't get any wifi list. In my source, variable "isAvailable" is returned false.
To resolve this problem I tried this additionally.
Add "com.apple.developer.networking.HotspotHelper" with boolean type and "true"
-> result of this: I can build the project, but I can't install the app on my device. I get an error message like this "The executable was signed with invalid entitlements".
I don't know why is not working.
Do I miss something to authenticate to use the Network Extension library or NEHotspotHelper??
Or is there any error in my source??
**
After I requested the Network Extension library to apple, I got the message like this from apple.
message content from apple
Thank you for requesting information about the Network Extension framework. Please note that as of November 10, 2016, this process is not required for developers who wish to use App Proxy, Content Filter, or Packet Tunnel APIs. To use these services please navigate to your Developer Account at https://developer.apple.com/account/ and select the Network Extension capability for the App ID you will be using for your app.
If you are requesting an entitlement for Hotspot Helper APIs your request will be addressed at our earliest convenience.
Regards,
Developer Technical Support
Apple Worldwide Developer Relations
It's worth highlighting that it's not actually possible to get a list of available Wifi hotspots on an iOS device. Please have a read of this post:
Apple Developer Forums: List available wifi network
You need to complete a questionnaire at https://developer.apple.com/contact/network-extension, and then you can use NEHotspotHelper to return a list of hotspots.
Apple sends this message:
In your provisioning profile settings in Apple Developer page take an expanded view (Add "com.apple.developer.HotspotHelper" with boolean type and "true"), how this
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