Monday, April 21, 2025
HomeGolangInformation Caught in TLS Server Buffer - Getting Assist

Information Caught in TLS Server Buffer – Getting Assist


Hello all,

I’m encountering a difficulty whereas implementing a check for SMTP STARTTLS performance. Particularly, when the TLS handshake fails, I wish to check {that a} QUIT command is distributed earlier than gracefully closing the connection.

Subject:

  • The QUIT command seems to get caught within the TLS connection handler’s buffer
  • This habits is intermittent and appears to rely upon how shortly the command is distributed
  • Including a small delay earlier than sending the QUIT command resolves the problem

To isolate the issue, I’ve created a minimal replica case utilizing easy plain textual content communication, with out SMTP-specific code.

package deal essential

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"math/massive"
	"internet"
	"strings"
	"sync"
	"testing"
	"time"
)

func TestTLSHandshake(t *testing.T) {
	ln := newLocalListener(t)
	defer ln.Shut()

	wg := sync.WaitGroup{}
	wg.Add(1)

	go func() {
		// Consumer
		clientConn, _ := internet.Dial("tcp", ln.Addr().String())
		defer clientConn.Shut()

		_, _ = clientConn.Write([]byte("STARTTLSrn"))

		// Look ahead to readiness
		buf := make([]byte, 1024)
		_, _ = clientConn.Learn(buf)
		fmt.Printf("[Client] Obtained reply: %s", string(buf))

		// Attempt TLS handshake
		tlsConn := tls.Consumer(clientConn, &tls.Config{
			ServerName: "localhost",
		})

		// Handshake ought to fail as a result of shopper would not belief self-signed cert
		err := tlsConn.Handshake()
		if err != nil {
			fmt.Println("[Client] Handshake err: ", err)
		}

		// Delay to forestall QUIT from being consumed into TLS buffer
		time.Sleep(100 * time.Millisecond)

		_, _ = clientConn.Write([]byte("QUITrn"))

		wg.Finished()
	}()

	serverConn, _ := ln.Settle for()
	defer serverConn.Shut()

	quitReceived := false

	scanner := bufio.NewScanner(serverConn)
	for scanner.Scan() {
		enter := scanner.Textual content()

		fmt.Printf("[Server] Obtained: '%s'n", enter)

		swap enter {
		case "STARTTLS":
			// Generate check cert
			cert, privateKey, _ := generateTestCertificatePair()

			// Setup TLS
			keypair, _ := tls.X509KeyPair(cert, privateKey)
			config := &tls.Config{Certificates: []tls.Certificates{keypair}}

			// Reply readiness
			_, _ = serverConn.Write([]byte("Prepared for handshakern"))

			tlsConn := tls.Server(serverConn, config)

			handshakeErr := tlsConn.Handshake()
			if handshakeErr != nil {
				fmt.Println("[Server] Handshake error: ", handshakeErr)
				proceed
			} else {
				fmt.Println("[Server] Handshake accomplished")
				t.Deadly("Handshake unexpectedly succeeded")
			}
		case "QUIT":
			quitReceived = true

			fmt.Println("[Server] Stop obtained")
		}
	}

	wg.Wait()

	if !quitReceived {
		t.Deadly("[Server] Stop not obtained")
	}
}

// Generate X509 encoded certificates for testing.
func generateTestCertificatePair() ([]byte, []byte, error) {
	group := "instance"
	host := "localhost"

	validFrom := time.Now()
	validTo := validFrom.Add(24 * time.Hour)

	// Generate non-public key
	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, nil, err
	}

	// Create certificates serial quantity
	serialNumberLimit := new(massive.Int).Lsh(massive.NewInt(1), 128)

	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return nil, nil, err
	}

	template := x509.Certificates{
		SerialNumber: serialNumber,
		Topic: pkix.Title{
			Group: []string{group},
			CommonName:   host,
		},
		NotBefore: validFrom,
		NotAfter:  validTo,

		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
	}

	hosts := strings.Cut up(host, ",")
	template.DNSNames = append(template.DNSNames, hosts...)

	// Create certificates
	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
	if err != nil {
		return nil, nil, err
	}

	// Encode certificates to PEM
	certBuffer := &bytes.Buffer{}
	if err := pem.Encode(certBuffer, &pem.Block{Sort: "CERTIFICATE", Bytes: derBytes}); err != nil {
		return nil, nil, err
	}

	// Encode non-public key to PEM
	privBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
	if err != nil {
		return nil, nil, err
	}

	keyBuffer := &bytes.Buffer{}
	if err := pem.Encode(keyBuffer, &pem.Block{Sort: "PRIVATE KEY", Bytes: privBytes}); err != nil {
		return nil, nil, err
	}

	return certBuffer.Bytes(), keyBuffer.Bytes(), nil
}

func newLocalListener(t *testing.T) internet.Listener {
	ln, _ := internet.Pay attention("tcp", "localhost:0")
	return ln
}

The above code can also be obtainable right here. Discover that if the delay is eliminated, the server is not going to obtain the QUIT command.


I perceive that TCP is a steady stream and the appliance ought to have the message boundary by itself. Nonetheless, I’m utilizing tls.Server() and don’t have management over the way it will deal with the TCP connection.

Is there something I can do to make sure that the server will be capable of obtain the QUIT command is obtained by the principle handler as an alternative of the TLS one?

Thanks to your assist!

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments