mtls

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: May 27, 2026 License: MIT Imports: 25 Imported by: 4

README

Go Reference

[m]TLS

A Go library for TLS/HTTPS using public key pinning instead of certificate authorities.

The Problem

Usually TLS/HTTPS relies on certificate authorities (CAs) to establish trust. This means:

  • Obtaining and renewing certificates from CAs
  • Managing certificate chains and trust stores
  • Trusting any certificate signed by a trusted CA

For services that communicate with known peers this is overkill.

The Solution

This library takes an SSH-like approach to TLS authentication. Just like SSH's known_hosts file lets you trust specific server keys directly, mtls lets you identify peers by their public key hash rather than CA signatures.

h1:2eYrKRe4K9Xf_HjOhdJjNPuH5P8sLN9XNgdgZKfqt1A

That's it. No certificates to issue, no chains to verify, no CAs to manage.

How It Works

First, let's take a look at a client connecting to an HTTPS server and verifying its public key:

Show Example Code
package main

import (
	"crypto/tls"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"

	"aead.dev/mtls"
)

// In this example, we configure, start and establish a
// secure TLS connection to a HTTPS server without
// configuring certificates or CAs.
func main() {
	// The server's private key
	const PrivateKey = "k1:xZnpcYtPdVMNLBBRaUO5HPEoK_jVrcc3MWR8BshkjJw"

	privKey, err := mtls.ParsePrivateKey(PrivateKey)
	if err != nil {
		log.Fatal(err)
	}

	// Our 'Hello World' server using a minimal TLS configuration.
	srv := http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			fmt.Printf("Hello from server [ identity=%s ]\n", r.TLS.ServerName)
		}),

		TLSConfig: &tls.Config{
			GetConfigForClient: (&mtls.Server{
				PrivateKey: privKey,
			}).GetConfigForClient,
		},
	}

	// Our client needs to know the server's identity in order
	// to verify the public key presented by the server during
	// TLS handshakes.
	identity := privKey.Identity()
	client := http.Client{
		Transport: &http.Transport{
			DialTLSContext: (&mtls.Client{
				// Here we define which identity to expect when connecting
				// to a server.
				GetPeerIdentity: func(_ string) (mtls.Identity, bool) {
					return identity, true
				},
			}).DialTLSContext,
		},
	}

	// Listen on port 4443 and start the HTTPS server.
	listener, err := net.Listen("tcp", "0.0.0.0:4443")
	if err != nil {
		log.Fatal(err)
	}
	defer listener.Close()
	go func() { log.Print(srv.ServeTLS(listener, "", "")) }()

	// Connect to our server, perform a TLS handshake,
	// verify that the server's public key corresponds
	// to the expected identity and print the response
	// to the terminal.
	resp, err := client.Get("https://127.0.0.1:4443")
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	// At this point we have established a secure TLS
	// connection without configuring certificates or
	// CAs.
	if _, err := io.Copy(os.Stdout, resp.Body); err != nil {
		log.Fatal(err)
	}
}

This example produces the following output:

Hello from server [ identity=h1:l4AoVm6xKAVGsfo8J_ttCOC6Odgq3GJLHg5NtAdOAr0 ]

Instead of verifying that the server presents a certificate issued by a trusted CA, the client verifies that the server presents a public key matching an expected identity (SHA-256 hash). However, the client does not authenticate itself to the server.

We can modify our initial example as following to mutually authenticate during the TLS handshake:

Show Example Code
package main

import (
	"crypto/tls"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"

	"aead.dev/mtls"
)

// In this example, we configure, start and establish a
// secure mutual TLS connection to a HTTPS server without
// configuring certificates or CAs.
func main() {
	const PrivateKeyServer = "k1:xZnpcYtPdVMNLBBRaUO5HPEoK_jVrcc3MWR8BshkjJw"
	const PrivateKeyClient = "k1:uNTwsVpygybzFuHPYE04Luw2te-D2Efr5xnxycGpt4c"

	srvKey, err := mtls.ParsePrivateKey(PrivateKeyServer)
	if err != nil {
		log.Fatal(err)
	}
	clientKey, err := mtls.ParsePrivateKey(PrivateKeyClient)
	if err != nil {
		log.Fatal(err)
	}

	srvIdentity := srvKey.Identity()
	clientIdentity := clientKey.Identity()

	// Our 'Hello World' server using a minimal TLS configuration.
	// Our server needs to know the client's identity in order to
	// verify the public key presented by the client during TLS
	// handshakes.
	srv := http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			fmt.Printf("Hello from server [ identity=%s ] \n      to   client [ identity=%s ]\n",
				r.TLS.ServerName,
				mtls.CertificateIdentity(r.TLS.PeerCertificates[0]),
			)
		}),

		TLSConfig: &tls.Config{
			GetConfigForClient: (&mtls.Server{
				PrivateKey:     srvKey,
				PeerIdentities: []mtls.Identity{clientIdentity},
			}).GetConfigForClient,
		},
	}

	// Our client needs to know the server's identity in order
	// to verify the public key presented by the server during
	// TLS handshakes.
	client := http.Client{
		Transport: &http.Transport{
			DialTLSContext: (&mtls.Client{
				PrivateKey: clientKey,
				// Here we define which identity to expect when connecting
				// to a server.
				GetPeerIdentity: func(_ string) (mtls.Identity, bool) {
					return srvIdentity, true
				},
			}).DialTLSContext,
		},
	}

	// Listen on port 4443 and start the HTTPS server.
	listener, err := net.Listen("tcp", "0.0.0.0:4443")
	if err != nil {
		log.Fatal(err)
	}
	defer listener.Close()
	go func() { log.Print(srv.ServeTLS(listener, "", "")) }()

	// Connect to our server, perform a TLS handshake,
	// verify that the server's public key corresponds
	// to the expected identity and print the response
	// to the terminal.
	resp, err := client.Get("https://127.0.0.1:4443")
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	// At this point we have established a secure TLS
	// connection without configuring certificates or
	// CAs.
	if _, err := io.Copy(os.Stdout, resp.Body); err != nil {
		log.Fatal(err)
	}
}

This example produces the following output:

Hello from server [ identity=h1:l4AoVm6xKAVGsfo8J_ttCOC6Odgq3GJLHg5NtAdOAr0 ] 
      to   client [ identity=h1:z5PgEqVUH_gwBt7oNKX9p9tchzL0i98U6O9C_aM4Y-k ]

Now, the server verifies that the public key presented by the client matches the expected client identity and the client verifies that the public key presented by the server matches the expected server identity.

FAQs

How can TLS without CA-signed certificates be secure?

TL;DR: Because peers get to know others public keys out-of-band and don't have to rely on a trusted third party for this.

A certificate is a cryptographically signed statement claiming that some public key P is associated with a named entity. For example, public key of example.com is P.

When a CA issues a certificate it verifies that whoever requests the certficate:

  • has the private key that corresponds to the public key in the certificate.
  • and controlls or is responsible for the name(s) in the certificate.

If you want get a CA-issued certificate for example.com with the public key P then you have to proof to the CA that you have the corresponding private key and that you are currently controlling the example.com domain.

Basically, all a CA is doing is creating (temporal) cryptographically signed statements about which public key belongs to which entity. However, if we know the public keys of our peers beforehand, we don't need a thrid party telling us.

In fact, pinning our peer's public key is strictly more secure than relying on CA-issued certificates because we no longer have to trust that the CA only ever issues "correct" certificates. A CA becoming malicious or getting compromised is no longer a risk in our threat model.

When do I need to renew/change my keys?

TL;DR: You don't have to. You can change them whenever you like and you should change them if they could be compromised.

Certificates expire and have to be renewed mainly because a certificate is a cryptographically signed statement associating a public key with named entities. For example, the public key of example.com is P.

Such statements are (ideally) true at some point in time but may not be forever. For example, the ownership of the example.com domain may change or the corresponding private key gets lost or compromised. A certificates can be revoked explicitly but the fact that it got revoked has to be recorded and distributed to everyone until it actually expires. If certificates didn’t expire, this information would need to be recorded forever.

Certificate renewal is primarily not about changing the key pair. It's perfectly fine to reuse the same key pair when renewing a certificate. However, most implementations generate a new key pair, as key generation is cheap.

With public key pinning, there’s no third party issuing signed statements that could expire or become invalid, so there’s nothing to renew. While key pairs can be changed, this can be done at any time by updating all peers that rely on the key pair or public key hash—such as through a configuration update.

If a private key is potentially compromised, it should be replaced, regardless of whether certificates or public key pinning is being used.

Can I use both, CA-issued certificates and public key pinning, at the same time?

TL;DR: Yes. For example with a separate tls.Config at the client and server

During the TLS handshake, the TLS client can indicate to which server it's trying to connect to via the server name indication (SNI) extension. A TLS server may be responsible for multiple domains. For example, foo.com as well as bar.com. A client trying to connect to this server has to include the domain name in the SNI such that the server knows whether it should present the certificate issued for foo.com or the one for bar.com.

This mechanism can also be used to distingush between handshakes expecting a certificate issued for some domain(s) and handshakes expecting a particular public key.

http.Server{
    TLSConfig: &tls.Config{
        GetConfigForClient: (&mtls.Server{
            // The server's private key. Clients need to know the corresponding
            // public key hash.
            PrivateKey: privKey,

            // Alternative TLS configuration used when clients don't provide a SNI matching the
            // server's public key hash.
            Config: &tls.Config{
                GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
                    // TODO: Return the certificiate matching the client hello.
                },
            },
        }).GetConfigForClient,
    },
}

With such a configuration, the server behaves like a "regular" TLS/HTTPS server serving signed certificates issued for domains unless a client specifically asks for the public key corresponding the server's PrivateKey.

Similarly, a client can only use key pinning for specific servers:

client := http.Client{
    Transport: &http.Transport{
        DialTLSContext: (&mtls.Client{
            // We only expect a particular public key (matching srvIdentity) if
            // we are connecting to the DB server. Otherwise, we use "regular"
            // X.509 certificate verification. See Config below.
            GetPeerIdentity: func(addr string) (mtls.Identity, bool) {
                if addr == DBServer {
                    return srvIdentity, true
                }
                return mtls.Identity{}, false
            },

            // The TLS config used for other TLS handshakes.
            Config: &tls.Config{},
        }).DialTLSContext,
    },
}

Under the hood, the client sends the public key hash as SNI whenever it expects a particular public key from the server and the server only response with its public key when it receives a SNI containing its public key hash. For example:

SNI=h1:l4AoVm6xKAVGsfo8J_ttCOC6Odgq3GJLHg5NtAdOAr0

This has the nice property that clients cannot detect whether a server would serve such a public key unless they know the hash of the public key. For such clients, the server behaves like any other TLS server.

Getting Started

go get aead.dev/mtls@latest

This downloads the mtls module. It has no dependencies.

Add the aead.dev/mtls module to your go.mod file. The documentation contains examples on how to configure clients and servers.

Documentation

Overview

Example

Shows how to send a GET request from a client to server over a mTLS connection. The client and server verify the identity of their peers.

package main

import (
	"crypto/tls"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"net/http/httptest"
	"strings"
	"time"

	"aead.dev/mtls"
)

