From 51edf090a49f028c9ce6be01280a6638a8379fbd Mon Sep 17 00:00:00 2001 From: Jacob Evan Shreve Date: Fri, 28 Aug 2020 10:29:59 -0400 Subject: [PATCH] Add some initial elements of TLS 1.3 support --- tapdance/common.go | 19 +++++++++--- tapdance/conn_raw.go | 69 +++++++++++++++++++++++++++++++++++++------- tapdance/utils.go | 31 ++++++++++++++++++++ 3 files changed, 105 insertions(+), 14 deletions(-) diff --git a/tapdance/common.go b/tapdance/common.go index bf1a3880..128d5e1b 100644 --- a/tapdance/common.go +++ b/tapdance/common.go @@ -95,12 +95,18 @@ func (m *tdTagType) Str() string { } // First byte of tag is for FLAGS -// bit 0 (1 << 7) determines if flow is bidirectional(0) or upload-only(1) -// bits 1-5 are unassigned -// bit 6 determines whether PROXY-protocol-formatted string will be sent -// bit 7 (1 << 0) signals to use TypeLen outer proto +// +// bit | meaning +// -------|--------- +// 0 | is the flow bidirectional (0) or upload-only (1) ? +// 1 | is the connection TLS 1.3 (1) ? +// 2-5 | unassigned +// 6 | will PROXY-protocol-formatted string will be sent (1) ? +// 7 | use TypeLen outer proto (1) ? +// var ( tdFlagUploadOnly = uint8(1 << 7) + tdFlagTLS13 = uint8(1 << 6) tdFlagProxyHeader = uint8(1 << 1) tdFlagUseTIL = uint8(1 << 0) ) @@ -158,6 +164,11 @@ var tapDanceSupportedCiphers = []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + + // TLS 1.3 + tls.TLS_AES_128_GCM_SHA256, // 4865 tls13.refraction.network + // tls.TLS_AES_256_GCM_SHA384, // 4866 www.checkintocash.com + // tls.TLS_CHACHA20_POLY1305_SHA256, } // How much time to sleep on trying to connect to decoys to prevent overwhelming them diff --git a/tapdance/conn_raw.go b/tapdance/conn_raw.go index 08c5c80a..e77ca321 100644 --- a/tapdance/conn_raw.go +++ b/tapdance/conn_raw.go @@ -196,6 +196,7 @@ func (tdRaw *tdRawConn) tryDialOnce(ctx context.Context, expectedTransition pb.S " with connection ID: " + hex.EncodeToString(tdRaw.remoteConnId[:]) + ", method: " + tdRaw.tagType.Str()) rttToStationStartTs := time.Now() + fmt.Println(string(tdRequest)) _, err = tdRaw.tlsConn.Write([]byte(tdRequest)) if err != nil { Logger().Errorf(tdRaw.idStr() + @@ -292,7 +293,7 @@ func (tdRaw *tdRawConn) establishTLStoDecoy(ctx context.Context) error { config.ServerName) } // parrot Chrome 62 ClientHello - tdRaw.tlsConn = tls.UClient(dialConn, &config, tls.HelloChrome_62) + tdRaw.tlsConn = tls.UClient(dialConn, &config, tls.HelloChrome_72) err = tdRaw.tlsConn.BuildHandshakeState() if err != nil { dialConn.Close() @@ -304,6 +305,7 @@ func (tdRaw *tdRawConn) establishTLStoDecoy(ctx context.Context) error { return err } tdRaw.tlsConn.SetDeadline(deadline) + Logger().Infof("Session %d starting Handshake", tdRaw.sessionId) err = tdRaw.tlsConn.Handshake() if err != nil { dialConn.Close() @@ -337,31 +339,67 @@ func (tdRaw *tdRawConn) closeWrite() error { return tdRaw.tcpConn.CloseWrite() } +func (tdRaw *tdRawConn) isTLS13() bool { + return tdRaw.tlsConn.ConnectionState().Version == tls.VersionTLS13 +} + +// Generate tag for the initial TapDance request +// Documentation for tag: +// https://github.com/refraction-networking/gotapdance/wiki/Tagging-and-Signalling#payload func (tdRaw *tdRawConn) prepareTDRequest(handshakeType tdTagType) (string, error) { - // Generate tag for the initial TapDance request buf := new(bytes.Buffer) // What we have to encrypt with the shared secret using AES - masterKey := tdRaw.tlsConn.HandshakeState.MasterSecret - - // write flags + // Byte 0: Flags flags := default_flags if tdRaw.tagType == tagHttpPostIncomplete { flags |= tdFlagUploadOnly } + if tdRaw.isTLS13() { + flags |= tdFlagTLS13 + } if err := binary.Write(buf, binary.BigEndian, flags); err != nil { return "", err } - buf.Write([]byte{0}) // Unassigned byte + + // Byte 1: Unassigned + buf.Write([]byte{0}) + + // Bytes 2-3: Cipher Suite negotiatedCipher := tdRaw.tlsConn.HandshakeState.State12.Suite.Id - if tdRaw.tlsConn.HandshakeState.ServerHello.Vers == tls.VersionTLS13 { + if negotiatedCipher == 0 || tdRaw.isTLS13() { negotiatedCipher = tdRaw.tlsConn.HandshakeState.State13.Suite.Id } buf.Write([]byte{byte(negotiatedCipher >> 8), byte(negotiatedCipher & 0xff)}) + + // Bytes 4-51: handshake master secret + masterKey := tdRaw.tlsConn.HandshakeState.MasterSecret buf.Write(masterKey[:]) - buf.Write(tdRaw.tlsConn.HandshakeState.ServerHello.Random) - buf.Write(tdRaw.tlsConn.HandshakeState.Hello.Random) - buf.Write(tdRaw.remoteConnId[:]) // connection id for persistence + + // Make up for shorter master keys in TLS 1.3 + buf.Write(make([]byte, 48 - len(masterKey))) + + // Bytes 52-115: protocol secrets + if (tdRaw.isTLS13()) { + + // For TLS 1.3 we use the traffic secret + client_sec, server_sec := tdRaw.tlsConn.TrafficSecrets() + buf.Write(server_sec) + buf.Write(client_sec) + fmt.Printf("client %x\n", client_sec) + fmt.Printf("server %x\n", server_sec) + } else { + + // For <= TLS 1.2 we use the handshake randoms + buf.Write(tdRaw.tlsConn.HandshakeState.ServerHello.Random) + buf.Write(tdRaw.tlsConn.HandshakeState.Hello.Random) + } + + // Bytes 116-131: remote connection ID + buf.Write(tdRaw.remoteConnId[:]) + + // buf.Len() should == 132 + // fmt.Println(buf.Len()) err := WriteTlsLog(tdRaw.tlsConn.HandshakeState.Hello.Random, tdRaw.tlsConn.HandshakeState.MasterSecret) @@ -447,6 +485,17 @@ Content-Disposition: form-data; name=\"td.zip\" if tdRaw.tagType == tagHttpGetComplete { httpTag += "\r\n\r\n" } + + // Add an extra padding byte for non TLS 1.3 connections. + // + // 1.3 has a ciphertext feature where it encrypts the actual header type + // byte at the end of the sent plaintext. This shifts the offset we need to + // read by 1. Rather than try extracting at both offsets, we adjust non-1.3 + // connections to have the same offset. + if ! tdRaw.isTLS13() { + httpTag += " " + } + Logger().Debugf("Generated HTTP TAG:\n%s\n", httpTag) return httpTag, nil } diff --git a/tapdance/utils.go b/tapdance/utils.go index aa07b984..c9309a7e 100644 --- a/tapdance/utils.go +++ b/tapdance/utils.go @@ -282,3 +282,34 @@ func errIsTimeout(err error) bool { } return false } + +func extractTelexTag(packet []byte) []byte { + + // Strip TLS payload + payload := packet[5:] + out := make([]byte, 180) + in_offset := len(payload) - 257 + out_offset := 0 + + // While there are packets left to read and bytes left to fill + for in_offset < len(payload) - 3 && out_offset < 180 { + in_range := payload[in_offset:(in_offset)+4] + out_range := out[out_offset:(out_offset)+3] + + scaler := 64*64*64 + store := 0 + for i := 0; i < 4; i++ { + store += (int(in_range[i] & 0x3F) * scaler) + scaler /= 64 + } + + for i := 0; i < 3; i++ { + out_range[i] = byte((store >> (8 * (2-i))) & 0xFF) + } + + in_offset += 4 + out_offset += 3 + } + + return out +}