crypto.go 4.33 KB
Newer Older
Frank Denis's avatar
Frank Denis committed
1 2 3 4 5
package main

import (
	"bytes"
	"crypto/rand"
6
	"crypto/sha512"
Frank Denis's avatar
Frank Denis committed
7 8
	"errors"

9
	"github.com/jedisct1/dlog"
Frank Denis's avatar
Frank Denis committed
10
	"github.com/jedisct1/xsecretbox"
11 12
	"golang.org/x/crypto/curve25519"
	"golang.org/x/crypto/nacl/box"
Frank Denis's avatar
Frank Denis committed
13
	"golang.org/x/crypto/nacl/secretbox"
Frank Denis's avatar
Frank Denis committed
14 15 16
)

const (
17 18 19
	NonceSize        = xsecretbox.NonceSize
	HalfNonceSize    = xsecretbox.NonceSize / 2
	TagSize          = xsecretbox.TagSize
Frank Denis's avatar
Frank Denis committed
20 21
	PublicKeySize    = 32
	QueryOverhead    = ClientMagicLen + PublicKeySize + HalfNonceSize + TagSize
22
	ResponseOverhead = len(ServerMagic) + NonceSize + TagSize
Frank Denis's avatar
Frank Denis committed
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
)

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)")
		}
	}
}
Frank Denis's avatar
Frank Denis committed
46

47 48 49 50 51 52 53 54 55 56 57 58 59 60
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) {
Frank Denis's avatar
Frank Denis committed
61 62 63
	nonce, clientNonce := make([]byte, NonceSize), make([]byte, HalfNonceSize)
	rand.Read(clientNonce)
	copy(nonce, clientNonce)
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
	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
	}
Frank Denis's avatar
Frank Denis committed
80
	minQuestionSize := QueryOverhead + len(packet)
Frank Denis's avatar
Frank Denis committed
81
	if proto == "udp" {
82
		minQuestionSize = Max(proxy.questionSizeEstimator.MinQuestionSize(), minQuestionSize)
Frank Denis's avatar
Frank Denis committed
83 84 85 86 87
	} else {
		var xpad [1]byte
		rand.Read(xpad[:])
		minQuestionSize += int(xpad[0])
	}
Frank Denis's avatar
Frank Denis committed
88 89
	paddedLength := Min(MaxDNSUDPPacketSize, (Max(minQuestionSize, QueryOverhead)+63) & ^63)
	if QueryOverhead+len(packet)+1 > paddedLength {
Frank Denis's avatar
Frank Denis committed
90 91 92
		err = errors.New("Question too large; cannot be padded")
		return
	}
93
	encrypted = append(serverInfo.MagicQuery[:], publicKey[:]...)
Frank Denis's avatar
Frank Denis committed
94
	encrypted = append(encrypted, nonce[:HalfNonceSize]...)
Frank Denis's avatar
Frank Denis committed
95 96
	padded := pad(packet, paddedLength-QueryOverhead)
	if serverInfo.CryptoConstruction == XChacha20Poly1305 {
97
		encrypted = xsecretbox.Seal(encrypted, nonce, padded, sharedKey[:])
Frank Denis's avatar
Frank Denis committed
98 99 100
	} else {
		var xsalsaNonce [24]byte
		copy(xsalsaNonce[:], nonce)
101
		encrypted = secretbox.Seal(encrypted, padded, &xsalsaNonce, sharedKey)
Frank Denis's avatar
Frank Denis committed
102
	}
Frank Denis's avatar
Frank Denis committed
103 104 105
	return
}

106
func (proxy *Proxy) Decrypt(serverInfo *ServerInfo, sharedKey *[32]byte, encrypted []byte, nonce []byte) ([]byte, error) {
Frank Denis's avatar
Frank Denis committed
107 108 109
	serverMagicLen := len(ServerMagic)
	responseHeaderLen := serverMagicLen + NonceSize
	if len(encrypted) < responseHeaderLen+TagSize+int(MinDNSPacketSize) ||
110
		len(encrypted) > responseHeaderLen+TagSize+int(MaxDNSPacketSize) ||
Frank Denis's avatar
Frank Denis committed
111
		!bytes.Equal(encrypted[:serverMagicLen], ServerMagic[:]) {
112
		return encrypted, errors.New("Invalid message size or prefix")
Frank Denis's avatar
Frank Denis committed
113 114 115 116 117
	}
	serverNonce := encrypted[serverMagicLen:responseHeaderLen]
	if !bytes.Equal(nonce[:HalfNonceSize], serverNonce[:HalfNonceSize]) {
		return encrypted, errors.New("Unexpected nonce")
	}
Frank Denis's avatar
Frank Denis committed
118 119 120
	var packet []byte
	var err error
	if serverInfo.CryptoConstruction == XChacha20Poly1305 {
121
		packet, err = xsecretbox.Open(nil, serverNonce, encrypted[responseHeaderLen:], sharedKey[:])
Frank Denis's avatar
Frank Denis committed
122 123 124 125
	} else {
		var xsalsaServerNonce [24]byte
		copy(xsalsaServerNonce[:], serverNonce)
		var ok bool
126
		packet, ok = secretbox.Open(nil, encrypted[responseHeaderLen:], &xsalsaServerNonce, sharedKey)
Frank Denis's avatar
Frank Denis committed
127 128 129 130
		if !ok {
			err = errors.New("Incorrect tag")
		}
	}
Frank Denis's avatar
Frank Denis committed
131
	if err != nil {
Frank Denis's avatar
Frank Denis committed
132
		return encrypted, err
Frank Denis's avatar
Frank Denis committed
133 134 135 136 137 138 139
	}
	packet, err = unpad(packet)
	if err != nil || len(packet) < MinDNSPacketSize {
		return encrypted, errors.New("Incorrect padding")
	}
	return packet, nil
}