diff --git a/dnscrypt-proxy/config.go b/dnscrypt-proxy/config.go index e588f01c..382682b1 100644 --- a/dnscrypt-proxy/config.go +++ b/dnscrypt-proxy/config.go @@ -27,6 +27,7 @@ type Config struct { KeepAlive int `toml:"keepalive"` CertRefreshDelay int `toml:"cert_refresh_delay"` CertIgnoreTimestamp bool `toml:"cert_ignore_timestamp"` + EphemeralKeys bool `toml:"dnscrypt_ephemeral_keys"` LBStrategy string `toml:"lb_strategy"` BlockIPv6 bool `toml:"block_ipv6"` Cache bool @@ -69,6 +70,7 @@ func newConfig() Config { KeepAlive: 5, CertRefreshDelay: 240, CertIgnoreTimestamp: false, + EphemeralKeys: false, Cache: true, CacheSize: 256, CacheNegTTL: 60, @@ -236,6 +238,7 @@ func ConfigLoad(proxy *Proxy, svcFlag *string) error { proxy.certRefreshDelay = time.Duration(config.CertRefreshDelay) * time.Minute proxy.certRefreshDelayAfterFailure = time.Duration(10 * time.Second) proxy.certIgnoreTimestamp = config.CertIgnoreTimestamp + proxy.ephemeralKeys = config.EphemeralKeys if len(config.ListenAddresses) == 0 { dlog.Debug("No local IP/port configured") } diff --git a/dnscrypt-proxy/crypto.go b/dnscrypt-proxy/crypto.go index 3be68aae..a5e3288d 100644 --- a/dnscrypt-proxy/crypto.go +++ b/dnscrypt-proxy/crypto.go @@ -3,9 +3,13 @@ package main import ( "bytes" "crypto/rand" + "crypto/sha512" "errors" + "github.com/jedisct1/dlog" "github.com/jedisct1/xsecretbox" + "golang.org/x/crypto/curve25519" + "golang.org/x/crypto/nacl/box" "golang.org/x/crypto/nacl/secretbox" ) @@ -40,10 +44,39 @@ func unpad(packet []byte) ([]byte, error) { } } -func (proxy *Proxy) Encrypt(serverInfo *ServerInfo, packet []byte, proto string) (encrypted []byte, clientNonce []byte, err error) { +func ComputeSharedKey(cryptoConstruction CryptoConstruction, secretKey *[32]byte, serverPk *[32]byte, providerName *string) (sharedKey [32]byte) { + if cryptoConstruction == XChacha20Poly1305 { + var err error + sharedKey, err = xsecretbox.SharedKey(*secretKey, *serverPk) + if err != nil { + dlog.Criticalf("[%v] Weak public key", providerName) + } + } else { + box.Precompute(&sharedKey, serverPk, secretKey) + } + return +} + +func (proxy *Proxy) Encrypt(serverInfo *ServerInfo, packet []byte, proto string) (sharedKey *[32]byte, encrypted []byte, clientNonce []byte, err error) { nonce, clientNonce := make([]byte, NonceSize), make([]byte, HalfNonceSize) rand.Read(clientNonce) copy(nonce, clientNonce) + var publicKey *[PublicKeySize]byte + if proxy.ephemeralKeys { + h := sha512.New512_256() + h.Write(clientNonce) + h.Write(proxy.proxySecretKey[:]) + var ephSk [32]byte + h.Sum(ephSk[:0]) + var xPublicKey [PublicKeySize]byte + curve25519.ScalarBaseMult(&xPublicKey, &ephSk) + publicKey = &xPublicKey + xsharedKey := ComputeSharedKey(serverInfo.CryptoConstruction, &ephSk, &serverInfo.ServerPk, nil) + sharedKey = &xsharedKey + } else { + sharedKey = &serverInfo.SharedKey + publicKey = &proxy.proxyPublicKey + } minQuestionSize := QueryOverhead + len(packet) if proto == "udp" { minQuestionSize = Max(proxy.questionSizeEstimator.MinQuestionSize(), minQuestionSize) @@ -57,20 +90,20 @@ func (proxy *Proxy) Encrypt(serverInfo *ServerInfo, packet []byte, proto string) err = errors.New("Question too large; cannot be padded") return } - encrypted = append(serverInfo.MagicQuery[:], proxy.proxyPublicKey[:]...) + encrypted = append(serverInfo.MagicQuery[:], publicKey[:]...) encrypted = append(encrypted, nonce[:HalfNonceSize]...) padded := pad(packet, paddedLength-QueryOverhead) if serverInfo.CryptoConstruction == XChacha20Poly1305 { - encrypted = xsecretbox.Seal(encrypted, nonce, padded, serverInfo.SharedKey[:]) + encrypted = xsecretbox.Seal(encrypted, nonce, padded, sharedKey[:]) } else { var xsalsaNonce [24]byte copy(xsalsaNonce[:], nonce) - encrypted = secretbox.Seal(encrypted, padded, &xsalsaNonce, &serverInfo.SharedKey) + encrypted = secretbox.Seal(encrypted, padded, &xsalsaNonce, sharedKey) } return } -func (proxy *Proxy) Decrypt(serverInfo *ServerInfo, encrypted []byte, nonce []byte) ([]byte, error) { +func (proxy *Proxy) Decrypt(serverInfo *ServerInfo, sharedKey *[32]byte, encrypted []byte, nonce []byte) ([]byte, error) { serverMagicLen := len(ServerMagic) responseHeaderLen := serverMagicLen + NonceSize if len(encrypted) < responseHeaderLen+TagSize+int(MinDNSPacketSize) || @@ -85,12 +118,12 @@ func (proxy *Proxy) Decrypt(serverInfo *ServerInfo, encrypted []byte, nonce []by var packet []byte var err error if serverInfo.CryptoConstruction == XChacha20Poly1305 { - packet, err = xsecretbox.Open(nil, serverNonce, encrypted[responseHeaderLen:], serverInfo.SharedKey[:]) + packet, err = xsecretbox.Open(nil, serverNonce, encrypted[responseHeaderLen:], sharedKey[:]) } else { var xsalsaServerNonce [24]byte copy(xsalsaServerNonce[:], serverNonce) var ok bool - packet, ok = secretbox.Open(nil, encrypted[responseHeaderLen:], &xsalsaServerNonce, &serverInfo.SharedKey) + packet, ok = secretbox.Open(nil, encrypted[responseHeaderLen:], &xsalsaServerNonce, sharedKey) if !ok { err = errors.New("Incorrect tag") } diff --git a/dnscrypt-proxy/dnscrypt_certs.go b/dnscrypt-proxy/dnscrypt_certs.go index 0ff3c68c..7babc560 100644 --- a/dnscrypt-proxy/dnscrypt_certs.go +++ b/dnscrypt-proxy/dnscrypt_certs.go @@ -8,10 +8,8 @@ import ( "time" "github.com/jedisct1/dlog" - "github.com/jedisct1/xsecretbox" "github.com/miekg/dns" "golang.org/x/crypto/ed25519" - "golang.org/x/crypto/nacl/box" ) type CertInfo struct { @@ -120,16 +118,7 @@ func FetchCurrentDNSCryptCert(proxy *Proxy, serverName *string, proto string, pk } var serverPk [32]byte copy(serverPk[:], binCert[72:104]) - var sharedKey [32]byte - if cryptoConstruction == XChacha20Poly1305 { - sharedKey, err = xsecretbox.SharedKey(proxy.proxySecretKey, serverPk) - if err != nil { - dlog.Criticalf("[%v] Weak public key", providerName) - continue - } - } else { - box.Precompute(&sharedKey, &serverPk, &proxy.proxySecretKey) - } + sharedKey := ComputeSharedKey(cryptoConstruction, &proxy.proxySecretKey, &serverPk, &providerName) certInfo.SharedKey = sharedKey highestSerial = serial certInfo.CryptoConstruction = cryptoConstruction diff --git a/dnscrypt-proxy/example-dnscrypt-proxy.toml b/dnscrypt-proxy/example-dnscrypt-proxy.toml index 7546b978..b3e774ae 100644 --- a/dnscrypt-proxy/example-dnscrypt-proxy.toml +++ b/dnscrypt-proxy/example-dnscrypt-proxy.toml @@ -109,6 +109,14 @@ keepalive = 10 cert_refresh_delay = 240 +## DNSCrypt: Create a new, unique key for every single DNS query +## This may improve privacy but can also have a significant impact on CPU usage +## Only enable if you don't have a lot of network load + +# dnscrypt_ephemeral_keys = false + + + ## DoH: Disable TLS session tickets ## increases privacy but also latency - Bump keepalive up to compensate. diff --git a/dnscrypt-proxy/proxy.go b/dnscrypt-proxy/proxy.go index 93155ab3..8ccf00ab 100644 --- a/dnscrypt-proxy/proxy.go +++ b/dnscrypt-proxy/proxy.go @@ -16,6 +16,7 @@ import ( type Proxy struct { proxyPublicKey [32]byte proxySecretKey [32]byte + ephemeralKeys bool questionSizeEstimator QuestionSizeEstimator serversInfo ServersInfo timeout time.Duration @@ -193,7 +194,7 @@ func (proxy *Proxy) tcpListenerFromAddr(listenAddr *net.TCPAddr) error { return nil } -func (proxy *Proxy) exchangeWithUDPServer(serverInfo *ServerInfo, encryptedQuery []byte, clientNonce []byte) ([]byte, error) { +func (proxy *Proxy) exchangeWithUDPServer(serverInfo *ServerInfo, sharedKey *[32]byte, encryptedQuery []byte, clientNonce []byte) ([]byte, error) { pc, err := net.DialUDP("udp", nil, serverInfo.UDPAddr) if err != nil { return nil, err @@ -207,10 +208,10 @@ func (proxy *Proxy) exchangeWithUDPServer(serverInfo *ServerInfo, encryptedQuery return nil, err } encryptedResponse = encryptedResponse[:length] - return proxy.Decrypt(serverInfo, encryptedResponse, clientNonce) + return proxy.Decrypt(serverInfo, sharedKey, encryptedResponse, clientNonce) } -func (proxy *Proxy) exchangeWithTCPServer(serverInfo *ServerInfo, encryptedQuery []byte, clientNonce []byte) ([]byte, error) { +func (proxy *Proxy) exchangeWithTCPServer(serverInfo *ServerInfo, sharedKey *[32]byte, encryptedQuery []byte, clientNonce []byte) ([]byte, error) { pc, err := net.DialTCP("tcp", nil, serverInfo.TCPAddr) if err != nil { return nil, err @@ -227,7 +228,7 @@ func (proxy *Proxy) exchangeWithTCPServer(serverInfo *ServerInfo, encryptedQuery if err != nil { return nil, err } - return proxy.Decrypt(serverInfo, encryptedResponse, clientNonce) + return proxy.Decrypt(serverInfo, sharedKey, encryptedResponse, clientNonce) } func (proxy *Proxy) clientsCountInc() bool { @@ -273,15 +274,15 @@ func (proxy *Proxy) processIncomingQuery(serverInfo *ServerInfo, clientProto str if len(response) == 0 { var ttl *uint32 if serverInfo.Proto == StampProtoTypeDNSCrypt { - encryptedQuery, clientNonce, err := proxy.Encrypt(serverInfo, query, serverProto) + sharedKey, encryptedQuery, clientNonce, err := proxy.Encrypt(serverInfo, query, serverProto) if err != nil { return } serverInfo.noticeBegin(proxy) if serverProto == "udp" { - response, err = proxy.exchangeWithUDPServer(serverInfo, encryptedQuery, clientNonce) + response, err = proxy.exchangeWithUDPServer(serverInfo, sharedKey, encryptedQuery, clientNonce) } else { - response, err = proxy.exchangeWithTCPServer(serverInfo, encryptedQuery, clientNonce) + response, err = proxy.exchangeWithTCPServer(serverInfo, sharedKey, encryptedQuery, clientNonce) } if err != nil { serverInfo.noticeFailure(proxy)