From 9ca281d82c6fb5875b6b31a39d0739ff05bfb68e Mon Sep 17 00:00:00 2001 From: Nikolay Govorov Date: Wed, 24 Apr 2024 08:21:20 +0100 Subject: [PATCH] support TLS --- Makefile | 4 ++ toytlv/cert.pem | 31 +++++++++ toytlv/key.pem | 54 +++++++++++++++ toytlv/peer.go | 18 ----- toytlv/tlv.go | 1 + toytlv/transport.go | 142 ++++++++++++++++++++++++++++++++++++++- toytlv/transport_test.go | 10 ++- 7 files changed, 238 insertions(+), 22 deletions(-) create mode 100644 toytlv/cert.pem create mode 100644 toytlv/key.pem diff --git a/Makefile b/Makefile index e34dcb0..b445d20 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,10 @@ ragel: lint: golangci-lint run ./... +.PHONY: tlsgen +tlsgen: + openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 1 + .PHONY: update-pebble update-pebble: go mod edit -replace github.com/cockroachdb/pebble=github.com/drpcorg/pebble@master diff --git a/toytlv/cert.pem b/toytlv/cert.pem new file mode 100644 index 0000000..0d0a0a1 --- /dev/null +++ b/toytlv/cert.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFQzCCAyugAwIBAgIUXkqeHsvnkiMe4yjySqigWAaryVYwDQYJKoZIhvcNAQEL +BQAwMTELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxDTALBgNVBAoM +BGRSUEMwHhcNMjQwNDI0MDcxODExWhcNMjcwMTE4MDcxODExWjAxMQswCQYDVQQG +EwJVUzETMBEGA1UECAwKU29tZS1TdGF0ZTENMAsGA1UECgwEZFJQQzCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL6HOs+h2TWFXdbQGs3dBxYhqFSB81pe +gLxkeRd1hvwDTbm6W4MuXBfpV8akd2P6kpa+2VxdWRsx9q1CskvsIsHS6XE/qKPr +9nx1jgNmzQ5KiKkT0RcIWMkR09rhUy5qx7N7yqa2zrdtMYvQUOo563Nl/hwD/495 +wQgFd4w56f9UIBjbnf9V/iV/+ic00on95xmlkx3/F3ug2iDbS6CrVZOWPDj1p4Lk +27bxUTY7EpC23eF5iyDth1cwgRxVDIps5Fz6g3iOH8h55qmwnqx9c+t7VaKHnOJE +onHLxiitoTbzyJ7XAXO61foHDFdmuIuO/9PzDdpiFwYQBO1GcicsWjmoLrlOx2oe +tb1+uBy9PM/00U7n0a059Ekuv4acme+I6IHCgdKERbb2bro5d5ncM6shoGSWY3MU +mqDXP4DoElbO8Q0CStFTrKmT0d4dTHEuJcbD4ZYBPrRJOV1jjoDlQzlZAES4tJMj +Wp04G6WwnMFmdBkUyE2V4Xt00jkc1k1C9tXnjTu3BHL+YYiS5b2R2d3A2o9c35w7 +LyFQ6K7WaAus6Rnj8uqS0O25TOfsHCERpJavK/WOE47vnZDfDgpTDhNiPMMz1W8S +N0557FjtLYfI3B8JHMldFZR4n1VYfEg5HL/6KU3S7OIQSGgq/6Bmd6AO9chlibg9 +xBrmDGqakNkHAgMBAAGjUzBRMB0GA1UdDgQWBBTvhR8YVgQ18f+qFJ9j+YitMhPj +EjAfBgNVHSMEGDAWgBTvhR8YVgQ18f+qFJ9j+YitMhPjEjAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCHkGRnAo0eLt/gahxm2ey6X4nNw/av1TBa +xdbSIXfubam8kdddHs6Yk3Dnxim/DDmRN292TeeoWCc3IsbmQyPv0iptTd27hMDj +d8casciVJlB9x9TYjsfWf5b9e/cIatWYC6dk1G7lK0Yci2WgEBShXgu/Ba/SqyFN +k1hzoSFIQ+j9twPoPv1ZxDJ0AqZiwbFOpDvw8YplVgJ7ovAi07LtAXBxoPARqHkV +NIxsktHn4b0CprBjkQC2XXP8IDjLYmvnDAGUiQOMluOB2LaISc4RIubv/bcTsTh9 +1FgawcD/tckeUZBaowzVu2bZ3o3VIhf2HEnFkesnqSTjn1pgjg2232tzHvc+o4xG +MpArlPGwVjGJPR/vZRa7d00b2O5CbfNcPin3EsWKeIYCaLZbZk/6fYK9sHXhbUOu +cEWuIzdXm/LPZbHm+tzVAmROArtMMeq1D+ugWqYJ63w/D6aDAygcEVyO62F1Cw7V +QH7puwfa9y48tvr43emnQwGTI3dv6Ikxs+ApHZImTPdzDdrBaRDyE9Ltr4Tq2czu +iD3W61pLaaSuVEMHzYYcTuo+lRVCdTG7ahEIDTxvWuTIJ1E2O/C5XxoduidGgZsM +YH/o06eoIdVP2Y7IPRJoI85r4DRmad1Iv38ZwpVL86MwNy9Rt1tQadaSQb43SQb/ +QdO9DkM7Mg== +-----END CERTIFICATE----- diff --git a/toytlv/key.pem b/toytlv/key.pem new file mode 100644 index 0000000..2457ce4 --- /dev/null +++ b/toytlv/key.pem @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +ppVc8JF88cLzaXBX8tf5Ow== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/toytlv/peer.go b/toytlv/peer.go index 5bd4d4f..51ac088 100644 --- a/toytlv/peer.go +++ b/toytlv/peer.go @@ -11,24 +11,6 @@ import ( "github.com/drpcorg/chotki/toyqueue" ) -type ConnType = uint - -const ( - TCP ConnType = iota + 1 - TLS - QUIC -) - -const ( - TYPICAL_MTU = 1500 - MAX_OUT_QUEUE_LEN = 1 << 20 // 16MB of pointers is a lot - - MAX_RETRY_PERIOD = time.Minute - MIN_RETRY_PERIOD = time.Second / 2 -) - -type Jack func(conn net.Conn) toyqueue.FeedDrainCloser - type Peer struct { conn atomic.Pointer[net.Conn] inout toyqueue.FeedDrainCloser diff --git a/toytlv/tlv.go b/toytlv/tlv.go index 04c5f3a..55c19a2 100644 --- a/toytlv/tlv.go +++ b/toytlv/tlv.go @@ -10,6 +10,7 @@ import ( const CaseBit uint8 = 'a' - 'A' var ( + ErrAddressInvalid = errors.New("the address invalid") ErrAddressDuplicated = errors.New("the address already used") ErrIncomplete = errors.New("incomplete data") diff --git a/toytlv/transport.go b/toytlv/transport.go index 25aa0d1..5b36723 100644 --- a/toytlv/transport.go +++ b/toytlv/transport.go @@ -2,15 +2,40 @@ package toytlv import ( "context" + "crypto/tls" + "crypto/x509" + "errors" "log/slog" "net" + "net/url" + "os" + "strings" "sync" "sync/atomic" "time" + "github.com/drpcorg/chotki/toyqueue" "golang.org/x/exp/constraints" ) +type ConnType = uint + +const ( + TCP ConnType = iota + 1 + TLS + QUIC +) + +const ( + TYPICAL_MTU = 1500 + MAX_OUT_QUEUE_LEN = 1 << 20 // 16MB of pointers is a lot + + MAX_RETRY_PERIOD = time.Minute + MIN_RETRY_PERIOD = time.Second / 2 +) + +type Jack func(conn net.Conn) toyqueue.FeedDrainCloser + // A TCP/TLS/QUIC server/client for the use case of real-time async communication. // Differently from the case of request-response (like HTTP), we do not // wait for a request, then dedicating a thread to processing, then sending @@ -26,6 +51,9 @@ type Transport struct { conns sync.Map // *Peer listens sync.Map // net.Listener + + CertFile, KeyFile string + ClientCertFiles []string } func NewTransport(jack Jack) *Transport { @@ -80,11 +108,36 @@ func (de *Transport) Disconnect(addr string) (err error) { } func (t *Transport) Listen(ctx context.Context, addr string) error { - listener, err := net.Listen("tcp", addr) + connType, address, err := parseAddr(addr) if err != nil { return err } + var listener net.Listener + switch connType { + case TCP: + config := net.ListenConfig{} + if listener, err = config.Listen(ctx, "tcp", address); err != nil { + return err + } + + case TLS: + config := net.ListenConfig{} + if listener, err = config.Listen(ctx, "tcp", address); err != nil { + return err + } + + tlsConfig, err := t.tlsConfig() + if err != nil { + return err + } + + listener = tls.NewListener(listener, tlsConfig) + + case QUIC: + return errors.New("QUIC unimplemented") + } + if _, ok := t.listens.LoadOrStore(addr, listener); ok { listener.Close() return ErrAddressDuplicated @@ -120,13 +173,13 @@ func (t *Transport) KeepConnecting(ctx context.Context, addr string) { for !t.closed.Load() { time.Sleep(connBackoff + talkBackoff) - conn, err := net.Dial("tcp", addr) + conn, err := t.createDialConnect(ctx, addr) if err != nil { slog.Error("couldn't connect", "addr", addr, "err", err) connBackoff = min(MAX_RETRY_PERIOD, connBackoff*2) continue } - + connBackoff = MIN_RETRY_PERIOD peer := &Peer{inout: t.jack(conn)} @@ -207,6 +260,89 @@ func (t *Transport) keepPeer(ctx context.Context, addr string, peer *Peer) error return nil } +func (t *Transport) createDialConnect(ctx context.Context, addr string) (net.Conn, error) { + connType, address, err := parseAddr(addr) + if err != nil { + return nil, err + } + + var conn net.Conn + switch connType { + case TCP: + var d net.Dialer + if conn, err = d.DialContext(ctx, "tcp", address); err != nil { + return nil, err + } + + case TLS: + var d tls.Dialer + if d.Config, err = t.tlsConfig(); err != nil { + return nil, err + } + + if conn, err = d.DialContext(ctx, "tcp", address); err != nil { + return nil, err + } + + case QUIC: + return nil, errors.New("QUIC unimplemented") + } + + return conn, err +} + +func (t *Transport) tlsConfig() (*tls.Config, error) { + cert, err := tls.LoadX509KeyPair(t.CertFile, t.KeyFile) + if err != nil { + return nil, err + } + + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS13, + Certificates: []tls.Certificate{cert}, + } + + if len(t.ClientCertFiles) > 0 { + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + tlsConfig.ClientCAs = x509.NewCertPool() + + for _, file := range t.ClientCertFiles { + if clientCert, err := os.ReadFile(file); err != nil { + return nil, err + } else { + tlsConfig.ClientCAs.AppendCertsFromPEM(clientCert) + } + } + } + + return tlsConfig, nil +} + +func parseAddr(addr string) (ConnType, string, error) { + u, err := url.Parse(addr) + if err != nil { + return TCP, "", err + } + + var conn ConnType + + switch u.Scheme { + case "", "tcp", "tcp4", "tcp6": + conn = TCP + case "tls": + conn = TLS + case "quic": + conn = QUIC + default: + return conn, addr, ErrAddressInvalid + } + + u.Scheme = "" + address := strings.TrimPrefix(u.String(), "//") + + return conn, address, nil +} + func min[T constraints.Ordered](s ...T) T { if len(s) == 0 { var zero T diff --git a/toytlv/transport_test.go b/toytlv/transport_test.go index a44319c..4b5ee9f 100644 --- a/toytlv/transport_test.go +++ b/toytlv/transport_test.go @@ -48,7 +48,9 @@ func (c *TestConsumer) Close() error { } func TestTCPDepot_Connect(t *testing.T) { - loop := "127.0.0.1:32000" + loop := "tcp://127.0.0.1:32000" + + cert, key := "cert.pem", "key.pem" lCon := TestConsumer{} lCon.co.L = &lCon.mx @@ -58,6 +60,9 @@ func TestTCPDepot_Connect(t *testing.T) { err := l.Listen(context.Background(), loop) assert.Nil(t, err) + l.CertFile = cert + l.KeyFile = key + cCon := TestConsumer{} cCon.co.L = &cCon.mx c := NewTransport(func(conn net.Conn) toyqueue.FeedDrainCloser { @@ -66,6 +71,9 @@ func TestTCPDepot_Connect(t *testing.T) { err = c.Connect(context.Background(), loop) assert.Nil(t, err) + c.CertFile = cert + c.KeyFile = key + time.Sleep(time.Second * 2) // TODO: use events // send a record