From d1d1515eb29c5096ee072fd3ba2d9f4c4fd63a7a Mon Sep 17 00:00:00 2001 From: igolaizola <11333576+igolaizola@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:09:48 +0200 Subject: [PATCH] Fix client and add server - Upgraded the project to latest go version and go modules. - Added github action to launch tests. - Fixed client using retrial workaround. - Added server side code, still not working though. --- .github/workflows/ci.yaml | 26 ++++++++++ go.mod | 3 ++ resumetls.go | 57 ++++++++++++++------ resumetls_test.go | 106 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 172 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/ci.yaml create mode 100644 go.mod diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..c82c776 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,26 @@ +name: ci + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + ci: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup go + uses: actions/setup-go@v4 + with: + go-version-file: 'go.mod' + - name: Build + run: go build -v ./... + - name: Lint + uses: golangci/golangci-lint-action@v3 + - name: Test + run: go test -v ./... diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..98e2a57 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/igolaizola/resume-tls + +go 1.22.2 diff --git a/resumetls.go b/resumetls.go index 6dd07aa..2202f6e 100644 --- a/resumetls.go +++ b/resumetls.go @@ -5,13 +5,12 @@ import ( "crypto/rand" "crypto/tls" "io" - "io/ioutil" "net" "reflect" - intio "github.com/igarciaolaizola/resume-tls/internal/io" - intnet "github.com/igarciaolaizola/resume-tls/internal/net" - intref "github.com/igarciaolaizola/resume-tls/internal/reflect" + intio "github.com/igolaizola/resume-tls/internal/io" + intnet "github.com/igolaizola/resume-tls/internal/net" + intref "github.com/igolaizola/resume-tls/internal/reflect" ) // State is buffered handshake data @@ -32,16 +31,40 @@ type Conn struct { *tls.Conn } -// Client returs a resumable tls conn +// Client returns a resumable tls client conn func Client(conn net.Conn, cfg *tls.Config, state *State) (*Conn, error) { + return newConn(tls.Client, conn, cfg, state) +} + +// Server returns a resumable tls server conn +func Server(conn net.Conn, cfg *tls.Config, state *State) (*Conn, error) { + return newConn(tls.Server, conn, cfg, state) +} + +// newConn returns a resumable tls conn +func newConn(tlsConn func(net.Conn, *tls.Config) *tls.Conn, conn net.Conn, cfg *tls.Config, state *State) (*Conn, error) { if state != nil { - return clientResume(conn, cfg, state) + var err error + // Client hello message sometimes consumes more random bytes than the + // ones provided by the state. Probably due to how elliptic curves keys + // are generated. + // We have tested empirically that retrying 10 times is enough to get a + // successful handshake. Here, we set the limit to 20 to be on the safe + // side. + for i := 0; i < 20; i++ { + var c *Conn + c, err = resume(tlsConn, conn, cfg, state) + if err == nil { + return c, err + } + } + return nil, err } - return clientInitialize(conn, cfg), nil + return initialize(tlsConn, conn, cfg), nil } -// client initializes a resumable TLS client conn -func clientInitialize(conn net.Conn, cfg *tls.Config) *Conn { +// initializes a resumable TLS client conn +func initialize(tlsConn func(net.Conn, *tls.Config) *tls.Conn, conn net.Conn, cfg *tls.Config) *Conn { connBuf := &bytes.Buffer{} randBuf := &bytes.Buffer{} @@ -64,12 +87,12 @@ func clientInitialize(conn net.Conn, cfg *tls.Config) *Conn { overrideRand: ovRand, connBuffer: connBuf, randBuffer: randBuf, - Conn: tls.Client(ovConn, cfg), + Conn: tlsConn(ovConn, cfg), } } -// clientResume resumes a resumable TLS client conn -func clientResume(conn net.Conn, cfg *tls.Config, state *State) (*Conn, error) { +// resume resumes a resumable TLS client conn +func resume(tlsConn func(net.Conn, *tls.Config) *tls.Conn, conn net.Conn, cfg *tls.Config, state *State) (*Conn, error) { rnd := cfg.Rand if rnd == nil { rnd = rand.Reader @@ -81,24 +104,24 @@ func clientResume(conn net.Conn, cfg *tls.Config, state *State) (*Conn, error) { ovConn := &intnet.OverrideConn{ Conn: conn, OverrideReader: io.MultiReader(bytes.NewBuffer(state.conn), conn), - OverrideWriter: ioutil.Discard, + OverrideWriter: io.Discard, } cfg.Rand = ovRand - cli := tls.Client(ovConn, cfg) - if err := cli.Handshake(); err != nil { + c := tlsConn(ovConn, cfg) + if err := c.Handshake(); err != nil { return nil, err } ovRand.OverrideReader = nil ovConn.OverrideReader = nil ovConn.OverrideWriter = nil - setSeq(cli, state.inSeq, state.outSeq) + setSeq(c, state.inSeq, state.outSeq) return &Conn{ handshaked: true, connBuffer: bytes.NewBuffer(state.conn), randBuffer: bytes.NewBuffer(state.rand), - Conn: cli, + Conn: c, }, nil } diff --git a/resumetls_test.go b/resumetls_test.go index fd2b1df..0939c7a 100644 --- a/resumetls_test.go +++ b/resumetls_test.go @@ -56,6 +56,12 @@ fi06KUiLh/4rJtf2wph2wN8SPAY4yQkopFlDYTJNmhhYsKTGIhrpww== -----END RSA PRIVATE KEY-----` func TestClient(t *testing.T) { + for i := 0; i < 100; i++ { + testClient(t) + } +} + +func testClient(t *testing.T) { sConn, cConn := net.Pipe() pair, err := tls.X509KeyPair([]byte(cert), []byte(key)) @@ -77,18 +83,18 @@ func TestClient(t *testing.T) { // Launch server in another goroutine go func() { if err := srv.Handshake(); err != nil { - t.Fatal(err) + panic(err) } // Loop of read write for i := 0; i < 2; i++ { recv := make([]byte, 1024) n, err := srv.Read(recv) if err != nil { - t.Fatal(err) + panic(err) } if _, err := srv.Write(recv[:n]); err != nil { - t.Fatal(err) + panic(err) } } }() @@ -140,3 +146,97 @@ func TestClient(t *testing.T) { t.Errorf("messages missmatch: %s != %s", message, recv[:n]) } } + +func TestServer(t *testing.T) { + for i := 0; i < 100; i++ { + testServer(t) + } +} + +func testServer(t *testing.T) { + sConn, cConn := net.Pipe() + + pair, err := tls.X509KeyPair([]byte(cert), []byte(key)) + if err != nil { + t.Fatal(err) + } + + cli := tls.Client(sConn, &tls.Config{ + InsecureSkipVerify: true, + }) + + srv, err := Server(cConn, &tls.Config{ + InsecureSkipVerify: true, + Certificates: []tls.Certificate{pair}, + }, nil) + if err != nil { + t.Fatal(err) + } + + // Launch client in another goroutine + go func() { + if err := cli.Handshake(); err != nil { + panic(err) + } + // Loop of read write + for i := 0; i < 2; i++ { + recv := make([]byte, 1024) + n, err := cli.Read(recv) + if err != nil { + panic(err) + } + + if _, err := cli.Write(recv[:n]); err != nil { + panic(err) + } + } + }() + + // Initial handshake + if err := srv.Handshake(); err != nil { + t.Fatal(err) + } + + // Test write and read + message := []byte("Hello") + if _, err := srv.Write(message); err != nil { + t.Fatal(err) + } + + recv := make([]byte, 1024) + n, err := srv.Read(recv) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(message, recv[:n]) { + t.Errorf("messages missmatch: %s != %s", message, recv[:n]) + } + + // Extract TLS state + state := srv.State() + + // Resume server + srv2, err := Server(cConn, &tls.Config{ + InsecureSkipVerify: true, + Certificates: []tls.Certificate{pair}, + }, state) + if err != nil { + t.Fatal(err) + } + + // Test write and read on resumed client + if _, err := srv2.Write(message); err != nil { + t.Fatal(err) + } + + recv = make([]byte, 1024) + n, err = srv.Read(recv) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(message, recv[:n]) { + t.Errorf("messages missmatch: %s != %s", message, recv[:n]) + } +}