Category Archives: Security

Add Google Authenticator Style 2FA (Two Factor Auth) to FreeBSD SSH

In the current climate we are all concerned with security, so this article will show you how to enforce Google Authenticator style (there are many other apps other than Google’s!) two-factor authentication on FreeBSD servers SSH.

First, we need to install a package which includes the libraries and tools for the authentication:

pkg install oath-toolkit pam_per_user

You could also install this from ports if you prefer ports-based installations.

Next we will create a new token for a user (we’ll call the user twofa):

head -10 /dev/urandom | md5 | cut -b 1-30
oathtool –totp -v <hex code from above cmd>

in the example above, my random hex code was d1ac1a3d57deeefd6b83c3c340feea.

In the output from the oathtool command, the following line shows the code you need to add to your Google Authenticator style app… for me it was:

Base32 secret: 2GWBUPKX33XP224DYPBUB7XK

You can ignore the rest of the output.  If you wanted to use a QR code to scan in your app, this is the text you need to put in your QR generator:

otpauth://totp/<user>@<host>?secret=<base32 secret>

The “<user>@<host>” part of the text is the descriptive name shown in the app and doesn’t need to be in that format, it’s just a useful reminder!

Next we need to add the reference to map this user to this authenticator code.  Using your favourite editor, edit (or create if it’s the first user) the file: /usr/local/etc/users.oath and add:

HOTP/T30/6 <username> – <hex code>

Note: you use the hex code here, not the base32 secret.  For example:

HOTP/T30/6 twofa – d1ac1a3d57deeefd6b83c3c340feea

The “HOTP/T30/6” tells it that we’re using a TOTP code with 30 second rolling window that generates 6 digit codes (this is what Google Authenticator style apps will return to us)

This file needs to be secure, so make sure you set the permissions correctly:

chmod 0600 /usr/local/etc/users.oath

Next, we need to configure the “per-user” PAM module which allows us to selectively choose which users have to use TOTP auth.  Using your favourite editor, create the file: /usr/local/etc/pam_per_user.map and add:

twofa : sshd-oath
* : sshd-default

If you wanted to enforce 2FA for all users, change the last line from sshd-default to sshd-oath.  Again, we want this file to be secure, so set the permissions:

chmod 0600 /usr/local/etc/pam_per_user.map

So now we have the code added to our authenticator app, and the reference added to our config file… all we need to do is tell SSH to enforce it!

First, make a copy of your existing sshd file as the “default” (fallback, non-2FA) option, and also a template for our new “oath” copy:

cp /etc/pam.d/sshd /etc/pam.d/sshd-default
cp /etc/pam.d/sshd /etc/pam.d/sshd-oath

Using your favourite editor, edit the file: /etc/pam.d/sshd and comment out the following lines:

#auth sufficient pam_opie.so no_warn no_fake_prompts
#auth requisite pam_opieaccess.so no_warn allow_local
#auth sufficient pam_krb5.so no_warn try_first_pass
#auth sufficient pam_ssh.so no_warn try_first_pass
#auth required pam_unix.so no_warn try_first_pass

then add the following line after the last “auth” line:

auth required /usr/local/lib/security/pam_per_user.so.1 /usr/local/etc/pam_per_user.map

This tells PAM authentication for SSHD that we need to require a lookup via the per-user module.  Now edit the file: /etc/pam.d/sshd-oath and comment out the following lines:

#auth sufficient pam_opie.so no_warn no_fake_prompts
#auth requisite pam_opieaccess.so no_warn allow_local

then add the following line after the last “auth” line:

auth required /usr/local/lib/security/pam_oath.so usersfile=/usr/local/etc/users.oath

Finally, we have to set a few settings in SSHD so that it can prompt for the code (this isn’t strictly necessary as these are all the default options anyway, but sometimes it is best to enforce them in case they change in future versions)… edit the file /etc/ssh/sshd_config and add the following at the bottom of the file:

ChallengeResponseAuthentication yes
PasswordAuthentication no
UsePAM yes

This enforces the use of PAM which will enforce the use of the per-user and 2FA modules.  Finally we have to restart the SSH service:

service sshd restart

And if we now try to SSH to the server, we will see the following:

