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 ¶
- type Client
- type Identity
- type IdentityError
- type PrivateKey
- func (pk *PrivateKey) Identity() Identity
- func (pk *PrivateKey) MarshalText() ([]byte, error)
- func (pk *PrivateKey) Private() crypto.PrivateKey
- func (pk *PrivateKey) Public() crypto.PublicKey
- func (pk *PrivateKey) Sign(random io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error)
- func (pk *PrivateKey) String() string
- func (pk *PrivateKey) UnmarshalText(text []byte) error
- type Server
- type Signer
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
}
Output:
func (*Client) DialTLSContext ¶
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 ¶
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) MarshalBinary ¶
MarshalBinary returns a binary representation of the identity.
func (Identity) MarshalText ¶
MarshalText returns a textual representation of the identity.
func (Identity) 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 ¶
UnmarshalBinary parses the binary representation of an identity.
func (*Identity) UnmarshalText ¶
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")
}
}
Output:
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,
},
}
}
Output:
func (*Server) GetConfigForClient ¶
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.