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.
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:
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.
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
seriously, read it! ↩