% ssh -l twofa 192.0.2.1
Password for twofa@totp-test.local:
One-time password (OATH) for `twofa’:
Password for twofa@totp-test.local:
One-time password (OATH) for `twofa’:
Last login: Mon Nov 6 19:57:56 2017 from test.local

I deliberately entered an invalid code the first time, so it re-prompted for a code.  The second time I entered a valid code from the authenticator and you can see that it allowed us access.

To add a new user token, simply repeat the instructions for generating a hex code, base32 secret and adding to the users.oath file (also add an entry to the pam_per_user.map file unless you set the default to be sshd-oath).  No reload of SSH is needed to add new users.

To change an existing user’s token, do the same as adding a new user except update the existing entry in the file.

There can only be a single entry for each user, so you can’t add multiple tokens for a user.  However, you could add the same token onto multiple authenticator devices.

Every attempt to authenticate a 2FA user will modify the users.oath file.  For example the above modifed the entry in the file to this:

HOTP/T30/6 twofa – d1ac1a3d57deeefd6b83c3c340feea 0 217227 2017-11-06T19:58:12L

The additional parameters are:

  • 0 – The sequence number (this is unused in TOTP, it’s only used for HOTP)
  • 217227 – The last valid 2FA code entered
  • 2017-11-06T19:58:12L – The datestamp of the last valid code entry

Be very careful setting this up for the first time.  I would advise having console access available in case you make a mistake and can’t authenticate!

This should help make your FreeBSD system a little more secure by adding two-factor authentication to SSH logins.  You can also apply this to any PAM-based authentication mechanism but that’s beyond the scope of this article.

For applications, I recommend the Authy app under Android (and perhaps other mobile devices) as it can be used on multiple devices, and migrated between devices easily too.  They also include a desktop version now.

IPsec Encryption Between FreeBSD Hosts

With the passing of the evil IP Bill in the UK, security has to be focused on more closely – so I will try to do some more security related posts in the coming weeks…

By encrypting the data between my FreeBSD hosts, nobody can eavesdrop my communication (at least not without my knowledge!).  This article will show how to encrypt all IP data between two static IPs that are FreeBSD hosts.

For the sake of this article, I will assume it is between two FreeBSD 11 hosts as these already have the necessary kernel options set.  (Pre-11 FreeBSD requires a custom kernel with the various IPSEC additions being added first)

  • Host 1 has an IPv4 IP of 192.0.2.1 and an IPv6 IP of 2001:db8::1.
  • Host 2 has an IPv4 IP of 192.0.2.2 and an IPv6 IP of 2001:db8::2.
  • We will use AES-CTR encryption (in the hopes that our CPU has accellerated instructions for it via the aesni kernel module) which requires a 288-bit key to be passed to it.  (this comprises of 256 bits of key, and 32 bytes of nonce)

On both hosts, ensure that the aesni module is loaded by adding to /boot/loader.conf:

aesni_load=”YES”

You can also load it without rebooting using:

kldload aesni

Next, we can construct our rules.  Create the file /etc/ipsec.conf with these contents:

# flush out existing keys
spdflush ;
flush ;

# configure encryption between 192.0.2.1 and 192.0.2.2 in both directions
add 192.0.2.1 192.0.2.2 esp 0x6abe98ab
-m transport
-E aes-ctr 0x0d3ccb8132cbc81e625f7381d60492a27841eac320b6e01296a4c99dd9b4be0968fbb70e ;
add 192.0.2.2 192.0.2.1 esp 0x3075a69c
-m transport
-E aes-ctr 0xf6f780906d22eb00163c99ce12d99c2e246a45ff59bea5799486d5a3492a5d093a582e06 ;

# configure encryption between 2001:db8::1 and 2001:db8::2 in both directions
add 2001:db8::1 2001:db8::2 esp 0x10f71984
-m transport
-E aes-ctr 0x9b0a45098f9c28e3e1150c9cd169af5d304b89ab1be30cefcb6b1adc593955a551f441b2 ;
add 2001:db8::2 2001:db8::1 esp 0x6c11688d
-m transport
-E aes-ctr 0xd55f97a9e946121fc792725272d9e27c2bd5db4606fa7d734ee2a4344d55f3249cd65fcb ;

