gostls

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 20, 2026 License: BSD-2-Clause Imports: 18 Imported by: 0

README

gostls

A pure-Go TLS 1.2 client with GOST cipher suites, shaped like crypto/tls: Dialer, Conn, Config. It speaks the ordinary AES / ECDHE / ChaCha20-Poly1305 suites and the GOST suites (GOST 28147-89, Kuznyechik / Magma CTR-OMAC, VKO / GOST-2018 key exchange) that the standard library does not.

Pure-Go, BSD-2-Clause, CGO_ENABLED=0. It is the pure-Go backend for the go-tlsdialer-shaped dialer; the alternative cgo OpenSSL backend lives in that separate dialer module.

This module was extracted from go.bigb.es/tlsdialer.

Layout

gostls/                 package gostls — crypto/tls-shaped Dialer / Conn / Config
  config.go conn.go dialer.go suites.go
  internal/handshake/   client state machine
  internal/ke/          key exchange (ECDHE / DHE / RSA; VKO GOST 2001/2012; GOST-2018)
  internal/record/      record layer (null / CBC-HMAC / AEAD / ChaCha20 / GOST CNT-IMIT)
  internal/suites/      suite registry, key schedule, PRF (incl. GOST suites)

GOST primitives are provided by gostcrypto; GOST X.509 by its x509gost subpackage. gostls supplies the TLS protocol use of them (the GOST PRFs, the CTR-ACPKM record mode, the VKO/GOST-2018 key-exchange glue).

Usage

d := &gostls.Dialer{
	Config: &gostls.Config{
		ServerName: "example.com",
		// RootCAs/RootCAPEMs verify ordinary chains; GOSTRoots verifies
		// GOST-signed server certificates.
	},
}
conn, err := d.DialContext(ctx, "tcp", "example.com:443")
if err != nil {
	return err
}
defer conn.Close()
// conn is a *gostls.Conn (net.Conn); the handshake has already completed.

To negotiate a GOST suite, request it explicitly (GOST suites are registered in every build but are not offered by default):

id, _ := gostls.LookupSuiteByName("GOST2012-KUZNYECHIK-KUZNYECHIKOMAC")
cfg.CipherSuites = []uint16{id}

Build & test

gostcrypto is pinned by pseudo-version in go.mod, so standalone builds resolve it from the module proxy — no sibling checkout or replace is required. (For co-development the workspace go.work overrides it with the local ../gostcrypto sibling.)

CGO_ENABLED=0 go build ./...
CGO_ENABLED=0 go test ./...

Licensing

BSD-2-Clause; links zero GPL code. Depends on gostcrypto (BSD-2-Clause), filippo.io/bigmod, and golang.org/x/crypto. See NOTICE.

Documentation

Overview

Package gostls implements a TLS 1.2 client with a crypto/tls-shaped API.

This implementation is TLS 1.2 only. TLS 1.3, session resumption, renegotiation, ALPN extensibility, and 0-RTT are not supported. There is no server role.

Cipher suite availability: AES-128-CBC, AES-256-CBC, AES-128-GCM, AES-256-GCM, and CHACHA20-POLY1305 (RFC 7905) suites are included by default. GOST suites are registered in every build (clean-room backend) but are not in the default negotiation set; request them explicitly via Config.CipherSuites.

Example

Example dials a TLS 1.2 server, completes the handshake, and exchanges application data. The server here is an in-process crypto/tls echo server using a freshly generated CA, so the example is self-contained.

package main

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"io"
	"log"
	"math/big"
	"net"
	"time"

	stdtls "crypto/tls"

	"github.com/bigbes/gostls"
)

func main() {
	srv := newExampleServer()
	defer srv.Close()

	d := &gostls.Dialer{
		Config: &gostls.Config{
			// Trust only the example CA. In production, omit RootCAs/RootCAPEMs
			// to use the host's system trust store.
			RootCAPEMs: [][]byte{srv.CACertPEM},
			ServerName: "test.example.com",
		},
	}

	conn, err := d.DialContext(context.Background(), "tcp", srv.Addr)
	if err != nil {
		log.Fatalf("dial: %v", err)
	}

	defer func() { _ = conn.Close() }()

	if _, err := conn.Write([]byte("ping")); err != nil {
		log.Fatalf("write: %v", err)
	}

	buf := make([]byte, 4)
	if _, err := io.ReadFull(conn, buf); err != nil {
		log.Fatalf("read: %v", err)
	}

	fmt.Printf("server echoed: %s\n", buf)

}

type exampleServer struct {
	Addr      string
	CACertPEM []byte
	listener  net.Listener
}

func (s *exampleServer) Close() { _ = s.listener.Close() }

