April 1st is already over in some time zones :)

This reverts commit dac52ab42a.
This commit is contained in:
Frank Denis 2018-04-01 16:35:32 +02:00
parent dac52ab42a
commit adb0c94a61
6 changed files with 387 additions and 8 deletions

View File

@ -43,6 +43,7 @@ type Config struct {
SourceRequireDNSSEC bool `toml:"require_dnssec"`
SourceRequireNoLog bool `toml:"require_nolog"`
SourceRequireNoFilter bool `toml:"require_nofilter"`
SourceDNSCrypt bool `toml:"dnscrypt_servers"`
SourceDoH bool `toml:"doh_servers"`
SourceIPv4 bool `toml:"ipv4_servers"`
SourceIPv6 bool `toml:"ipv6_servers"`
@ -71,6 +72,7 @@ func newConfig() Config {
SourceRequireNoFilter: true,
SourceIPv4: true,
SourceIPv6: false,
SourceDNSCrypt: true,
SourceDoH: true,
MaxClients: 250,
FallbackResolver: DefaultFallbackResolver,
@ -285,6 +287,7 @@ func ConfigLoad(proxy *Proxy, svcFlag *string) error {
config.SourceRequireNoLog = false
config.SourceIPv4 = true
config.SourceIPv6 = true
config.SourceDNSCrypt = true
config.SourceDoH = true
}
@ -435,7 +438,8 @@ func (config *Config) loadSource(proxy *Proxy, requiredProps ServerInformalPrope
continue
}
}
if !(config.SourceDoH && registeredServer.stamp.proto == StampProtoTypeDoH) {
if !((config.SourceDNSCrypt && registeredServer.stamp.proto == StampProtoTypeDNSCrypt) ||
(config.SourceDoH && registeredServer.stamp.proto == StampProtoTypeDoH)) {
continue
}
dlog.Debugf("Adding [%s] to the set of wanted resolvers", registeredServer.name)

View File

@ -0,0 +1,182 @@
package main
import (
"bytes"
"encoding/binary"
"errors"
"strings"
"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 {
ServerPk [32]byte
SharedKey [32]byte
MagicQuery [ClientMagicLen]byte
CryptoConstruction CryptoConstruction
ForwardSecurity bool
}
func FetchCurrentDNSCryptCert(proxy *Proxy, serverName *string, proto string, pk ed25519.PublicKey, serverAddress string, providerName string, isNew bool) (CertInfo, int, error) {
if len(pk) != ed25519.PublicKeySize {
return CertInfo{}, 0, errors.New("Invalid public key length")
}
if !strings.HasSuffix(providerName, ".") {
providerName = providerName + "."
}
if serverName == nil {
serverName = &providerName
}
query := new(dns.Msg)
query.SetQuestion(providerName, dns.TypeTXT)
client := dns.Client{Net: proto, UDPSize: uint16(MaxDNSUDPPacketSize)}
in, rtt, err := client.Exchange(query, serverAddress)
if err != nil {
dlog.Noticef("[%s] TIMEOUT", *serverName)
return CertInfo{}, 0, err
}
now := uint32(time.Now().Unix())
certInfo := CertInfo{CryptoConstruction: UndefinedConstruction}
highestSerial := uint32(0)
for _, answerRr := range in.Answer {
binCert, err := packTxtString(strings.Join(answerRr.(*dns.TXT).Txt, ""))
if err != nil {
dlog.Warnf("[%v] Unable to unpack the certificate", providerName)
continue
}
if len(binCert) < 124 {
dlog.Warnf("[%v] Certificate too short", providerName)
continue
}
if !bytes.Equal(binCert[:4], CertMagic[:4]) {
dlog.Warnf("[%v] Invalid cert magic", providerName)
continue
}
cryptoConstruction := CryptoConstruction(0)
switch esVersion := binary.BigEndian.Uint16(binCert[4:6]); esVersion {
case 0x0001:
cryptoConstruction = XSalsa20Poly1305
case 0x0002:
cryptoConstruction = XChacha20Poly1305
default:
dlog.Noticef("[%v] Unsupported crypto construction", providerName)
continue
}
signature := binCert[8:72]
signed := binCert[72:]
if !ed25519.Verify(pk, signed, signature) {
dlog.Warnf("[%v] Incorrect signature", providerName)
continue
}
serial := binary.BigEndian.Uint32(binCert[112:116])
tsBegin := binary.BigEndian.Uint32(binCert[116:120])
tsEnd := binary.BigEndian.Uint32(binCert[120:124])
if tsBegin >= tsEnd {
dlog.Warnf("[%v] certificate ends before it starts (%v >= %v)", providerName, tsBegin, tsEnd)
continue
}
ttl := tsEnd - tsBegin
if ttl > 86400*7 {
dlog.Infof("[%v] the key validity period for this server is excessively long (%d days), significantly reducing reliability and forward security.", providerName, ttl/86400)
daysLeft := (tsEnd - now) / 86400
if daysLeft < 1 {
dlog.Criticalf("[%v] certificate will expire today -- Switch to a different resolver as soon as possible", providerName)
} else if daysLeft <= 7 {
dlog.Warnf("[%v] certificate is about to expire -- if you don't manage this server, tell the server operator about it", providerName)
} else if daysLeft <= 30 {
dlog.Infof("[%v] certificate will expire in %d days", providerName, daysLeft)
}
certInfo.ForwardSecurity = false
} else {
certInfo.ForwardSecurity = true
}
if !proxy.certIgnoreTimestamp {
if now > tsEnd || now < tsBegin {
dlog.Debugf("[%v] Certificate not valid at the current date", providerName)
continue
}
}
if serial < highestSerial {
dlog.Debugf("[%v] Superseded by a previous certificate", providerName)
continue
}
if serial == highestSerial {
if cryptoConstruction < certInfo.CryptoConstruction {
dlog.Debugf("[%v] Keeping the previous, preferred crypto construction", providerName)
continue
} else {
dlog.Debugf("[%v] Upgrading the construction from %v to %v", providerName, certInfo.CryptoConstruction, cryptoConstruction)
}
}
if cryptoConstruction != XChacha20Poly1305 && cryptoConstruction != XSalsa20Poly1305 {
dlog.Noticef("[%v] Cryptographic construction %v not supported", providerName, cryptoConstruction)
continue
}
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)
}
certInfo.SharedKey = sharedKey
highestSerial = serial
certInfo.CryptoConstruction = cryptoConstruction
copy(certInfo.ServerPk[:], serverPk[:])
copy(certInfo.MagicQuery[:], binCert[104:112])
if isNew {
dlog.Noticef("[%s] OK (crypto v%d) - rtt: %dms", *serverName, cryptoConstruction, rtt.Nanoseconds()/1000000)
} else {
dlog.Infof("[%s] OK (crypto v%d) - rtt: %dms", *serverName, cryptoConstruction, rtt.Nanoseconds()/1000000)
}
}
if certInfo.CryptoConstruction == UndefinedConstruction {
return certInfo, 0, errors.New("No useable certificate found")
}
return certInfo, int(rtt.Nanoseconds() / 1000000), nil
}
func isDigit(b byte) bool { return b >= '0' && b <= '9' }
func dddToByte(s []byte) byte {
return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0'))
}
func packTxtString(s string) ([]byte, error) {
bs := make([]byte, len(s))
msg := make([]byte, 0)
copy(bs, s)
for i := 0; i < len(bs); i++ {
if bs[i] == '\\' {
i++
if i == len(bs) {
break
}
if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) {
msg = append(msg, dddToByte(bs[i:]))
i += 2
} else if bs[i] == 't' {
msg = append(msg, '\t')
} else if bs[i] == 'r' {
msg = append(msg, '\r')
} else if bs[i] == 'n' {
msg = append(msg, '\n')
} else {
msg = append(msg, bs[i])
}
} else {
msg = append(msg, bs[i])
}
}
return msg, nil
}

