Firmware Password Manager for OS X

Firmware Password Manager for OS X

firmware_lockscreen

Overview


Firmware Password Manager (FWPM) is my approach to the problem of managing firmware passwords on Apple computers. When I began this project there were no solutions, besides setting the password and never changing it. FWPM allows you to update your firmware passwords quickly and easily. It was originally written as a shell script, but it has been rewritten in Python with many new features. These include Slack integration and a variety of flags to support different management systems. Coinciding with this blog post, I’m releasing version 2.1 of Firmware Password Manager to GitHub. Version 2.1 is a general code cleanup and offers some new security features which I discuss below.

What is the firmware password?


The firmware password is a very important part of the security of an Apple computer. Having a firmware password set will require a user to enter the password when they want to use the Startup Manager to select another device to boot the computer from. In a public access or lab environment this prevents unauthorized users from booting from a potentially malicious device. In the case of a lost or stolen laptop with a firmware password and full disk encryption (FDE) enabled, the data on the disk will be unreadable and the machine itself unusable to the thief.

Why should I use FWPM?


As I mentioned at the start, the current philosophy of Mac management where the firmware password is concerned is to “Set it and forget it.” We felt this wasn’t management at all. Consider this situation,  a system administrator leaves their position at an organization and the firmware password must be changed quickly. There isn’t a solution for rapidly deploying a change to the firmware password. None of the management solutions we investigated, including the popular JAMF Casper Suite, have a method for changing firmware passwords. The JAMF Software Server (JSS) allows you to set a password and activate it, but you can’t change it.

Casper’s Firmware Password interface:Screen Shot 2016-02-02 at 4.29.53 PM

How does FWPM work?


FWPM relies on a text file I call the keylist, containing the current and the new firmware password. In our environment, the keylist also contains all of our previously used passwords. This allows us to update machines that were previously unmanaged and may have any of a variety of previously set passwords. When we reach the point of 100% enrollment in our Casper management system, it will be possible to remove the older entries.

FWPM uses Apple’s firmwarepasswd tool to makes changes to the firmware password. When the script runs it reads the keyfile to discover the new and current passwords. It then verifies that the keylist current password is the current firmware password. If it is not, it will continue trying previous passwords contained in the keylist until it finds the correct password or runs out entries to try. Upon finding the current password, it then updates to the new password. If you have enabled Slack messaging, the script will contact the group  you have chosen and post an informative message.

Example Slack messages sent from Firmware Password Manager:

example slack messages

FWPM will securely remove the keyfile after the script has run, whether it was successful changing the firmware password or not.

How can I use FWPM?


The original FWPM was tightly bound to our radmind managment system. The second version was designed to be much more flexible in its use. The script itself can live on the client until it is called locally or remotely. The delivery of the keyfile is the most important and flexible part of the process. For example, it could be wrapped in an installer and use a postflight script to call FWPM.

The following script could be used in an Apple Remote Desktop “Send UNIX command”1 or a Casper script or policy, or other client management solutions using applicable options. It’s designed to use the Secure Copy (/usr/bin/scp) command, from the Secure Shell (SSH) package, to securely copy your keyfile over the network to the client and then run FWPM. It requires the Python pexpect module to be installed. You can find installation instructions and additional documentation here.

#!/usr/bin/env python

import subprocess
import os
import pexpect

