utls/u_conn_test.go
Gaukas Wang 8094658e76
new: Support TLS-PSK (TLS 1.3) (#231)
* uTLS: X25519Kyber768Draft00 hybrid post-quantum key agreement by cloudflare/go (#222)

* crypto/tls: Add hybrid post-quantum key agreement  (#13)

* import: client-side KEM from cloudflare/go

* import: server-side KEM from cloudflare/go

* fix: modify test to get rid of CFEvents.

Note: uTLS does not promise any server-side functionality, and this change is made to be able to conduct unit tests which requires both side to be able to handle KEM Curves.

Co-authored-by: Christopher Wood <caw@heapingbits.net>
Co-Authored-By: Bas Westerbaan <bas@westerbaan.name>

----

Based on:

* crypto/tls: Add hybrid post-quantum key agreement 

Adds X25519Kyber512Draft00, X25519Kyber768Draft00, and
P256Kyber768Draft00 hybrid post-quantum key agreements with temporary
group identifiers.

The hybrid post-quantum key exchanges uses plain X{25519,448} instead
of HPKE, which we assume will be more likely to be adopted. The order
is chosen to match CECPQ2.

Not enabled by default.

Adds CFEvents to detect `HelloRetryRequest`s and to signal which
key agreement was used.

Co-authored-by: Christopher Wood <caw@heapingbits.net>

 [bas, 1.20.1: also adds P256Kyber768Draft00]
 [pwu, 1.20.4: updated circl to v1.3.3, moved code to cfevent.go]

* crypto: add support for CIRCL signature schemes

* only partially port the commit from cloudflare/go. We would stick to the official x509 at the cost of incompatibility.

Co-Authored-By: Bas Westerbaan <bas@westerbaan.name>
Co-Authored-By: Christopher Patton <3453007+cjpatton@users.noreply.github.com>
Co-Authored-By: Peter Wu <peter@lekensteyn.nl>

* crypto/tls: add new X25519Kyber768Draft00 code point

Ported from cloudflare/go to support the upcoming new post-quantum keyshare.

----

* Point tls.X25519Kyber768Draft00 to the new 0x6399 identifier while the
  old 0xfe31 identifier is available as tls.X25519Kyber768Draft00Old.
* Make sure that the kem.PrivateKey can always be mapped to the CurveID
  that was linked to it. This is needed since we now have two ID
  aliasing to the same scheme, and clients need to be able to detect
  whether the key share presented by the server actually matches the key
  share that the client originally sent.
* Update tests, add the new identifier and remove unnecessary code.

Link: https://mailarchive.ietf.org/arch/msg/tls/HAWpNpgptl--UZNSYuvsjB-Pc2k/
Link: https://datatracker.ietf.org/doc/draft-tls-westerbaan-xyber768d00/02/
Co-Authored-By: Peter Wu <peter@lekensteyn.nl>
Co-Authored-By: Bas Westerbaan <bas@westerbaan.name>

---------

Co-authored-by: Bas Westerbaan <bas@westerbaan.name>
Co-authored-by: Christopher Patton <3453007+cjpatton@users.noreply.github.com>
Co-authored-by: Peter Wu <peter@lekensteyn.nl>

* new: enable PQ parrots (#225)

* Redesign KeySharesEcdheParameters into KeySharesParameters which supports multiple types of keys.

* Optimize program logic to prevent using unwanted keys

* new: more parrots and safety update (#227)

* new: PQ and other parrots

Add new preset parrots:
- HelloChrome_114_Padding_PSK_Shuf
- HelloChrome_115_PQ
- HelloChrome_115_PQ_PSK

* new: ShuffleChromeTLSExtensions

Implement a new function `ShuffleChromeTLSExtensions(exts []TLSExtension) []TLSExtension`.

* update: include psk parameter for parrot-related functions

Update following functions' prototype to accept an optional pskExtension (of type *FakePreSharedKeyExtension):
- `UClient(conn net.Conn, config *Config, clientHelloID ClientHelloID)` => `UClient(conn net.Conn, config *Config, clientHelloID ClientHelloID, pskExtension ...*FakePreSharedKeyExtension)`
- `UTLSIdToSpec(id ClientHelloID)` => `UTLSIdToSpec(id ClientHelloID, pskExtension ...*FakePreSharedKeyExtension)`

* new: pre-defined error from UTLSIdToSpec

Update UTLSIdToSpec to return more comprehensive errors by pre-defining them, allowing easier error comparing/unwrapping.

* new: UtlsPreSharedKeyExtension

In `u_pre_shared_key.go`, create `PreSharedKeyExtension` as an interface, with 3 implementations:
- `UtlsPreSharedKeyExtension` implements full support for `pre_shared_key` less resuming after seeing HRR.
- `FakePreSharedKeyExtension` uses CipherSuiteID, SessionSecret and Identities to calculate the corresponding binders and send them, without setting the internal states. Therefore if the server accepts the PSK and tries to resume, the connection fails.
- `HardcodedPreSharedKeyExtension` allows user to hardcode Identities and Binders to be sent in the extension without setting the internal states. Therefore if the server accepts the PSK and tries to resume, the connection fails.

TODO: Only one of FakePreSharedKeyExtension and HardcodedPreSharedKeyExtension should be kept, the other one should be just removed. We still need to learn more of the safety of hardcoding both Identities and Binders without recalculating the latter.

* update: PSK minor changes and example

* Updates PSK implementations for more comprehensible interfaces when applying preset/json/raw fingerprints.
* Revert FakePreSharedKeyExtension to the old implementation. Add binder size checking.
* Implement TLS-PSK example

New bug: setting `tls.Config.ClientSessionCache` will cause PSK to fail. Currently users must set only `tls.UtlsPreSharedKeyExtension.ClientSessionCacheOverride`.

* fix: PSK failing if config session cache set

* Fix a bug causing PSK to fail if Config.ClientSessionCache is set.
* Removed `ClientSessionCacheOverride` from `UtlsPreSharedKeyExtension`. Set the `ClientSessionCache` in `Config`!

Co-Authored-By: zeeker999 <13848632+zeeker999@users.noreply.github.com>

* Optimize tls resumption (#235)

* feat: bug fix and refactor

* feat: improve example docs: add detailed explanation about the design feat: add assertion on uApplyPatch

* fix: address comments
feat: add option `OmitEmptyPsk` and throw error on empty psk by default
feat: revert changes to public interfaces

* fix: weird residue caused by merging conflict

* fix: remove merge conflict residue code

---------

Co-authored-by: Bas Westerbaan <bas@westerbaan.name>
Co-authored-by: Christopher Patton <3453007+cjpatton@users.noreply.github.com>
Co-authored-by: Peter Wu <peter@lekensteyn.nl>
Co-authored-by: zeeker999 <13848632+zeeker999@users.noreply.github.com>
Co-authored-by: 3andne <52860475+3andne@users.noreply.github.com>
2023-08-27 12:48:31 -06:00

745 lines
22 KiB
Go

// Copyright 2017 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tls
import (
"bytes"
"crypto/tls"
"fmt"
"io"
"net"
"os"
"os/exec"
"runtime/debug"
"strings"
"testing"
"time"
)
// helloStrategy is a sum type interface which allows us to pass either a ClientHelloID or a ClientHelloSpec and then act accordingly
type helloStrategy interface {
helloName() string
}
type helloID struct {
id ClientHelloID
}
func (hid *helloID) helloName() string {
return hid.id.Str()
}
type helloSpec struct {
name string
spec *ClientHelloSpec
}
func (hs *helloSpec) helloName() string {
return hs.name
}
func TestUTLSMarshalNoOp(t *testing.T) {
str := "We rely on clientHelloMsg.marshal() not doing anything if clientHelloMsg.raw is set"
uconn := UClient(&net.TCPConn{}, &Config{ServerName: "foobar"}, HelloGolang)
msg, _, err := uconn.makeClientHello()
if err != nil {
t.Errorf("Got error: %s; expected to succeed", err)
}
msg.raw = []byte(str)
marshalledHello, err := msg.marshal()
if err != nil {
t.Errorf("clientHelloMsg.marshal() returned error: %s", err.Error())
}
if strings.Compare(string(marshalledHello), str) != 0 {
t.Errorf("clientHelloMsg.marshal() is not NOOP! Expected to get: %s, got: %s", str, string(marshalledHello))
}
}
func TestUTLSHandshakeClientParrotGolang(t *testing.T) {
hello := &helloID{HelloGolang}
t.Skip("Skipping golang parroting tests until adjusting for new fingerprints")
testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t, hello)
testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t, hello)
testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t, hello)
testUTLSHandshakeClientECDHE_ECDSA_AES256_CBC_SHA(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t, hello)
testUTLSHandshakeClientECDHE_ECDSA_AES128_CBC_SHA(t, hello)
testUTLSHandshakeClientRSA_AES128_GCM_SHA256(t, hello)
}
func TestUTLSHandshakeClientParrotChrome_70(t *testing.T) {
hello := &helloID{HelloChrome_70}
testUTLSHandshakeClientTLS13_AES_128_GCM_SHA256(t, hello)
testUTLSHandshakeClientTLS13_AES_256_GCM_SHA384(t, hello)
testUTLSHandshakeClientTLS13_CHACHA20_POLY1305_SHA256(t, hello)
//testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t, hello)
//testUTLSHandshakeClientECDHE_ECDSA_AES256_GCM_SHA256(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES256_GCM_SHA256(t, hello)
//testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t, hello)
testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t, hello)
testUTLSHandshakeClientRSA_AES128_GCM_SHA256(t, hello)
}
func TestUTLSHandshakeClientParrotChrome_58(t *testing.T) {
hello := &helloID{HelloChrome_58}
// TODO: EC tests below are disabled because latest version of reference OpenSSL doesn't support p256 nor p384
// nor X25519 and I can't find configuration flag to enable it. Therefore I can't record replays.
//testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t, hello)
//testUTLSHandshakeClientECDHE_ECDSA_AES256_GCM_SHA256(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES256_GCM_SHA256(t, hello)
//testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t, hello)
testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t, hello)
testUTLSHandshakeClientRSA_AES128_GCM_SHA256(t, hello)
}
func TestUTLSHandshakeClientParrotFirefox_63(t *testing.T) {
hello := &helloID{HelloFirefox_63}
testUTLSHandshakeClientTLS13_AES_128_GCM_SHA256(t, hello)
testUTLSHandshakeClientTLS13_AES_256_GCM_SHA384(t, hello)
testUTLSHandshakeClientTLS13_CHACHA20_POLY1305_SHA256(t, hello)
testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t, hello)
testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t, hello)
testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t, hello)
//testUTLSHandshakeClientECDHE_ECDSA_AES256_GCM_SHA256(t, hello) TODO: enable when OpenSSL supports it
testUTLSHandshakeClientECDHE_RSA_AES256_GCM_SHA256(t, hello)
testUTLSHandshakeClientECDHE_ECDSA_AES256_CBC_SHA(t, hello)
testUTLSHandshakeClientECDHE_ECDSA_AES128_CBC_SHA(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t, hello)
}
func TestUTLSHandshakeClientParrotFirefox_55(t *testing.T) {
hello := &helloID{HelloFirefox_55}
testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t, hello)
testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t, hello)
testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t, hello)
//testUTLSHandshakeClientECDHE_ECDSA_AES256_GCM_SHA256(t, hello) TODO: enable when OpenSSL supports it
testUTLSHandshakeClientECDHE_RSA_AES256_GCM_SHA256(t, hello)
testUTLSHandshakeClientECDHE_ECDSA_AES256_CBC_SHA(t, hello)
testUTLSHandshakeClientECDHE_ECDSA_AES128_CBC_SHA(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t, hello)
testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t, hello)
}
func TestUTLSHandshakeClientParrotChrome_58_setclienthello(t *testing.T) {
hello := &helloID{HelloChrome_58}
config := getUTLSTestConfig()
opensslCipherName := "ECDHE-RSA-AES128-GCM-SHA256"
test := &clientTest{
name: "UTLS-setclienthello-" + opensslCipherName + "-" + hello.helloName(),
args: []string{"-cipher", opensslCipherName},
config: config,
}
runUTLSClientTestTLS12(t, test, hello)
}
// tests consistency of fingerprint after HelloRetryRequest
// chrome 70 is used, due to only specifying X25519 in keyshare, but being able to generate P-256 curve too
// openssl server, configured to use P-256, will send HelloRetryRequest
func TestUTLSHelloRetryRequest(t *testing.T) {
hello := &helloID{HelloChrome_70}
config := testConfig.Clone()
config.CurvePreferences = []CurveID{X25519, CurveP256}
test := &clientTest{
name: "UTLS-HelloRetryRequest-" + hello.helloName(),
args: []string{"-cipher", "ECDHE-RSA-AES128-GCM-SHA256", "-curves", "P-256"},
config: config,
}
runUTLSClientTestTLS13(t, test, hello)
}
func TestUTLSRemoveSNIExtension(t *testing.T) {
hello := &helloID{HelloChrome_70}
config := getUTLSTestConfig()
opensslCipherName := "ECDHE-RSA-AES128-GCM-SHA256"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName() + "-OmitSNI",
args: []string{"-cipher", opensslCipherName},
config: config,
}
runUTLSClientTestForVersion(t, test, "TLSv12-", "-tls1_2", hello, true)
}
func TestUTLSServerNameIP(t *testing.T) {
hello := &helloID{HelloChrome_70}
config := getUTLSTestConfig()
config.ServerName = "1.1.1.1"
opensslCipherName := "ECDHE-RSA-AES128-GCM-SHA256"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName() + "-ServerNameIP",
args: []string{"-cipher", opensslCipherName},
config: config,
}
runUTLSClientTestForVersion(t, test, "TLSv12-", "-tls1_2", hello, true)
}
func TestUTLSEmptyServerName(t *testing.T) {
hello := &helloID{HelloChrome_70}
config := getUTLSTestConfig()
config.ServerName = ""
opensslCipherName := "ECDHE-RSA-AES128-GCM-SHA256"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName() + "-EmptyServerName",
args: []string{"-cipher", opensslCipherName},
config: config,
}
runUTLSClientTestForVersion(t, test, "TLSv12-", "-tls1_2", hello, true)
}
/*
*
HELPER FUNCTIONS BELOW
*
*/
func getUTLSTestConfig() *Config {
testUTLSConfig := &Config{
Time: func() time.Time {
return time.Unix(0, 0)
},
Rand: zeroSource{},
InsecureSkipVerify: true,
MinVersion: VersionSSL30,
MaxVersion: VersionTLS13,
CipherSuites: allCipherSuites(),
ServerName: "foobar.com",
}
return testUTLSConfig
}
func testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t *testing.T, hello helloStrategy) {
config := getUTLSTestConfig()
opensslCipherName := "ECDHE-RSA-AES128-SHA"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName(),
args: []string{"-cipher", opensslCipherName},
config: config,
}
runUTLSClientTestTLS12(t, test, hello)
}
func testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t *testing.T, hello helloStrategy) {
config := getUTLSTestConfig()
opensslCipherName := "ECDHE-RSA-AES256-SHA"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName(),
args: []string{"-cipher", opensslCipherName},
config: config,
}
runUTLSClientTestTLS12(t, test, hello)
}
func testUTLSHandshakeClientECDHE_ECDSA_AES128_CBC_SHA(t *testing.T, hello helloStrategy) {
config := getUTLSTestConfig()
opensslCipherName := "ECDHE-ECDSA-AES128-SHA"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName(),
args: []string{"-cipher", opensslCipherName},
cert: testECDSACertificate,
key: testECDSAPrivateKey,
config: config,
}
runUTLSClientTestTLS12(t, test, hello)
}
func testUTLSHandshakeClientECDHE_ECDSA_AES256_CBC_SHA(t *testing.T, hello helloStrategy) {
config := getUTLSTestConfig()
opensslCipherName := "ECDHE-ECDSA-AES256-SHA"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName(),
args: []string{"-cipher", opensslCipherName},
cert: testECDSACertificate,
key: testECDSAPrivateKey,
config: config,
}
runUTLSClientTestTLS12(t, test, hello)
}
func testUTLSHandshakeClientRSA_AES128_GCM_SHA256(t *testing.T, hello helloStrategy) {
config := getUTLSTestConfig()
opensslCipherName := "AES128-GCM-SHA256"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName(),
args: []string{"-cipher", opensslCipherName},
config: config,
}
runUTLSClientTestTLS12(t, test, hello)
}
func testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t *testing.T, hello helloStrategy) {
config := getUTLSTestConfig()
opensslCipherName := "ECDHE-ECDSA-AES128-GCM-SHA256"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName(),
args: []string{"-cipher", opensslCipherName},
cert: testECDSACertificate,
key: testECDSAPrivateKey,
config: config,
}
runUTLSClientTestTLS12(t, test, hello)
}
func testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t *testing.T, hello helloStrategy) {
config := getUTLSTestConfig()
opensslCipherName := "ECDHE-RSA-AES128-GCM-SHA256"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName(),
args: []string{"-cipher", opensslCipherName},
config: config,
}
runUTLSClientTestTLS12(t, test, hello)
}
func testUTLSHandshakeClientECDHE_ECDSA_AES256_GCM_SHA256(t *testing.T, hello helloStrategy) {
config := getUTLSTestConfig()
opensslCipherName := "ECDHE-ECDSA-AES256-GCM-SHA256"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName(),
args: []string{"-cipher", opensslCipherName},
cert: testECDSACertificate,
key: testECDSAPrivateKey,
config: config,
}
runUTLSClientTestTLS12(t, test, hello)
}
func testUTLSHandshakeClientECDHE_RSA_AES256_GCM_SHA256(t *testing.T, hello helloStrategy) {
config := getUTLSTestConfig()
opensslCipherName := "ECDHE-RSA-AES128-GCM-SHA256"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName(),
args: []string{"-cipher", opensslCipherName},
config: config,
}
runUTLSClientTestTLS12(t, test, hello)
}
func testUTLSHandshakeClientTLS13_AES_128_GCM_SHA256(t *testing.T, hello helloStrategy) {
config := getUTLSTestConfig()
opensslCipherName := "TLS_AES_128_GCM_SHA256"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName(),
args: []string{"-ciphersuites", opensslCipherName},
config: config,
}
runUTLSClientTestTLS13(t, test, hello)
}
func testUTLSHandshakeClientTLS13_AES_256_GCM_SHA384(t *testing.T, hello helloStrategy) {
config := getUTLSTestConfig()
opensslCipherName := "TLS_AES_256_GCM_SHA384"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName(),
args: []string{"-ciphersuites", opensslCipherName},
config: config,
}
runUTLSClientTestTLS13(t, test, hello)
}
func testUTLSHandshakeClientTLS13_CHACHA20_POLY1305_SHA256(t *testing.T, hello helloStrategy) {
config := getUTLSTestConfig()
opensslCipherName := "TLS_CHACHA20_POLY1305_SHA256"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName(),
args: []string{"-ciphersuites", opensslCipherName},
config: config,
}
runUTLSClientTestTLS13(t, test, hello)
}
func testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t *testing.T, hello helloStrategy) {
config := getUTLSTestConfig()
config.CipherSuites = []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305}
opensslCipherName := "ECDHE-RSA-CHACHA20-POLY1305"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName(),
args: []string{"-cipher", opensslCipherName},
config: config,
}
runUTLSClientTestTLS12(t, test, hello)
}
func testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t *testing.T, hello helloStrategy) {
config := getUTLSTestConfig()
config.CipherSuites = []uint16{TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305}
opensslCipherName := "ECDHE-ECDSA-CHACHA20-POLY1305"
test := &clientTest{
name: "UTLS-" + opensslCipherName + "-" + hello.helloName(),
args: []string{"-cipher", opensslCipherName},
config: config,
cert: testECDSACertificate,
key: testECDSAPrivateKey,
}
runUTLSClientTestTLS12(t, test, hello)
}
func runUTLSClientTestForVersion(t *testing.T, template *clientTest, prefix, option string, hello helloStrategy, omitSNI bool) {
test := *template
test.name = prefix + test.name
if len(test.args) == 0 {
test.args = defaultClientCommand
}
test.args = append([]string(nil), test.args...)
test.args = append(test.args, option)
test.runUTLS(t, *update, hello, omitSNI)
}
func runUTLSClientTestTLS12(t *testing.T, template *clientTest, hello helloStrategy) {
runUTLSClientTestForVersion(t, template, "TLSv12-", "-tls1_2", hello, false)
}
func runUTLSClientTestTLS13(t *testing.T, template *clientTest, hello helloStrategy) {
runUTLSClientTestForVersion(t, template, "TLSv13-", "-tls1_3", hello, false)
}
func (test *clientTest) runUTLS(t *testing.T, write bool, hello helloStrategy, omitSNIExtension bool) {
checkOpenSSLVersion()
var clientConn, serverConn net.Conn
var recordingConn *recordingConn
var childProcess *exec.Cmd
var stdin opensslInput
var stdout *opensslOutputSink
if write {
var err error
recordingConn, childProcess, stdin, stdout, err = test.connFromCommand()
if err != nil {
t.Fatalf("Failed to start subcommand: %s", err)
}
clientConn = recordingConn
} else {
clientConn, serverConn = localPipe(t)
}
config := test.config
if config == nil {
t.Error("Explicit config is mandatory")
return
}
var client *UConn
switch h := hello.(type) {
case *helloID:
client = UClient(clientConn, config, h.id)
case *helloSpec:
client = UClient(clientConn, config, HelloCustom)
if err := client.ApplyPreset(h.spec); err != nil {
t.Errorf("got error: %v; expected to succeed", err)
return
}
default:
panic("unknown helloStrategy")
}
if omitSNIExtension {
if err := client.RemoveSNIExtension(); err != nil {
t.Error("Failed to remove SNI extension")
return
}
}
if strings.HasPrefix(test.name, "TLSv12-UTLS-setclienthello-") {
err := client.BuildHandshakeState()
if err != nil {
t.Errorf("Client.BuildHandshakeState() failed: %s", err)
return
}
// TODO: fix this name hack if we ever decide to use non-standard testing object
err = client.SetClientRandom([]byte("Custom ClientRandom h^xbw8bf0sn3"))
if err != nil {
t.Errorf("Client.SetClientRandom() failed: %s", err)
return
}
}
doneChan := make(chan bool)
go func() {
defer func() {
if err := recover(); err != nil {
fmt.Printf("panic occurred: %v\n %s\n", err, string(debug.Stack()))
}
// Give time to the send buffer to drain, to avoid the kernel
// sending a RST and cutting off the flow. See Issue 18701.
time.Sleep(10 * time.Millisecond)
client.Close()
clientConn.Close()
doneChan <- true
}()
if _, err := client.Write([]byte("hello\n")); err != nil {
t.Errorf("Client.Write failed: %s", err)
return
}
for i := 1; i <= test.numRenegotiations; i++ {
// The initial handshake will generate a
// handshakeComplete signal which needs to be quashed.
if i == 1 && write {
<-stdout.handshakeComplete
}
// OpenSSL will try to interleave application data and
// a renegotiation if we send both concurrently.
// Therefore: ask OpensSSL to start a renegotiation, run
// a goroutine to call client.Read and thus process the
// renegotiation request, watch for OpenSSL's stdout to
// indicate that the handshake is complete and,
// finally, have OpenSSL write something to cause
// client.Read to complete.
if write {
stdin <- opensslRenegotiate
}
signalChan := make(chan struct{})
go func() {
defer close(signalChan)
buf := make([]byte, 256)
n, err := client.Read(buf)
if test.checkRenegotiationError != nil {
newErr := test.checkRenegotiationError(i, err)
if err != nil && newErr == nil {
return
}
err = newErr
}
if err != nil {
t.Errorf("Client.Read failed after renegotiation #%d: %s", i, err)
return
}
buf = buf[:n]
if !bytes.Equal([]byte(opensslSentinel), buf) {
t.Errorf("Client.Read returned %q, but wanted %q", string(buf), opensslSentinel)
}
if expected := i + 1; client.handshakes != expected {
t.Errorf("client should have recorded %d handshakes, but believes that %d have occurred", expected, client.handshakes)
}
}()
if write && test.renegotiationExpectedToFail != i {
<-stdout.handshakeComplete
stdin <- opensslSendSentinel
}
<-signalChan
}
if test.sendKeyUpdate {
if write {
<-stdout.handshakeComplete
stdin <- opensslKeyUpdate
}
doneRead := make(chan struct{})
go func() {
defer close(doneRead)
buf := make([]byte, 256)
n, err := client.Read(buf)
if err != nil {
t.Errorf("Client.Read failed after KeyUpdate: %s", err)
return
}
buf = buf[:n]
if !bytes.Equal([]byte(opensslSentinel), buf) {
t.Errorf("Client.Read returned %q, but wanted %q", string(buf), opensslSentinel)
}
}()
if write {
// There's no real reason to wait for the client KeyUpdate to
// send data with the new server keys, except that s_server
// drops writes if they are sent at the wrong time.
<-stdout.readKeyUpdate
stdin <- opensslSendSentinel
}
<-doneRead
if _, err := client.Write([]byte("hello again\n")); err != nil {
t.Errorf("Client.Write failed: %s", err)
return
}
}
if test.validate != nil {
if err := test.validate(client.ConnectionState()); err != nil {
t.Errorf("validate callback returned error: %s", err)
}
}
// If the server sent us an alert after our last flight, give it a
// chance to arrive.
if write && test.renegotiationExpectedToFail == 0 {
client.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
if _, err := client.Read(make([]byte, 1)); err != nil {
if netErr, ok := err.(net.Error); !ok || !netErr.Timeout() {
t.Errorf("final Read returned an error: %s", err)
}
}
}
}()
if !write {
flows, err := test.loadData()
if err != nil {
t.Fatalf("%s: failed to load data from %s: %v", test.name, test.dataPath(), err)
}
for i, b := range flows {
if i%2 == 1 {
serverConn.SetWriteDeadline(time.Now().Add(2 * time.Second)) // [uTLS] 1min -> 2sec
serverConn.Write(b)
continue
}
bb := make([]byte, len(b))
serverConn.SetReadDeadline(time.Now().Add(2 * time.Second)) // [uTLS] 1min -> 2sec
_, err := io.ReadFull(serverConn, bb)
if err != nil {
t.Fatalf("%s #%d: %s", test.name, i, err)
}
if !bytes.Equal(b, bb) {
t.Fatalf("%s #%d: mismatch on read: got:%x want:%x", test.name, i, bb, b)
}
}
// Give time to the send buffer to drain, to avoid the kernel
// sending a RST and cutting off the flow. See Issue 18701.
time.Sleep(10 * time.Millisecond)
serverConn.Close()
}
<-doneChan
if write {
path := test.dataPath()
out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
t.Fatalf("Failed to create output file: %s", err)
}
defer out.Close()
recordingConn.Close()
close(stdin)
childProcess.Process.Kill()
childProcess.Wait()
if len(recordingConn.flows) < 3 {
os.Stdout.Write(stdout.all)
t.Fatalf("Client connection didn't work")
}
recordingConn.WriteTo(out)
fmt.Printf("Wrote %s\n", path)
}
}
func TestUTLSMakeConnWithCompleteHandshake(t *testing.T) {
serverConn, clientConn := net.Pipe()
masterSecret := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}
clientRandom := []byte{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}
serverRandom := []byte{80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}
serverTls := MakeConnWithCompleteHandshake(serverConn, tls.VersionTLS12, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
masterSecret, clientRandom, serverRandom, false)
clientTls := MakeConnWithCompleteHandshake(clientConn, tls.VersionTLS12, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
masterSecret, clientRandom, serverRandom, true)
clientMsg := []byte("Hello, world!")
serverMsg := []byte("Test response!")
go func() {
clientTls.Write(clientMsg)
resp := make([]byte, 20)
read, err := clientTls.Read(resp)
if !bytes.Equal(resp[:read], serverMsg) {
t.Errorf("client expected to receive: %v, got %v\n",
serverMsg, resp[:read])
}
if err != nil {
t.Errorf("error reading client: %+v\n", err)
}
clientConn.Close()
}()
buf := make([]byte, 20)
read, err := serverTls.Read(buf)
if !bytes.Equal(buf[:read], clientMsg) {
t.Errorf("server expected to receive: %v, got %v\n",
clientMsg, buf[:read])
}
if err != nil {
t.Errorf("error reading client: %+v\n", err)
}
serverTls.Write(serverMsg)
}