View File

@ -271,7 +271,22 @@ func (proxy *Proxy) processIncomingQuery(serverInfo *ServerInfo, clientProto str
}
if len(response) == 0 {
var ttl *uint32
if serverInfo.Proto == StampProtoTypeDoH {
if serverInfo.Proto == StampProtoTypeDNSCrypt {
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)
} else {
response, err = proxy.exchangeWithTCPServer(serverInfo, encryptedQuery, clientNonce)
}
if err != nil {
serverInfo.noticeFailure(proxy)
return
}
} else if serverInfo.Proto == StampProtoTypeDoH {
tid := TransactionID(query)
SetTransactionID(query, 0)
serverInfo.noticeBegin(proxy)

View File

@ -2,6 +2,7 @@ package main
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
@ -16,6 +17,7 @@ import (
"github.com/VividCortex/ewma"
"github.com/jedisct1/dlog"
"golang.org/x/crypto/ed25519"
)
const (
@ -205,12 +207,49 @@ func (serversInfo *ServersInfo) getOne() *ServerInfo {
}
func (serversInfo *ServersInfo) fetchServerInfo(proxy *Proxy, name string, stamp ServerStamp, isNew bool) (ServerInfo, error) {
if stamp.proto == StampProtoTypeDoH {
if stamp.proto == StampProtoTypeDNSCrypt {
return serversInfo.fetchDNSCryptServerInfo(proxy, name, stamp, isNew)
} else if stamp.proto == StampProtoTypeDoH {
return serversInfo.fetchDoHServerInfo(proxy, name, stamp, isNew)
}
return ServerInfo{}, errors.New("Unsupported protocol")
}
func (serversInfo *ServersInfo) fetchDNSCryptServerInfo(proxy *Proxy, name string, stamp ServerStamp, isNew bool) (ServerInfo, error) {
if len(stamp.serverPk) != ed25519.PublicKeySize {
serverPk, err := hex.DecodeString(strings.Replace(string(stamp.serverPk), ":", "", -1))
if err != nil || len(serverPk) != ed25519.PublicKeySize {
dlog.Fatalf("Unsupported public key for [%s]: [%s]", name, stamp.serverPk)
}
dlog.Warnf("Public key [%s] shouldn't be hex-encoded any more", string(stamp.serverPk))
stamp.serverPk = serverPk
}
certInfo, rtt, err := FetchCurrentDNSCryptCert(proxy, &name, proxy.mainProto, stamp.serverPk, stamp.serverAddrStr, stamp.providerName, isNew)
if err != nil {
return ServerInfo{}, err
}
remoteUDPAddr, err := net.ResolveUDPAddr("udp", stamp.serverAddrStr)
if err != nil {
return ServerInfo{}, err
}
remoteTCPAddr, err := net.ResolveTCPAddr("tcp", stamp.serverAddrStr)
if err != nil {
return ServerInfo{}, err
}
return ServerInfo{
Proto: StampProtoTypeDNSCrypt,
MagicQuery: certInfo.MagicQuery,
ServerPk: certInfo.ServerPk,
SharedKey: certInfo.SharedKey,
CryptoConstruction: certInfo.CryptoConstruction,
Name: name,
Timeout: proxy.timeout,
UDPAddr: remoteUDPAddr,
TCPAddr: remoteTCPAddr,
initialRtt: rtt,
}, nil
}
func (serversInfo *ServersInfo) fetchDoHServerInfo(proxy *Proxy, name string, stamp ServerStamp, isNew bool) (ServerInfo, error) {
if len(stamp.serverAddrStr) > 0 {
addrStr := stamp.serverAddrStr

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/csv"
"errors"
"fmt"
"io"
@ -208,13 +209,58 @@ func NewSource(xTransport *XTransport, urls []string, minisignKeyStr string, cac
}
func (source *Source) Parse(prefix string) ([]RegisteredServer, error) {
if source.format == SourceFormatV2 {
if source.format == SourceFormatV1 {
return source.parseV1(prefix)
} else if source.format == SourceFormatV2 {
return source.parseV2(prefix)
}
dlog.Fatal("Unexpected source format")
return []RegisteredServer{}, nil
}
func (source *Source) parseV1(prefix string) ([]RegisteredServer, error) {
var registeredServers []RegisteredServer
csvReader := csv.NewReader(strings.NewReader(source.in))
records, err := csvReader.ReadAll()
if err != nil {
return registeredServers, nil
}
for lineNo, record := range records {
if len(record) == 0 {
continue
}
if len(record) < 14 {
return registeredServers, fmt.Errorf("Parse error at line %d", 1+lineNo)
}
if lineNo == 0 {
continue
}
name := prefix + record[0]
description := record[2]
serverAddrStr := record[10]
providerName := record[11]
serverPkStr := record[12]
props := ServerInformalProperties(0)
if strings.EqualFold(record[7], "yes") {
props |= ServerInformalPropertyDNSSEC
}
if strings.EqualFold(record[8], "yes") {
props |= ServerInformalPropertyNoLog
}
stamp, err := NewDNSCryptServerStampFromLegacy(serverAddrStr, serverPkStr, providerName, props)
if err != nil {
return registeredServers, err
}
registeredServer := RegisteredServer{
name: name, stamp: stamp, description: description,
}
dlog.Debugf("Registered [%s] with stamp [%s]", name, stamp.String())
registeredServers = append(registeredServers, registeredServer)
}
return registeredServers, nil
}
func (source *Source) parseV2(prefix string) ([]RegisteredServer, error) {
var registeredServers []RegisteredServer
in := string(source.in)

View File

@ -3,6 +3,7 @@ package main
import (
"encoding/base64"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"net"
@ -10,19 +11,23 @@ import (
"strings"
"github.com/jedisct1/dlog"
"golang.org/x/crypto/ed25519"
)
type StampProtoType uint8
const (
StampProtoTypePlain = StampProtoType(0x00)
StampProtoTypeDoH = StampProtoType(0x02)
StampProtoTypePlain = StampProtoType(0x00)
StampProtoTypeDNSCrypt = StampProtoType(0x01)
StampProtoTypeDoH = StampProtoType(0x02)
)
func (stampProtoType *StampProtoType) String() string {
switch *stampProtoType {
case StampProtoTypePlain:
return "Plain"
case StampProtoTypeDNSCrypt:
return "DNSCrypt"
case StampProtoTypeDoH:
return "DoH"
default:
@ -40,6 +45,23 @@ type ServerStamp struct {
proto StampProtoType
}
func NewDNSCryptServerStampFromLegacy(serverAddrStr string, serverPkStr string, providerName string, props ServerInformalProperties) (ServerStamp, error) {
if net.ParseIP(serverAddrStr) != nil {
serverAddrStr = fmt.Sprintf("%s:%d", serverAddrStr, DefaultPort)
}
serverPk, err := hex.DecodeString(strings.Replace(serverPkStr, ":", "", -1))
if err != nil || len(serverPk) != ed25519.PublicKeySize {
return ServerStamp{}, fmt.Errorf("Unsupported public key: [%s]", serverPkStr)
}
return ServerStamp{
serverAddrStr: serverAddrStr,
serverPk: serverPk,
providerName: providerName,
props: props,
proto: StampProtoTypeDNSCrypt,
}, nil
}
func NewServerStampFromString(stampStr string) (ServerStamp, error) {
if !strings.HasPrefix(stampStr, "sdns://") && !strings.HasPrefix(stampStr, "dnsc://") {
return ServerStamp{}, errors.New("Stamps are expected to start with sdns://")
@ -51,12 +73,58 @@ func NewServerStampFromString(stampStr string) (ServerStamp, error) {
if len(bin) < 1 {
return ServerStamp{}, errors.New("Stamp is too short")
}
if bin[0] == uint8(StampProtoTypeDoH) {
if bin[0] == uint8(StampProtoTypeDNSCrypt) {
return newDNSCryptServerStamp(bin)
} else if bin[0] == uint8(StampProtoTypeDoH) {
return newDoHServerStamp(bin)
}
return ServerStamp{}, errors.New("Unsupported stamp version or protocol")
}
// id(u8)=0x01 props addrLen(1) serverAddr pkStrlen(1) pkStr providerNameLen(1) providerName
func newDNSCryptServerStamp(bin []byte) (ServerStamp, error) {
stamp := ServerStamp{proto: StampProtoTypeDNSCrypt}
if len(bin) < 66 {
return stamp, errors.New("Stamp is too short")
}
stamp.props = ServerInformalProperties(binary.LittleEndian.Uint64(bin[1:9]))
binLen := len(bin)
pos := 9
len := int(bin[pos])
if 1+len >= binLen-pos {
return stamp, errors.New("Invalid stamp")
}
pos++
stamp.serverAddrStr = string(bin[pos : pos+len])
pos += len
if net.ParseIP(strings.TrimRight(strings.TrimLeft(stamp.serverAddrStr, "["), "]")) != nil {
stamp.serverAddrStr = fmt.Sprintf("%s:%d", stamp.serverAddrStr, DefaultPort)
}
len = int(bin[pos])
if 1+len >= binLen-pos {
return stamp, errors.New("Invalid stamp")
}
pos++
stamp.serverPk = bin[pos : pos+len]
pos += len
len = int(bin[pos])
if len >= binLen-pos {
return stamp, errors.New("Invalid stamp")
}
pos++
stamp.providerName = string(bin[pos : pos+len])
pos += len
if pos != binLen {
return stamp, errors.New("Invalid stamp (garbage after end)")
}
return stamp, nil
}
// id(u8)=0x02 props addrLen(1) serverAddr hashLen(1) hash providerNameLen(1) providerName pathLen(1) path
func newDoHServerStamp(bin []byte) (ServerStamp, error) {
@ -120,13 +188,38 @@ func newDoHServerStamp(bin []byte) (ServerStamp, error) {
}
func (stamp *ServerStamp) String() string {
if stamp.proto == StampProtoTypeDoH {
if stamp.proto == StampProtoTypeDNSCrypt {
return stamp.dnsCryptString()
} else if stamp.proto == StampProtoTypeDoH {
return stamp.dohString()
}
dlog.Fatal("Unsupported protocol")
return ""
}
func (stamp *ServerStamp) dnsCryptString() string {
bin := make([]uint8, 9)
bin[0] = uint8(StampProtoTypeDNSCrypt)
binary.LittleEndian.PutUint64(bin[1:9], uint64(stamp.props))
serverAddrStr := stamp.serverAddrStr
if strings.HasSuffix(serverAddrStr, ":"+strconv.Itoa(DefaultPort)) {
serverAddrStr = serverAddrStr[:len(serverAddrStr)-1-len(strconv.Itoa(DefaultPort))]
}
bin = append(bin, uint8(len(serverAddrStr)))
bin = append(bin, []uint8(serverAddrStr)...)
bin = append(bin, uint8(len(stamp.serverPk)))
bin = append(bin, stamp.serverPk...)
bin = append(bin, uint8(len(stamp.providerName)))
bin = append(bin, []uint8(stamp.providerName)...)
str := base64.RawURLEncoding.EncodeToString(bin)
return "sdns://" + str
}
func (stamp *ServerStamp) dohString() string {
bin := make([]uint8, 9)
bin[0] = uint8(StampProtoTypeDoH)