Skip to content

Commit ea16088

Browse files
authored
Update README.md
1 parent f440da0 commit ea16088

1 file changed

Lines changed: 71 additions & 0 deletions

File tree

README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,77 @@ If a private key is potentially compromised, it should be replaced, regardless o
292292
key pinning is being used.
293293
</details>
294294

295+
<details>
296+
<summary>Can I use both, CA-issued certificates and public key pinning, at the same time?</summary>
297+
298+
**TL;DR: Yes. For example with a separate `tls.Config` at the [client](https://pkg.go.dev/aead.dev/mtls#Client.Config) and [server](https://pkg.go.dev/aead.dev/mtls#Server.Config)**
299+
300+
During the TLS handshake, the TLS client can indicate to which server it's trying to connect to via
301+
the server name indication ([SNI](https://www.rfc-editor.org/rfc/rfc3546#section-3.1)) extension.
302+
A TLS server may be responsible for multiple domains. For example, `foo.com` as well as `bar.com`.
303+
A client trying to connect to this server has to include the domain name in the SNI such that the
304+
server knows whether it should present the certificate issued for `foo.com` or the one for `bar.com`.
305+
306+
This mechanism can also be used to distingush between handshakes expecting a certificate issued for some
307+
domain(s) and handshakes expecting a particular public key.
308+
309+
```go
310+
http.Server{
311+
TLSConfig: &tls.Config{
312+
GetConfigForClient: (&mtls.Server{
313+
// The server's private key used. Clients need to know the corresponding
314+
// public key hash.
315+
PrivateKey: privKey,
316+
317+
// Alternative TLS configuration used when clients don't provide a SNI matching the
318+
// server's public key hash.
319+
Config: &tls.Config{
320+
GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
321+
// TODO: Return the certificiate matching the client hello.
322+
},
323+
},
324+
}).GetConfigForClient,
325+
},
326+
}
327+
```
328+
329+
With such a configuration, the server behaves like a "regular" TLS/HTTPS server serving signed certificates
330+
issued for domains unless a client specifically asks for the public key corresponding the server's `PrivateKey`.
331+
332+
Similarly, a client can only use key pinning for specific servers:
333+
334+
```go
335+
client := http.Client{
336+
Transport: &http.Transport{
337+
DialTLSContext: (&mtls.Client{
338+
// We only expect a particular public key (matching srvIdentity) if
339+
// we are connecting to the DB server. Otherwise, we use "regular"
340+
// X.509 certificate verification. See Config below.
341+
GetPeerIdentity: func(addr string) (mtls.Identity, bool) {
342+
if addr == DBServer {
343+
return srvIdentity, true
344+
}
345+
return mtls.Identity{}, false
346+
},
347+
348+
// The TLS config used for other TLS handshakes.
349+
Config: &tls.Config{},
350+
}).DialTLSContext,
351+
},
352+
}
353+
```
354+
355+
Under the hood, the client sends the public key hash as SNI whenever it expects a particular public key
356+
from the server and the server only response with its public key when it receives a SNI containing its
357+
public key hash. For example:
358+
```
359+
SNI=h1:l4AoVm6xKAVGsfo8J_ttCOC6Odgq3GJLHg5NtAdOAr0
360+
```
361+
362+
This has the nice property that clients cannot detect whether a server would serve a different public key
363+
unless they know the hash of the public key. For such clients, the server behaves like any other TLS server.
364+
</details>
365+
295366
## Getting Started
296367

297368
```sh

0 commit comments

Comments
 (0)