def main():
    # define users, passwords and paths to tools
    scp_user =            'remote_scp_user'
    scp_password =        'remote_scp_password'
    scp_server =          'remote_scp_server'
    keyfile_remote_path = '/remote/path/to/keyfile.txt'
    keyfile_local_path =  '/tmp/keyfile.txt'

    fwpw_manager_path =   '/usr/local/bin/firmware_password_manager.py'
    fwpw_manager_flags =  '-k ' + keyfile_local_path + ' -s -#'
    fwpw_manager_command = fwpw_manager_path + " " + fwpw_manager_flags

    if os.geteuid():
        print "Must be root to run script."
        exit(2)

    try:
        # begin the scp command
        child = pexpect.spawn("/usr/bin/scp " + scp_user + "@" + scp_server + ":" + keyfile_remote_path + " " + keyfile_local_path)

        exit_condition = False
        while not exit_condition:
            # check the current prompts and notifications from scp and act on the results
            result = child.expect(['^.*100\%', 'Password:', 'A.*\(yes/no\)\?', 'ssh.*refused$', '.*denied', pexpect.EOF, pexpect.TIMEOUT])
            if result == 0:
                print "keyfile aquired."
                break
            if result == 1:
                child.sendline(scp_password)
                print "password sent"
            elif result == 2:
                child.sendline("yes")
                print "fingerprint accepted"
            else:
                if result == 3:
                    print "Connection refused. Exiting."
                elif result == 4:
                    print "Password refused. Exiting."
                elif result == 5:
                    print "Unexpected EOF. Exiting."
                elif result == 6:
                    print "Connection timeout. Exiting."
                else:
                    print "Unknown error. Exiting."
                child.close()
                quit()

    # handle any unexpected errors.
    except Exception as e:
        print "Unknown error [%s]. Exiting." % e
        quit()

    try:
        # begin firmware password manager and store the text it returns for later use
        fwpw_man_results = subprocess.check_output(fwpw_manager_path + " " + fwpw_manager_flags, shell=True)
    except Exception as e:
        print e

if __name__ == '__main__':
    main()

Integrating with JAMF Software Server


FWPM can easily be used to replace the limited tools included in the JSS. An extension attribute discovers the current state of the keyfile hash on the client. Smart groups then use the results of the EA to build groups that have the current hash and those that don’t. Finally, a policy can be created scoped to the group without the current hash that will then run FWPM to update those machines. Below you will find screenshots and source code to implement much of this solution.

Our Casper dashboard showing counts of machines with current and non-current passwords:

Here is Python code for an extension attribute that will check the the current state of the firmware password hash. You can then use this data to build smart groups based on whether or not the hash is current or not.

#!/usr/bin/python

import subprocess

fwpw_hash_raw = subprocess.check_output(["/usr/sbin/nvram", "fwpw-hash"])
fwpw_hash_raw = fwpw_hash_raw.split('\n')[0]
fwpw_hash     = fwpw_hash_raw.split('\t')[1]
fwpw_version  = fwpw_hash.split(':')[0]

if fwpw_version == "2":
    print "<result>"+fwpw_hash+"</result>"
else:
    print "<result>Bad</result>"
The JSS Smart Group for selecting computers with the correct hash:

fwpm smart group

What’s new in 2.1?


Version 2.1 of FWPM adds a couple new features, as well as bug fixes. Having a plaintext keyfile is just plain nuts. It’s completely insecure and it’s bothered me since the beginning. I’ve added the ability to create and use a base64-encoded property list (plist). While not quite encryption, this does offer a level of obfuscation and utility that should make it easier to hide the keyfile in plain sight, if necessary.

example of a plain text keyfile:
#old:testPassword
#old:newFirmwarePassw0rd
#old:s3cur3F!w3p4ssw0rd
#current:R3411yL0ng4ndH4rdT0Typ3
new:sp1nn1ngR1ms
example of a obfuscated plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>data</key>
    <string>I2IyeGs6ZEdWemRGQmhjM04zYjNKaywjYjJ4azpibVYzUm1seWJYZGhjbVZRWVhOemR6QnlaQT09LCNiMnhrOmN6TmpkWEl6UmlGM00zQTBjM04zTUhKaywjWTNWeWNtVnVkQT09OlVqTTBNVEY1VERCdVp6UnVaRWcwY21SVU1GUjVjRE09LG5ldzpjM0F4Ym00eGJtZFNNVzF6LA==</string>
</dict>
</plist>

I’ve also added a flag to reboot the computer (-b, –reboot), which will put your changes into effect immediately.

Where can I download FWPM?


FWPM is hosted on our Github page here. You will also find complete documentation, technical discussions and additional usage scenarios, as well as the source code for Firmware Password Manager.

Firmware Password Manager’s GitHub page:

Pasted image at 2016_02_02 04_44 PM

1. ARD’s Send UNIX Command will only run Bash scripts. However, you can wrap Python within Bash. Please visit the FWPM GitHub page for more information.

2 Comments
  • steve kelsey
    Posted at 00:09h, 30 March Reply

    “Thank You” for all your hard work on your Firmware Password Manager (FWPM), and then sharing the information about programmatically working with Apple’s FwPw.

  • Todd McDaniel
    Posted at 16:10h, 30 March Reply

    Thanks, Steve!

Leave a Reply