dnscrypt-proxy/dnscrypt-proxy/crypto.go

111 lines
3.5 KiB
Go

package main
import (
"bytes"
"crypto/rand"
"errors"
"github.com/jedisct1/xsecretbox"
"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 (proxy *Proxy) Encrypt(serverInfo *ServerInfo, packet []byte, proto string) (encrypted []byte, clientNonce []byte, err error) {
nonce, clientNonce := make([]byte, NonceSize), make([]byte, HalfNonceSize)
rand.Read(clientNonce)
copy(nonce, clientNonce)
minQuestionSize := QueryOverhead + len(packet)
if proto == "udp" {
minQuestionSize = Max(proxy.questionSizeEstimator.MinQuestionSize(), minQuestionSize)
} else {
var xpad [1]byte
rand.Read(xpad[:])
minQuestionSize += int(xpad[0])
}
paddedLength := Min(MaxDNSUDPPacketSize, (Max(minQuestionSize, QueryOverhead)+63) & ^63)
if QueryOverhead+len(packet)+1 > paddedLength {
err = errors.New("Question too large; cannot be padded")
return
}
if serverInfo.CryptoConstruction == SIDHXChacha20Poly1305 {
encrypted = append(serverInfo.MagicQuery[:], proxy.proxySIDHPublicKey[:]...)
} else {
encrypted = append(serverInfo.MagicQuery[:], proxy.proxyPublicKey[:]...)
}
encrypted = append(encrypted, nonce[:HalfNonceSize]...)
padded := pad(packet, paddedLength-QueryOverhead)
if serverInfo.CryptoConstruction == XChacha20Poly1305 || serverInfo.CryptoConstruction == SIDHXChacha20Poly1305 {
encrypted = xsecretbox.Seal(encrypted, nonce, padded, serverInfo.SharedKey[:])
} else {
var xsalsaNonce [24]byte
copy(xsalsaNonce[:], nonce)
encrypted = secretbox.Seal(encrypted, padded, &xsalsaNonce, &serverInfo.SharedKey)
}
return
}
func (proxy *Proxy) Decrypt(serverInfo *ServerInfo, 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:], serverInfo.SharedKey[:])
} else {
var xsalsaServerNonce [24]byte
copy(xsalsaServerNonce[:], serverNonce)
var ok bool
packet, ok = secretbox.Open(nil, encrypted[responseHeaderLen:], &xsalsaServerNonce, &serverInfo.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
}