# force encryption between 192.0.2.1 and 192.0.2.2 in both directions
spdadd 192.0.2.1 192.0.2.2 any -P in ipsec esp/transport//require ;
spdadd 192.0.2.2 192.0.2.1 any -P out ipsec esp/transport//require ;
# force encryption between 2001:db8::1 and 2001:db8::2 in both directions
spdadd 2001:db8::1 2001:db8::2 any -P in ipsec esp/transport//require ;
spdadd 2001:db8::2 2001:db8::1 any -P out ipsec esp/transport//require ;

On Host 2 you need to swap the words “in” and “out” in the spdadd lines (just those words, not the lines themselves!)

In the above example you would set your own SPI id (the number after the word “esp”) – this is a 32-bit integer that can be listed in decimal or hex format and should be higher than 100.

You would also set your own AES keys.  You just need 36 bytes of random key data in hex format.  The above keys were generated at random.

You can also see that we are using different keys for each direction.  This isn’t required, and you can use the same key for both directions if you wish.

In the configuration file, the ‘add’ lines configure which encryption parameters will be used for communication – but does not actually activate them.  The ‘spdadd’ lines force encryption between the two IPs.  Notice that you don’t tell it which keys to use, they are acquired from the ‘add’ lines above them.

Now all you need to do is configure your system to apply the ipsec rules on boot.  Add the following lines to the end of your /etc/rc.conf file:

ipsec_enable=”YES”
ipsec_file=”/etc/ipsec.conf”

You can activate them immediately with:

service ipsec start

You should be very careful with this especially the first time as you may lock yourself out of your machine(s) !  Always ensure you have a way back in just in case – e.g. console access.

Should you be locked out, and manage to log back in via alternative means you can flush all your ipsec rules with the following command:

service ipsec stop

Remember to do this on both ends!  Also remember that this will remove ALL ipsec rules.

This method sadly can’t be used for entire subnets (other than adding rules for each IP!) so is not always the preferred method… but it’s great for quick single IP endpoints.

OpenVPN Setup in FreeBSD (with NAT for IPv4 and IPv6)

It seems people always use Linux when it comes to setting up an OpenVPN server for internet access, so here’s how to do it on FreeBSD.

You’ll need at least FreeBSD 10.3, but here’s how to do it.

Our interface will be igb0, our IPv4 public IP will be 192.0.2.10 and our IPv6 public IP will be 2001:DB8::10 – these will both be shared via NAT.

First we need to install some packages…

pkg install bash easy-rsa openvpn

Now we need to load some kernel drivers, add to /boot/loader.conf:

net.inet.ip.fw.default_to_accept=”1″
aesni_load=”YES”
crypto_load=”YES”
if_bridge_load=”YES”
if_tap_load=”YES”
ipfw_load=”YES”
ipfw_nat_load=”YES”

You will need to reboot to activate them, but you can do that at the end of the process.

We need to configure pf to NAT our IPv6 IPs, create /etc/pf.conf with these contents:

v6_wan_if=”igb0″
v6_wan_ip=”2001:DB8::10″

no nat on $v6_wan_if inet6 from $v6_wan_ip to any
nat on $v6_wan_if inet6 from any to any -> $v6_wan_ip

and we need to activate the pf config in /etc/rc.conf by adding:

pf_enable=”YES”
pf_rules=”/etc/pf.conf”
pf_program=”/sbin/pfctl”
pf_flags=””

We’re going to handle IPv4 nat using ipfw instead of pf (this may not be the best of things, but it works well) – we’ll also do some firewalling at the same time.

Create a file called /usr/local/etc/rc.d/000.ipfw.sh with the following contents:

#!/bin/sh

case “$1” in
‘start’)
/sbin/ipfw -f flush

/sbin/ipfw nat 4 config log ip 192.0.2.10

/sbin/ipfw add 00100 allow ipv6-icmp from :: to ff02::/16 // allow DAD
/sbin/ipfw add 00110 allow ipv6-icmp from fe80::/10 to fe80::/10 // allow RS RA NS NA Redirects
/sbin/ipfw add 00120 allow ipv6-icmp from fe80::/10 to ff02::/16 // allow RS RA NS NA Redirects
/sbin/ipfw add 00130 allow ipv6-icmp from any to any icmp6types 1 // allow destination unreachables
/sbin/ipfw add 00140 allow ipv6-icmp from any to any icmp6types 2,135,136 // allow NS/NA/toobig ICMPs

