How to use EndpointSecurity API properly in MacOS 10.15? - macos

I added entitlement and disable SIP :com.apple.developer.endpoint-security.client still get ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED,How to use
EndpointSecurity API properly under Xcode 11?Do I need to run as root?
Code for your reference:
#import <EndpointSecurity/EndpointSecurity.h>
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
es_client_t *client;
es_new_client_result_t res = es_new_client(&client, ^(es_client_t *client, const es_message_t *message) {
NSLog(#"Hi");
});
if (res == ES_NEW_CLIENT_RESULT_SUCCESS){
NSApplication.sharedApplication.keyWindow.contentView.layer.backgroundColor = NSColor.redColor.CGColor;
NSLog(#"ES_NEW_CLIENT_RESULT_SUCCESS");
}else if(res == ES_NEW_CLIENT_RESULT_ERR_INTERNAL){
NSLog(#"ES_NEW_CLIENT_RESULT_ERR_INTERNAL");
}else if(res == ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED){
NSLog(#"ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED");
}else if(res == ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED){
NSLog(#"ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED");
}else if(res == ES_NEW_CLIENT_RESULT_ERR_INVALID_ARGUMENT){
NSLog(#"ES_NEW_CLIENT_RESULT_ERR_INVALID_ARGUMENT");
}
BOOL isSubscribeSuccess = es_subscribe(client, ES_EVENT_TYPE_NOTIFY_OPEN);
if (isSubscribeSuccess == true){
NSLog(#"isSubscribeSuccess");
}else{
NSLog(#"SubscribeErrpr");
}
}
In entitlements:
<key>com.apple.developer.endpoint-security.client</key>
<true/>

Yes, all you need is run as root.
Or set the debug process as root by tweaking scheme settings. (Product > Scheme > Edit Scheme...)

According to EndpointSecurity/ESTypes.h:
///The caller is not permitted to connect. They lack Transparency,
Consent, and Control (TCC) approval form the user.
So, ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED indicates that your process was not approved by the user as is required for using EndpointSecurity API. The documentation for es_new_client in EndpointSecurity/ESClient.h states:
The application calling this interface must also be approved by users
via Transparency, Consent & Control (TCC) mechanisms using
the Privacy Preferences pane and adding the application to Full Disk
Access.
A dialog asking the user for permission should come up automatically when you call EndpointSecurity API from a system extension. Otherwise, the user must allow its use manually. BTW, for testing purposes with a command line tool calling EndpointSecurity API I had to add Terminal to Full Disk Access.

Related

Camera and location permission at runtime in Xamarin forms

I have created a Xamarin forms application and it requires two permission i.e i)Location and ii)Camera.
I need to get both permissions at runtime but unfortunately I am able to get only one permission at a time when app starts.
Is there any solution to request both permission simultaneously at runtime.
It's a good advice not to ask for permissions when the application starts, just because the user might not know what is it for.
Also check and ask for permission when you need it. For example, ask for Camera permission just before opening the camera.
You can do all this using Xamarin.Essentials. If your project does not have it, follow the documentation instructions to set it up.
And then you can do everything in your shared code (for all platforms!)
There are tons of examples, I will write one. You can ask for the permissions one after the other, or in two different moments
public async void GoToCameraPage()
{
var status = await Permissions.CheckStatusAsync<Permissions.Camera>();
if (status == PermissionStatus.Granted)
{
//We have permission!
await Navigation.PushAsync(new CameraPage());
}
else
{
//Let the user know why
await DisplayAlert("Permission needed", "I will need Camera permission for this action", "Ok");
//Ask for the permission
status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
if (status == PermissionStatus.Granted)
{
//YES, now we have permission
}
else
{
//Ok, maybe I will ask again
}
}
}

Xamarin.Forms android check whether user clicked Deny and Don't ask again

In my xamarin.forms app. I am using xamarin.Essentials to check and request permissions. I can check whether a permission is granted or not in android. The problem I am facing is when user clicks "Deny and Don't ask again" the status still shows Denied. How can we know whether user selected Deny and Dont ask so that I can show a warning message according to that. Any help is appreciated.
For check and request permission in Xamarin.Forms you could also use the plugin Permission.Plugin from nuget .
The option Deny and Don't ask again will only appear when you deny the request the second time . However , there is no such a specific status to tag it .
As a workaround , we could define a custom property to save the time that we deny .
int DenyCount = 0;
private async void FabOnClick(object sender, EventArgs eventArgs)
{
var status = await CrossPermissions.Current.CheckPermissionStatusAsync<LocationPermission>();
if (status != PermissionStatus.Granted)
{
if (await CrossPermissions.Current.ShouldShowRequestPermissionRationaleAsync(Permission.Location))
{
//await DisplayAlert("Need location", "Gunna need that location", "OK");
DenyCount++;
if(DenyCount>=2)
{
//...
}
}
status = await CrossPermissions.Current.RequestPermissionAsync<LocationPermission>();
}
if (status == PermissionStatus.Granted)
{
//Query permission
DenyCount = 0; // reset the value of DenyCount
}
else if (status == PermissionStatus.Restricted)
{
//location denied
}
}
Xamarin.Essentials provides a method for detecting whether the user has clicked to "don't ask again". You can check if a dialog should be shown by calling ShouldShowRationale:
var canAskAgain = Xamarin.Essentials.Permissions.ShouldShowRationale<Permission_Type>();
var didCheckDontAskAgain = !canAskAgain;
If this is true, that means the user did not click "Deny and Don't Ask Again".
Also, note that on Android 12 (and maybe 11, not sure), if you click "Deny" mulitple times, Android assumes that you mean "Don't ask again". The user may never explicitly check that option, but it may be inferred by Android.

Facebook SDK inside iOS app extension cannot find logged in user?

I've created a sample app Share extension. Followed the Apple guides, which means my project consists of a main app and a "share extension" target.
I've setup my Facebook SDK inside the main app, since the app's settings has some FB login/status functionality. It works well according to expectation: users can login and do some shares.
But I also want the logged-in user to be available to the extension target itself. When my extension comes up (in any app), I check Facebook's login status in viewDidLoad:, and it outputs "not logged in":
if ([FBSDKAccessToken currentAccessToken]) {
NSLog(#"logged in");
} else {
NSLog(#"not logged in");
}
The same code outputs "logged in" if called from within the main app. I suspect it has something to do with the fact that the extension target has a different bundle ID that looks like this: .suffix and I guess FB SDK is trying to read the user ID off the keychain cache, but maybe it's reading it off the wrong keychain due to the different bundle IDs... But it could be other reasons as well I guess.
Any idea how to keep Facebook SDK "logged in" inside the extension after the login itself occurred in the containing main app?
I have the exact same problem, as I develop a Message Extension App with a Facebook Login.
To solve the problem, I put the Login Button in the App and use the shared UserDefaults (with a group name) to store a property :
/*
* Get / sets if is FB connected (because we can't use FB SDK in Extension)
*/
public var isFacebookConnected : Bool {
get {
// Getting Bool from Shared UserDefaults
let defaults : NSUserDefaults = NSUserDefaults.init(suiteName: "MY_GROUP_NAME")!
let isFBConnected : Bool? = defaults.boolForKey("IsFacebookConnected")
if isFBConnected == nil {
return false
} else {
return isFBConnected!
}
}
set {
// Setting in UserDefaults
let defaults : NSUserDefaults = NSUserDefaults.init(suiteName: "MY_GROUP_NAME")!
defaults.setBool(newValue, forKey: "IsFacebookConnected")
defaults.synchronize()
}
}
This property is accessed from the App or from the Extension.
Then, in my Login / Logout methods I add :
//FBLogin
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
if let error = error {
print(error.localizedDescription)
return
}
if result.isCancelled
{
return
}
// Set in Shared UserDefaults if connected / not connected
ConfigManager.sharedInstance.isFacebookConnected = true
...
}
func loginButtonDidLogOut(loginButton: FBSDKLoginButton!) {
// OK, keeping track
ConfigManager.sharedInstance.isFacebookConnected = false
}
Then, I keep track of my flag when login / logout.
Warning : this method is not bullet proof as I can't know if Facebook has been uninstalled, but it is robust enough for me.
I believe you'll need to take advantage of Keychain sharing capability in the app and extension to solve this.
https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps

Keychain Access as root user from MAC Application

I am working on MAC Application to create VPN Connection from MY Application.
After a lot of Research i found that i need to run application as ROOT to store User password and sharedSecretKey in SYSTEM keychain.
Application user will not gonna open application as ROOT so that i need to add user password and sharedsecretkey in SYSTEM KEYCHAIN without ROOT Access.
I search on web on this and found that Apple Provide this code : https://developer.apple.com/library/mac/samplecode/SMJobBless/Introduction/Intro.html
https://developer.apple.com/library/mac/samplecode/EvenBetterAuthorizationSample/Introduction/Intro.html
but didn't understand how can i use this 2 code in my application to store user's password and SharedSecretKey in SYSTEM KEYCHAIN WITH OUT ROOT ACCESS.
Any help will be appreciated.
Thanks in advance.
Here is my code to add Password in SYSTEM KEYCHAIN Which is work great if i run my code as ROOT.
// Vendor dependencies
#import <Security/SecKeychain.h>
// Local dependencies
#import "VPNKeychain.h"
// These are the applications which are going to get access to new Keychain items.
// How do we know them? Just create a VPN service manualy and run the following command:
// security dump-keychain -a /Library/Keychains/System.keychain
// Among the results, you will find your VPN service and you can see the paths that have access to it
static const char * trustedAppPaths[] = {
"/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/Helpers/SCHelper", "/System/Library/PreferencePanes/Network.prefPane/Contents/XPCServices/com.apple.preference.network.remoteservice.xpc",
"/System/Library/CoreServices/SystemUIServer.app",
"/usr/sbin/pppd",
"/usr/sbin/racoon",
"/usr/libexec/configd",
};
// This class contains all code we need to handle System Keychain Items
// Exit status codes: 60-79
#implementation VPNKeychain
// This will create a PPP Password Keychain Item
+ (int) createPasswordKeyChainItem:(NSString*)label forService:(NSString*)service withAccount:(NSString*)account andPassword:(NSString*)password {
return [self createItem:label withService:service account:account description:#"PPP Password" andPassword:password];
}
// This will create an IPSec Shared Secret Keychain Item
+ (int) createSharedSecretKeyChainItem:(NSString*)label forService:(NSString*)service withPassword:(NSString*)password {
service = [NSString stringWithFormat:#"%#.SS", service];
return [self createItem:label withService:service account:#"" description:#"IPSec Shared Secret" andPassword:password];
}
// A generic method to create Keychain Items holding Network service passwords
+ (int) createItem:(NSString*)label withService:(NSString*)service account:(NSString*)account description:(NSString*)description andPassword:(NSString*)password {
// This variable will hold all sorts of operation status responses
OSStatus status;
// Converting the NSStrings to char* variables which we will need later
const char *labelUTF8 = [label UTF8String];
const char *serviceUTF8 = [service UTF8String];
const char *accountUTF8 = [account UTF8String];
const char *descriptionUTF8 = [description UTF8String];
const char *passwordUTF8 = [password UTF8String];
// This variable is soon to hold the System Keychain
SecKeychainRef keychain = NULL;
status = SecKeychainCopyDomainDefault(kSecPreferencesDomainSystem, &keychain);
if (status == errSecSuccess) {
NSLog(#"Succeeded opening System Keychain");
} else {
NSLog(#"Could not obtain System Keychain: %#", SecCopyErrorMessageString(status, NULL));
return 60;
}
NSLog(#"Unlocking System Keychain");
status = SecKeychainUnlock(keychain, 0, NULL, FALSE);
if (status == errSecSuccess) {
NSLog(#"Succeeded unlocking System Keychain");
} else {
NSLog(#"Could not unlock System Keychain: %#", SecCopyErrorMessageString(status, NULL));
return 61;
}
// This variable is going to hold our new Keychain Item
SecKeychainItemRef item = nil;
SecAccessRef access = nil;
status = SecAccessCreate(CFSTR("Some VPN Test"), (__bridge CFArrayRef)(self.trustedApps), &access);
if(status == noErr) {
NSLog(#"Created empty Keychain access object");
} else {
NSLog(#"Could not unlock System Keychain: %#", SecCopyErrorMessageString(status, NULL));
return 62;
}
// Putting together the configuration options
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},
};
SecKeychainAttributeList attributes = {sizeof(attrs) / sizeof(attrs[0]), attrs};
status = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &attributes, (int)strlen(passwordUTF8), passwordUTF8, keychain, access, &item);
if(status == noErr) {
NSLog(#"Successfully created Keychain Item");
} else {
NSLog(#"Creating Keychain item failed: %#", SecCopyErrorMessageString(status, NULL));
return 63;
}
return 0;
}
+(NSArray*) trustedApps {
NSMutableArray *apps = [NSMutableArray array];
SecTrustedApplicationRef app;
OSStatus err;
for (int i = 0; i < (sizeof(trustedAppPaths) / sizeof(*trustedAppPaths)); i++) {
err = SecTrustedApplicationCreateFromPath(trustedAppPaths[i], &app);
if (err == errSecSuccess) {
//NSLog(#"SecTrustedApplicationCreateFromPath succeeded: %#", SecCopyErrorMessageString(err, NULL));
} else {
NSLog(#"SecTrustedApplicationCreateFromPath failed: %#", SecCopyErrorMessageString(err, NULL));
}
[apps addObject:(__bridge id)app];
}
return apps;
}
In OS X, applications do not handle users' credentials directly, but instead request the system to do so, via the function call AuthorizationCopyRights, which is documented in Authorization Services.
A Gui application cannot directly perform administrative (root) actions and since Yosemite (10.10), Gui applications cannot run as root. Instead, your application must use a 'helper' application via XPC services, which is what the SMJobBless and BetterAuthorization samples demonstrate. You can read more about XPC here.
In your case, you would need to create such a helper application, which will have the necessary rights to access the system keychain.
Note that if you plan to distribute your application via the Apple Store, the application must be sandboxed and cannot use any security services, such as calling the function AuthorizationCopyRights.
It is explained in the sample code that you link to, see ReadMe.txt:
Once you run the sample you'll be prompted for an admin user name and
password. Enter your admin user name and password and, if all goes
well, the sample's window will show "The Helper Tool is available!"
indicating that everything is OK. If not, you can look in the console
log for information about the failure.
So generally, your application will have to ask for admin credentials at some point.
Update:
This should be done through a privileged helper tool, as demonstrated in cited SMJobBless example. Your helper tool should perform keychain access for your app. Here are main steps to install such helper tool:
Create authorisation object with AuthorizationCreate function.
Perform preauthorisation on the object with given set of rights using
AuthorizationCopyRights function. This will in fact result in asking your user for admin credentials.
Verify, install and register helper tool with launchd using
SMJobBless function.
Once the helper tool is installed and registered you should use NSXPCConnection to talk to your helper tool. See Sandboxing with NSXPCConnection sample code for details on how to achieve it.

Is it possible to check whether the location services are active?

is it possible to check whether the location services are active?
I mean Settings > Location > Location services
There is probably no direct API for calling, but could it work with the GeoCoordinateWatcher?
GeoCoordinateWatcher g = new GeoCoordinateWatcher();
g.Start();
if (g.Permission.Equals(GeoPositionPermission.Granted))
{
//Your location services is enabled. Go ahead.
//Your codes goes here.
}
else if (g.Permission.Equals(GeoPositionPermission.Denied) || g.Permission.Equals(GeoPositionPermission.Unknown))
{
MessageBox.Show("Location services are disabled. To enable them, Goto Settings - Location - Enable Location Services.", "Location services", MessageBoxButton.OK);
}
You can use the following code to determine the status of the Location service:
var watcher = new GeoCoordinateWatcher();
if (GeoPositionStatus.Disabled == watcher.Status)
{
// Watcher is disabled.
}
More realistically, you'll want to pay more attention to change to the status (just because the service isn't disabled doesn't mean you've got location data), so you shoudl take a look at the MSDN Documentation for working with the Location service.
There's also a good post on filtering and emulating location data using the Reactive extensions, which is perfect for that pre-device testing, though to save you time on that front the Widnows Phone Team have released the Windows Phone GPS Emulator.
Even with the started GeoCoordinateWatcher you will get NoData if the sensor is disabled. What you should try using instead is TryStart:
GeoCoordinateWatcher g = new GeoCoordinateWatcher();
MessageBox.Show(g.TryStart(false,TimeSpan.FromSeconds(30)).ToString());
If it returns False, it means that the sensor is disabled. If it returns True, it is enabled. Set an appropriate timeout period (in the snippet above I am using 30 seconds) and delegate this process to a secondary thread, so it won't hang the UI.
You can add a StatusChanged event to your GeoCoordinateWatcher and test for GeoPositionPermission.Denied in the permissions when it fires.
watcher = new GeoCoordinateWatcher();
watcher.StatusChanged += new EventHandler<GeoPositionStatusChangedEventArgs>(watcher_StatusChanged);
watcher.Start();
void watcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
{
if (watcher.Permission == GeoPositionPermission.Denied)
{
// Location services were disabled
}
}
Hope that helps.
Made this one based on TeJ's answer.
public override void OnNavigatedTo()
{
using (var watcher = new GeoCoordinateWatcher())
{
try
{
watcher.Start();
}
finally
{
IsAllowedInSystem = watcher.Permission.Equals(GeoPositionPermission.Granted);
watcher.Stop();
}
}
}
And my apps' ToggleSwitch.IsEnabled is binded to IsAllowedInSystem.
When i'm switching to Location Service, disable it and return back to app, my ToggleSwitch is disabled (also a string "Please, enable Location Service in System settings" in visible). When i'm switching to Location Service, enable it and return back to my app, my ToggleSwitch is enabled and user can set it up.

Resources