func main() {
	// The client and server private key. Private keys should never be exposed!
	const (
		ServerPrivateKey = "k2:RsxEXi8ebLIWI8BI9MJHGBqKa1keq67Ds8hrgetZV1M" // Only known to the server
		ClientPrivateKey = "k2:RtSJhCLcHMAcMlIynDayappxUNl0iSQtggHKNCUXrdQ" // Only known to the client
	)

	serverKey, err := mtls.ParsePrivateKey(ServerPrivateKey)
	if err != nil {
		log.Fatal(err)
	}
	clientKey, err := mtls.ParsePrivateKey(ClientPrivateKey)
	if err != nil {
		log.Fatal(err)
	}

	// The public key identites to the corresponding private keys.
	// Identities aren't secrets and you have to have the identity
	// of your connection peer to authenticate and verify the connection.
	var (
		ServerIdentity = serverKey.Identity() // This must be known by the client to verify the server
		ClientIdentity = clientKey.Identity() // This must be known by the server to verify the client
	)

	// Create a simple HTTPS server that computes the peer identity from the TLS handshake information
	// and responds with: "Hello <IDENTITY>"
	server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		id, err := mtls.PeerIdentity(r.TLS)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		w.Write([]byte("Hello " + id.String()))
	}))

	// Configure and start the HTTPS server.
	server.TLS = &tls.Config{
		GetConfigForClient: (&mtls.Server{
			PrivateKey:     serverKey,
			PeerIdentities: []mtls.Identity{ClientIdentity},
		}).GetConfigForClient,
	}
	server.StartTLS()

	// Create a new client that authenticates to the HTTPS server with its private/public key pair.
	// Note that this a new client, not server.Client(), that does not trust the server certificate
	// generated by the httptest.Server automatically.
	client := http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			DialTLSContext: (&mtls.Client{
				PrivateKey:      clientKey, // Set a private key to auth. to the server
				GetPeerIdentity: func(string) (mtls.Identity, bool) { return ServerIdentity, true },
				Dialer:          net.Dialer{Timeout: 10 * time.Second},
			}).DialTLSContext,
			ForceAttemptHTTP2:     true,
			MaxIdleConns:          100,
			IdleConnTimeout:       90 * time.Second,
			ExpectContinueTimeout: 1 * time.Second,
		},
	}

	// Send a simple GET request to the server and print the response to stdout.
	// Here, the client verifies the server's identity and the server verifies the
	// client's identity. Hence, mutual TLS authentication.
	resp, err := client.Get(server.URL)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	var buf strings.Builder
	if _, err := io.Copy(&buf, resp.Body); err != nil {
		log.Fatal(err)
	}
	fmt.Println(buf.String())
}
Output:
Hello h1:OyfAzRVITGK2QUjHrYg0IC7y5hVjt93FZVucAgSuPeE

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	// PrivateKey is the private key used to authenticate
	// to the mTLS server by sending the corresponding
	// public key during the TLS handshake. If not set,
	// then the client does not authenticate itself.
	//
	// The server has to know the corresponding public key
	// identity to verify the client.
	PrivateKey Signer

	// PeerIdentities contains a static mapping from network
	// addresses to identities. When establishing a connection
	// to one of the addresses, the client performs a mTLS handshake
	// expecting a public key that matches the identity assigned
	// to the address.
	//
	// For mapping identities to network address dynamicially set
	// GetPeerIdentity.
	PeerIdentities map[string]Identity

	// GetPeerIdentity is called whenever the client establishes a
	// network connection. The addr is the server's network address
	// as passed to [crypto/tls.Dial] - usually host:port. If it returns
	// true, the client performs a mTLS handshake expecting a public key
	// that matches the returned identity.
	GetPeerIdentity func(addr string) (Identity, bool)

	// Config is the TLS config used when connecting to other TLS servers
	// not within PeerIdentities or for which GetPeerIdentity returns false.
	Config *tls.Config

	// MinVersion contains the minimum TLS version that is acceptable for
	// mTLS connections. For specifying the minimum TLS versions for regular
	// TLS connections use Config.MinVersion.
	//
	// By default, TLS 1.2 is currently used as the minimum. TLS 1.0 is the
	// minimum supported by this package.
	MinVersion uint16

	// MaxVersion contains the maximum TLS version that is acceptable for
	// mTLS connections. For specifying the maximum TLS versions for regular
	// TLS connections use Config.MaxVersion.
	MaxVersion uint16

	// NextProtos is a list of supported application level protocols for mTLS
	// connections, in order of preference. If both peers support ALPN, the
	// selected protocol will be one from this list, and the connection will
	// fail if there is no mutually supported protocol. If NextProtos is empty
	// or the peer doesn't support ALPN, the connection will succeed and
	// ConnectionState.NegotiatedProtocol will be empty.
	//
	// For specifying the supported application level protocols for regular
	// TLS connections use Config.NexProtos.
	NextProtos []string

	// CipherSuites is a list of enabled TLS 1.0–1.2 cipher suites for mTLS
	// connections. The order of the list is ignored. Note that TLS 1.3
	// ciphersuites are not configurable.
	//
	// If CipherSuites is nil, a safe default list is used. The default cipher
	// suites might change over time.
	//
	// For specifying the enabled TLS 1.0-1.2 cipher suites for regular
	// TLS connections use Config.CipherSuites.
	CipherSuites []uint16

	// Dialer contains options for connecting to a network address.
	Dialer net.Dialer
	// contains filtered or unexported fields
}