// newExampleServer generates a CA + leaf certificate for "test.example.com"
// and starts a TLS 1.2 listener that echoes whatever it receives. It panics on
// any error, which is acceptable for an example.
func newExampleServer() *exampleServer {
	caKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		panic(err)
	}

	caTmpl := &x509.Certificate{
		SerialNumber:          big.NewInt(1),
		Subject:               pkix.Name{CommonName: "Example CA"},
		NotBefore:             time.Now().Add(-time.Hour),
		NotAfter:              time.Now().Add(time.Hour),
		IsCA:                  true,
		BasicConstraintsValid: true,
		KeyUsage:              x509.KeyUsageCertSign,
	}

	caDER, err := x509.CreateCertificate(rand.Reader, caTmpl, caTmpl, &caKey.PublicKey, caKey)
	if err != nil {
		panic(err)
	}

	caCert, err := x509.ParseCertificate(caDER)
	if err != nil {
		panic(err)
	}

	leafKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		panic(err)
	}

	leafTmpl := &x509.Certificate{
		SerialNumber: big.NewInt(2),
		Subject:      pkix.Name{CommonName: "test.example.com"},
		DNSNames:     []string{"test.example.com"},
		NotBefore:    time.Now().Add(-time.Hour),
		NotAfter:     time.Now().Add(time.Hour),
		KeyUsage:     x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
	}

	leafDER, err := x509.CreateCertificate(rand.Reader, leafTmpl, caCert, &leafKey.PublicKey, caKey)
	if err != nil {
		panic(err)
	}

	cfg := &stdtls.Config{
		Certificates: []stdtls.Certificate{{
			Certificate: [][]byte{leafDER},
			PrivateKey:  leafKey,
		}},
		MinVersion: stdtls.VersionTLS12,
		MaxVersion: stdtls.VersionTLS12,
	}

	ln, err := stdtls.Listen("tcp", "127.0.0.1:0", cfg)
	if err != nil {
		panic(err)
	}

	go func() {
		for {
			c, acceptErr := ln.Accept()
			if acceptErr != nil {
				return
			}

			go func(conn net.Conn) {
				defer func() { _ = conn.Close() }()

				buf := make([]byte, 4096)
				for {
					n, readErr := conn.Read(buf)
					if readErr != nil {
						return
					}

					if _, writeErr := conn.Write(buf[:n]); writeErr != nil {
						return
					}
				}
			}(c)
		}
	}()

	return &exampleServer{
		Addr:      ln.Addr().String(),
		CACertPEM: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caDER}),
		listener:  ln,
	}
}
Output:
server echoed: ping

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func BackendTag

func BackendTag() string

BackendTag returns a string identifying the TLS backend compiled into this binary: "default" for the pure-Go backend, "openssl" for the CGO backend.

Example

ExampleBackendTag reports which TLS backend is compiled in. The pure-Go gostls module always reports "default"; the cgo OpenSSL backend (in the separate dialer module) reports "openssl".

package main

import (
	"fmt"

	"github.com/bigbes/gostls"
)

func main() {
	fmt.Println(gostls.BackendTag())

}
Output:
default

func LookupSuiteByName

func LookupSuiteByName(name string) (uint16, bool)

LookupSuiteByName returns the IANA ID and true for the named suite, or 0 and false if the name is not registered.

Example

ExampleLookupSuiteByName resolves a suite name to its IANA cipher suite ID.

package main

import (
	"fmt"

	"github.com/bigbes/gostls"
)

func main() {
	id, ok := gostls.LookupSuiteByName("ECDHE-RSA-AES128-GCM-SHA256")
	fmt.Printf("0x%04X %t\n", id, ok)

	_, ok = gostls.LookupSuiteByName("NO-SUCH-SUITE")
	fmt.Println(ok)

}
Output:
0xC02F true
false

Types

type Certificate

type Certificate struct {
	// Certificate is the parsed certificate (optional; used for display only).
	Certificate *x509.Certificate
	// RawCertificate holds the DER-encoded certificate.
	RawCertificate []byte
	// PrivateKey is the private key corresponding to the certificate's public
	// key. Required when using mutual TLS (client authentication).
	PrivateKey crypto.PrivateKey
}

Certificate holds a certificate and its private key. Mirrors crypto/tls.Certificate in structure.

type Config

type Config struct {
	// RootCAs defines the set of root certificate authorities used to verify
	// the server's certificate. If nil, the host's default root CA set is used.
	//
	// RootCAs and RootCAPEMs are mutually exclusive. Setting both is an error.
	RootCAs *x509.CertPool

	// RootCAPEMs holds PEM-encoded trusted root certificates. Each element may
	// be a single PEM block or a concatenation of multiple PEM blocks. Only
	// CERTIFICATE-typed blocks are used; other block types (keys, etc.) are
	// ignored, but each element must contain at least one CERTIFICATE block.
	//
	// This field is the preferred way to supply an in-memory trust store when
	// using the openssl backend, where RootCAs is not supported.
	//
	// RootCAs and RootCAPEMs are mutually exclusive. Setting both is an error.
	RootCAPEMs [][]byte

	// GOSTRoots is the trust store for GOST-signed server certificates. The
	// stdlib pools (RootCAs/RootCAPEMs) cannot verify GOST signatures, so a
	// GOST trust anchor must be supplied here as a parsed x509gost certificate.
	// If the server presents a GOST-signed certificate and GOSTRoots is empty,
	// verification fails (unless InsecureSkipVerify). It is not consulted for
	// non-GOST certificates.
	GOSTRoots []*x509gost.Certificate

	// GOSTIntermediates optionally bridges a GOST-signed leaf to a root in
	// GOSTRoots through intermediate CA certificates. Leave nil for a direct
	// leaf-signed-by-root chain.
	GOSTIntermediates []*x509gost.Certificate

	// Certificates contains client certificates for mutual TLS (mTLS) client
	// authentication. When the server sends a CertificateRequest during the
	// handshake, the first entry in this slice is offered. If the server's
	// requested signature algorithms do not include any algorithm supported by
	// the key, an empty Certificate message is sent (which is valid per RFC 5246).
	//
	// Each Certificate.PrivateKey must be *rsa.PrivateKey or *ecdsa.PrivateKey;
	// any other type causes Handshake() to return a hard error. If
	// Certificate.Certificate is nil, RawCertificate is parsed on demand; an
	// empty RawCertificate is a hard error.
	Certificates []Certificate

	// ServerName is the server name sent in the SNI extension and used for
	// certificate verification. If empty and InsecureSkipVerify is false,
	// certificate verification will fail because x509 requires a DNS name.
	ServerName string

	// CipherSuites specifies the list of cipher suite IDs to offer in the
	// ClientHello. If nil, all registered suites whose cipher is AES-128-CBC,
	// AES-256-CBC, AES-128-GCM, AES-256-GCM, or CHACHA20-POLY1305 are offered.
	CipherSuites []uint16

	// InsecureSkipVerify controls whether the client verifies the server's
	// certificate chain and host name.
	//
	// WARNING: Setting this to true disables all certificate authentication.
	// The connection is susceptible to man-in-the-middle attacks. Use only
	// for debugging or with VerifyPeerCertificate performing custom verification.
	InsecureSkipVerify bool

	// VerifyPeerCertificate is called after normal certificate verification
	// (or instead of it if InsecureSkipVerify is true). The first argument is
	// the raw DER bytes of the certificates provided by the server. The second
	// is the verified chains (empty when InsecureSkipVerify is true).
	//
	// A non-nil error aborts the handshake with bad_certificate.
	VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error

	// Rand provides the source of entropy for random values used during the
	// handshake: client random, client ephemeral keys, RSA pre-master secret,
	// and CBC per-record IVs.
	//
	// If nil, crypto/rand.Reader is used (the correct default for production).
	// Override in tests to produce deterministic handshake bytes.
	Rand io.Reader
}

Config is the TLS 1.2 client configuration. Field names and semantics mirror crypto/tls.Config where the feature exists in stdlib.

Only a subset of crypto/tls.Config is implemented.

Example (CipherSuites)

ExampleConfig_cipherSuites shows how to request a specific cipher suite — here a GOST suite, which is registered in every build but is not offered by default. Resolve the IANA ID by name and set it on Config.CipherSuites.

package main

import (
	"fmt"
	"log"

	"github.com/bigbes/gostls"
)

func main() {
	id, ok := gostls.LookupSuiteByName("GOST2012-KUZNYECHIK-KUZNYECHIKOMAC")
	if !ok {
		log.Fatal("GOST suite not registered")
	}

	cfg := &gostls.Config{
		ServerName:   "gost.example.com",
		CipherSuites: []uint16{id}, // offer only this suite.
	}

	fmt.Printf("offering 0x%04X (%d suite)\n", cfg.CipherSuites[0], len(cfg.CipherSuites))

}
Output:
offering 0xC100 (1 suite)

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

Conn is a TLS 1.2 client connection implementing net.Conn.

The zero value is not usable; create one with NewConn or Dialer.DialContext.

func NewConn

func NewConn(c net.Conn, config *Config) *Conn

NewConn creates a Conn wrapping the given net.Conn with the provided Config. This is the primary way to create a *Conn directly — Dialer.DialContext also creates one internally.

func (*Conn) Close

func (c *Conn) Close() error