/sbin/ipfw add 00500 nat 4 ip4 from any to any via igb0 // NATv4

/sbin/ipfw add 00700 check-state

/sbin/ipfw add 01000 allow all from any to any via lo0 // allow loopback
/sbin/ipfw add 01010 deny all from any to 127.0.0.0/8
/sbin/ipfw add 01020 deny all from 127.0.0.0/8 to any
/sbin/ipfw add 01030 deny all from any to ::1
/sbin/ipfw add 01040 deny all from ::1 to any

/sbin/ipfw add 02000 allow ipv6-icmp from :: to ff02::/16 // allow DAD
/sbin/ipfw add 02010 allow ipv6-icmp from fe80::/10 to fe80::/10 // allow RS RA NS NA Redirects
/sbin/ipfw add 02020 allow ipv6-icmp from fe80::/10 to ff02::/16 // allow RS RA NS NA Redirects
/sbin/ipfw add 02030 allow ipv6-icmp from any to any icmp6types 1 // allow destination unreachables
/sbin/ipfw add 02040 allow ipv6-icmp from any to any icmp6types 2,135,136 // allow NS/NA/toobig ICMPs

/sbin/ipfw add 03000 allow tcp from any to any established
/sbin/ipfw add 03100 allow all from any to any frag
/sbin/ipfw add 03200 allow tcp from me to any setup
/sbin/ipfw add 03300 allow udp from me to any 53 keep-state
/sbin/ipfw add 03400 allow udp from me to any 123 keep-state

;;
‘stop’)
;;
*)
echo “Please specify ‘start’ or ‘stop'”
;;
esac

and ensure it runs on boot:

chmod a+x /usr/local/etc/rc.d/000.ipfw.sh

Now… onto OpenVPN configuration.

Create a file called /usr/local/etc/openvpn/openvpn.conf with the following contents:

script-security 2
local 192.0.2.10
port 1194
proto udp
dev tun
ca /usr/local/etc/openvpn/keys/ca.crt
cert /usr/local/etc/openvpn/keys/vpn.crt
key /usr/local/etc/openvpn/keys/vpn.key  # This file should be kept secret
crl-verify /usr/local/etc/openvpn/keys/crl.pem
dh /usr/local/etc/openvpn/keys/dh.pem
tun-ipv6
ifconfig-pool-persist /usr/local/etc/openvpn/ipp.txt
server 172.31.255.0 255.255.255.0
server-ipv6 fc00:da::/64
push “redirect-gateway def1 bypass-dhcp”
push “dhcp-option DNS 8.8.8.8”
push “route-ipv6 ::/1”
push “route-ipv6 8000::/1”
client-config-dir /usr/local/etc/openvpn/ccd
keepalive 10 30
comp-lzo
persist-key
persist-tun
status /var/log/openvpn-status.log
log-append /var/log/openvpn.log
verb 4

You’ll notice we’re using fc00:da::/64 as the IPv6 prefix for VPN clients, this is a range reserved for local usage so will not conflict with any globally reachable IP addresses.

We need to configure OpenVPN’s certificate authority now:

easyrsa init-pki
easyrsa build-ca
(enter password, set Common Name to VPN hostname)
easyrsa gen-dh
easyrsa gen-req vpn nopass
(set Common Name to VPN hostname)
easyrsa sign server vpn
(answer ‘yes’ and use password from CA step above)
easyrsa gen-crl
(enter password from CA step above)

Now we need to copy some files into the OpenVPN working directory:

mkdir /usr/local/etc/openvpn/ccd
cd /usr/local/share/easy-rsa/pki
mkdir -p /usr/local/etc/openvpn/keys
cp -p ca.crt crl.pem dh.pem index* serial* private/vpn.key issued/vpn.crt /usr/local/etc/openvpn/keys/

We’re now ready to add some users… repeat the following for each new client you want to create:

easyrsa gen-req client1 nopass
easyrsa sign client client1
cd /usr/local/share/easy-rsa/pki
cp -p private/client1.key issued/client1.crt /usr/local/etc/openvpn/keys/

and also create a file for each called /usr/local/etc/openvpn/ccd/client1 (where client1 is the username) containing:

