touch2sudo: Enable remote sudo two-factor authentication using Mac Touch ID

Binu Ramakrishnan
7 min readJan 20, 2019

--

The Touch ID on MacBook Pro and MacBook Air is an impressive feature that enables you to use your fingerprint for authentication. In this write-up, I’m going to show how we can extend this feature to authenticate remote SSH sudo commands.

sudo

As a good security practice, running sudo commands require some form of authentication. If you are using password based authentication scheme, you are often prompted for passwords before running sudo commands. Typing password all the time is cumbersome and a counterproductive activity for engineers. It also forces users to keep passwords on remote servers, which is not considered secure. Hence to avoid storing passwords in servers, we need to choose a different authentication scheme for sudo. One option is to completely disable sudo authentication. This is not recommended because as a user, you may accidentally do dangerous or irrevocable actions using your root privileges — imagine you unknowingly execute some scripts that contain sudo commands. Besides that, sudo authentication puts additional security barrier against local privilege escalation attacks in case of a server compromise.

Solution in a nutshell

There are a number of PAM based solutions available that support various authentication schemes for sudo. The solution we describe here use pam-ssh-agent-auth — a PAM module that does SSH key authentication for sudo. pam-ssh-agent-auth is based on SSH agent forwarding feature that allows the PAM module to authenticate sudo command using key cached in ssh-agent running on your workstation (Mac).

You can enable agent forwarding by adding sudo identities (keys) to your local ssh-agent, and then connect to remote host with ssh -A command-line flag (or by setting AgentForward flag in your ssh config file).

ssh-add is the command line tool to add identities (SSH private keys) to ssh-agent. An interesting optional feature provided by ssh-add for adding identities is -c option. It allows the user to force a confirmation program for the added identities before being used for authentication. For any authentication request for keys that require confirmation, ssh-agent attempts to execute SSH_ASKPASS confirmation program. The core of this writeup is a custom SSH_ASKPASS confirmation program — touch2sudo which authenticates your remote sudo commands using Mac Touch ID.

touch2sudo

touch2sudo is a standalone program, if executed authenticates the user either through Touch ID or password. A successful authentication (confirmation) is signaled by a zero exit status from touch2sudo program. To authenticate sudo commands, we configure touch2sudo as SSH_ASKPASS confirmation program, invoked by ssh-agent.

You can find the touch2sudo source code here:

https://github.com/prbinu/touch2sudo

The end to end setup is described in the following sections.

Mac configuration

Fingerprint authentication is done locally on Mac, but it acts as a gating mechanism for remote sudo authentication.

If you haven’t setup Touch ID, you can find the instructions from Apple here.

touch2sudo installation

You may either install touch2sudo binary using brew OR build it from source

Install using brew

brew tap prbinu/touch2sudo
brew install touch2sudo

Build from source

$ git clone https://github.com/prbinu/touch2sudo
$ cd touch2sudo
  1. Open touch2sudo.xcodeproj file using Xcode
  2. Build: (Product -> Build) If the build is successful, you would see this dialog:

3. Archive: (Product -> Archive -> Distribute Content -> Build Products -> Next -> Save) Save the archive folder. The touch2sudo executable binary will be in the <ArchiveDir>/Product/usr/local/bin path

4. Install: Copy touch2sudo binary to /usr/local/bin

Configure ssh-agent with touch2sudo

Generate a new SSH key pair for sudo:

$ ssh-keygen -t rsa -b 2048 -C binu-sudo@binu.localGenerating public/private rsa key pair.
Enter file in which to save the key (/Users/binu/.ssh/id_rsa): id_rsa_sudo
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_rsa_sudo.
Your public key has been saved in id_rsa_sudo.pub.
The key fingerprint is: SHA256:2848NrL7O4UNaCh7kgECmt2zlphtllZjs/VwjdPxfe0 binu-sudo@binu.local
The key's randomart image is:
+---[RSA 2048]----+
|o . |
|oo.. + o ..|
|o...o =.o.+ o . +|
| +oB.=o+.. ..|
| o X=..S .+ E|
| =+ . o. o |
| o . .. |
| .+= |
| o**= |
+----[SHA256]-----+