Close sends a close_notify alert (when the handshake completed) and closes the underlying connection. It never waits on an in-flight handshake: closing the transport unblocks a handshake currently blocked on I/O, which then returns an error. This is why the decision below uses the handshakeOK flag rather than driving handshakeOnce (which would block).

func (*Conn) Handshake

func (c *Conn) Handshake() error

Handshake runs the TLS 1.2 client handshake if it has not been run yet. It is called automatically by Read and Write if needed.

func (*Conn) LocalAddr

func (c *Conn) LocalAddr() net.Addr

LocalAddr returns the local network address.

func (*Conn) Read

func (c *Conn) Read(b []byte) (int, error)

Read reads decrypted application data from the connection. It triggers the handshake if needed.

func (*Conn) RemoteAddr

func (c *Conn) RemoteAddr() net.Addr

RemoteAddr returns the remote network address.

func (*Conn) SetDeadline

func (c *Conn) SetDeadline(t time.Time) error

SetDeadline sets the read and write deadlines on the underlying connection.

func (*Conn) SetReadDeadline

func (c *Conn) SetReadDeadline(t time.Time) error

SetReadDeadline sets the deadline for future Read calls.

func (*Conn) SetWriteDeadline

func (c *Conn) SetWriteDeadline(t time.Time) error

SetWriteDeadline sets the deadline for future Write calls.

func (*Conn) Write

func (c *Conn) Write(b []byte) (int, error)

Write encrypts and sends b over the connection. It triggers the handshake if needed. Records are split into chunks of at most 16384 bytes each.

type ConnectionState

type ConnectionState struct {
	// Version is the TLS version used; always 0x0303 (TLS 1.2).
	Version uint16
	// CipherSuite is the IANA cipher suite ID negotiated.
	CipherSuite uint16
	// Suite is the full suite descriptor, or nil before handshake.
	Suite *suites.Suite
}

ConnectionState holds information about a TLS connection.

type Dialer

type Dialer struct {
	// NetDialer is the underlying network dialer used to establish the TCP
	// (or other) connection before the TLS handshake. If nil, a zero-value
	// net.Dialer is used.
	NetDialer *net.Dialer

	// Config is the TLS configuration applied to each new connection.
	// If nil, a zero-value Config is used (which requires InsecureSkipVerify
	// or a matching RootCAs + ServerName to succeed).
	Config *Config
}

Dialer dials TLS connections. Its API mirrors crypto/tls.Dialer.

func (*Dialer) DialContext

func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error)

DialContext connects to addr using the network dialer, then performs a TLS handshake and returns the fully negotiated connection.

The returned net.Conn is a *Conn. The handshake is completed before DialContext returns.

ctx controls the lifetime of the dial; it is not propagated to subsequent Read/Write calls on the returned Conn.

gostls is the pure-Go TLS backend (CGO_ENABLED=0 compatible). The OpenSSL backend lives in the separate go-tlsdialer module, which selects between gostls and OpenSSL.

type SuiteInfo

type SuiteInfo struct {
	// ID is the IANA TLS cipher suite number.
	ID uint16
	// Name is the OpenSSL-style name (e.g. "ECDHE-RSA-AES128-GCM-SHA256").
	Name string
}

SuiteInfo is a minimal descriptor of a registered TLS cipher suite, exported for use by callers outside this module's internal packages.

func AllSuites

func AllSuites() []SuiteInfo

AllSuites returns info about every registered cipher suite. The order is stable but unspecified (registration order).

Example

ExampleAllSuites iterates the registered cipher suites. The order is stable but unspecified, so this example reports whether a known suite is present rather than printing the whole list.

package main

import (
	"fmt"

	"github.com/bigbes/gostls"
)

func main() {
	want := "GOST2012-MAGMA-MAGMAOMAC"
	found := false

	for _, s := range gostls.AllSuites() {
		if s.Name == want {
			found = true

			break
		}
	}

	fmt.Printf("%s registered: %t\n", want, found)

}
Output:
GOST2012-MAGMA-MAGMAOMAC registered: true

Directories

Path Synopsis
internal
handshake
Package handshake provides TLS 1.2 handshake message marshaling/unmarshaling and the multi-hash transcript accumulator (RFC 5246 §7.4).
Package handshake provides TLS 1.2 handshake message marshaling/unmarshaling and the multi-hash transcript accumulator (RFC 5246 §7.4).
ke
Package ke implements TLS 1.2 key exchange methods.
Package ke implements TLS 1.2 key exchange methods.
suites
Package suites defines the TLS 1.2 cipher suite registry, PRF, and key schedule for github.com/bigbes/gostls.
Package suites defines the TLS 1.2 cipher suite registry, PRF, and key schedule for github.com/bigbes/gostls.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL