Skip to content

[Not ready for review yet] Work towards supporting TLS1.3 #1098

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 19 commits into from

Conversation

lbarman
Copy link

@lbarman lbarman commented Apr 18, 2020

Superseeded by #1101, Closing

Hello, this is a work in progress which aims at upgrading the TLS version to 1.3 in the scan functionality. This follows issue #1089.

I'll update this PR with the relevant info as I progress. Comments along the way are welcome !

Warning

My goal was to have a small and simple PR to show my workflow; the more I progress, the more I realize this PR cannot be small.

Roadmap

My goal: cfssl scan -family TLSSession google.com works and uses TLS1.3 instead of 1.2
Secondary goal: minimal changes (as I am unaware of many constraints on this codebase). Despite my attempts, I could not avoid changing the modules & vendor/ folder, sorry for the large diff.

TODOs:

  • Upgrade wire format to be compatible with TLS1.3
  • Identify & code needed extensions for cfssl scan
  • Wire-in these required extensions in the client/server handshakes.
  • Figure out why the tests in crypto/tls are ignored & fail

Minimum required for TLS1.3

Cipher Suites:

  • TLS_AES_128_GCM_SHA256 (added)
  • rsa_pkcs1_sha256 (already there)
  • ecdsa_secp256r1_sha256 (already there)
  • rsa_pss_rsae_sha256 (not needed for scan ?)

New extensions:

  • supported_version
  • key_share
  • server_name
  • signature_algorithms (already there)
  • signature_algorithms_cert
  • cookie
  • supported_groups

(last 4 are coded for marshalling but not yet used in the client)

Questions / Decisions to be made

Where to split between TLS 1.2 / 1.3

The reference implementation has a whole different client/server files for TLS1.3; these files are cleaner than what is done in this PR, where TLS 1.2/1.3 are multiplexed into the same client code. Not sure what is the best approach here. Currently we have very ugly things like:

hs := &clientHandshakeState{
	...
	suite:           suite,
	suiteTLS13:      suiteTLS13,
	finishedHash:    finishedHashTLS12,
	transcriptTLS13: transcriptTLS13,
}

Where suite and finishedHash are not needed in TLS1.3. Using polymorphism here seems like a bad clutch. It would be cleaner to do like in the reference implementation, with handshake_client.go and handshake_client_tls13.go (I didn't start this way because I wanted to do the minimal changes to enable 1.3, now I'm a bit stuck in a bad place).

Relation with the reference implementation

In general, do we want to "copy-paste" the reference implementation ? Only the needed features ? "Carefully" copy-paste (additionally double-checking that it is conform with the RFC) ?

Suggestions / Future improvements of this PR

Use cryptobyte package

Go's reference implementation has a nice way of building the packets with the cryptobyte package. It seems less error-prone and avoid a duplication (1st computing the size, then actually embedding the info).

Example: in cfssl's scan/crypto/tls/handshake_messages.go, the code:

// compute size
if len(m.supportedCurves) > 0 {
    extensionsLength += 2 + 2 * len(m.supportedCurves)
    numExtensions++
}
[...]
// embed curves
if len(m.supportedCurves) > 0 {
    // http://tools.ietf.org/html/rfc4492#section-5.5.1
    z[0] = byte(extensionSupportedCurves >> 8)
    z[1] = byte(extensionSupportedCurves)
    l: = 2 + 2 * len(m.supportedCurves)
    z[2] = byte(l >> 8)
    z[3] = byte(l)
    l -= 2
    z[4] = byte(l >> 8)
    z[5] = byte(l)
    z = z[6: ]
    for _, curve: = range m.supportedCurves {
        z[0] = byte(curve >> 8)
        z[1] = byte(curve)
        z = z[2: ]
    }
}

is reduced to the following in Golang's crypto/tls/handshake_messages.go:

if len(m.supportedCurves) > 0 {
    // RFC 4492, sections 5.1.1 and RFC 8446, Section 4.2.7
    b.AddUint16(extensionSupportedCurves)
    b.AddUint16LengthPrefixed(func(b * cryptobyte.Builder) {
        b.AddUint16LengthPrefixed(func(b * cryptobyte.Builder) {
            for _, curve: = range m.supportedCurves {
                b.AddUint16(uint16(curve))
            }
        })
    })
}

Minor

Add a CLI option to explicitly scan in 1.2 or 1.3 ?

@lbarman lbarman marked this pull request as draft April 18, 2020 19:07
Add wire format for extensions: supportedVersions, cookie,
keyShares, earlyData, pskModes, pskIdentities & binders

Tested successfully against fuzzer (careful: `test.sh` don't test this).
Command:
`go test -mod=vendor -race -covermode=atomic github.com/cloudflare/cfssl/scan/crypto/tls -v -run TestMarshalUnmarshal`

Not wired to actual creation of handshakes (=> these extensions are just
ready to be used but not activated by default)

Does not include other TLS1.3 messages: encryptedExtensionsMsg, endOfEarlyData,
keyUpdateMsg, newSessionTicketMsg, certificateRequestMsg, certificateMsg
@codecov-io
Copy link

codecov-io commented Apr 19, 2020

Codecov Report

Merging #1098 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #1098   +/-   ##
=======================================
  Coverage   56.27%   56.27%           
=======================================
  Files          77       77           
  Lines        7309     7309           
=======================================
  Hits         4113     4113           
  Misses       2727     2727           
  Partials      469      469           

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6b49bea...fea5b00. Read the comment docs.

Breaking because we don't support the correct suites yet.
Careful: our interface for a suite uses an aead function defined as
`func(key, fixedNonce []byte) cipher.AEAD`

...whereas the reference implementation returns a struct `aead` which has
the additional method:

```golang
type aead interface {
	cipher.AEAD

	// explicitNonceLen returns the number of bytes of explicit nonce
	// included in each record. This is eight for older AEADs and
	// zero for modern ones.
	explicitNonceLen() int
}
```
@lbarman
Copy link
Author

lbarman commented Apr 20, 2020

Reason for force-push: I started adding the non-mandatory suites with chacha20poly1305 which modified both go.mod and vendor/, adding thousands of diff lines, and it's not required for TLS1.3. Reverting to do the minimal changes required for TLS1.3 and have a clean diff for review.

lbarman added 14 commits April 20, 2020 12:22
Note: crypto suites are split between suite/suiteTLS13 in
clientHandshakeState; the latter is *unused* yet!
Handshake doesn't complete upon receiving the ServerHello due to some
legacy TLS1.2 logic
TLS1.2 uses "finishedHash" which is incompatible with 1.3; now both are
coded and there is a (fairly ugly) if-switch.

NOTE: hashes are not computed correctly (will not work when the hash is
used for cert validation), but the handshake successfully terminates.
(Seems unavoidable since HKDF is used in TLS1.3's key schedule)
I wanted to avoid large changes, but notably conn.go is fairly different
in the reference implementation, where it works for both TLS 1.2 and
1.3, so I don't see the value in manually patching our current conn.go.

I also use their interface aead (instead of cipher.AEAD) which I
tried to avoid using at first.
It compiles but still lacks logic from TLS1.3
- encryptedExtensionMsg
- endOfEarlyData
- keyUpdateMsg
- newSessionTicketMsg
- certificateRequestMsg
- certificateMsg

Note: these new message use the cryptobyte package, but not the old ones
Note: this is the commit that was really needed to swap for TLS1.3; all
previous commits were an attempt to patch the current implementation
towards 1.3, but that was long and error-prone. This is a clean change
on top of the copy-pasted reference implementation

Note2: Grading is still not updated to 1.3
Note3: I didn't update/run the tests (which the reference implementation
do not have)
@lbarman lbarman closed this Apr 22, 2020
@lbarman lbarman mentioned this pull request Apr 22, 2020
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants