Setup 2FA SSH on MacOS Sierra

For some time, I have been using 2FA for most of my sensitive logins on the Internet. From Google to FB to WordPress to Git. Any login with sensitive information is setup with 2FA using (FIDO U2F).

Anyway, the one setup that has been missing (don’t ask me why I had not done is sooner) was our home MacMini. Our MacMini stays on all of the time and serves as our home computer. We use it for everything, from a Plex Server to printer server, to file server, you name it.

I like to be able to access it remotely and as such I have setup SSH on it. However, until recently, it was pretty open. I looked online for support on how to setup 2FA and below are the steps I followed to get it to work:

Basic definitions

  • Server: This is the host to which you want to connect and on which you’ll be installing 2FA
  • Client: This is any host other the the server


  1. You need to have xcode, command line tools and homebrew installed. You should do this from the
    1. Install xcode: That you need to do from the App Store. Just open the App Store,  look for xcode and install it
    2. Install command line tools:
      xcode-select --install

      That should bring up a prompt on the screen asking if you want to install command line tools. Click Installinstall xcode on mavericks step 1

    3. Install homebrew:
      /usr/bin/ruby -e "$(curl -fsSL install/master/install)"
  2. Get the latest release of Google Authenticator. Download and unzip.
  3. Build and install Google Authenticator:
    sudo make install
  4. Update sshd to use Google Authenticator
    1. Make a copy of /etc/pam.d/sshd:
      sudo cp /etc/pam.d/sshd /etc/pam.d/
    2. Make a copy of /etc/ssh/sshd_config:
      sudo cp /etc/ssh/sshd_config /etc/ssh/
    3. Update sshd to make use of the Google Authenticator shared object (
      //This will let you edit the file
      sudo vi /etc/pam.d/sshd
      //Add this line below the "auth" section 
      auth       required       /usr/local/lib/security/
      //Save and exit 
    4. Update sshd_config:
      //Open the file for edit:
      sudo vi /etc/ssh/sshd_config
      //Look for #ChallengeResponseAuthentication yes and remove the hash
      ChallengeResponseAuthentication yes
      //Save and exit
  5. Restart sshd for the changes to take effect:
    sudo launchctl unload  /System/Library/LaunchDaemons/ssh.plist
    sudo launchctl load  /System/Library/LaunchDaemons/ssh.plist
  6. Setup Google Authenticator for the desired user
    1. Assuming you have performed all of the actions above logged in as the desired user, just continue, otherwise exit and login as the desired user.
    2. Setup Google Authenticator:
      //Locate the folder where you unzipped Google Authenticator and execute google-authenticator
      Do you want authentication tokens to be time-based (y/n) y
      Your new secret key is: ABCDEFGHIJKLMNOP 
      Your verification code is 000000
      Your emergency scratch codes are:
      Do you want me to update your "/Users/<your-username>/.google_authenticator" file (y/n) y
      Do you want to disallow multiple uses of the same authentication
      token? This restricts you to one login about every 30s, but it increases
      your chances to notice or even prevent man-in-the-middle attacks (y/n) y
      By default, tokens are good for 30 seconds and in order to compensate for
      possible time-skew between the client and the server, we allow an extra
      token before and after the current time. If you experience problems with poor
      time synchronization, you can increase the window from its default
      size of 1:30min to about 4min. Do you want to do so (y/n) y
      If the computer that you are logging into isn't hardened against brute-force
      login attempts, you can enable rate-limiting for the authentication module.
      By default, this limits attackers to no more than 3 login attempts every 30s.
      Do you want to enable rate-limiting (y/n) y
    3. With a browser open the long URL. This will generate a QR Code. Scan the code using your favorite Google Authenticator App. I personally like Authy as it can sync between devices.
  7. Close all open SSH connections you may have with the server.
  8. From a client ssh into the host and voila, 2FA works 😀 username$ ssh
    Verification code:
    Last login: Thu Dec  7 16:09:24 2017 from username$


The internet is nothing, if not for a bunch of really smart people that love to share their experiences and findings. I was able to get this to work thanks to these posts:


You 2.0: Getting Unstuck

At one time or another, many of us feel stuck: in the wrong job, the wrong relationship, the wrong city – the wrong life. Psychologists and self-help gurus have all kinds of advice for us when we feel rudderless. This week on Hidden Brain, we conclude our You 2.0 series with a favorite episode exploring a new idea from an unlikely source: Silicon Valley.

* Duration: 29:09, Played: 14:23

* Published: 8/29/17 03:01:18

* Episode Download Link (27 MB):

* Episode Feed: Hidden Brain – in JMeter against some URLs


Recently I had to investigate an issue where certain JMeter tests pointing to an HTTPS end-point were resulting in this error:

Initially I thought it would be an easily fix by simply using the HTTP3.x sampler, rather than the default HTTP4.x.


The solution was to upgrade JMeter to version 3.2 (we were running 2.13 for good reasons).

How it was troubleshooted

Bad Certificates

If you do a search on that error you will be led to believe it’s an issue with the validity of the certificate being issued. To address this, it is suggested to:

  1. Upgrade Java to the latest
  2. Replace the UnlimitedJCEPolicyJDK8 JARs under <java>/jre/lib/security/
  3. Download the certificates from the server and added to the trusted cacerts

It’s the SNI

Given it was not the bad certificates, I enabled SSL debugging and got the following:

Allow unsafe renegotiation: false
Allow legacy hello messages: true
Is initial handshake: true
Is secure renegotiation: false
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 for TLSv1
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 for TLSv1
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 for TLSv1.1
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 for TLSv1.1
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 for TLSv1.1
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 for TLSv1.1
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 for TLSv1.1
%% No cached client session
*** ClientHello, TLSv1.2
RandomCookie:  GMT: 1495568515 bytes = { 82, 170, 89, 172, 24, 224, 26, 122, 144, 41, 158, 48, 243, 159, 93, 186, 63, 66, 119, 137, 96, 246, 218, 179, 67, 45, 163, 50 }
Session ID:  {}
Compression Methods:  { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA256withDSA, SHA224withECDSA, SHA224withRSA, SHA224withDSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA
[write] MD5 and SHA1 hashes:  len = 239
0000: 01 00 00 EB 03 03 59 25   91 83 52 AA 59 AC 18 E0  ......Y%..R.Y...
0010: 1A 7A 90 29 9E 30 F3 9F   5D BA 3F 42 77 89 60 F6  .z.).0..].?Bw.`.
0020: DA B3 43 2D A3 32 00 00   64 C0 24 C0 28 00 3D C0  ..C-.2..d.$.(.=.
0030: 26 C0 2A 00 6B 00 6A C0   0A C0 14 00 35 C0 05 C0  &.*.k.j.....5...
0040: 0F 00 39 00 38 C0 23 C0   27 00 3C C0 25 C0 29 00  ..9.8.#.'.<.%.).
0050: 67 00 40 C0 09 C0 13 00   2F C0 04 C0 0E 00 33 00  g.@...../.....3.
0060: 32 C0 2C C0 2B C0 30 00   9D C0 2E C0 32 00 9F 00  2.,.+.0.....2...
0070: A3 C0 2F 00 9C C0 2D C0   31 00 9E 00 A2 C0 08 C0  ../...-.1.......
0080: 12 00 0A C0 03 C0 0D 00   16 00 13 00 FF 01 00 00  ................
0090: 5E 00 0A 00 34 00 32 00   17 00 01 00 03 00 13 00  ^...4.2.........
00A0: 15 00 06 00 07 00 09 00   0A 00 18 00 0B 00 0C 00  ................
00B0: 19 00 0D 00 0E 00 0F 00   10 00 11 00 02 00 12 00  ................
00C0: 04 00 05 00 14 00 08 00   16 00 0B 00 02 01 00 00  ................
00D0: 0D 00 1C 00 1A 06 03 06   01 05 03 05 01 04 03 04  ................
00E0: 01 04 02 03 03 03 01 03   02 02 03 02 01 02 02     ...............
TestPlan_ThreadGroup 1-1, RECV TLSv1.2 ALERT:  fatal, handshake_failure
TestPlan_ThreadGroup 1-1, called closeSocket()
TestPlan_ThreadGroup 1-1, handling exception: Received fatal alert: handshake_failure
TestPlan_ThreadGroup 1-1, called close()
TestPlan_ThreadGroup 1-1, called closeInternal(true)

I confirmed the cipher suites available were compatible with the cipher used in the certificate and by searching for errors around the RECV TLSv1.2 ALERT: fatal, handshake_failure error, the SNI possibility showed itself.

Remember that we could run the same test (same load, specs, etc) against some URLs that we also encrypted using TLSv1.2 so we knew the delta was in how TLS was setup (and we unfortunately didn’t have access to the server logs).

To confirm if SNI was the problem we ran this:

openssl s_client -connect <your host>:443

If you get an error, your host requires SNI. To confirm, SNI is the solution do this:

openssl s_client -connect <your host>:443 -servername '<your host>'

If you get a valid response (which you should), your server requires SNI.

Now that we knew the server required it, we just need to find out how to enable it.

And if you search for “sni jmeter” you get this post. Which basically confirms it was broken on HTTPClient4.x but later fixed. And given we used JMeter 2.13, it meant upgrading to JMeter 3.2 (at the time of this writing it was the latest)

Upgrading to JMeter 3.2 solved the issue. Good luck.