173 lines
4.9 KiB
Go
173 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
crypto_rand "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"
|
|
)
|
|
|
|
const (
|
|
NonceSize = xsecretbox.NonceSize
|
|
HalfNonceSize = xsecretbox.NonceSize / 2
|
|
TagSize = xsecretbox.TagSize
|
|
PublicKeySize = 32
|
|
QueryOverhead = ClientMagicLen + PublicKeySize + HalfNonceSize + TagSize
|
|
ResponseOverhead = len(ServerMagic) + NonceSize + TagSize
|
|
)
|
|
|
|
func pad(packet []byte, minSize int) []byte {
|
|
packet = append(packet, 0x80)
|
|
for len(packet) < minSize {
|
|
packet = append(packet, 0)
|
|
}
|
|
return packet
|
|
}
|
|
|
|
func unpad(packet []byte) ([]byte, error) {
|
|
for i := len(packet); ; {
|
|
if i == 0 {
|
|
return nil, errors.New("Invalid padding (short packet)")
|
|
}
|
|
i--
|
|
if packet[i] == 0x80 {
|
|
return packet[:i], nil
|
|
} else if packet[i] != 0x00 {
|
|
return nil, errors.New("Invalid padding (delimiter not found)")
|
|
}
|
|
}
|
|
}
|
|
|
|
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 XChaCha20 public key", providerName)
|
|
}
|
|
} else {
|
|
box.Precompute(&sharedKey, serverPk, secretKey)
|
|
c := byte(0)
|
|
for i := 0; i < 32; i++ {
|
|
c |= sharedKey[i]
|
|
}
|
|
if c == 0 {
|
|
dlog.Criticalf("[%v] Weak XSalsa20 public key", providerName)
|
|
if _, err := crypto_rand.Read(sharedKey[:]); err != nil {
|
|
dlog.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
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)
|
|
if _, err := crypto_rand.Read(clientNonce); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
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)
|
|
} else {
|
|
var xpad [1]byte
|
|
if _, err := crypto_rand.Read(xpad[:]); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
minQuestionSize += int(xpad[0])
|
|
}
|
|
paddedLength := Min(MaxDNSUDPPacketSize, (Max(minQuestionSize, QueryOverhead)+1+63) & ^63)
|
|
if serverInfo.knownBugs.fragmentsBlocked && proto == "udp" {
|
|
paddedLength = MaxDNSUDPSafePacketSize
|
|
} else if serverInfo.Relay != nil && proto == "tcp" {
|
|
paddedLength = MaxDNSPacketSize
|
|
}
|
|
if QueryOverhead+len(packet)+1 > paddedLength {
|
|
err = errors.New("Question too large; cannot be padded")
|
|
return
|
|
}
|
|
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, sharedKey[:])
|
|
} else {
|
|
var xsalsaNonce [24]byte
|
|
copy(xsalsaNonce[:], nonce)
|
|
encrypted = secretbox.Seal(encrypted, padded, &xsalsaNonce, sharedKey)
|
|
}
|
|
return
|
|
}
|
|
|
|
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) ||
|
|
len(encrypted) > responseHeaderLen+TagSize+int(MaxDNSPacketSize) ||
|
|
!bytes.Equal(encrypted[:serverMagicLen], ServerMagic[:]) {
|
|
return encrypted, errors.New("Invalid message size or prefix")
|
|
}
|
|
serverNonce := encrypted[serverMagicLen:responseHeaderLen]
|
|
if !bytes.Equal(nonce[:HalfNonceSize], serverNonce[:HalfNonceSize]) {
|
|
return encrypted, errors.New("Unexpected nonce")
|
|
}
|
|
var packet []byte
|
|
var err error
|
|
if serverInfo.CryptoConstruction == XChacha20Poly1305 {
|
|
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, sharedKey)
|
|
if !ok {
|
|
err = errors.New("Incorrect tag")
|
|
}
|
|
}
|
|
if err != nil {
|
|
return encrypted, err
|
|
}
|
|
packet, err = unpad(packet)
|
|
if err != nil || len(packet) < MinDNSPacketSize {
|
|
return encrypted, errors.New("Incorrect padding")
|
|
}
|
|
return packet, nil
|
|
}
|