Xt_sslpin


With xt_sslpin you can pin certificates at the netfilter level. Keeping users save from malicious or not trusted certificates.

I do assume you know how SSL/TLS works and why certificates are an important concept. If you need a refresher on the topic I highly recommend reading the following pages:

Certificate pinning

For an introduction to SSL/TLS certificate pinning refer to the OWASP pinning cheat sheet. The section “What Should Be Pinned” introduces two different pinning methods namely public key pinning and certificate pinning. fredburger/xt_sslpin lets you do public key pinning. xt_sslpin was designed for certificate pinning. Pros and cons of the two methods are nicely covered by the “What Should Be Pinned” - chapter1. The following two sections cover implementation details of the two modules.

Public key pinning: fredburger/xt_sslpin

As mentioned in the fredburger/README.md public keys are directly specified in the matching iptables rule:

iptables -I <chain> .. -m sslpin [!] –pubkey <alg>:<pubkey-hex> [–debug] ..

fredburger/xt_sslpin extracts public keys from a certificates transported in the server’s certificate message and matches rules on the server’s changeCipherSpec message.

handshake fredburger_xt_sslpin fredburger/xt_sslpin intercepted SSL/TLS handshake

Example usage

In order to get the public key algorithm as well as the subjectPublicKeyInfo from a certificate you can use the following (bash-)command:

echo                                                                            \
| openssl s_client -connect github.com:443 -servername github.com 2>/dev/null   \
| tee                                                                           \
  >(                                                                            \
    openssl x509 -text                                                          \
    | grep 'Public Key Algorithm'                                               \
  )                                                                             \
  >(                                                                            \
    openssl x509 -pubkey -noout                                                 \
    | sed '1d; $d'                                                              \
    | base64 -d                                                                 \
    | hexdump -v -e '1/1 "%.2x"'                                                \
  )                                                                             \
&>/dev/null

Using the just fetched information we can now start writing a rule which drops connections matching the github public key.

Public Key Algorithm: rsaEncryption
30820122300d06092a864886f70d01010105000382010f003082010a0282010100e7885cf2965c97181cba98e203f17f399191c26fd996e7284064cd4ca98112036cae7fe6c619e05a63f06c0bd468b3fffd3efd25cfb5597329c4c8b3f4f2bac9945116e228d1dd9bc78db7340ea138bd914ed6e77ecfb2d0f152fd84e94127a54eeabe16ec2db39bfa680c1e37231c603d070726e491da2c1680dc70137327dd8073c2391150d47373abff88d2c99c33c6ef6476606507378732fb2a747f125fd98d6a15ed5f1469c199c18948f0dfa3e037eb3d18b586ada7ddd364f4bb1f58cdde5ece4331ba4a84010ec02882228ef6963c025b2bfe765cb848cb6be918dca5ca78bf0d00f5f1b04f4fe646d6ebf44103fd2ee63f8e83be14a0ce4e57abe30203010001

Note that the retrieved hex-bytes contain the public key preceded by an algorithm identifier. fredburger/xt_sslpin uses representation strings (rsa, dsa, ec) for algorithm identification. This means we’ve to translate the first couple of bytes to such a string.

ALGORITHM_ID="30820122300d06092a864886f70d01010105000382010f00"
ALGORITHM="rsa"

KEY="3082010a0282010100e7885cf2965c97181cba98e203f17f399191c26fd996e7284064cd4ca98112036cae7fe6c619e05a63f06c0bd468b3fffd3efd25cfb5597329c4c8b3f4f2bac9945116e228d1dd9bc78db7340ea138bd914ed6e77ecfb2d0f152fd84e94127a54eeabe16ec2db39bfa680c1e37231c603d070726e491da2c1680dc70137327dd8073c2391150d47373abff88d2c99c33c6ef6476606507378732fb2a747f125fd98d6a15ed5f1469c199c18948f0dfa3e037eb3d18b586ada7ddd364f4bb1f58cdde5ece4331ba4a84010ec02882228ef6963c025b2bfe765cb848cb6be918dca5ca78bf0d00f5f1b04f4fe646d6ebf44103fd2ee63f8e83be14a0ce4e57abe30203010001"

iptables -I INPUT \
    -m conntrack --ctstate ESTABLISHED \
    -p tcp --sport 443 \
    -m sslpin --debug --pubkey "${ALGORITHM}:${KEY}" \
    -j DROP

Connecting to github using curl -4 https://github.com yields:

