Documentation
¶
Overview ¶
Package gostls implements a TLS 1.2 client with a crypto/tls-shaped API.
It speaks the ordinary AES / ECDHE / ChaCha20-Poly1305 suites and, in addition, the GOST suites that the standard library does not: GOST 28147-89, Kuznyechik and Magma in CTR-OMAC mode, and the VKO / GOST-2018 key exchanges. The implementation is pure Go, builds with CGO_ENABLED=0, and links no GPL code; the GOST primitives come from github.com/tarantool/go-gostcrypto, GOST-signed X.509 from its github.com/tarantool/go-gostcrypto/x509gost subpackage.
Scope ¶
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 suites ¶
AES-128-CBC, AES-256-CBC, AES-128-GCM, AES-256-GCM, and CHACHA20-POLY1305 (RFC 7905) suites are included in the default negotiation set. GOST suites are registered in every build but are not offered by default; request them explicitly via Config.CipherSuites, resolving the ID with LookupSuiteByName.
Usage ¶
Dial a server the same way you would with crypto/tls:
d := &gostls.Dialer{
Config: &gostls.Config{ServerName: "example.com"},
}
conn, err := d.DialContext(ctx, "tcp", "example.com:443")
if err != nil {
return err
}
defer conn.Close()
The returned Conn is a net.Conn whose handshake has already completed. To negotiate a GOST suite, name it explicitly:
id, ok := gostls.LookupSuiteByName("GOST2012-KUZNYECHIK-KUZNYECHIKOMAC")
if !ok {
return errors.New("suite not registered")
}
cfg.CipherSuites = []uint16{id}
Ordinary chains are verified against Config.RootCAs or Config.RootCAPEMs; GOST-signed server certificates are verified against Config.GOSTRoots.
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"
"math/big"
"net"
"time"
stdtls "crypto/tls"
"github.com/tarantool/go-gostls"
"github.com/tarantool/go-gostls/internal/testutil"
)
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",
},
}
// On error these print and return rather than calling log.Fatal, so the
// deferred Close calls still run and the mismatch surfaces as a failed
// Output comparison instead of a killed test binary.
conn, err := d.DialContext(context.Background(), "tcp", srv.Addr)
if err != nil {
fmt.Println("dial:", err)
return
}
defer func() { _ = conn.Close() }()
_, err = conn.Write([]byte("ping"))
if err != nil {
fmt.Println("write:", err)
return
}
buf := make([]byte, 4)
_, err = io.ReadFull(conn, buf)
if err != nil {
fmt.Println("read:", err)
return
}
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 (via testutil.Must), which is acceptable for an example.
func newExampleServer() *exampleServer {
caKey := testutil.Must(rsa.GenerateKey(rand.Reader, 2048))
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 := testutil.Must(x509.CreateCertificate(rand.Reader, caTmpl, caTmpl, &caKey.PublicKey, caKey))
caCert := testutil.Must(x509.ParseCertificate(caDER))
leafKey := testutil.Must(rsa.GenerateKey(rand.Reader, 2048))
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 := testutil.Must(x509.CreateCertificate(rand.Reader, leafTmpl, caCert, &leafKey.PublicKey, caKey))
cfg := &stdtls.Config{
Certificates: []stdtls.Certificate{{
Certificate: [][]byte{leafDER},
PrivateKey: leafKey,
}},
MinVersion: stdtls.VersionTLS12,
MaxVersion: stdtls.VersionTLS12,
}
ln := testutil.Must(stdtls.Listen("tcp", "127.0.0.1:0", cfg))
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
}
_, writeErr := conn.Write(buf[:n])
if 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 ¶
- Constants
- func BackendTag() string
- func LookupSuiteByName(name string) (uint16, bool)
- type Certificate
- type Config
- type Conn
- func (c *Conn) Close() error
- func (c *Conn) ConnectionState() ConnectionState
- func (c *Conn) Handshake() error
- func (c *Conn) LocalAddr() net.Addr
- func (c *Conn) Read(b []byte) (int, error)
- func (c *Conn) RemoteAddr() net.Addr
- func (c *Conn) SetDeadline(t time.Time) error
- func (c *Conn) SetReadDeadline(t time.Time) error
- func (c *Conn) SetWriteDeadline(t time.Time) error
- func (c *Conn) Write(b []byte) (int, error)
- type ConnectionState
- type Dialer
- type SuiteInfo
Examples ¶
Constants ¶
const ( VersionTLS10 = 0x0301 VersionTLS11 = 0x0302 VersionTLS12 = 0x0303 VersionTLS13 = 0x0304 )
TLS protocol version numbers, spelled and valued as in crypto/tls.
This module implements TLS 1.2 and nothing else, so VersionTLS12 is the only version Config.MinVersion/MaxVersion can actually select. The neighbouring constants exist for source compatibility: code carrying the usual `MinVersion: tls.VersionTLS12` hygiene line — or any other bound — keeps compiling after the package is swapped, and a bound that excludes TLS 1.2 is reported at handshake time instead of being silently ignored.
Variables ¶
This section is empty.
Functions ¶
func BackendTag ¶
func BackendTag() string
BackendTag returns the identifier of the TLS backend this module implements, always "default" — the pure-Go one. The tag exists for the caller that does have a choice: go-tlsdialer picks between gostls and its cgo OpenSSL backend and reports which one it ended up with. This module carries no second backend, so the value never varies.
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/tarantool/go-gostls"
)
func main() {
fmt.Println(gostls.BackendTag())
}
Output: default
func LookupSuiteByName ¶
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/tarantool/go-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 holds the DER-encoded certificate chain: the leaf first,
// followed by any intermediates needed to let the peer build a path to a
// CA it trusts. The chain is sent verbatim in the client Certificate
// message. At least one entry is required, and no entry may be empty.
Certificate [][]byte
// PrivateKey is the private key corresponding to the leaf certificate's
// public key. Required when using mutual TLS (client authentication).
PrivateKey crypto.PrivateKey
// Leaf is the parsed leaf certificate, i.e. Certificate[0]. It is optional,
// is never consulted by this package, and never substitutes for the DER in
// Certificate — which is what actually goes on the wire, and which is
// validated at handshake start whether Leaf is set or not.
Leaf *x509.Certificate
}
Certificate holds a certificate chain and its private key. The field layout matches crypto/tls.Certificate, so a value built for crypto/tls can be assigned field by field without reshaping.
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, chain included. 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. So does an
// empty Certificate chain or an empty entry within it.
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
// MinVersion is the lowest TLS version the caller is willing to accept.
// Zero means "unset", which is the only value with no effect on behaviour:
// the handshake always negotiates TLS 1.2 regardless.
//
// The field exists so that configurations written against crypto/tls port
// over unchanged. A non-zero bound is honoured to the extent it can be —
// one that excludes TLS 1.2 fails the handshake rather than being ignored.
MinVersion uint16
// MaxVersion is the highest TLS version the caller is willing to accept.
// Zero means "unset". See MinVersion for why the field exists and what a
// non-zero value does.
MaxVersion uint16
}
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"
"github.com/tarantool/go-gostls"
)
func main() {
id, ok := gostls.LookupSuiteByName("GOST2012-KUZNYECHIK-KUZNYECHIKOMAC")
if !ok {
fmt.Println("GOST suite not registered")
return
}
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)
func (*Config) Clone ¶
Clone returns a copy of c that shares no slice with the original: the slice fields are copied element by element, down to the DER chains inside Certificates, so appending to or overwriting a clone's slices cannot be observed through c.
Pointer-valued and interface-valued fields are shared, not duplicated — RootCAs, the x509gost certificates in GOSTRoots/GOSTIntermediates, each Certificate's PrivateKey and Leaf, Rand and VerifyPeerCertificate. They are configuration handed in by the caller and treated as immutable once set, which is exactly how crypto/tls.Config.Clone treats its own.
A nil receiver clones to nil, so an unset *Config can be passed through Clone without a guard at the call site.
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 Client ¶
Client wraps conn in a *Conn that speaks the client side of TLS, deferring the handshake to the first Read, Write or explicit Handshake call.
It is the crypto/tls-compatible spelling of NewConn and does exactly the same thing; both are supported, so code ported from crypto/tls compiles unchanged while code written against gostls need not be rewritten.
func Dial ¶
Dial connects to addr with a zero-value net.Dialer, performs the TLS handshake and returns the negotiated connection. It is the shorthand entry point that crypto/tls callers reach for most often.
config may be nil, which is treated as a zero-value Config — a configuration that only succeeds with InsecureSkipVerify or a matching RootCAs and ServerName, so most callers do pass one.
func DialWithDialer ¶
DialWithDialer is Dial over a caller-supplied net.Dialer, so that the local address, keep-alive and resolver settings of an existing dialer carry over to the TLS connection. A nil dialer means a zero-value one.
The dialer's Timeout and Deadline bound the TCP connect and the TLS handshake together, not just the connect. Use a Dialer with DialContext to bound them by a context instead.
func NewConn ¶
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 ¶
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) ConnectionState ¶
func (c *Conn) ConnectionState() ConnectionState
ConnectionState returns the state of the connection. Before the handshake completes it reports the zero value, with HandshakeComplete false; it never starts a handshake itself, matching crypto/tls.
The returned value is a snapshot and is safe to read while other goroutines use the connection.
func (*Conn) Handshake ¶
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) Read ¶
Read reads decrypted application data from the connection. It triggers the handshake if needed.
func (*Conn) RemoteAddr ¶
RemoteAddr returns the remote network address.
func (*Conn) SetDeadline ¶
SetDeadline sets the read and write deadlines on the underlying connection.
func (*Conn) SetReadDeadline ¶
SetReadDeadline sets the deadline for future Read calls.
func (*Conn) SetWriteDeadline ¶
SetWriteDeadline sets the deadline for future Write calls.
type ConnectionState ¶
type ConnectionState struct {
// Version is the negotiated TLS version: VersionTLS12 once the handshake
// completed, zero before that. This module negotiates nothing else.
Version uint16
// HandshakeComplete is true once the handshake finished successfully. Every
// other field is zero valued until it is.
HandshakeComplete bool
// CipherSuite is the IANA number of the negotiated cipher suite. Resolve it
// to a name with AllSuites if you need one.
CipherSuite uint16
// ServerName is the name sent in SNI and verified against the certificate.
// It is the effective name: Config.ServerName when set, otherwise the host
// taken from the dial address.
ServerName string
// PeerCertificates is the chain the server sent, leaf first, in stdlib
// shape. A GOST-signed certificate appears here too, in the stdlib view
// that x509gost derives from it — which is enough to read the subject and
// validity, but not the GOST key or signature. Use GOSTPeerCertificates
// for those.
PeerCertificates []*x509.Certificate
// GOSTPeerCertificates is the same chain, in the same order, parsed as
// x509gost certificates. An entry is nil when that certificate is one
// x509gost declines to parse but crypto/x509 accepts — a GOST signature
// over a non-GOST key, or an unknown curve paramset. Such certificates are
// legal and do occur in mixed PKIs, so they do not fail the handshake;
// they simply have no GOST view. PeerCertificates is never sparse.
GOSTPeerCertificates []*x509gost.Certificate
}
ConnectionState describes what a handshake negotiated.
The stdlib-named fields carry stdlib meanings, so code that logs the negotiated suite or inspects the peer chain ports over unchanged. The fields crypto/tls has that this module cannot fill — everything about TLS 1.3, session resumption, ALPN and OCSP — are absent rather than present and always zero, so that reading a value never implies a feature that is not there.
GOSTPeerCertificates has no stdlib counterpart: a GOST-signed certificate cannot be expressed as an *x509.Certificate, and since negotiating GOST is the point of this module, the peer chain has to be reachable in the form that carries the GOST key and signature.
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) Dial ¶
Dial is DialContext with context.Background(): it connects to addr and completes the TLS handshake, with no bound on the attempt beyond the ones NetDialer carries.
It returns a net.Conn holding a *Conn, mirroring crypto/tls.Dialer.Dial; the package-level Dial returns the concrete type instead.
func (*Dialer) DialContext ¶
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/tarantool/go-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
Source Files
¶
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/tarantool/go-gostls.
|
Package suites defines the TLS 1.2 cipher suite registry, PRF, and key schedule for github.com/tarantool/go-gostls. |
|
testutil
Package testutil holds the assertion helpers shared by the gostls test suites.
|
Package testutil holds the assertion helpers shared by the gostls test suites. |