A Client structure is used to configure a mTLS client.

It establishes a mTLS connection for any server within PeerIdentities or for which GetPeerIdentity returns true. Without a private key, a client does not authenticate itself to the mTLS server(s).

Once a client has been passed to a TLS function, it must no longer be modified.

Example

ExampleClient shows how to configure a simple HTTP client verifying a pinned public key for the server running at "10.1.2.3:443". For all other servers it uses regular TLS certificate verification. The client does not authenticate itself to the server.

Hence, the server running at "10.1.2.3:443" does not have to have a certificate issued by a CA trusted by the client.

package main

import (
	"crypto/tls"
	"log"
	"net"
	"net/http"
	"time"

	"aead.dev/mtls"
)

func main() {
	// The server's identity - required to verify mTLS handshakes with the server.
	const Identity = "h1:dKNb3WhlZ1dxE6VSI1mH7FAd2EPTijEU37RHvkhuT7Y"

	srvIdentity, err := mtls.ParseIdentity(Identity)
	if err != nil {
		log.Fatalf("failed to parse identity: %v", err)
	}

	clientConf := mtls.Client{
		// Map a set of server addresses to identites. The client expects
		// that the server with address X has a public identity Y.
		PeerIdentities: map[string]mtls.Identity{
			"10.1.2.3:443": srvIdentity,
		},
		GetPeerIdentity: nil, // If the mapping isn't static, consider this callback instead

		// Optionally, configure mTLS connections, like minimal supported
		// version or HTTP/2 support.
		MinVersion: tls.VersionTLS13,
		NextProtos: []string{"h2", "http/1.1"},

		// Optionally, provide a custom net.Dialer to customize network timeouts, keepalives, etc.
		Dialer: net.Dialer{
			Timeout: 10 * time.Second,
		},

		// Optionally, if the client should also verify certificates from other TLS servers,
		// e.g. some external systems that serve regular certificates issued by trusted CAs,
		// provide the regular TLS client config here.
		Config: &tls.Config{
			RootCAs: nil, // The set of root CA certificates the client trusts.
		},
	}
	client := http.Client{
		Transport: &http.Transport{
			Proxy:                 http.ProxyFromEnvironment,
			DialTLSContext:        clientConf.DialTLSContext, // Use the mTLS config
			ForceAttemptHTTP2:     true,
			MaxIdleConns:          100,
			IdleConnTimeout:       90 * time.Second,
			ExpectContinueTimeout: 1 * time.Second,
		},
	}
	_ = client

}

func (*Client) DialTLSContext

func (c *Client) DialTLSContext(ctx context.Context, network, addr string) (net.Conn, error)

DialTLSContext connects to the given network address and initiates a TLS handshake, returning the resulting TLS connection.

The provided Context must be non-nil. If the context expires before the connection is complete, an error is returned. Once successfully connected, any expiration of the context will not affect the connection.

The returned Conn, if any, will always be of type *crypto/tls.Conn.

type Identity

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

An Identity is a cryptographic checksum over some data, usually a public key. Two identities, A and B, are equal when A == B is true.

Its zero value is a valid identity but won't match any public key.

func CertificateIdentity added in v0.2.0

func CertificateIdentity(cert *x509.Certificate) Identity

CertificateIdentity returns the identity of the certificate's public key.

func ParseIdentity

func ParseIdentity(s string) (Identity, error)

ParseIdentity parses s and returns it as Identity.

If s is the empty string, it returns the Identity zero value - for which IsZero returns true - and no error.

func PeerIdentity

func PeerIdentity(state *tls.ConnectionState) (Identity, error)

PeerIdentity returns the Identity of the peer's public key or an error if it did not provide any certificate during the TLS handshake.

A TLS client should always receive a certificate containing the server's public key.

A TLS server has to request a certificate and the client might not have one or choose to not send it.

func (Identity) IsZero added in v0.2.0

func (i Identity) IsZero() bool

IsZero returns true if i is the Identity zero value.

func (Identity) MarshalBinary

func (i Identity) MarshalBinary() ([]byte, error)

MarshalBinary returns a binary representation of the identity.

func (Identity) MarshalText

