Skip to content
Snippets Groups Projects
Commit 8ad50829 authored by David Fifield's avatar David Fifield
Browse files

QUIC TLS certs and keys.

This adds --quic-tls-cert and --quic-tls-key options to the server
(separate from --cert and --key); and a quic-tls-pubkey SOCKS arg (with
equivalent --quic-tls-pubkey command-line option) to the client.
parent a21a08eb
Branches
Tags
No related merge requests found
......@@ -43,6 +43,20 @@ The possible SOCKS args are:
of **url** in the DNS request and TLS SNI field.
The URL's true domain name will still appear in the Host header
of HTTP requests.
**quic-tls-pubkey**=__PUBKEYHASH__::
+
--
Server public key hashes to accept for the inner QUIC TLS layer.
These have nothing to do with the outer HTTPS layer, which verifies
certificates in the usual PKI way.
The format of __PUBKEYHASH__ is a base64-encoded SHA-256 hash of the Subject Public Key Info, as in HPKP.
This argument may be used more than once;
all public key hashes provided are considered good to verify server certificates.
To generate a public key hash from a certificate file,
----
$ openssl x509 -in quic.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
----
--
**utls**=__CLIENTHELLOID__::
+
--
......@@ -130,6 +144,11 @@ OPTIONS
**--log**=__FILENAME__::
Name of a file to write log messages to (default stderr).
**--quic-tls-pubkey**=__PUBKEYHASH__[,__PUBKEYHASH__]...::
Comma-separated list of server public key hashes to accept for the inner QUIC TLS layer.
The option may be given only once, but you can separate multiple hashes using commas.
Prefer using the **quic-tls-pubkey** SOCKS arg over using this command line option.
**--url**=__URL__::
URL to correspond with. Prefer using the **url** SOCKS arg
on a bridge line over using this command line option.
......
......
......@@ -28,24 +28,51 @@ up certificates:
* **--cert**=__FILENAME__ and **--key**=__FILENAME__ allow use to use
your own externally acquired certificate.
Besides the external HTTPS-layer TLS, you will need to configure certificates
for the internal QUIC TLS layer
using the **--quic-tls-cert** and **--quic-tls-key** options.
You cannot use an automatic Let's Encrypt certificate for this layer,
but you also do not have to get it signed by a CA
(you can use a self-signed certificate),
because the client will authenticate it by its public key.
To generate a certificate and private key for the QUIC layer:
----
$ openssl genpkey -algorithm ED25519 > quic.key
$ openssl req -new -key quic.key -x509 -days 1000 -nodes -out quic.crt
Country Name (2 letter code) [AU]:.
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:.
Organization Name (eg, company) [Internet Widgits Pty Ltd]:.
Organizational Unit Name (eg, section) []:.
Common Name (e.g. server FQDN or YOUR name) []:meek-quic
Email Address []:.
$ cat quic.key quic.crt > quic.pem
----
You can pass quic.pem to both the **--quic-tls-cert** and **--quic-tls-key** options.
To renew the certificate using the same key:
----
$ openssl req -new -key quic.pem -x509 -days 1000 -nodes -out quic.pem.new
$ mv quic.pem.new quic.pem
----
Configuration for meek-server usually appears in a torrc file. Here is a
sample configuration using automatic Let's Encrypt certificates:
----
ExtORPort auto
ServerTransportListenAddr 0.0.0.0:443
ServerTransportPlugin meek exec ./meek-server --acme-hostnames meek-server.example --log meek-server.log
ServerTransportPlugin meek exec ./meek-server --acme-hostnames meek-server.example --quic-tls-cert=quic.pem --quic-tls-key=quic.pem --log meek-server.log
----
Here is a sample configuration using externally acquired certificates:
----
ExtORPort auto
ServerTransportListenAddr meek 0.0.0.0:8443
ServerTransportPlugin meek exec ./meek-server 8443 --cert cert.pem --key key.pem --log meek-server.log
ServerTransportPlugin meek exec ./meek-server 8443 --cert cert.pem --key key.pem --quic-tls-cert=quic.pem --quic-tls-key=quic.pem --log meek-server.log
----
To listen on ports 80 and 443 without needed to run as root, on Linux,
you can use the `setcap` program, part of libcap2:
----
setcap 'cap_net_bind_service=+ep' /usr/local/bin/meek-server
$ setcap 'cap_net_bind_service=+ep' /usr/local/bin/meek-server
----
OPTIONS
......@@ -84,6 +111,19 @@ OPTIONS
**ServerTransportListenAddr** option in torrc, rather than use the
**--port** option.
**--quic-tls-cert**=__FILENAME__::
Name of a PEM-encoded TLS certificate for the inner QUIC TLS layer.
The certificate will be reloaded at runtime if the file changes.
The inner QUIC TLS layer is entirely independent of the outer HTTPS layer
that is configured using **--cert** and **--key**.
**--quic-tls-key**=__FILENAME__::
Name of a PEM-encoded TLS private key file for the inner QUIC TLS layer.
It may be the same file as **--quic-tls-cert**.
The private key will be reloaded at runtime if the file changes.
The inner QUIC TLS layer is entirely independent of the outer HTTPS layer
that is configured using **--cert** and **--key**.
**-h**, **--help**::
Display a help message and exit.
......
......
......@@ -38,6 +38,7 @@ import (
"net/url"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
......@@ -79,6 +80,7 @@ var options struct {
ProxyURL *url.URL
UseHelper bool
UTLSName string
QUICTLSPubkeyHashes []string
}
// urlAddr is a net.Addr representation of a url.URL.
......@@ -162,6 +164,13 @@ func handleSOCKS(conn *pt.SocksConn) error {
utlsOK = true
}
var pubkeyHashes []string
if arg, ok := conn.Req.Args["quic-tls-pubkey"]; ok {
pubkeyHashes = arg
} else {
pubkeyHashes = options.QUICTLSPubkeyHashes
}
// First we check --helper: if it was specified, then we always use the
// helper, and utls is disallowed. Otherwise, we use utls if requested;
// or else fall back to native net/http.
......@@ -184,8 +193,15 @@ func handleSOCKS(conn *pt.SocksConn) error {
pconn := NewPollingPacketConn(urlAddr{info.URL}, &info)
defer pconn.Close()
// The TLS configuration of the inner QUIC layer (this has nothing to do
// with the domain-fronted outer HTTPS layer).
tlsConfig := &tls.Config{
// We set InsecureSkipVerify and VerifyPeerCertificate so as to
// do our own certificate verification, using direct lookup
// against the quic-tls-pubkey hashes rather than signatures by
// root CAs.
InsecureSkipVerify: true,
VerifyPeerCertificate: makeVerifyPeerPublicKey(pubkeyHashes),
NextProtos: []string{quicNextProto},
}
quicConfig := &quic.Config{
......@@ -304,6 +320,7 @@ func checkProxyURL(u *url.URL) error {
func main() {
var helperAddr string
var logFilename string
var quicTLSPubkey string
var proxy string
var err error
......@@ -311,6 +328,7 @@ func main() {
flag.StringVar(&helperAddr, "helper", "", "address of HTTP helper (browser extension)")
flag.StringVar(&logFilename, "log", "", "name of log file")
flag.StringVar(&proxy, "proxy", "", "proxy URL")
flag.StringVar(&quicTLSPubkey, "quic-tls-pubkey", "", "server public key hashes for QUIC TLS")
flag.StringVar(&options.URL, "url", "", "URL to request if no url= SOCKS arg")
flag.StringVar(&options.UTLSName, "utls", "", "uTLS Client Hello ID")
flag.Parse()
......@@ -349,6 +367,8 @@ func main() {
}
}
options.QUICTLSPubkeyHashes = strings.Split(quicTLSPubkey, ",")
// Disable the default ProxyFromEnvironment setting.
// httpRoundTripper.Proxy is overridden below if options.ProxyURL is
// set.
......
......
package main
import (
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"errors"
)
// makeVerifyPeerPublicKey returns a function, compatible with the tls.Config
// CertifyPeerCertificate, that only checks that the leaf certificate has one of
// a set of set of public keys. Public keys are identified by base64-encoded
// SHA-256 hashes of the Subject Public Key Info, as in HPKP (RFC 7469).
func makeVerifyPeerPublicKey(pubkeyHashes []string) func([][]byte, [][]*x509.Certificate) error {
// Compare to https://github.com/golang/go/issues/31792.
return func(certificates [][]byte, _ [][]*x509.Certificate) error {
if len(certificates) < 1 {
return errors.New("no certificates presented")
}
leaf, err := x509.ParseCertificate(certificates[0])
if err != nil {
return err
}
rawHash := sha256.Sum256(leaf.RawSubjectPublicKeyInfo)
hash := base64.StdEncoding.EncodeToString(rawHash[:])
for _, allowed := range pubkeyHashes {
if hash == allowed {
return nil
}
}
return errors.New("unexpected public key hash " + hash)
}
}
......@@ -87,11 +87,14 @@ type State struct {
conn *QueuePacketConn
}
func NewState(localAddr net.Addr, cert *tls.Certificate) (*State, error) {
func NewState(
localAddr net.Addr,
getCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error),
) (*State, error) {
pconn := NewQueuePacketConn(localAddr, quicIdleTimeout)
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{*cert},
GetCertificate: getCertificate,
NextProtos: []string{quicNextProto},
}
quicConfig := &quic.Config{
......@@ -290,6 +293,7 @@ func handleStream(ctx context.Context, stream quic.Stream) error {
func initServer(addr *net.TCPAddr,
getCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error),
quicTLSGetCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error),
listenAndServe func(*http.Server, chan<- error)) (*http.Server, error) {
// We're not capable of listening on port 0 (i.e., an ephemeral port
// unknown in advance). The reason is that while the net/http package
......@@ -301,11 +305,7 @@ func initServer(addr *net.TCPAddr,
return nil, fmt.Errorf("cannot listen on port %d; configure a port using ServerTransportListenAddr", addr.Port)
}
cert, err := generateTLSCertificate()
if err != nil {
return nil, err
}
state, err := NewState(addr, cert)
state, err := NewState(addr, quicTLSGetCertificate)
if err != nil {
return nil, err
}
......@@ -348,8 +348,11 @@ func initServer(addr *net.TCPAddr,
return server, err
}
func startServer(addr *net.TCPAddr) (*http.Server, error) {
return initServer(addr, nil, func(server *http.Server, errChan chan<- error) {
func startServer(
addr *net.TCPAddr,
quicTLSGetCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error),
) (*http.Server, error) {
return initServer(addr, nil, quicTLSGetCertificate, func(server *http.Server, errChan chan<- error) {
log.Printf("listening with plain HTTP on %s", addr)
err := server.ListenAndServe()
if err != nil {
......@@ -359,8 +362,12 @@ func startServer(addr *net.TCPAddr) (*http.Server, error) {
})
}
func startServerTLS(addr *net.TCPAddr, getCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error)) (*http.Server, error) {
return initServer(addr, getCertificate, func(server *http.Server, errChan chan<- error) {
func startServerTLS(
addr *net.TCPAddr,
getCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error),
quicTLSGetCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error),
) (*http.Server, error) {
return initServer(addr, getCertificate, quicTLSGetCertificate, func(server *http.Server, errChan chan<- error) {
log.Printf("listening with HTTPS on %s", addr)
err := server.ListenAndServeTLS("", "")
if err != nil {
......@@ -385,6 +392,7 @@ func main() {
var certFilename, keyFilename string
var logFilename string
var port int
var quicTLSCertFilename, quicTLSKeyFilename string
flag.StringVar(&acmeEmail, "acme-email", "", "optional contact email for Let's Encrypt notifications")
flag.StringVar(&acmeHostnamesCommas, "acme-hostnames", "", "comma-separated hostnames for automatic TLS certificate")
......@@ -393,6 +401,8 @@ func main() {
flag.StringVar(&keyFilename, "key", "", "TLS private key file")
flag.StringVar(&logFilename, "log", "", "name of log file")
flag.IntVar(&port, "port", 0, "port to listen on")
flag.StringVar(&quicTLSCertFilename, "quic-tls-cert", "", "certificate file for QUIC TLS")
flag.StringVar(&quicTLSKeyFilename, "quic-tls-key", "", "private key file for QUIC TLS")
flag.Parse()
var err error
......@@ -466,6 +476,20 @@ func main() {
log.Fatalf("You must use either --acme-hostnames, or --cert and --key.")
}
// Set up the certificate and private key for the inner QUIC TLS layer.
// This is entirely separate from the HTTPS-layer TLS that is set up
// just above.
var quicTLSGetCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error)
if quicTLSCertFilename != "" && quicTLSKeyFilename != "" {
ctx, err := newCertContext(quicTLSCertFilename, quicTLSKeyFilename)
if err != nil {
log.Fatal(err)
}
quicTLSGetCertificate = ctx.GetCertificate
} else {
log.Fatal("The --quic-tls-cert and --quic-tls-key options are required.")
}
log.Printf("starting version %s (%s)", programVersion, runtime.Version())
servers := make([]*http.Server, 0)
for _, bindaddr := range ptInfo.Bindaddrs {
......@@ -492,9 +516,9 @@ func main() {
var server *http.Server
if disableTLS {
server, err = startServer(bindaddr.Addr)
server, err = startServer(bindaddr.Addr, quicTLSGetCertificate)
} else {
server, err = startServerTLS(bindaddr.Addr, getCertificate)
server, err = startServerTLS(bindaddr.Addr, getCertificate, quicTLSGetCertificate)
}
if err != nil {
pt.SmethodError(bindaddr.MethodName, err.Error())
......
......
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"math/big"
)
func generateTLSCertificate() (*tls.Certificate, error) {
// https://golang.org/src/crypto/tls/generate_cert.go
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
template := x509.Certificate{
SerialNumber: big.NewInt(1),
}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
return nil, err
}
keyDER, err := x509.MarshalECPrivateKey(key)
if err != nil {
return nil, err
}
cert, err := tls.X509KeyPair(
pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}),
pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}),
)
return &cert, err
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment