Re-implement ephemeral keys for DNSCrypt

This commit is contained in:
Frank Denis 2018-04-09 03:12:34 +02:00
parent 70dca19326
commit ca80b69b3a
5 changed files with 60 additions and 26 deletions

View File

@ -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")
}

View File

@ -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")
}

View File

@ -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

View File

@ -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.

View File

@ -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)