TLS

Hertz supports TLS secure transmission, helping users achieve data confidentiality and integrity.

If you need TLS, Please use go net lib instead. netpoll is now working on it but not ready yet.

In tls.Config, the parameters that the server and client both can use are as follows:

Parameter Introduce
Certificates Used to add certificates, multiple certificates can be configured.
Both ends automatically select the first certificate for verification.
VerifyPeerCertificate Used to verify the peer certificate.
Called after certificate verification on either side.
VerifyConnection After both certificates are verified, TLS connection verification is performed.
NextProtos Used to set supported application layer protocols.
CipherSuites Used to negotiate encryption policies, supports TLS 1.0-1.2.
MaxVersion Used to set the maximum version supported by TLS, currently 1.3.

Server

Hertz provides the WithTLS Option in the server package for configuring TLS services. But currently Hertz only supports TLS in the standard network library, Netpoll network library support is still on the way. The Transporter of WithTLS is set by default to the Transporter of the standard library.

// WithTLS sets TLS config to start a tls server.
// NOTE: If a tls server is started, it won't accept non-tls request.
func WithTLS(cfg *tls.Config) config.Option {
	return config.Option{F: func(o *config.Options) {
		route.SetTransporter(standard.NewTransporter)
		o.TLS = cfg
	}}
}

Parameter

In tls.Config, in addition to the above basic parameters, the parameters that can be configured by the server are as follows:

Parameter Introduce
GetCertificate Returns a certificate based on client SNI information or when the certificate set is empty.
GetClientCertificate It is used to return the client certificate when the server requires to verify the client certificate.
GetConfigForClient When the server receives ClientHello from the client, it returns the configuration information.
If non-empty configuration information is returned, it will be used for this TLS connection.
ClientAuth Used for client authentication policy settings, defaults to NoClientCert.
ClientCAs When ClientAuth is enabled, used to verify the authenticity of the client certificate.

The main process of server-side TLS:

  1. Load the root certificate to verify the authenticity of the client.
  2. Load the server certificate to send to the client to verify the authenticity of the server.
  3. Configure tls.Config.
  4. Use WithTLS to configure server-side TLS. By default, the standard library’s Transporter is used.

Sample Code

ca.key, ca.crt, server.key and server.crt in this example are all generated by openssl. First generate the CA’s private key and certificate, the command is as follows:

openssl ecparam -genkey -name prime256v1 -out ca.key
openssl req -new -key ca.key -out ca.req
# country=cn, common name=ca.example.com
openssl x509 -req -in ca.req -signkey ca.key -out ca.crt -days 365

Generate the private key and certificate of the server through CA signature, the command is as follows:

openssl ecparam -genkey -name prime256v1 -out server.key
openssl req -new -key server.key -out server.req
# country=cn, common name=server.example.com
openssl x509 -req -in server.req -CA ca.crt -CAkey ca.key -out server.crt -CAcreateserial -days 365

Server-side sample code:

package main

// ...

func main() {
	// load server certificate
	cert, err := tls.LoadX509KeyPair("./tls/server.crt", "./tls/server.key")
	if err != nil {
		fmt.Println(err.Error())
	}
	// load root certificate
	certBytes, err := ioutil.ReadFile("./tls/ca.crt")
	if err != nil {
		fmt.Println(err.Error())
	}
	caCertPool := x509.NewCertPool()
	ok := caCertPool.AppendCertsFromPEM(certBytes)
	if !ok {
		panic("Failed to parse root certificate.")
	}
	// set server tls.Config
	cfg := &tls.Config{
		// add certificate
		Certificates: []tls.Certificate{cert},
		MaxVersion:   tls.VersionTLS13,
		// enable client authentication
		ClientAuth:   tls.RequireAndVerifyClientCert,
		ClientCAs:    caCertPool,
		// cipher suites supported
		CipherSuites: []uint16{
			tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
		},
		// set application protocol http2
		NextProtos: []string{http2.NextProtoTLS},
	}
	// set TLS server
	// default is standard.NewTransporter
	h := server.Default(server.WithTLS(cfg), server.WithHostPorts(":8443"))

	h.GET("/ping", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "TLS test\n")
	})

	h.Spin()
}

For a complete usage example, see example .

Client

Parameter

In tls.Config, in addition to the above basic parameters, the parameters that can be configured by the client are as follows:

Parameter Introduce
ServerName Validate the hostname against the returned certificate information.
InsecureSkipVerify It is used for whether the client enables server-side certificate verification.
RootCAs The certificate used by the client to authenticate the server.

The main process of client-side TLS:

  1. Load the root certificate to verify the authenticity of the server.
  2. Load the client certificate for sending to the server to verify the authenticity of the client.
  3. Configure tls.Config.
  4. Use WithTLS to configure client-side TLS, which uses the standard library’s Dialer by default.

Sample Code

Generate the client’s private key and certificate through CA signature, the command is as follows:

openssl ecparam -genkey -name prime256v1 -out client.key
openssl req -new -key client.key -out client.req
# country=cn, common name=client.example.com
openssl x509 -req -in client.req -CA ca.crt -CAkey ca.key -out client.crt -CAcreateserial -days 365

Client sample code:

package main

// ...

func main() {
	// load root certificate to verify the client validity
	certBytes, err := ioutil.ReadFile("./tls/ca.crt")
	if err != nil {
		fmt.Println(err.Error())
	}
	caCertPool := x509.NewCertPool()
	ok := caCertPool.AppendCertsFromPEM(certBytes)
	if !ok {
		panic("Failed to parse root certificate.")
	}
    // load client certificate to send to server
	cert, err := tls.LoadX509KeyPair("./tls/client.crt", "./tls/client.key")
	if err != nil {
		fmt.Println(err.Error())
	}
    // set TLS configuration
	cfg := &tls.Config{
		MaxVersion:   tls.VersionTLS13,
		Certificates: []tls.Certificate{cert},
		// verify the server certificate
		RootCAs:      caCertPool,
		// ignored the server certificate
		InsecureSkipVerify: true,
	}

	c, err := client.NewClient(
		// default dialer is standard
		client.WithTLSConfig(cfg),
		client.WithDialer(standard.NewDialer()),
	)
	if err != nil {
		fmt.Println(err.Error())
	}
    // ...
}

Note:Currently, Hertz TLS server is not supported Netpoll network library temporarily.
c, err := client.NewClient(client.WithTLSConfig(cfg), client.WithDialer(netpoll.NewDialer()) support is still on the way.

For a complete usage example, see example .

Autotls Middleware

Hertz provides autotls extension adaptation Let’s Encrypt, which is convenient for users to automatically configure TLS services.

Installation

go get github.com/hertz-contrib/autotls

Configuration

NewTlsConfig

The autotls extension provides NewTlsConfig to help users support LetsEncrypt HTTPS servers with one line of code.

The NewTlsConfig function signature is as follows:

func NewTlsConfig(domains ...string) *tls.Config

sample code:

package main

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	"github.com/hertz-contrib/autotls"
)

func main() {
	h := server.Default(
		server.WithTLS(autotls.NewTlsConfig("example1.com", "example2.com")),
		server.WithHostPorts(":https"),
	)

	// Ping handler
	h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
		ctx.JSON(200, map[string]interface{}{
			"ping": "pong",
		})
	})

	hlog.Fatal(autotls.Run(h))
}

RunWithContext

The autotls extension provides RunWithContext to help users support LetsEncrypt HTTPS servers with a single line of code while enabling graceful shutdown of the service.

The RunWithContext function signature is as follows:

func RunWithContext(ctx context.Context, h *server.Hertz) error

sample code:

package main

import (
	"context"
	"os/signal"
	"syscall"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	"github.com/hertz-contrib/autotls"
)

func main() {
	// Create context that listens for the interrupt signal from the OS.
	ctx, stop := signal.NotifyContext(
		context.Background(),
		syscall.SIGINT,
		syscall.SIGTERM,
	)
	defer stop()

	h := server.Default(
		server.WithTLS(autotls.NewTlsConfig("example1.com", "example2.com")),
		server.WithHostPorts(":https"),
	)

	// Ping handler
	h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
		ctx.JSON(200, map[string]interface{}{
			"ping": "pong",
		})
	})

	hlog.Fatal(autotls.RunWithContext(ctx, h))
}

NewServerWithManagerAndTlsConfig

The autotls extension provides NewServerWithManagerAndTlsConfig to help users automate certificate management and TLS configuration.

The NewServerWithManagerAndTlsConfig function signature is as follows:

func NewServerWithManagerAndTlsConfig(m *autocert.Manager, tlsc *tls.Config, opts ...config.Option) *server.Hertz

sample code:

package main

import (
	"context"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	"github.com/hertz-contrib/autotls"
	"golang.org/x/crypto/acme/autocert"
)

func main() {
	m := autocert.Manager{
		Prompt:     autocert.AcceptTOS,
		HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),
		Cache:      autocert.DirCache("/var/www/.cache"),
	}

	h := autotls.NewServerWithManagerAndTlsConfig(&m, nil)

	// Ping handler
	h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
		ctx.JSON(200, map[string]interface{}{
			"ping": "pong",
		})
	})

	hlog.Fatal(autotls.Run(h))
}

For a complete usage example, see example .

Note

Client raise error not support tls

Hertz uses netpoll as the network library by default and currently netpoll does not support TLS. To use TLS you need to switch to the standard network library with the following code:

import (
    "github.com/cloudwego/hertz/pkg/app/client"
    "github.com/cloudwego/hertz/pkg/network/standard"
    "github.com/cloudwego/hertz/pkg/protocol"
)

func main() {
	clientCfg := &tls.Config{
		InsecureSkipVerify: true,
	}
	c, err := client.NewClient(
		client.WithTLSConfig(clientCfg), 
		client.WithDialer(standard.NewDialer()), 
	)
}

Last modified July 18, 2023 : feat: shortcodes codetab (#709) (6996e7c)