Skip to content

Commit b9f971e

Browse files
committed
Rotate keys on IP changes also for classical dnscrypt
1 parent c414494 commit b9f971e

5 files changed

Lines changed: 156 additions & 61 deletions

File tree

dnscrypt-proxy/crypto.go

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,19 @@ func unpad(packet []byte) ([]byte, error) {
4444
}
4545
}
4646

47+
func isClassicDNSCryptConstruction(cryptoConstruction CryptoConstruction) bool {
48+
return cryptoConstruction == XSalsa20Poly1305 || cryptoConstruction == XChacha20Poly1305
49+
}
50+
4751
func ComputeSharedKey(
4852
cryptoConstruction CryptoConstruction,
4953
secretKey *[32]byte,
5054
serverPk *[32]byte,
5155
providerName *string,
5256
) (sharedKey [32]byte) {
57+
if !isClassicDNSCryptConstruction(cryptoConstruction) {
58+
return [32]byte{}
59+
}
5360
if cryptoConstruction == XChacha20Poly1305 {
5461
var err error
5562
sharedKey, err = xsecretbox.SharedKey(*secretKey, *serverPk)
@@ -75,6 +82,64 @@ func ComputeSharedKey(
7582
return sharedKey
7683
}
7784

85+
// setDNSCryptClientKeyLocked installs a fresh client keypair. The caller must
86+
// hold proxy.cryptoKeyMu for writing.
87+
func (proxy *Proxy) setDNSCryptClientKeyLocked(secretKey [32]byte) {
88+
proxy.proxySecretKey = secretKey
89+
curve25519.ScalarBaseMult(&proxy.proxyPublicKey, &proxy.proxySecretKey)
90+
}
91+
92+
// recomputeServerSharedKeyLocked refreshes a classic server's shared key from
93+
// the current client key. The caller must hold proxy.cryptoKeyMu.
94+
func (proxy *Proxy) recomputeServerSharedKeyLocked(serverInfo *ServerInfo) {
95+
if !isClassicDNSCryptConstruction(serverInfo.CryptoConstruction) {
96+
return
97+
}
98+
serverInfo.SharedKey = ComputeSharedKey(
99+
serverInfo.CryptoConstruction,
100+
&proxy.proxySecretKey,
101+
&serverInfo.ServerPk,
102+
&serverInfo.Name,
103+
)
104+
}
105+
106+
func (proxy *Proxy) initDNSCryptClientKey() error {
107+
var secretKey [32]byte
108+
if _, err := crypto_rand.Read(secretKey[:]); err != nil {
109+
return err
110+
}
111+
proxy.cryptoKeyMu.Lock()
112+
proxy.setDNSCryptClientKeyLocked(secretKey)
113+
proxy.cryptoKeyMu.Unlock()
114+
return nil
115+
}
116+
117+
func (proxy *Proxy) rotateDNSCryptClientKey() error {
118+
var secretKey [32]byte
119+
if _, err := crypto_rand.Read(secretKey[:]); err != nil {
120+
return err
121+
}
122+
proxy.cryptoKeyMu.Lock()
123+
proxy.setDNSCryptClientKeyLocked(secretKey)
124+
proxy.serversInfo.Lock()
125+
for _, serverInfo := range proxy.serversInfo.inner {
126+
proxy.recomputeServerSharedKeyLocked(serverInfo)
127+
}
128+
proxy.serversInfo.Unlock()
129+
proxy.cryptoKeyMu.Unlock()
130+
return nil
131+
}
132+
133+
func (proxy *Proxy) computeSharedKey(
134+
cryptoConstruction CryptoConstruction,
135+
serverPk *[32]byte,
136+
providerName *string,
137+
) [32]byte {
138+
proxy.cryptoKeyMu.RLock()
139+
defer proxy.cryptoKeyMu.RUnlock()
140+
return ComputeSharedKey(cryptoConstruction, &proxy.proxySecretKey, serverPk, providerName)
141+
}
142+
78143
func (proxy *Proxy) Encrypt(
79144
serverInfo *ServerInfo,
80145
packet []byte,
@@ -90,9 +155,12 @@ func (proxy *Proxy) Encrypt(
90155
copy(nonce, clientNonce)
91156
var publicKey *[PublicKeySize]byte
92157
if proxy.ephemeralKeys {
158+
proxy.cryptoKeyMu.RLock()
159+
secretKey := proxy.proxySecretKey
160+
proxy.cryptoKeyMu.RUnlock()
93161
h := sha512.New512_256()
94162
h.Write(clientNonce)
95-
h.Write(proxy.proxySecretKey[:])
163+
h.Write(secretKey[:])
96164
var ephSk [32]byte
97165
h.Sum(ephSk[:0])
98166
var xPublicKey [PublicKeySize]byte
@@ -101,8 +169,12 @@ func (proxy *Proxy) Encrypt(
101169
xsharedKey := ComputeSharedKey(serverInfo.CryptoConstruction, &ephSk, &serverInfo.ServerPk, nil)
102170
sharedKey = &xsharedKey
103171
} else {
104-
sharedKey = &serverInfo.SharedKey
105-
publicKey = &proxy.proxyPublicKey
172+
proxy.cryptoKeyMu.RLock()
173+
serverSharedKey := serverInfo.SharedKey
174+
proxyPublicKey := proxy.proxyPublicKey
175+
proxy.cryptoKeyMu.RUnlock()
176+
sharedKey = &serverSharedKey
177+
publicKey = &proxyPublicKey
106178
}
107179
minQuestionSize := QueryOverhead + len(packet)
108180
if proto == "udp" {

dnscrypt-proxy/dnscrypt_certs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ func FetchCurrentDNSCryptCert(
209209
} else {
210210
var serverPk [32]byte
211211
copy(serverPk[:], binCert[72:104])
212-
sharedKey := ComputeSharedKey(cryptoConstruction, &proxy.proxySecretKey, &serverPk, &providerName)
212+
sharedKey := proxy.computeSharedKey(cryptoConstruction, &serverPk, &providerName)
213213
certInfo.SharedKey = sharedKey
214214
certInfo.PqPublicKey = nil
215215
certInfo.PqCertContext = nil

dnscrypt-proxy/netmon.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type networkMonitor struct {
3131
mu sync.Mutex
3232
last string
3333
fingerprint func() string
34+
onChange func()
3435
}
3536

3637
func newNetworkMonitor() *networkMonitor {
@@ -77,17 +78,24 @@ func (monitor *networkMonitor) start(ctx context.Context, interval time.Duration
7778
func (monitor *networkMonitor) check() {
7879
fingerprint := monitor.currentFingerprint()
7980
monitor.mu.Lock()
80-
defer monitor.mu.Unlock()
8181
if monitor.last == "" {
8282
monitor.last = fingerprint
83+
monitor.mu.Unlock()
8384
return
8485
}
8586
if monitor.last == fingerprint {
87+
monitor.mu.Unlock()
8688
return
8789
}
8890
monitor.last = fingerprint
8991
monitor.epochValue.Add(1)
90-
dlog.Notice("Network change detected; rotating PQ resumption tickets")
92+
onChange := monitor.onChange
93+
monitor.mu.Unlock()
94+
95+
dlog.Notice("Network change detected; rotating DNSCrypt client state")
96+
if onChange != nil {
97+
onChange()
98+
}
9199
}
92100

93101
func (monitor *networkMonitor) currentFingerprint() string {

dnscrypt-proxy/proxy.go

Lines changed: 67 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package main
22

33
import (
44
"context"
5-
crypto_rand "crypto/rand"
65
"encoding/binary"
76
"net"
87
"os"
@@ -15,61 +14,63 @@ import (
1514
"github.com/jedisct1/dlog"
1615
clocksmith "github.com/jedisct1/go-clocksmith"
1716
stamps "github.com/jedisct1/go-dnsstamps"
18-
"golang.org/x/crypto/curve25519"
1917
netproxy "golang.org/x/net/proxy"
2018
)
2119

2220
type Proxy struct {
23-
pluginsGlobals PluginsGlobals
24-
serversInfo ServersInfo
25-
questionSizeEstimator QuestionSizeEstimator
26-
registeredServers []RegisteredServer
27-
dns64Resolvers []string
28-
dns64Prefixes []string
29-
serversBlockingFragments []string
30-
ednsClientSubnets []*net.IPNet
31-
queryLogIgnoredQtypes []string
32-
localDoHListeners []*net.TCPListener
33-
queryMeta []string
34-
enableHotReload bool
35-
udpListeners []*net.UDPConn
36-
sources []*Source
37-
tcpListeners []*net.TCPListener
38-
registeredRelays []RegisteredServer
39-
listenAddresses []string
40-
localDoHListenAddresses []string
41-
monitoringUI MonitoringUIConfig
42-
monitoringInstance *MonitoringUI
43-
xTransport *XTransport
44-
allWeeklyRanges *map[string]WeeklyRanges
45-
routes *map[string][]string
46-
captivePortalMap *CaptivePortalMap
47-
nxLogFormat string
48-
localDoHCertFile string
49-
localDoHCertKeyFile string
50-
captivePortalMapFile string
51-
localDoHPath string
52-
cloakFile string
53-
forwardFile string
54-
blockIPFormat string
55-
blockIPLogFile string
56-
allowedIPFile string
57-
allowedIPFormat string
58-
allowedIPLogFile string
59-
queryLogFormat string
60-
blockIPFile string
61-
allowNameFile string
62-
allowNameFormat string
63-
allowNameLogFile string
64-
blockNameLogFile string
65-
blockNameFormat string
66-
blockNameFile string
67-
queryLogFile string
68-
blockedQueryResponse string
69-
userName string
70-
nxLogFile string
71-
proxySecretKey [32]byte
72-
proxyPublicKey [32]byte
21+
pluginsGlobals PluginsGlobals
22+
serversInfo ServersInfo
23+
questionSizeEstimator QuestionSizeEstimator
24+
registeredServers []RegisteredServer
25+
dns64Resolvers []string
26+
dns64Prefixes []string
27+
serversBlockingFragments []string
28+
ednsClientSubnets []*net.IPNet
29+
queryLogIgnoredQtypes []string
30+
localDoHListeners []*net.TCPListener
31+
queryMeta []string
32+
enableHotReload bool
33+
udpListeners []*net.UDPConn
34+
sources []*Source
35+
tcpListeners []*net.TCPListener
36+
registeredRelays []RegisteredServer
37+
listenAddresses []string
38+
localDoHListenAddresses []string
39+
monitoringUI MonitoringUIConfig
40+
monitoringInstance *MonitoringUI
41+
xTransport *XTransport
42+
allWeeklyRanges *map[string]WeeklyRanges
43+
routes *map[string][]string
44+
captivePortalMap *CaptivePortalMap
45+
nxLogFormat string
46+
localDoHCertFile string
47+
localDoHCertKeyFile string
48+
captivePortalMapFile string
49+
localDoHPath string
50+
cloakFile string
51+
forwardFile string
52+
blockIPFormat string
53+
blockIPLogFile string
54+
allowedIPFile string
55+
allowedIPFormat string
56+
allowedIPLogFile string
57+
queryLogFormat string
58+
blockIPFile string
59+
allowNameFile string
60+
allowNameFormat string
61+
allowNameLogFile string
62+
blockNameLogFile string
63+
blockNameFormat string
64+
blockNameFile string
65+
queryLogFile string
66+
blockedQueryResponse string
67+
userName string
68+
nxLogFile string
69+
proxySecretKey [32]byte
70+
proxyPublicKey [32]byte
71+
// cryptoKeyMu guards proxySecretKey, proxyPublicKey, and the classic
72+
// SharedKey of every ServerInfo while the client key is rotated.
73+
cryptoKeyMu sync.RWMutex
7374
ServerNames []string
7475
DisabledServerNames []string
7576
requiredProps stamps.ServerInformalProperties
@@ -130,6 +131,17 @@ func (proxy *Proxy) registerLocalDoHListener(listener *net.TCPListener) {
130131
proxy.listenersMu.Unlock()
131132
}
132133

134+
func (proxy *Proxy) handleNetworkChange() {
135+
if proxy.ephemeralKeys {
136+
return
137+
}
138+
if err := proxy.rotateDNSCryptClientKey(); err != nil {
139+
dlog.Errorf("Unable to rotate DNSCrypt client key after network change: %v", err)
140+
return
141+
}
142+
dlog.Notice("Rotated DNSCrypt client key after network change")
143+
}
144+
133145
func (proxy *Proxy) addDNSListener(listenAddrStr string) {
134146
udp := "udp"
135147
tcp := "tcp"
@@ -264,11 +276,11 @@ func (proxy *Proxy) StartProxy() {
264276
proxy.questionSizeEstimator = NewQuestionSizeEstimator()
265277
proxy.netMonitor = newNetworkMonitor()
266278
proxy.netMonitor.init()
267-
go proxy.netMonitor.start(context.Background(), defaultNetworkMonitorInterval)
268-
if _, err := crypto_rand.Read(proxy.proxySecretKey[:]); err != nil {
279+
proxy.netMonitor.onChange = proxy.handleNetworkChange
280+
if err := proxy.initDNSCryptClientKey(); err != nil {
269281
dlog.Fatal(err)
270282
}
271-
curve25519.ScalarBaseMult(&proxy.proxyPublicKey, &proxy.proxySecretKey)
283+
go proxy.netMonitor.start(context.Background(), defaultNetworkMonitorInterval)
272284

273285
// Initialize and start the monitoring UI if enabled
274286
if proxy.monitoringUI.Enabled {

dnscrypt-proxy/serversInfo.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,8 @@ func (serversInfo *ServersInfo) refreshServer(proxy *Proxy, name string, stamp s
288288
}
289289
newServer.rtt = ewma.NewMovingAverage(RTTEwmaDecay)
290290
newServer.rtt.Set(float64(newServer.initialRtt))
291+
proxy.cryptoKeyMu.RLock()
292+
proxy.recomputeServerSharedKeyLocked(&newServer)
291293
serversInfo.Lock()
292294
found := false
293295
for i, oldServer := range serversInfo.inner {
@@ -301,6 +303,7 @@ func (serversInfo *ServersInfo) refreshServer(proxy *Proxy, name string, stamp s
301303
serversInfo.inner = append(serversInfo.inner, &newServer)
302304
}
303305
serversInfo.Unlock()
306+
proxy.cryptoKeyMu.RUnlock()
304307
if !found {
305308
proxy.serversInfo.registerServer(name, stamp)
306309
}

0 commit comments

Comments
 (0)