func (i Identity) MarshalText() ([]byte, error)

MarshalText returns a textual representation of the identity.

func (Identity) String

func (i Identity) String() string

String returns a string representation of the identity.

In contrast to Identity.MarshalText, it returns the empty string if i is the zero value.

func (*Identity) UnmarshalBinary

func (i *Identity) UnmarshalBinary(b []byte) error

UnmarshalBinary parses the binary representation of an identity.

func (*Identity) UnmarshalText

func (i *Identity) UnmarshalText(text []byte) error

UnmarshalText parses the textual representation of an identity.

type IdentityError

type IdentityError struct {
	PeerIdentity Identity // Identity received from the connection peer
	Identity     Identity // Expected peer identity
}

IdentityError is an error that occurs when a peer does not provide a certificate during the TLS handshake or sends a public key that doesn't match an expected identity value.

func (IdentityError) Error

func (e IdentityError) Error() string

Error returns the IdentityError's error message.

type PrivateKey

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

PrivateKey is a Signer with a directly accessible private key.

func GenerateKey added in v0.4.0

func GenerateKey() (*PrivateKey, error)

GenerateKey generates a new PrivateKey.

Currently, it returns an Ed25519 private key. However, this might change and callers must not rely on the concrete key type.

Example

ExampleGenerateKey shows to generate a PrivateKey.

package main

import (
	"log"

	"aead.dev/mtls"
)

func main() {
	priv, err := mtls.GenerateKey()
	if err != nil {
		log.Fatalf("failed to generate key: %v", err)
	}

	if priv.Identity().IsZero() {
		log.Fatal("generated key has an empty identity")
	}

}

func NewPrivateKey added in v0.4.0

func NewPrivateKey(priv crypto.PrivateKey) (*PrivateKey, error)

NewPrivateKey returns a new PrivateKey wrapping the given private key.

Currently supported types are ed25519.PrivateKey, *ecdsa.PrivateKey and *rsa.PrivateKey.

func ParsePrivateKey

func ParsePrivateKey(s string) (*PrivateKey, error)

ParsePrivateKey parses s and returns it as PrivateKey.

func (*PrivateKey) Identity

func (pk *PrivateKey) Identity() Identity

Identity returns the identity of the private key's public key.

func (*PrivateKey) MarshalText added in v0.4.0

func (pk *PrivateKey) MarshalText() ([]byte, error)

MarshalText returns the key's textual representation.

func (*PrivateKey) Private

func (pk *PrivateKey) Private() crypto.PrivateKey

Private returns the underlying private key. Either a ed25519.PrivateKey, *ecdsa.PrivateKey or *rsa.PrivateKey.

func (*PrivateKey) Public

func (pk *PrivateKey) Public() crypto.PublicKey

Public returns the public key corresponding to the private key.

func (*PrivateKey) Sign added in v0.4.0

func (pk *PrivateKey) Sign(random io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error)

Sign signs message with the private key. See crypto.Signer for algorithm-specific requirements on message and opts.

func (*PrivateKey) String

func (pk *PrivateKey) String() string

String returns the key's string representation.

Its output is equivalent to PrivateKey.MarshalText.

func (*PrivateKey) UnmarshalText added in v0.4.0

func (pk *PrivateKey) UnmarshalText(text []byte) error

UnmarshalText parses a private key's textual representation. See PrivateKey.MarshalText for the accepted formats.

type Server