Start ssh-agent

$ export SSH_ASKPASS=/usr/local/bin/touch2sudo
$ export DISPLAY=0
$ eval $(ssh-agent)
Agent pid 51863
$ ssh-add -L
The agent has no identities.
$ env | grep SSH
SSH_AGENT_PID=51863
SSH_AUTH_SOCK=/var/folders/hm/x1_38yz53td1jty5xgs39dxm2lm58d/T//ssh-JssXsflTuHrC/agent.51862
SSH_ASKPASS=/usr/local/bin/touch2sudo
$ ssh-add -c id_rsa_sudo
Identity added: id_rsa_sudo (binu-sudo@binu.local)
The user must confirm each use of the key

Now let’s configure the remote server.

Linux (Ubuntu) server configuration

Test Environment: Ubuntu 16.04

Follow the steps below to setup pam-ssh-agent-auth on your server:

Download pam_ssh_agent_auth

$ wget "https://downloads.sourceforge.net/project/pamsshagentauth/pam_ssh_agent_auth/v0.10.3/pam_ssh_agent_auth-0.10.3.tar.bz2"$ bunzip2 pam_ssh_agent_auth-0.10.3.tar.bz2
$ tar -xvf pam_ssh_agent_auth-0.10.3.tar
$ cd pam_ssh_agent_auth-0.10.3/

Build and install

# install build dependencies
$ sudo apt update -y
$ sudo apt install libssl-dev libpam0g-dev -y
$ sudo apt install gcc make checkinstall -y
# build
$ ./configure --libexecdir=/lib/x86_64-linux-gnu/security --with-mantype=man && make
# install pam_ssh_agent_auth PAM module
$ sudo checkinstall

PAM configuration

For demonstration purpose, I have created a user bob on remote host, and copied my SSH authentication keys to ~/.ssh/authorized_keys file:

$ sudo useradd -m -s /bin/bash -U bob
$ sudo -su bob
$ mkdir /home/bob/.ssh
$ chmod 700 /home/bob/.ssh
# Copy SSH login public key (from Mac)
$ LOGIN_PUBKEY="<login public key from Mac>"
$ echo $LOGIN_PUBKEY > /home/bob/.ssh/authorized_keys

On your Mac, generate new SSH key pair for sudo purpose. Copy the public key to server as follows:

# On your Linux server
$ sudo mkdir /etc/ssh/sudo_authorized_keys
# Copy sudo public key (from Mac)
$ SUDO_PUBKEY="<sudo public key from Mac>"
$ echo $SUDO_PUBKEY | sudo tee -a /etc/ssh/sudo_authorized_keys/bob
$ sudo chmod 644 /etc/ssh/sudo_authorized_keys/bob

SSH agent forwarding allows a remote host to forward authentication requests back to ssh-agent running on your workstation. This is commonly used in scenarios where users need to hop from one server to other. In such cases, without agent forwarding, the user needs to copy private keys to intermediate hosts — which is insecure (another option is to to SSH ProxyCommand).

To authenticate the sudo command, pam_ssh_agent_auth has to connect with the ssh-agent running on your Mac. The SSH_AUTH_SOCK environment variable on your remote host exports ssh-agent’s connect path (Unix domain socket file), and has to be made available for the PAM module.

$ sudo visudo# Add the following line to the sudoers file, save and exit.
Defaults env_keep += SSH_AUTH_SOCK

Create a pam_sudo file, and add the following line. This will force bob to authenticate sudo commands.

$ echo "bob ALL = (ALL) ALL" | sudo tee /etc/sudoers.d/pam_sudo# Apparently you can also use groups (% prefix) instead of user.
#%<unix-group-name> ALL = (ALL) ALL

