Menu blog

Secure Transport above TLS

by Ross Kinder

When we designed we made technology choices that would make us resistant to critical vulnerabilities: we selected technology carefully, deliverately wrote secure code, and studied critical vulnerabilities.

We were concerned by the widespread use of SSL man-in-the-middle proxies, which break the security primitives of TLS. For more on how broken TLS MITM is, check out this analysis and this resarch–the upshot is that we've really shot ourselves in the foot by deploying SSL “inspection” tools.

Although we don’t use OpenSSL ourselves, we lived through its recent spate of vulnerabilities. We were concerned that a vulnerability in a TLS implementation might leak credentials between our web and mobile clients and our servers.

We designed the client-to-server protocol assuming that we can't count on TLS to provide either confidentiality or integrity.

Here’s how it works

On first run the client generates a key pair in secure storage that it will use to identify itself. We'll laster use the public part of this key as a device ID. The private key is used to sign the session setup. The client initiates Elliptic Curve Diffie-Hellman key exchange using the NIST P521 curve and SHA-512 by telling the server its QA.

POST /auth/v1/init HTTP/1.1
Host: server
Content-Length: 0
X-Session: BAFj0sPBvSNI7Xki6nqgwXjFu/fYu5KcAVd0gfjLTEP6lEIvBcXqqF8JWQANeROz2W3L8rQ3gu8Zgd8FPcuoVCIZJwGJG1mLVzUDpzSmBKSADA0XbzW3PE1GIuewCxo8qz47K3+Xe0UguWUa7ACkef9wEGEcYO29NdohOjlzEFAjesvZqw

The server generates its keys and derives xk according to ECDH. It responds with QB, its X.509 certificate chain, and the SHA-512 of (QA,QB,xk) signed by the server's certificate.

HTTP/1.1 200 OK
Content-Type: x-grooveid/session-init-reply
Date: Wed, 31 May 2017 17:59:10 GMT
Content-Length: 1628

  "c": [
  "q": "BACBCAoF9/3mBCqbNVpW1kHpNJ8IgqsoGPmc3TaDVpCusRp+WllJScwGCBTD/JGZkYKM3K4acoeEm8C5hMvuAfg5DQCxiJGhKgWGl/tcDad97lpiCjoimKQxVMcoZr+KKEnnO3McnPLsItOB79oeQqhPgrdJlyQPI1ZrrwXhpwXgKaub/A==",
  "s": "MIGIAkIByFlG85SgYeWb2tAoyNxRpFqVDE3AijsP6mFqd/fNB5qgiusulZ50X0mx6uzcWubdCnfUs9nfNEryqPJC78AUCS8CQgC+HdAdOzSlCjzf9AsKszEJjPZqE8QEVBTa7EZk4q8eDfnOkc4lVNOdlalRVeIUzrRuWbvkg4tPHncAbBz/QkFCRg=="

In the response c is the certificate chain, q is QB, and s is the signature.

The client verifies the server certificate chain against the root certificate it ships with. It derives xk and verifies that the server’s signature is valid. It signs the same hash with it’s long-term device_key and sends the signature back to the server.

POST /auth/v1/verify HTTP/1.1
Host: server
Content-Length: 383
X-Session: BAFj0sPBvSNI7Xki6nqgwXjFu/fYu5KcAVd0gfjLTEP6lEIvBcXqqF8JWQANeROz2W3L8rQ3gu8Zgd8FPcuoVCIZJwGJG1mLVzUDpzSmBKSADA0XbzW3PE1GIuewCxo8qz47K3+Xe0UguWUa7ACkef9wEGEcYO29NdohOjlzEFAjesvZqw

  "c": "BAEPY7A9MCr7bYm4FWaogGEDcrRtTL1AetlwC1OYPX405kFkHcHdDyfNRCNrWGmRILTK7p3f9TSwoelsvzAcnJmJpAHoIwzNidPIuB6cDn+5YVDNwoMmTQ3JDK5RRjOfDZ/kA4AjLcJJpBf39sIKsm3mH/A2WHGBIVf5britj9SinJRrrA==",
  "s": "MIGIAkIBur2HFTUtSsCr2BM+LEs9x3vA6v5xIvXtJykGHL2TcVI98nYs6Q3NJtwEcy3U/vSKkqQL3KVFxluU3tb8U+CSqIsCQgG04hDfwVzUVdRnC79WeHrh5ffOo78/PAHZshRNb/x9Ws17BVEy0RoYXzDM/GCZR3BS/JM2T4wt2Hnn5ZM8nYn64Q=="

In the response c is the client certificate chain, q is QB, and s is the signature.

The server verifies the client’s signature and the session is established. The client’s public key doubles as a device identifier.

The client’s QA doubles as a session identifier, and is included in all requests in the X-Session header.

From this point on, all requests and responses use AES256 in GCM mode using a key derived from xk .

POST /auth/v1/data HTTP/1.1
Host: server
Transfer-Encoding: chunked
Content-Type: application/x-grooveid-encrypted
X-Session: BAFj0sPBvSNI7Xki6nqgwXjFu/fYu5KcAVd0gfjLTEP6lEIvBcXqqF8JWQANeROz2W3L8rQ3gu8Zgd8FPcuoVCIZJwGJG1mLVzUDpzSmBKSADA0XbzW3PE1GIuewCxo8qz47K3+Xe0UguWUa7ACkef9wEGEcYO29NdohOjlzEFAjesvZqw

[… encrypted data …]

And the response:

HTTP/1.1 200 OK
Content-Type: application/x-grooveid-encrypted
Date: Wed, 31 May 2017 17:59:10 GMT
Content-Length: 287

[ … encrypted data …]

Dangers Avoided

We're insulated from TLS man-in-the-middle, and from TLS implementation defects. But we are also “rolling our own” crypto–a practice that is generally frowned upon. We've taken steps to minimize this risk.

First, most cryptographic protocols allow various ciphers, hash algorithms, and key agreement protocols. The parties must negotiate at runtime which ciphers to use. This is designed to smooth the integration of new algorithms and removal of old ones. However, the requisite negotiation has proven to be problematic over time. Instead, we've selected currently secure ciphers and assigned a version the entire protocol. If SHA-512 or the P521 Curve fall out of favor, we’ll change it by incrementing the protocol version and deprecating the old version.

Second, we’re using cipher, hash and curve implementations from the go standard library, which has an active security team, and a good track record.

Third, although the wire protocol is different, we’ve stolen the basic session setup from SSH, whose security properties are well studied and known.

And finally we were scared shitless doing this. Being scared shitless helps write good defensive code.

And if all this fails, in the end the is still protected by the underlying TLS implementation.

Be we aren’t counting on it.