type Server struct {
	// PrivateKey is the server's private key used to authenticate
	// to mTLS clients by sending the corresponding public key during
	// the TLS handshake. If not set, the server will use Config for
	// all incoming TLS connections.
	//
	// Clients that try to establish a mTLS connection should send
	// the PrivateKey's public key identity as server name (SNI).
	//
	// Clients have to know the corresponding public key identity to
	// verify the server.
	PrivateKey Signer

	// PeerIdentities contains a static list of accepted peers. If set,
	// the server only accepts an incoming TLS connection from peers
	// that present one of the listed public keys during the TLS handshake.
	//
	// The server requests a certificate from the clinet only if PeerIdentities
	// is not nil, or VerifyPeerIdentity is set.
	PeerIdentities []Identity

	// VerifyPeerIdentity verifies the connection peer's identity during
	// the TLS handshake. If it returns an error, the handshake is aborted.
	//
	// The server requests a certificate from the clinet only if PeerIdentities
	// is not nil, or VerifyPeerIdentity is set.
	VerifyPeerIdentity func(Identity) error

	// Config is the TLS config used for regular TLS clients that don't send
	// the PrivateKey's public key identity as SNI.
	Config *tls.Config

	// MinVersion contains the minimum TLS version that is acceptable for
	// mTLS connections. For specifying the minimum TLS versions for regular
	// TLS connections use Config.MinVersion.
	//
	// By default, TLS 1.2 is currently used as the minimum. TLS 1.0 is the
	// minimum supported by this package.
	MinVersion uint16

	// MaxVersion contains the maximum TLS version that is acceptable for
	// mTLS connections. For specifying the maximum TLS versions for regular
	// TLS connections use Config.MaxVersion.
	MaxVersion uint16

	// NextProtos is a list of supported application level protocols for mTLS
	// connections, in order of preference. If both peers support ALPN, the
	// selected protocol will be one from this list, and the connection will
	// fail if there is no mutually supported protocol. If NextProtos is empty
	// or the peer doesn't support ALPN, the connection will succeed and
	// ConnectionState.NegotiatedProtocol will be empty.
	//
	// For specifying the supported application level protocols for regular
	// TLS connections use Config.NexProtos.
	NextProtos []string

	// CipherSuites is a list of enabled TLS 1.0–1.2 cipher suites for mTLS
	// connections. The order of the list is ignored. Note that TLS 1.3
	// ciphersuites are not configurable.
	//
	// If CipherSuites is nil, a safe default list is used. The default cipher
	// suites might change over time.
	//
	// For specifying the enabled TLS 1.0-1.2 cipher suites for regular
	// TLS connections use Config.CipherSuites.
	CipherSuites []uint16
	// contains filtered or unexported fields
}

A Server structure is used to configure a mTLS server.

Example

ExampleServer shows how to configure a simple HTTP server that serves a certificate with a pinned public key. All clients that know the server's identity are able to verify the server.

The server does not authenticate clients.

package main

import (
	"crypto/tls"
	"log"
	"net/http"

	"aead.dev/mtls"
)

func main() {
	const PrivateKey = "k2:IqYLb-5B3YvUR28WcJoGo3zhWa5GnrcJ9knLEWCHsRU"

	priv, err := mtls.ParsePrivateKey(PrivateKey)
	if err != nil {
		log.Fatalf("failed to parse private key: %v", err)
	}

	srvConf := mtls.Server{
		PrivateKey: priv, // Use the private key for mTLS connections

		// Optionally, configure mTLS connections, like minimal supported
		// version or HTTP/2 support.
		MinVersion: tls.VersionTLS13,
		NextProtos: []string{"h2", "http/1.1"},

		// Optionally, require and verify client public keys. Provide either
		// a static list of client identities or a callback to verify the
		// peer identity of an incoming mTLS connection.
		PeerIdentities:     []mtls.Identity{},
		VerifyPeerIdentity: func(peer mtls.Identity) error { return nil },

		// Optionally, set a public-facing TLS config for regular (e.g. non-mTLS) clients.
		Config: &tls.Config{
			Certificates:   nil, // Provide your public-facing certificates here
			GetCertificate: nil, // or here, if any
		},
	}
	_ = http.Server{
		Addr: ":443",
		TLSConfig: &tls.Config{
			GetConfigForClient: srvConf.GetConfigForClient,
		},
	}
}

func (*Server) GetConfigForClient

func (s *Server) GetConfigForClient(hello *tls.ClientHelloInfo) (*tls.Config, error)

GetConfigForClient returns a TLS config for a TLS client hello message.

It returns a configuration for mutual TLS when a private key is set and the client sends the corresponding public key identity via SNI. Otherwise, it returns Server.Config.

type Signer added in v0.4.0

type Signer interface {
	crypto.Signer

	// Identity returns a stable identifier for the Signer's public key.
	Identity() Identity
}

Signer extends crypto.Signer with an Identity method to identify the signing key.

Signer is useful for private keys that are not directly accessible. For example, keys in hardware modules.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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