OS X : let's play with security

parse error

I have a few shellscripts that need username and passwords to accomplish some tasks. For example, I wrote a shellscript to backup some data in an encrypted sparsebundle file that is stored on an AFP share. So I have a username and a password to mount the share, and some others credentials to mount the sparsebundle.

When it came to automating the backup process, I had to face a simple issue : where do I store these usernames and passwords ?

Hard-code them in the script ? Naaah.

Create some kind of read-only file with proper permissions ? Sounds rather risky.

So, what would be the right way to do this with OSX ?

The answer is kind of obvious : OSX ships with a nice tool called Keychain Access.app. This service is intended to store and retrieve credentials, keys, passwords, certificates,... in a secure fashion. But how can I access it from the shell ?

This is where security comes to the rescue ! security is a Command Line Interface that allows you to administer and manipulate keychains, keys and certificates. In other words, security allows you to access Keychain (the app) from the shell (local shell ! SSH ! shellscript !). Wonderful, isn't it ?

Although it comes with a manpage, let's see some examples.

Adding items to a keychain

First, let's add a generic password to the default keychain. Here is what I'm using to store the credentials for my sparsebundle file :

security add-generic-password -a "username" -D "Image Disk Password" -s "MyFile.sparsebundle" -w "THis_IS_so_s3crEt_!"

The -a option allows you to specify the account name. -D is for the description. -s allows you to specify the service name (here I chose to set it to the sparsebundle filename but you can put whatever you want). And -w allows you to specify the password.

Adding an Internet password is a little bit different :

security add-internet-password -a "username" -D "AFP Share Password" -s "myserver.example.com" -p "share" -r "afp " -l "My AFP Share" -w "THis_IS_4lso_very_s3crEt_!" /Library/Keychains/System.keychain

This time we had to specify the server with -s, a path (here, the name of the shared point) with -p, a label with -l and the protocol (could have been http, ftp, or whatever) with -r.

WARNING : the argument for the -r option MUST be exactly 4 characters long. So you have to write "afp ", not "afp".

Please also notice that we decided to add this entry to the System keychain which is located in /Library/Keychains/System.keychain. We could have chosen to add it to another existing keychain by providing the path to this keychain, or we could have created a brand new one with :

security create-keychain /path/to/my/new/Personnal.keychain

Retrieving items

Retrieving an item in a keychain is pretty easy. Here I choose to retrieve the item by service (remember the -s option we used when we created the entry ?) :

security find-generic-password -g -s "MyFile.sparsebundle"

Which outputs the following :

keychain: "/Users/francoiskubler/Library/Keychains/login.keychain"
class: "genp"
    0x00000007 <blob>="MyFile.sparsebundle"
    0x00000008 <blob>=<NULL>
    "cdat"<timedate>=0x32303134313232393130323735365A00  "20141229102756Z\000"
    "desc"<blob>="Image Disk Password"
    "mdat"<timedate>=0x32303134313232393130323735365A00  "20141229102756Z\000"
password: "THis_IS_so_s3crEt_!"

But I could have chosen to retrieve it by account name (remember the -a option ?) :

security find-generic-password -g -a "username"

Or by account name AND service AND description in a specific keychain file :

security find-generic-password -g -a "username" -s "MyFile.sparsebundle" -D "Image Disk Password" /path/to/my/new/Personnal.keychain

If you have more than one entry that matches your criteria, security will only output the first one that matches. So, be careful when you add a new entry in a keychain, and make sure you'll be able to retrieve it easily later by specifying a different label (-l option) or a comment (-j option) or whatever you prefer.

Parsing the output

Obviously, the output of the command is kind of useless and needs to be parsed. On OSX prior to 10.9, when retrieving a password with the -g option (which asks security to print the password), the password was printed on stderr, so you had to play with shell redirections and write something like the following :

my_precious=$(security 2>&1 > /dev/null find-generic-password -g -s "MyFile.sparsebundle" | cut -d "\"" -f 2)

Starting with OSX 10.9, you can drop the -g flag and use -w instead :

my_precious=$(security find-generic-password -w -s "MyFile.sparsebundle")

The cool part here is that you can retrieve more than just the password. You can also retrieve the username (tested on OSX >= 10.6) :

output=$(security 2>&1 find-internet-password -s "myserver.example.com" -r "afp " -g)
username=$(head -n 7 <<< "$output" | tail -n 1 | cut -d "\"" -f 4)
my_precious=$(head -n 1 <<< "$output" | cut -d "\"" -f 2)

Of course, depending on your needs, you'll have to modify the commands, but you should now get the idea. The most important part here is to remember that when called with the -g option, security will print the password to stderr instead of stdout.

More : Certificates, Keys, ...

There are a bunch of option that allows you to manage certificates, keys and identities (certificate + private key). I've never had to deal with that stuff so I can't provide any decent example. But they do exist and a quick look at the manpages should be enough to help you.