The last step is to edit /etc/pam.d/sudo file to enable PAM authentication for sudo. Add the following line to the beginning of this file. Note that the order of the line is important.

auth sufficient /lib/x86_64-linux-gnu/security/pam_ssh_agent_auth.so file=/etc/ssh/sudo_authorized_keys/%u

Here is the file after the update:

#%PAM-1.0auth sufficient /lib/x86_64-linux-gnu/security/pam_ssh_agent_auth.so file=/etc/ssh/sudo_authorized_keys/%usession    required   pam_env.so readenv=1 user_readenv=0
session required pam_env.so readenv=1 envfile=/etc/default/locale user_readenv=0
@include common-auth
@include common-account
@include common-session-noninteractive

Test touch2sudo

On your Mac:

$ export SSH_AUTH_SOCK=/var/folders/hm/x1_38yz53td1jty5xgs39dxm2lm58d/T//ssh-JssXsflTuHrC/agent.51862$ ssh -A bob@127.0.0.1 -p 2222Welcome to Ubuntu 16.04.5 LTS (GNU/Linux 4.4.0-141-generic x86_64)
...
Last login: Sun Jan 20 03:28:18 2019 from 10.0.2.2
bob@ubuntu-xenial:~$ sudo tailf /var/log/auth.log
...
Jan 20 03:32:07 ubuntu-xenial sudo[24375]: pam_ssh_agent_auth: matching key found: file /etc/ssh/sudo_authorized_keys/bob, line 2Jan 20 03:32:07 ubuntu-xenial sudo[24375]: pam_ssh_agent_auth: Found matching RSA key: f9:d4:6f:91:da:ae:10:18:26:3d:93:dc:e3:52:c7:4eJan 20 03:34:30 ubuntu-xenial sudo[24375]: pam_ssh_agent_auth: Authenticated: `bob' as `bob' using /etc/ssh/sudo_authorized_keys/bobJan 20 03:34:30 ubuntu-xenial sudo: bob : TTY=pts/2 ; PWD=/home/bob ; USER=root ; COMMAND=/bin/tailf /var/log/auth.logJan 20 03:34:30 ubuntu-xenial sudo: pam_unix(sudo:session): session opened for user root by bob(uid=0)

Security considerations

It is well known that agent forwarding has an unintended security consequence. When you enable agent forwarding, it establishes a forwarding socket from the connected server (say host-A) back to ssh-agent running on your workstation. This means your ssh-agent is accessible from host-A, and can perform authentication when you initiate a new SSH session from host-A to host-B.

The problem with this model is, if your session to host-A is active, anyone with sufficient permissions (e.g. sudo users or an attacker who managed to compromised the host) on host-A can impersonate you and connect to host-B by performing authentication using keys from your ssh-agent.

Mitigation

The following steps can help mitigate agent forwarding for sudo:

  1. Do not use your SSH login keys for sudo authentication, instead use dedicated key for sudo authentication.
  2. On the server, the authorized_key file that contains the sudo authentication public key should be owned by root. On Linux (Ubuntu), copy sudo public key to /etc/ssh/sudo_authorized_keys/<user> file.
  3. On your workstation, use dedicated ssh-agent for sudo.

Example:

$ eval $(ssh-agent)# The -c option is important.
$ ssh-add -c <sudo-auth-public-key-file>
$ ssh -A -i <ssh-login-auth-public-key-file> <remote-host>

Following the above steps can stop an adversary impersonate your identity and move laterally to other servers. However certain SSH constraints in your environment may preclude you from following this method.

Update (01/13/2022): Newer OpenSSH versions let users to override the default ssh-agent Agent using ForwardAgent option — that means you can have two ssh-agents one for SSH login, and another one to cache your sudo authentication key.

Useful links / References

http://evans.io/legacy/posts/ssh-agent-for-sudo-authentication/

https://github.com/mattrajca/sudo-touchid

https://jcs.org/notaweblog/2011/04/19/making_openssh_on_mac_os_x_more_secure

https://www.openssh.com/

--

--