@@ -11,7 +11,7 @@ Usually TLS/HTTPS relies on certificate authorities (CAs) to establish trust. Th
1111- Managing certificate chains and trust stores
1212- Trusting any certificate signed by a trusted CA
1313
14- For services that communicate with known peers, for example a web and DB server, this is overkill.
14+ For services that communicate with known peers this is overkill.
1515
1616** The Solution**
1717
@@ -24,6 +24,221 @@ h1:2eYrKRe4K9Xf_HjOhdJjNPuH5P8sLN9XNgdgZKfqt1A
2424
2525That's it. No certificates to issue, no chains to verify, no CAs to manage.
2626
27+ ## How It Works
28+
29+ First, let's take a look at a client connecting to an HTTPS server and verifying its public key:
30+
31+ <details >
32+
33+ <summary ><b >Show Example Code</b ></summary >
34+
35+ ``` go
36+ package main
37+
38+ import (
39+ " crypto/tls"
40+ " fmt"
41+ " io"
42+ " log"
43+ " net"
44+ " net/http"
45+ " os"
46+
47+ " aead.dev/mtls"
48+ )
49+
50+ // In this example, we configure, start and establish a
51+ // secure TLS connection to a HTTPS server without
52+ // configuring certificates or CAs.
53+ func main () {
54+ // The server's private key
55+ const PrivateKey = " k1:xZnpcYtPdVMNLBBRaUO5HPEoK_jVrcc3MWR8BshkjJw"
56+
57+ privKey , err := mtls.ParsePrivateKey (PrivateKey)
58+ if err != nil {
59+ log.Fatal (err)
60+ }
61+
62+ // Our 'Hello World' server using a minimal TLS configuration.
63+ srv := http.Server {
64+ Handler: http.HandlerFunc (func (w http.ResponseWriter , r *http.Request ) {
65+ fmt.Printf (" Hello from server [ identity=%s ]\n " , r.TLS .ServerName )
66+ }),
67+
68+ TLSConfig: &tls.Config {
69+ GetConfigForClient: (&mtls.Server {
70+ PrivateKey: privKey,
71+ }).GetConfigForClient ,
72+ },
73+ }
74+
75+ // Our client needs to know the server's identity in order
76+ // to verify the public key presented by the server during
77+ // TLS handshakes.
78+ identity := privKey.Identity ()
79+ client := http.Client {
80+ Transport: &http.Transport {
81+ DialTLSContext: (&mtls.Client {
82+ // Here we define which identity to expect when connecting
83+ // to a server.
84+ GetPeerIdentity: func (_ string ) (mtls.Identity , bool ) {
85+ return identity, true
86+ },
87+ }).DialTLSContext ,
88+ },
89+ }
90+
91+ // Listen on port 4443 and start the HTTPS server.
92+ listener , err := net.Listen (" tcp" , " 0.0.0.0:4443" )
93+ if err != nil {
94+ log.Fatal (err)
95+ }
96+ defer listener.Close ()
97+ go func () { log.Print (srv.ServeTLS (listener, " " , " " )) }()
98+
99+ // Connect to our server, perform a TLS handshake,
100+ // verify that the server's public key corresponds
101+ // to the expected identity and print the response
102+ // to the terminal.
103+ resp , err := client.Get (" https://127.0.0.1:4443" )
104+ if err != nil {
105+ log.Fatal (err)
106+ }
107+ defer resp.Body .Close ()
108+
109+ // At this point we have established a secure TLS
110+ // connection without configuring certificates or
111+ // CAs.
112+ if _ , err := io.Copy (os.Stdout , resp.Body ); err != nil {
113+ log.Fatal (err)
114+ }
115+ }
116+ ```
117+ </details >
118+
119+ [ This example] ( https://go.dev/play/p/0bLM3BfSvu- ) produces the following output:
120+ ```
121+ Hello from server [ identity=h1:l4AoVm6xKAVGsfo8J_ttCOC6Odgq3GJLHg5NtAdOAr0 ]
122+ ```
123+
124+ Instead of verifying that the server presents a certificate issued by a trusted CA, the client
125+ verifies that the server presents a public key matching an expected identity (SHA-256 hash).
126+ However, the client does not authenticate itself to the server.
127+
128+ We can modify our initial example as following to mutually authenticate during the TLS handshake:
129+
130+ <details >
131+
132+ <summary ><b >Show Example Code</b ></summary >
133+
134+ ``` go
135+ package main
136+
137+ import (
138+ " crypto/tls"
139+ " fmt"
140+ " io"
141+ " log"
142+ " net"
143+ " net/http"
144+ " os"
145+
146+ " aead.dev/mtls"
147+ )
148+
149+ // In this example, we configure, start and establish a
150+ // secure mutual TLS connection to a HTTPS server without
151+ // configuring certificates or CAs.
152+ func main () {
153+ const PrivateKeyServer = " k1:xZnpcYtPdVMNLBBRaUO5HPEoK_jVrcc3MWR8BshkjJw"
154+ const PrivateKeyClient = " k1:uNTwsVpygybzFuHPYE04Luw2te-D2Efr5xnxycGpt4c"
155+
156+ srvKey , err := mtls.ParsePrivateKey (PrivateKeyServer)
157+ if err != nil {
158+ log.Fatal (err)
159+ }
160+ clientKey , err := mtls.ParsePrivateKey (PrivateKeyClient)
161+ if err != nil {
162+ log.Fatal (err)
163+ }
164+
165+ srvIdentity := srvKey.Identity ()
166+ clientIdentity := clientKey.Identity ()
167+
168+ // Our 'Hello World' server using a minimal TLS configuration.
169+ // Our server needs to know the client's identity in order to
170+ // verify the public key presented by the client during TLS
171+ // handshakes.
172+ srv := http.Server {
173+ Handler: http.HandlerFunc (func (w http.ResponseWriter , r *http.Request ) {
174+ fmt.Printf (" Hello from server [ identity=%s ] \n to client [ identity %s ]\n " ,
175+ r.TLS .ServerName ,
176+ mtls.CertificateIdentity (r.TLS .PeerCertificates [0 ]),
177+ )
178+ }),
179+
180+ TLSConfig: &tls.Config {
181+ GetConfigForClient: (&mtls.Server {
182+ PrivateKey: srvKey,
183+ PeerIdentities: []mtls.Identity {clientIdentity},
184+ }).GetConfigForClient ,
185+ },
186+ }
187+
188+ // Our client needs to know the server's identity in order
189+ // to verify the public key presented by the server during
190+ // TLS handshakes.
191+ client := http.Client {
192+ Transport: &http.Transport {
193+ DialTLSContext: (&mtls.Client {
194+ PrivateKey: clientKey,
195+ // Here we define which identity to expect when connecting
196+ // to a server.
197+ GetPeerIdentity: func (_ string ) (mtls.Identity , bool ) {
198+ return srvIdentity, true
199+ },
200+ }).DialTLSContext ,
201+ },
202+ }
203+
204+ // Listen on port 4443 and start the HTTPS server.
205+ listener , err := net.Listen (" tcp" , " 0.0.0.0:4443" )
206+ if err != nil {
207+ log.Fatal (err)
208+ }
209+ defer listener.Close ()
210+ go func () { log.Print (srv.ServeTLS (listener, " " , " " )) }()
211+
212+ // Connect to our server, perform a TLS handshake,
213+ // verify that the server's public key corresponds
214+ // to the expected identity and print the response
215+ // to the terminal.
216+ resp , err := client.Get (" https://127.0.0.1:4443" )
217+ if err != nil {
218+ log.Fatal (err)
219+ }
220+ defer resp.Body .Close ()
221+
222+ // At this point we have established a secure TLS
223+ // connection without configuring certificates or
224+ // CAs.
225+ if _ , err := io.Copy (os.Stdout , resp.Body ); err != nil {
226+ log.Fatal (err)
227+ }
228+ }
229+ ```
230+ </details >
231+
232+ [ This example] ( https://go.dev/play/p/3ukumzgZNjC ) produces the following output:
233+ ```
234+ Hello from server [ identity=h1:l4AoVm6xKAVGsfo8J_ttCOC6Odgq3GJLHg5NtAdOAr0 ]
235+ to client [ identity h1:z5PgEqVUH_gwBt7oNKX9p9tchzL0i98U6O9C_aM4Y-k ]
236+ ```
237+
238+ Now, the server verifies that the public key presented by the client matches the
239+ expected client identity and the client verifies that the public key presented
240+ by the server matches the expected server identity.
241+
27242## Getting Started
28243
29244``` sh
@@ -34,10 +249,3 @@ This downloads the `mtls` module. It has no dependencies.
34249
35250Add the ` aead.dev/mtls ` module to your ` go.mod ` file.
36251The documentation contains [ examples] ( https://pkg.go.dev/aead.dev/mtls#example-package ) on how to configure clients and servers.
37-
38- ## How It Works
39-
40- 1 . Generate a private/public key pair.
41- 2 . Peers exchange identities (SHA-256 hash of public key) out-of-band.
42- For example, as part of their configuration.
43- 3 . During the TLS handshake, one or both sides verify that the other's public key matches the expected identity.
0 commit comments