kernel: [903.845472] xt_sslpin: sslparser: ServerHello handshake message (len = 109)
kernel: [903.845480] xt_sslpin: sslparser: Certificate handshake message (len = 3136)
kernel: [903.845488] xt_sslpin: sslparser: cn = "github.com"
kernel: [903.845505] xt_sslpin: sslparser: pubkey_alg = { name:"rsa", oid_asn1_hex:[2a864886f70d0101] }
kernel: [903.845513] xt_sslpin: sslparser: pubkey = [3082010a0282010100e7885cf2965c97181cba98e203f17f399191c26fd996e7284064cd4ca98112036cae7fe6c619e05a63f06c0bd468b3fffd3efd25cfb5597329 c4c8b3f4f2bac9945116e228d1dd9bc78db7340ea138bd914ed6e77ecfb2d0f152fd84e94127a54eeabe16ec2db39bfa680c1e37231c603d070726e491da2c1680dc70137327dd8073c2391150d47373abff88d2c99c33c6ef6476606507378732fb2a747f125fd98d6a 15ed5f1469c199c18948f0dfa3e037eb3d18b586ada7ddd364f4bb1f58cdde5ece4331ba4a84010ec02882228ef6963c025b2bfe765cb848cb6be918dca5ca78bf0d00f5f1b04f4fe646d6ebf44103fd2ee63f8e83be14a0ce4e57abe30203010001]
kernel: [903.845639] xt_sslpin: sslparser: ServerKeyExchange handshake message (len = 329)
kernel: [903.845647] xt_sslpin: sslparser: ServerDone handshake message (len = 0)
kernel: [903.945050] xt_sslpin: sslparser: ChangeCipherSpec record
kernel: [903.945070] xt_sslpin: rule matched (cn = "github.com")

You might wonder why curl still prints the github page. A quick look into the fredburger/README.md explains why:

Per connection, the incoming handshake data is parsed once across all -m sslpin iptables rules; upon receiving the SSL/TLS handshake ChangeCipherSpec message, the parsed certificate is checked by all rules. After this, the connection is marked as “finished”, and xt_sslpin will not do any further checking.

This means we only block the very first changeCipherSpec message. TCP will retransmit this message, and the connection will still succeed. Which is exactly what wireshark tells us:

TCP retransmit of the changeCipherSpec message Frame 89 is a retransmission of frame 67 which contains the changeCipherSpec message

Fingerprint pinning: xt_sslpin

xt_sslpin works based on fingerprints. A fingerprint is a SHA1 hash of a x509 and DER encoded certificate. A collection of fingerprints is called a fingerprint list. These lists are the key part when we specify an iptables rule:

iptables -I <chain> .. -m sslpin [!] –fpl <fingerprint list id> ..

xt_sslpin extracts certificates transported in the server’s certificate message, generates fingerprints and matches them against fingerprint lists. Then it starts matching the rules immediately. This change allows for early stage abortion of malicious connections.

handshake xt_sslpin xt_sslpin intercepted SSL/TLS handshake

Example usage

You can retrieve the fingerprint of the github.com certificate using the following command:

echo \
| openssl s_client -connect github.com:443 -servername github.com 2>/dev/null \
| openssl x509 -outform DER \
| sha1sum

For managing fingerprint lists the kernel exposes a user space API under /sys/kernel/xt_sslpin/. Using this API should be straightforward.

Operation Command
ADD echo fingerprint-sha1 > /sys/kernel/xt_sslpin/<list id>_add
REMOVE echo fingerprint-sha1 > /sys/kernel/xt_sslpin/<list id>_rm
LIST ls /sys/kernel/xt_sslpin/<list id>

Adding the just generated github.com fingerprint to list 0 is as simple as:

echo d79f076110b39293e349ac89845b0380c19e2f8b > /sys/kernel/xt_sslpin/0_add

The fingerprint list 0 can now be used in iptables as follows:

iptables -I INPUT \
    -m conntrack --ctstate ESTABLISHED \
    -p tcp --sport 443 \
    -m sslpin --fpl 0 
    -j DROP

Outlook

There are several to dos mentioned in the README.md. Apart from all them I’d like to merge the functionality from fredburger/xt_sslpin into xt_sslpin. In addition I’m currently evaluating how well the system works in a blacklisting scenario. Whitelisting should work just fine as mechanisms such as HPKP already do this in a similar way.

Remarks

  • Neither fredburger/xt_sslpin nor xt_sslpin does certificate chain validation or signature checks. This remains the responsibility of the client.
  • If you want to block connections permanently you could use connmark to mark and drop all packets from matching connections. Something like the following should do the trick:
iptables -A INPUT -j CONNMARK --restore-mark
iptables -A INPUT \
    -m conntrack --ctstate ESTABLISHED \
    -p tcp --sport 443 \
    -m sslpin --fpl 0 
    -j CONNMARK --set-mark 1
iptables -A INPUT -m connmark --mark 1 -j DROP
iptables -A INPUT -j CONNMARK --save-mark
  1. seriously, read it!