iroute 10.0.0.0 255.0.0.0
iroute 192.168.0.0 255.255.0.0
iroute 172.16.0.0 255.240.0.0

Now all we need to do is generate the configure file(s) for each client to use.

Create a file called /usr/local/etc/openvpn/client1.ovpn containing:

client
dev tun
remote 192.0.2.10 1194 udp
resolv-retry infinite
nobind
persist-key
persist-tun
tun-ipv6
mute-replay-warnings
<ca>
(contents of /usr/local/etc/openvpn/keys/ca.crt)
</ca>
<key>
(contents of /usr/local/etc/openvpn/keys/client1.key)
</key>
<cert>
(contents of /usr/local/etc/openvpn/keys/client1.crt)
</cert>
route-delay
comp-lzo
verb 3
mute 20

The file needs to contain the contents of three files, including the lines starting “—–”

Copy this .ovpn file to the client device’s config directory – it should also work with the Android OpenVPN client.

Finally, we need to tell OpenVPN to start when FreeBSD starts by adding the following to your /etc/rc.conf file:

openvpn_enable=”YES”

Now, simply reboot and everything should work fine!

If you need to revoke a user (e.g. if they lose their device, or they become compromised), then run the following commands:

easyrsa revoke client1
easyrsa gen-crl
cp /usr/local/share/easy-rsa/pki/crl.pem /usr/local/etc/openvpn/keys/
killall -USR1 openvpn

This will revoke the client from connecting without having to re-generate all other client certificates.

HTTP Public Key Pinning (HPKP)

As we move into an more secure environment, simply have HTTPS isn’t sufficient.  Many cases of forged SSL certificates for man-in-the-middle attacks have appeared recently.

These can be obtained through deception or hacking attemps on SSL CAs.

One method to help combat this is HTTP Public Key Pinning (HPKP) – this is where the webserver can communicate to the web browser an allowed certificate path, with a sufficiently long expiry time to be of use to return visitors.  It’s not perfect or ideal, but it’s better than nothing.

This post does NOT detail the most secure method of HPKP but a compromise in terms of usability vs security.  It will ensure that an unauthorised certificate would need to have been generated by one of your chosen SSL CAs only.  The more secure method of having dual online and offline keys is out of scope for this blog post and a more advanced topic.

The way this works is to publish a header in the HTTPS response detailing the allowed hashes of public keys of webserver keys or keysigner keys that are permitted to be in the certificate chain.  This is in addition to the normal process of validation of the full SSL certificate chain.

BEWARE: misconfiguration of this header can lead people to be unable to view your site for MONTHS so be careful!

First let’s look how to obtain the SHA-256 hash needed for a certificate.  We use OpenSSL to do this based on the .crt file… Let’s assume the certificate is in the cert.crt file…

openssl x509 -pubkey < cert.crt | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary| openssl enc -base64

This will provide a Base64 encoded string which we can use later.

Now we provide the information to the webserver… i’ll detail Apache 2.4 here, but other servers will have similar methods of adding header outputs.  We add the following into the VirtualHost block for the SSL site:

Header always set Public-Key-Pins “pin-sha256=\”HASH\”; pin-sha256=\”HASH\”; max-age=30;”

where the Base64 has generated previously replaces the first ‘HASH’.

HPKP requires a minimum of 2 hashes to be present, including one hash that is NOT present in the certificate chain.  This is to encourage you to ensure you have a backup plan.

In my examples, I use the hash of my SSL public key and the hashes of my chosen primary and secondary SSL certificate authority’s intermediate certificates.

In my case, I use StartSSL’s class 2 intermediate and RapidSSL’s SHA-2 under SHA-2 root intermediate.  To generate their hashes, use the above OpenSSL method on their CA certificates.

This means that I can use my existing key/csr with any SSL CA, or I can generate a new key with my primary or secondary CA.  It also satisfies the requirement for one of the hashes to not exist (my current certificate is not present via my secondary CA)

The max-age above is set to 30 seconds for testing purposes… once you’re completely happy with your choice, this should be raised to a much longer figure.  I use a 30 day period (2592000 seconds)

A good method of testing is to use Qualys SSLlabs tester at https://www.ssllabs.com/ssltest/ – this will show you the pinned list, including any currently in the chain in a different colour.