Skip to content

Commit ffd178d

Browse files
authored
Update README.md
1 parent 8829345 commit ffd178d

1 file changed

Lines changed: 216 additions & 8 deletions

File tree

README.md

Lines changed: 216 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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

2525
That'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

35250
Add the `aead.dev/mtls` module to your `go.mod` file.
36251
The 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

Comments
 (0)