Two Factor Authentication in OS X
Yet another take from a Macosxhints article.
This is a hacked up two factor authentication scheme for Mac OS X that relies on an external storage device like and iPod. You could also use a usb keychain, or anything that will mount. Used in combination with Filevault, this can provide a reasonable amount of security on your machine that requires the presence of your device.
The article on Macosxhints describes using a script to make sure a folder exists on your device. You call this script from a login hook which either prevents login if the folder does not exist, or allows it if it does.
I'd like to instead create a random data seed, and check it's hash on the client computer to make sure it's the same file. Plug in your ipod or usb keychain and open terminal. Type in the following:
dd if=/dev/random bs=1k count=1024 of=/Volumes/ipod/.login/randomseed
Make sure to change the ipod in the above to whatever you are calling your device. This will create a 1MB file filled with random data. It dumps it directly into your device in a hidden folder called .login. Unless someone got their hands on this file, it's extremely unlikely they would be able to duplicate it. Next we need to pull the hash value into a file on our machine:
MD5 -q /Volumes/ipod/.login/randomseed > ~/.login/hash
This will give you an MD5 hash in a hidden folder in your home directory that looks something like this:
2b913b93be5f241f7416b673c85b6641
Now that we have our random file on our ipod and a copy of the hash in our home folder, we can get to the fun stuff.
First we need to make sure that drives are auto mounted even when a user is not logged in:
sudo defaults write /Library/Preferences/SystemConfiguration/autodiskmount AutomountDisksWithoutUserLogin -bool true
Otherwise, external drives are not mounted until a user logs in, and that defeats the purpose of our script. You'll have to restart for this to take effect.
Now we have to adapt the script from Macosxhints to check for the hash instead of a folder. Here's the script.
#!/bin/sh
devicehash=`/sbin/md5 -q /Volumes/iPod/.login/randomseed`
storedhash=`/bin/cat /Users/$1/.hash`
if [ "$devicehash" == "$storedhash" ];
then
exit 0
else
/usr/bin/killall -HUP loginwindow
fi
What this does is compare the hash of the randomseed file on the ipod to the stored hash on the hard drive. Since this is a loginhook script, we can't use kill -9 -1 as loginhooks run as root and kill -9 -1 does bad things when it's run as root (check the man page). I use a killall instead. Long and short of it is, if the hashes match, then you log in, if they don't, you get the login screen again.
I saved this script in /Library/Hooks/login/myuser/login.sh and did a chown root login.sh and chmod 700 login.sh to change the owner to root and give only root permission to access the script.
Then you simply type this into the terminal:
sudo Defaults write com.apple.loginwindow LoginHook /path/to/script
If for some reason you can no longer login after this, say there's a typo in the script, boot into single user mode by holding down Command and S. When you get to the prompt, type:
mount -uw /
defaults delete com.apple.loginwindow LoginHook
Update
I have tried my best to find a way to make this work with fast user switching, and there is a limited way to do it. When you switch users from the fast user switching menu, the system writes a userid key to /System/Library/Preferences/com.apple.loginwindow.plist. We can use to see who is currently logged in. The problem is, when you chose a user from the login screen i.e. what we do when we have CGSession suspend our login, it doesn't update this plist. For a suspend script to work as a cron job, or even a background job while we're logged in, it has to know that we have control over the GUI at that moment, otherwise it will just indiscriminately suspend the current session regardless of who is running it.
Of course, if you were willing to be vigilant about saving your documents, you could make a script just kill the processes belonging to your user id. This will leave any other logged in users alone, but will kick you rather abruptly if your authentication device is not present. It wouldn't be much different from the previous script:
#!/bin/sh
devicehash=`/sbin/md5 -q /Volumes/iPod/.login/randomseed`
storedhash=`/bin/cat ~/.hash`
pid=`ps -x | grep -w loginwindow | grep -v grep | sed -e 's/^ *//' -e 's/ .*//'`
if [ “$devicehash” == “$storedhash” ]; then
exit
else
if [ $pid ]; then
kill $pid
else exit
fi
fi
Save this and move it someplace safe in your user folder. Chmod it to 770. Make sure this is in your user cron and not in the root cron, as it depends on being run as the user to pull the process id from ps. You could write a little deamon script to check the hash every couple of seconds, but I don't need it to do more than once a minute.
If someone can find a reliable way to find out who is current gui user, I have a much nicer script ready that will just switch to the login window, which will give you a chance to save any files.
Posted by Joe Mullins at September 29, 2004 05:38 PM | TrackBack

