Darwin • 40:44
Authorization Services facilitate access control to restricted areas of the operating system, allowing developers to restrict access by a principal to particular functionality in an application. This session explores how Authorization Services are used in applications that call system tools, software that restricts access to its own tools, and software installers that install privileged tools or require access to restricted areas of the operating system.
Speakers: Craig Keithley, Michael Brouwer
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
This is actually the security session on Authorization. I'm Craig Keithley. I'm Apple's security and cryptography evangelist. And today what we're going to talk about is Authorization in Mac OS X. There's a number of areas where authorization is useful in the operating system to check whether or not you have the rights to do things.
It's not quite the same thing as authentication, and we'll discuss that in greater detail, but you can make the operating system, you can make the user experience more secure by using the authorization APIs. So having said that, let me bring out Michael Brouwere and he'll talk about this in depth. Thanks.
came to talk about authorization. First, I want to go over some of the things that you'll learn in today's session. Um, the Authorization API was put into Mac OS X, um, because unlike Mac OS 9, there's actually, um, privileges There's a difference between applications that run as you, as a regular user, and applications that can modify certain parts of the system, like changing certain system configuration parameters. There's traditional Unix settings that you won't be able to modify unless you have privileges. Now, we tried to create this API in order to let you manage modifying those things in a way that doesn't necessarily compromise security.
So, let me go over some of the things you'll learn today. You'll learn how to make applications that need to access these privileged parts of the system. We'll also go over how you can make an installer that can install an application that needs to do these things. And you'll also learn how to use these privileged calls that exist in OS X without having to run your entire application as root, or as the user with UID 0. And I'll go over some of the reasons why you don't want to run your entire application as root.
So, first I'll talk a little bit about what authorization is. Often when we talk about authorization, people confuse it with authentication, and those are two different things. Authentication consists of identifying the person behind the computer, or identifying who's there currently doing something. Authorization, on the other hand, is once you've identified what the user is that's using the system, determining whether or not you should allow him to do something. And whatever that something is that depends on your application.
I'll talk a little bit about what the Authorization API does and doesn't do. I mean, there's some things that people wish it would do, but we can't right now. And so I'll go into a little bit more detail about the Authorization Services API. And finally, I'll cover some examples of how not to use it and how to use it. We've seen some things come up from developers and from people inside Apple and outside Apple that they were trying to use the API, and we saw some examples of good ways to use it, but we also saw some examples of bad ways to use it.
So, in order to kind of cover the things that have happened, the mistakes people have made, I want to kind of go over that and show some of the commonly made mistakes and show some of the things that you can do to avoid those mistakes or to not have to make those mistakes.
So, talked a little bit about authorization versus authentication. Want to make sure that everyone is aware of it. So, The key goal of the Authorization API is that you ask for permission to do something. Normally, that results in us asking the user for an administrative password. That is actually the authentication step that happens below the authorization. That doesn't have to be the way the problem gets resolved.
It could be something like user has to insert a smart card or you have to present a fingerprint, which is another form of authentication. If you've already authenticated earlier, it could be that you're automatically authorized to do what you're asking for permission to do. So rather than worrying about who the user is, when you're using authorization API, you just worry about what the operation is you're doing and whether that's allowed at the current time.
Now, why would you want to use Authorization Services? Because, as you'll see later, at the point where you would be calling Authorization Services in your program, your program could already do what it needs to do. I mean, you already need to have the privileges in order to make the change or the call into the system that you want to make.
The reason to use Authorization API is to make an explicit decision and to let an administrator actually configure who can do what, rather than having the application decide, "Oh, it's okay when on my iMac at home I'm changing the network settings. That's perfectly fine. Anyone should be able to do that, right?" Well, that's probably true in your iMac at home.
On the other hand, that's probably not true on that same iMac in an educational situation or in a lab or something, where you don't want users just changing the network settings. You want them changing the IP address. That's something that the administrator needs to be able to control. So, if you use the Authorization API, it allows the administrator to make the decisions on who can do what, rather than you as the programmer developer of an application that needs to do privileged operations.
Another thing is that using the Authorization API, all authorization goes through a central point. Everything funnels through Authorization Services. That makes it easier to audit your code and the system as a whole, because there's one place where all operations go through before they happen. Like I said before, it makes it configurable and, more importantly, securable. The default way we ship OS X out of the box, we try to be secure as far as who can access your machine over the network by not running any network services and things like that.
We don't really try to be secure as far as if you're sitting behind the computer, you can pretty much do everything with your computer, the default configuration. Now, like I said before, in a lab situation or a school situation, different environments, you might not want the person sitting behind the computer to be able to control everything on that machine.
So it's securable, it's configurable, it's flexible. And the other main advantage is you're explicitly making a decision. Often, traditionally in Unix and traditionally in many systems, authorization is something that's kind of done ad hoc. If you look at regular Unix login, the decision whether or not you can login depends on whether or not you can enter a username and password.
Well, that's not really doing authorization, that's just doing the authentication. The step whether it allows you to login then later is determined by looking at some file in some directory saying, "Is this user listed as one that's not allowed to login?" So that basically makes that you have... Um, one method for doing this for login.
For something else, like logging in through FTP, um, there might be a different file, and SSH has its own other file. So each separate service on the system has potentially some way of doing authorization, but not every service has it. So by having all applications go through this one API, It makes it easier to secure the system and much easier to see what's happening.
So, if you were to write an application that uses Authorization Services, there's basically three pieces involved. The security server that you see on the top is part of Mac OS X, and it's the central authority that keeps track of who's allowed to do what at any given time. Now, your application on the left, is just a regular application running without any privileges.
Now, within your application wrapper, you can ship a tool, and that tool should be able to run with elevated privileges. Most cases, it'll be running with user ID 0. In certain cases, it might only need access to be in a certain Unix group if all you need is to write to certain directories that are privileged based on group IDs rather than user IDs.
Now, when your application wants to make a change to the system or perform some operation that it wouldn't normally be able to do, it-- First asks the security server for authorization. And what that does is it makes some state in the security server remember that for this authorization, these things are allowed. And the next thing it does is it launches the tool and passes that authorization along to the tool.
Now, the tool, that's the very first thing it does, it decodes that authorization and checks the rights of that authorization with the security server to make sure that the app making the request is really allowed to do what it's asking for. And once it's verified that it's okay, it goes ahead and performs the operation.
So, something I want to say about operation, just that wasn't really clear to some people last year. The level of what such operations should be should be something that the user-- the user-visible operation. So it would be something like changing your network settings. And that might involve doing five different system calls into the kernel or writing three different configuration files and restarting a daemon and who knows what. You want to define that as one operation because from the user's point of view, he's only doing one thing. And if you're gonna ask for authorization, which might end up in a dialog for each operation, you really don't want five dialogs for one user operation.
So currently, Authorization API is password-based only. : This isn't entirely true because we will in Jaguar have a plugin architecture which isn't published yet as a public API, but if you go to the Darwin sources you can take a look at that because all the authorization API stuff is in Darwin. So there is a plugin architecture which, for one thing, allows us to hook up with Kerberos.
One of the new things we will be doing as of Jaguar as opposed to in 10.1 is we're using Directory Services rather than NetInfo. Out of the box it still uses NetInfo because out of the box Directory Services uses NetInfo. If you configure your system to use some LLAP server or some other service for directory services, Authorization API will use that as its backend.
Also in Jaguar, some of the lower-level parts of the system have been PAMified, so they'll all use the PAM API, and the default configuration for PAM actually goes through the Authorization API. So whichever way you configure the Authorization API, all forms of authorization and authentication on the system will go through one funnel, which is the Authorization Services API.
So what the Authorization API does is it answers the "do I have the right to do this?" question. It doesn't actually give you the ability to do something that you couldn't do before. Um, it basically helps you if your privileged tool or component in your application that potentially could already, say, reformat the hard drive, Whether it should do that on behalf of the user is a question, because if the user is just some kid that is using a regular user account and not the administrator for that system, he probably shouldn't be able to reformat your hard disk. Um, and it automatically provides the user interaction needed if it's needed.
So what it doesn't do, like I said before, it answers the "Do I have the right to do this?" question. It doesn't enable you to do something that you couldn't do before. And that's because Mac OS X is not capability-based, which basically--Mac OS X is still traditional Unix in terms of privileges.
Privileges are based on user ID, and as far as Unix is concerned, there's only two types of users. There's non-root users and there's root users. And so authorization services assumes that your application is using the standard Unix model, but aids you in making finer-grain decisions about who can do what.
So, case study. Here's an example of how OS X could be improved by using the Authorization API correctly. I'm sure you've all run into the problem once where you've thrown away something in the trash and Finder couldn't delete it later because you didn't have privileges to delete it because of the file system permissions on Mac OS X.
Well, if there's something in the trash that you can't delete, it would be--if Finder were to have a separate tool that knew how to empty the trash and use Authorization Services to determine whether or not it should do that, you could empty the trash even if there were files in there that you couldn't normally write, or the directories that you couldn't normally write, because that's what it's--that's what determines if you can remove a file. Um... Now, out of the box, we could maybe even configure it so that any user could empty the trash, because after all, even if the administrator threw something in the trash, I should still be able to empty it. It's trash after all.
But in a different environment, you might want to configure it where, well, if you want to empty the trash and there's stuff in there that you couldn't normally throw away, you have to enter an administrator password. So by having Finder use the Authorization API to do this operation that essentially, um, is not normally allowed, but have it use a privileged tool to do it. We could allow the site administrator or the user of the computer to determine what's needed in order to do this.
Now, the key thing in this is If you were to do this, you would want to ask for permission to empty the trash as the operation you're going to do, rather than asking for permission to remove each individual file. Since the high-level operation the user is performing is emptying the trash.
Now, I mean, you could implement it by calling this tool for every file you encounter that you can't delete. If you did it that way, depending on how the system is configured, the user might end up with 10,000 dialogues if there's 10,000 files in the trash that you can't delete. So in order to avoid that, you really want to try and define the operations that you ask the authorization API about as high-level as possible.
So I'll go into a little more detail about the Authorization Services itself. This is the actual API. First thing I'll cover is naming of rights. So in order-- the Authorization API works with things called rights. Now, rights don't actually give you the right to do something. They're just a name for something that, um... That correspond to the operation you want to perform.
So you could ask for the right to, in the example of the finder, finder would ask for the right to system.filesystem.empty.trash, or something like that. So it would ask for the right to empty the trash, and depending on how it was configured, it would either get that or it would get it after the user had entered some information, or it would be denied.
The other thing I'll go over is show an example of how to do the application communication. So basically how to do the communication between your application and the tool, like I showed in that slide.
[Transcript missing]
And finally, there's one call in Authorization API, which is probably the call that most developers end up using and end up ignoring the rest of Authorization Services, which is ExecuteWithPrivileges. And ExecuteWithPrivileges really meant as a sort of patch to be able to install privileged components in the system, but we really encourage developers to look at the rest of the Authorization API and use that and try and avoid using execute with privileges as much as possible.
and mainly it's intended to be used during installation and not during normal execution of your application. So naming of rights. We define rights in a hierarchical namespace. We've defined a couple of top-level domains, but we don't have an exhaustive list of every write that you could possibly think of, since we don't know what your application might define as the operations it needs to do. Since we want you to define higher-level operations, we can't really define one for each low-level system operation.
So some example write names that are currently in use are system.login.console, which is essentially when your system boots in Jaguar, login window actually asks for the right to log in on the console. And that triggers a bunch of things, amongst which, by default, would be the regular login window UI. If you configure it differently, it might actually trigger the login window UI with some additional Kerberos features or, depending--if you actually put the right plugins in place, you could completely customize that.
Another write that's used is system.login.pam, which is actually what Pam uses to--in the default module for Pam, will actually ask the Authorization API for the right to log in. And that's what you end up using if you SSH into your machine on Jaguar. What ends up happening is Pam actually calls into the Authorization API, which in turn goes-- goes back and calls into Directory Services. Again, you can configure that to do something else.
Some other examples: The DVD player app has two different rights. One is the setRegion.initial, which is the right to set the initial region on your DVD player. And the way that's configured out of the box is anyone can do that. It means the first time you pop a CD into your system, anyone can set the region to the region of that CD.
And it doesn't ask you for any administrative password or anything. Now, if you want to change the region, since you can only change the region five times or something--I don't know the exact number-- but you can only change the region on your DVD a certain number of times.
And after changing it that many times, you're basically your host because you can't use your DVD player anymore. So you don't really want your kid running up and shoving in CDs and changing the region five times in a row, at which point you can throw away your PowerBook. So in order to change the region, the default configuration actually asks for an administrative password.
And since that's not something that people normally do, we thought that's probably a good way to prevent you from accidentally changing your region too often. Now, another right that's being used is System.Preferences. Currently, that covers all of the preferences in System.Preferences. Hopefully, sometime in the future, we'll be splitting that into finer-grained authorization requests.
But so right now, you can configure, for example, whether or not you need to be administrator to change certain system preferences. And currently out of the box, the answer to that is yes. The other right, the last one, is the system.privilege.admin, and that's the actual right that execute with privileges uses, which I'll go into a little more detail in a second.
Why do you want to do application communication? Why do you want to split your app and your tool? Well, the reason is you don't want to run your application as root. For one thing, that would involve running the entire app kit or Carbon, depending on what type of application you have, with UID0.
And traditionally, any code you run with UID0 on a Unix system, you want to audit that code for buffer overruns, memory leaks, all kinds of things that might end up compromising the system. Well, you probably also don't want to load plug-ins in code that runs as root, unless you know you can trust those plug-ins. If you run the entire app kit or Carbon as root, that does all kinds of things that you probably don't want to do with UID0.
And in order to avoid having to audit this massive chunk of code, which is what Carbon and Cocoa are--and they are for a reason, because they have lots and lots of features-- but you don't really need all that code to run with elevated privileges. So what you want to do is take just the core part of your app that performs the actual operation that needs privileges and split that off into a separate application. You can then use both the APIs in Authorization Services and traditional Unix APIs like pipes, or you can use Mach ports or CF messages or whatever you want to use to communicate between your application and the tool that's performing these operations on behalf of your application.
So, recommended way to use the Authorization API would be in your app, and so the app being the GUI part of your app, you create an authorization ref. Then there's an optional step, which is, If you want to have the nifty UI where you have like a little lock icon and not allow the user to change anything unless you know that it's going to--unless the lock is unlocked, rather than asking the user for permission to do what he's doing when he hits the save button, you use copyrights to figure out what's currently allowed. Copyrights being authorization copyrights. All the Authorization Services APIs start with authorization.
So you can figure out whether the right you need is currently allowed, which might be the case, because depending on how the system is configured, you might already be allowed to do it, in which case, that's fine. You're done. On the other hand, if it's not allowed, you can do a call to copyrights with preauthorization. If the user indicates he wants to make changes, so he clicks the little lock button, you set the preauthorization flag, and you ask for the rights you need.
The only reason you would ever use preauthorization, and the only thing it really does, is if the configuration happens to be such that the timeout on the writes is zero, which means that even if you preauthorize, by the time you turn around and actually try and perform the operation, the user's going to get another dialog, it won't bother to ask the user the first time.
It'll just say, "Yeah, it's probably going to succeed later when you ask if the user enters the right password." It's basically just to avoid having to ask the user for his password twice if the Authorization Services API knows that by the time you end up turning around and doing the thing you were going to do, it's going to ask again.
So that's an optional step, though. Then, next thing you do is you pass the authorization ref to your privileged tool or to-- it could be a background-- faceless background app or daemon or whatever they call that in Unix. And you do that by externalizing the authorization ref. So there's a call you make that turns the authorization ref into a bag of bytes, which you can just pass around using whatever mechanism you want. You want to make sure you use something that can't be snooped by some other application, though, because that authorization ref allows your tool to perform certain operations. So you don't want just anyone being able to grab that.
Then in your tool, you call AuthorizationCreateFromExternalForm that takes that externalized ref and turns it back into a regular authorization ref. And then your tool calls AuthorizationCopyrights, which will tell you whether or not the operation is currently allowed. When your tool makes that call, it should also pass in the flag to allow UI. The UI won't actually happen in your tool. The UI will happen as part of Authorization Services in a separate process.
The reason you want to do it there is, depending on how the timeouts and things like that have been configured, it might be that the UI isn't needed until this point. And if copyrights tells you, yes, it's okay to go ahead and do it, then your tool can actually perform the operation you need to do.
So, a little bit more on pre-authorization. The application is allowed to pre-authorize for the rights that it is going to need later on. You still want the tool itself to always call copyrights just before doing the operation, and at that point it's not pre-authorizing. The reason you want to do that, one reason is it might not be possible to pre-authorize depending on the configuration. Another reason is you want to keep the final call to copyrights as close to the operation as possible.
And in order for--if you enable audit logging, in order for that to work, you don't want an audit log entry when the user just asks for permission to do something. You want the audit log entry when--you want the log entry when it actually happens. So you want to be able to distinguish between someone asking for permission to go and change something versus actually changing something. And so the final thing is that it allows for zero-length timeouts without double authentication dialogues.
So then there's the, what we call temporary solution for third party installers. It says temporary because, well, we know we have to provide something and this is all we have right now. Going forward, we'd like to make this more secure and potentially require maybe that your application is signed before this is even possible or things like that.
Right now it doesn't and it's kind of, we have to put this in because it's the only way third parties can get apps on the system that can do privileged operations. Using execute with privileges, though, is potentially dangerous because the user doesn't really know what it is you're about to run. It could be a virus, it could be something that destroys the entire hard disk.
So unless they have some way of trusting the program that's about to be run with execute with privileges, it's fairly dangerous. And we're enhancing the UI to make the user more and more aware of the fact that execute with privileges is a potentially dangerous thing. So we really encourage developers to not use this over and over again. So you don't want to use execute with privileges every time your app turns around and does something.
It's much better to have a tool that is set UID root but uses the authorization API to make sure that it's allowed to do what it's about to do. By making a regular authorization call rather than using execute with privileges each time. So the right you ask for in order to be able to use execute with privileges, which you can preauthorize if you want, is system.privilege.admin.
So, some of the things that came up when Richard and some of the other people in our group ended up auditing code that needed to run as root is, People writing tools that actually end up calling a system ten times in a row to call, like, binchamad or bin-- call different standard BSD tools or actually run shell scripts even.
And there's--if you're writing a tool that uses--that runs as root, whether that's because it got run by execute with privileges or because it set your ID, you really need to refer to the existing security policies and existing documentation out there in the Unix world about things that you should and shouldn't do in set your ID root code.
And amongst those things are never use system or P open. And there's lots and lots of reasons to not do that, and I won't go into details about it. But rather than calling user binchamad, you're better off just calling the system called chamad to do what you need it to do. Thank you.
The other thing is, and I've mentioned this a couple of times and I can't mention it enough, is you really don't want to run your entire application as root. You really want to split it up and run as little code as possible with elevated privileges because you want to be able to audit that code that runs as root doesn't have any security holes.
And finally, and this is something that we've seen a lot of examples of where this went wrong, is don't rely on credentials being shared or timeouts being greater than zero. What that means is the way we shipped the authorization configuration, which is something that the owner of the system or the system administrator can change, we shipped it by default where the authorizer can change the authorization configuration.
So we shipped it by default where the authorizer can change the authorization configuration. credentials are shared, meaning that if you log in as an administrator, there's the notion of an administrator credential floating around in the system. Any application that then needs to do some authorization call, based on needing an administrator username and password, will use that shared credential if the rule for that right says, yes, it's okay to use a shared credential, which is really convenient because it provides a sort of single sign-on ability.
Of course, it's not very secure. If you really care about security, you probably don't want to enable that shared flag. So you can actually go in and change that and turn that off. Now, it turns out a lot of applications break when you do that because what they do is they'll create an authorization ref, ask for certain privileges, delete the authorization ref, recreate a new one, and assume that the privileges they just asked for are still there.
So, you can actually do that. And then you can actually do that by using the authorization ref. And then you can actually do that by using the authorization ref. And then you can actually do that by using the authorization ref. And then you can actually do that by using the authorization ref. And then you can actually do that by using the authorization ref. And then which actually is only true if that shared flag is set.
The same is true for timeouts being greater than zero. That particular use case also assumes the timeouts are greater than zero, but even if you don't rely on things being shared, there's still a number of applications that rely on timeouts being greater than zero, for example, by creating an authorization ref and calling executeWithPrivages five times in a row. Well, if you do that, you might end up with five dialogs if the timeout is zero or the timeout is expired by the time you get to the next executeWithPrivages.
So what you really want to do in a case like that is run your tool once, and instead of running those five commands with executeWithPrivages, have your tool perform those five things as one high-level operation, because it's really one high-level operation that you're doing. It could be something like installing an application or changing your firewall settings or something like that.
So So what you do want to do is whenever you are using XQ with privileges, which you really should try not to do, : You want to call that at most once. So you never want to have more than one call to execute privileges in the entire lifetime of your application. Then another thing is it would be good if you could test with the most secure possible version of ETSI authorization. ETSI authorization is the configuration file that lets you configure what happens when a user asks for a particular write.
And I'll show you an example of the most secure possible Azure authorization in the next slide. If you set that in Azure authorization and your app still works and only asks for the password once, then you're fine. I've tried it with a number of things that use it, and some of them end up asking for the password 12 times in a row.
When you're defining writes or write names, you want to define them for user-initiated operations, not for low-level system operations. So in the example of Finder, you want to define a write for system.finder.emptytrash. You don't want to define a write for system.finder.deleteFile, at least not for the emptying the trash case.
You might have that in a different place if you actually want to be able to delete a file from a place where you couldn't normally write. But if you're doing something, you don't want to use a write like ask for permission to delete one file every time you delete a file from your trash.
Here's the ANSI authorization. It starts with the plist blurb. Then the authorization file is actually nothing more than a dictionary where the keys are prefixes to write names. and the values are some of the following keys-- group. The value itself is another dictionary, and that dictionary contains a couple of keys. Now, this one, there's the empty key, which basically matches every single write.
Maps to dictionary, which requires group admin, which basically requires you to enter a password of a user in the admin group. The shared flag is false, which means that if you authenticate in one application, you won't automatically unlock in another application, and the timeout is set to zero, which means that as soon as you make this call to the authorization API, the credential that you obtain during that call is only valid during that computation, and it's discarded immediately afterwards, which means that every time you do something, the user would have to enter his administrator password. Now, that's the most paranoid configuration you can choose, but it would be good if you're using authorization service to actually test with this configuration to make sure your app still behaves reasonably under these circumstances.
So, to go back, rights, like I said before, rights are not capabilities. So, it's up to you as the application developer to develop a tool that turns that right into a capability. Your tool that runs those privileges basically makes it into a capability. Authorization Services itself doesn't. Um, don't run your entire application as root, but limit it to a small component of your application. Can't say that enough.
When you do that, you create an authorization ref, externalize it, pass it to your tool, and then check for the privileges in your tool. and try and keep the amount of code that runs with UID0 as small as you possibly can. And finally, never run shell scripts, never call system, never call P open in code that's running as root. And in addition to that, check out some of the documentation that's around for BSD and some other Unix systems about writing code running as root, and some of the do's and don'ts there, like checking for buffer overruns and things like that.
And so finally we have some resources. There's the developer.apple.com security page, which I believe has a link to the now available documentation on Authorization Services. And there's CDSA 2.0, which is all of our security APIs are basically layered on top of CDSA, which is an open group standard cryptographic interface. It's worth checking out. And the PCC. This is some of the other things our group is working on.
Then there's some other sessions where you can check out some of these things. Tomorrow--no, sorry, all our other sessions are on Thursday, so you can enjoy security all day Thursday. Thursday morning, there's a talk about CDSA, and we'll go over CDSA in general, about the cryptographic services in there, support for certificates, support for making trust decisions, things like that.
After that, there's a session about our higher-level APIs layered on top of CDSA for managing X.509 certificates, for managing trust, storing certificates in keychains, and things like that, and generating keys, storing keys, et cetera. And then in the afternoon, there's a feedback forum, and after the feedback forum, there's a related session about Kerberos and Mac OS X, which I encourage you all to go and see, where we'll show some of the--I believe we'll show some of the integration between the authorization API and Kerberos and how that enables Kerberos, like, in login window.