I'm trying to develop a quick command line tool that will install a root CA into a MacOS System keychain. This is a chunk of the code that does the job.
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData);
SecKeychainRef keychain = nil;
OSStatus status = SecKeychainCopyDomainDefault(kSecPreferencesDomainSystem, &keychain);
if(status != errSecSuccess)
{
// log the error
return;
}
status = SecKeychainUnlock(keychain, 0, NULL, TRUE);
if(status != errSecSuccess)
{
// log the error
return;
}
status = SecTrustSettingsSetTrustSettings(certificate, kSecTrustSettingsDomainSystem, NULL);
NSDictionary * addCertificateQuery = #{(id)kSecValueRef: (__bridge id)certificate,
(id)kSecClass: (id)kSecClassCertificate,
(id)kSecAttrLabel: CERT_ATTRIBUTE_LABEL,
(id)kSecUseKeychain: (__bridge id)keychain,
(id)kSecReturnRef: #YES,
};
status = SecItemAdd((__bridge CFDictionaryRef)addCertificateQuery, NULL);
When using the XCode debugger as root, the certificate is correctly added to System Keychain, but when debugging as user (user has Admin privileges) and after getting prompted for a username and password of a user with Admin privileges for some reason SecItemAdd returns error -61: errSecWrPerm (No writing permissions for user).
I've been trying to find some documentation that explains why this happens but still haven't got a clue. So my question is: is there a particular reason why a user with admin privileges cannot add a certificate to System? Is writing on System only reserved to root?
I've also tried changing /Library/Keychains/System.keychain permissions but had the same result.
A user with "admin" privileges is still only a normal, non-root user, but one which is allowed to perform certain operations as root, for example under 'sudo' or the MacOS authorization prompts (which would enable certificate additions, but would require a user prompt and registration with the database at /var/db/auth.db). Authorizations can be popped up using AuthorizationExecuteWithPrivileges, with a good example being "security execute-with-privileges", which pops up the authentication and uses /usr/libexec/security_authtrampoline - a Setuid root binary.
You're right about writing to System being reserved for root only, and the situation becomes even more complication with System Integrity Protection (SIP), and with MacOS's move (in 10.15) to a read-only mounted root filesystem. The former (SIP) adds protections such that, without the proper com.apple.rootless.* entitlement you won't be able to modify files, even as root, and the latter (r/o mount) means that nothing (short of remounting, and/or holding system installer entitlement) won't enable you to write anything.
The "direct" approach you specify, of modifying the files in /Library/keychains will fail because note that the keychain file ownership is root's.
Adding certificates programmatically will require either root, or the com.apple.private.system-keychain entitlement (or one of its siblings - I forget which - see http://newosxbook.com/ent.jl?ent=com.apple.private.system-keychain&osVer=MacOS13 for a full list of entitlements).
Also note, that messing with the keychain could have some serious impact. This is where everything that is secret and holy to macOS - root certs, passwords, Apple ID, etc - is held. Beware. Once a root CA is installed, it can be trusted for all sorts of applications (including, to some extents, code signing, website proxying, and even more sinister uses). So there's a pretty good reason it's not given too just any user - since a batch operation without a prompt could surreptitiously inject such a CA, and open up avenues for malware.
References: "*OS Internals, Volume III", which deals with this in Chapter 11 and the authorizations in chapter 6. Website: newosxbook.com
Related
I want to know how the role based authorization works in FIWARE Keyrock. I have tested a scenario where a user A registers an application appA in Keyrock. The user B that is not on the authorized list for application appA can request a token for another application (appB, for example) and successfully access the appA with the token obtained from appB.
Another test performed was to include user A in the authorized list for appA, but with a role that has no permissions. Again, the user A gets access to appA with credentials from another application.
Can anyone explain me how this work, if it really work?
As #Álvaro said in comments, we can see an example of this configuration at this video.
When I saw this video, previously, I had ignored the exact part of permission configuration at Keyrock, because it was not of my interest. Now, I am interested in this functionality and I had forgot that this video presents such information.
Besides, below I put what I had to do for things work:
Install AZF:
sudo apt-get install openjdk-7-jdk (if you dont have)
sudo apt-get install tomcat7
wget http://repo1.maven.org/maven2/org/ow2/authzforce/authzforce-ce-server-dist/5.4.1/authzforce-ce-server-dist-5.4.1.deb (authzforce)
sudo apt-get install gdebi curl
sudo gdebi authzforce-ce-server-dist-5.4.1.deb
Configure Wilma PEP (config.js file):
config.azf = {
enabled: true,
protocol: 'http',
host: '10.30.0.21', //this is your authzforce ip
port: 8080, //6019,
custom_policy: undefined // use undefined to default policy checks (HTTP verb + path).
};
Configure Keyrock (local_settings.py file, located in /horizon/openstack_dashboard/local/local_settings.py)
ACCESS_CONTROL_URL = 'http://10.30.0.21:8080'
ACCESS_CONTROL_MAGIC_KEY = None # If you have problems, put something instead of None. Currently there is a reported bug related to this.
Remember to restart the services. To let the things work, you need to create the specific permission to the right endpoint of the application you want to secure/access. Once it is created, the AZF will be consulted by the Wilma PEP Proxy.
I hope it helps someone.
I'm using FiddlerCore to capture HTTP requests. Everything is working including SSL Captures as long as the Fiddler certificate is manually installed. I've been using manual installation through Fiddler's Options menu and that works fine.
However, if I use the FiddlerCore provided CertMaker class static methods to add the Fiddler certificate I find that I can use the certificate added to the cert root only in the current session. As soon as I shut down the application and start back up, CertMaker.rootCertExists() returns false.
I use the following code to install the certificate for the current user (from an explicit menu option at this point):
public static bool InstallCertificate()
{
if (!CertMaker.rootCertExists())
{
if (!CertMaker.createRootCert())
return false;
if (!CertMaker.trustRootCert())
return false;
}
return true;
}
The cert gets installed and I see it in the root cert store for the current user. If I capture SSL requests in the currently running application it works fine.
However, if I shut down the running exe, restart and call CertMaker.certRootExists() it returns false and if I try to capture SSL requests the SSL connection fails in the browser. If I recreate the cert and then re-run the requests in the browser while the app stays running it works again. I now end up with two certificates in the root store.
After exiting and relaunching certMaker.certRootExists() again returns false. Only way to get it to work is to register the cert - per exe session.
What am I doing wrong to cause the installation to not stick between execution of the same application?
I was able to solve this problem and create persistent certificates that are usable across EXE sessions, by removing the default CertMaker.dll and BcMakeCert.dll assemblies that FiddlerCore installs and using and distributing the makecert.exe executable instead.
makecert.exe appears to create certificates in such a way that they are usable across multiple runs of the an application, where the included assemblies are valid only for the current application's running session.
Update:
If you want to use the CertMaker.dll and BcMakeCert.dll that FiddlerCore installs by default, you have to effectively cache and set the certificate and private key, using Fiddlers internal preferences object. There are a couple of keys that hold the certificate after it's been created and you need to capture these values, and write them into some sort of configuration storage.
In the following example I have a static configuration object that holds the certificate and key (persisted to a config file when the app shuts down):
public static bool InstallCertificate()
{
if (!CertMaker.rootCertExists())
{
if (!CertMaker.createRootCert())
return false;
if (!CertMaker.trustRootCert())
return false;
// persist Fiddlers certificate into app specific config
App.Configuration.UrlCapture.Cert =
FiddlerApplication.Prefs.GetStringPref("fiddler.certmaker.bc.cert", null);
App.Configuration.UrlCapture.Key =
FiddlerApplication.Prefs.GetStringPref("fiddler.certmaker.bc.key", null);
}
return true;
}
public static bool UninstallCertificate()
{
if (CertMaker.rootCertExists())
{
if (!CertMaker.removeFiddlerGeneratedCerts(true))
return false;
}
// persist Fiddlers certificate into app specific config
App.Configuration.UrlCapture.Cert = null;
App.Configuration.UrlCapture.Key = null;
return true;
}
After installing a certificate this code captures the certificate and private key into the configuration object which persists that value later. For uninstallation, the values are cleared.
At the beginning of the application or the beginning of the capture process, prior to calling CertMaker.rootCertExists() the keys are set from the configuration values. I do this at the beginning of my capture form:
public FiddlerCapture()
{
InitializeComponent();
// read previously saved Fiddler certificate from app specific config
if (!string.IsNullOrEmpty(App.Configuration.UrlCapture.Cert))
{
FiddlerApplication.Prefs.SetStringPref("fiddler.certmaker.bc.key",
App.Configuration.UrlCapture.Key);
FiddlerApplication.Prefs.SetStringPref("fiddler.certmaker.bc.cert",
App.Configuration.UrlCapture.Cert);
}
}
Using this mechanism for saving and then setting the capture settings makes the certificates persist across multiple EXE sessions when using CertMaker.dll.
More detailed info is available this detailed blog post on FiddlerCore.
If anyone is still interested, I found an easier solution based on the demo that Fiddler provides. This demo simply calls CertMaker.trustRootCert(), and strangely enough, it sticks! The first time it will ask whether you want to install the certificate, but after that, the function just returns true and will not cause the pop-up to show.
Unlike your and mine original program, the certificate sticks without having to go to the trouble of letting it stick yourself, so I analysed the differences with the demo. One of the differences I noticed was that the demo didn't have a reference to CertMaker.dll and BCMakeCert.dll. After removing these references from my own solution, I got the same behaviour as the demo.
Unfortunately, I don't have an explanation to why this works, but I hope this still helps some people.
I am attempting to use the cpanel api2 to modify the zone file for one of the cpanel accounts. Below is the code I'm executing.
$xmlapi = new xmlapi($ip);
$xmlapi->set_host("192.168.1.1");
$xmlapi->set_port("2087");
$xmlapi->set_protocol('https');
$xmlapi->password_auth($cpusername,$cpuserpwd);
$xmlapi->hash_auth("root",$root_hash);
#print_r ($xmlapi);
$xmlapi->set_output("json");
$xmlapi->set_debug(1);
$xmlapi->api2_query($cpaccount, "ZoneEdit", "add_zone_record" , array($domain, $subdomain, $type, $ipaddr, $ttl));
The commented code authenticates using the system hash. The other using password authentication. I get the same error on both authentication methods.
When I set the user id and account to root, I get an internal 500 error:
Internal Server Error 500
Could not setuid to root at /usr/local/cpanel/Whostmgr/XMLUI/cPanel.pm line 82.
If I change the userid and the account that the zone file is for, I get the message:
{
"cpanelresult":{
"apiversion":2,
"func":"add_zone_record",
"data":[{
"result":{
"status":0,
"statusmsg":"You do not have permission to read the zone for : "
}
}],
"event":{
"result":1
},
"module":"ZoneEdit"
}
}
I'm stumped on what do next. When I login at the command line as one of the non-root accounts, I can su to root once I enter the root password.
Any assistance would be appreciated.
To close this out. With no solution in site, ended up writing a php script to generate the zone file from scratch with appropriate SOA serial number management, and scheduling a cron job to run as root to replace the zone file and reload it.
Put another way, never got the Cpanel interface to work so write it from scratch.
And if anyone is curious about the final generalized implementation, it is the smartDNS feature on my website and described in the blog.
My Mac application in Xcode has recently begun exhibiting strange symptoms when attempting to access iCloud. There haven't been any changes to my provisioning profiles, code signing identities, etc.
However, when running this code:
id token = [fileManager ubiquityIdentityToken];
NSLog(#"Token is: %#", token);
NSURL *iCloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];
NSLog(#"iCloud URL is: %#", iCloudURL);
The output is:
2013-10-28 08:17:12.372 MyApp[21101:303] Token is: (null)
2013-10-28 08:17:12.373 MyApp[21101:303] iCloud URL is: (null)
Which I find extremely strange, especially considering I am actually signed into iCloud on this machine. To be sure, I ran a quick test with Calendar, adding an event on an iPhone and ensuring that it showed up on the Calendar on my Mac.
But I was under the impression that [fileManager ubiquityIdentityToken]; would return whether the user was logged into iCloud, regardless of whether your entitlements, code signing, etc. was properly configured - indicating that it's less an issue on my end, and more an issue of the system's ability to return this value.
I have tried logging out (and back in) to iCloud, and have ensured that "Documents & Data" is enabled in the iCloud settings.
This was resolved by upgrading from the original Mavericks GM (build 13A598) to the official release (build 13A603.)
I'm trying to run a process with elevated privileges - specifically OpenVPN, which requires root privileges to add routes to the system.
Looking around for existing examples around leads me to AuthorizationExecuteWithPrivileges, which seems to be now deprecated.
I tried the new SMJobBless method but I have a few questions regarding its viability for this purpose. As I understand it, I can create a separate privileged tool and communicate with it via sockets to ask the tool to perform privileged commands. However, I can't seem to figure out how I can start the OpenVPN process and capture its standard output in real time doing it this way as the main application would not be starting the process itself.
Another option is to use setuid on the OpenVPN executable. Could I possibly use the helper installed by SMJobBless to set the file permissions and setuid on the executable, then run it normally via NSTask?
Edit:
Lastly is there some way to just run one single command with privileges without having to install anything permanently? Although this new method is more secure, it seems very heavy handed.
I managed to go the SMJobBless method by using a helper and communicating it with XPC (the method shown on Nathan de Vries's Blog). Using this helper I set the permissions on the external process to 04555 (setuid, rx). Then the SMJob is removed as it is no longer required. Essentially emulating an "one-off" privileged job.
Following that I was able to use NSTask to start the process and capture its output in my main application.
Additionally I have a check at the start to see if the permissions are set right on the executable, if not the SMJob helper is re-blessed and permissions set.
If anyone has a cleaner solution, feel free to share. Thanks!
I had the same problem as you, needed it for a OpenVPN Manager App for MacOs X. Your solution is far from optimum because you open the openvpn binary for everyone setting setuid root.
This is a security hole and should be avoided, as it is totally unnecessary when you are using smjobbless helper. This helper runs as root and could do everything you want for you and with administrative privileges, so you can launch openvpn via this helper without setting setuid root on openvpn binary.
Apple designed this process as only your App, the Main App, can communicate with this helper as your Main App and your helper are signed with your developer certificates.
Any malicious App can't use this helper.
When you look at Nathans code you see, that he managed it to send messages to this helper and to get answers from this helper.
In his example there is sth like "Hey there Helper App" and the answer is "Hey there Host App".
So to get sth useful out of this you only have to send commands to the helper app, extract these commands on helper side and launch them with elevated privileges as the helper App runs with elevated privileges.
Look at Nathans code, there is sty like (in smjobblessappcontroller.m):
xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
const char* request = "openvpn --config OpenvpnConnection.ovpn";
xpc_dictionary_set_string(message, "request", request);
[self appendLog:[NSString stringWithFormat:#"Sending request: %s", request]];
xpc_connection_send_message_with_reply(connection, message, dispatch_get_main_queue(), ^(xpc_object_t event) {
const char* response = xpc_dictionary_get_string(event, "reply");
[self appendLog:[NSString stringWithFormat:#"Received response: %s.", response]];
});
With this you send the openvpn command to your helper App. You only have to extract this command on helper side to launch the process with elevated privileges.
Look at smjobblesshelper.c and do sth like (in __XPC_Peer_Event_Handler else branch):
const char *response = xpc_dictionary_get_string(event, "request");
In string response you have your openvpn command, now simple launch it:
system(response);
Thats all, this goes with elevated privileges. Now you can use this in your App perhaps in an IBAction push button in your main app, to start openvpn connections as you want every time a user clicks this button.