2018-04-01 16:35:32 +02:00
package main
import (
"bytes"
"encoding/binary"
"errors"
2019-10-19 22:08:02 +02:00
"net"
2018-04-01 16:35:32 +02:00
"strings"
"time"
"github.com/jedisct1/dlog"
"github.com/miekg/dns"
"golang.org/x/crypto/ed25519"
)
type CertInfo struct {
ServerPk [ 32 ] byte
SharedKey [ 32 ] byte
MagicQuery [ ClientMagicLen ] byte
CryptoConstruction CryptoConstruction
ForwardSecurity bool
}
2020-03-13 19:45:25 +01:00
func FetchCurrentDNSCryptCert ( proxy * Proxy , serverName * string , proto string , pk ed25519 . PublicKey , serverAddress string , providerName string , isNew bool , relayUDPAddr * net . UDPAddr , relayTCPAddr * net . TCPAddr , knownBugs ServerBugs ) ( CertInfo , int , bool , error ) {
2018-04-01 16:35:32 +02:00
if len ( pk ) != ed25519 . PublicKeySize {
2020-03-13 19:45:25 +01:00
return CertInfo { } , 0 , false , errors . New ( "Invalid public key length" )
2018-04-01 16:35:32 +02:00
}
if ! strings . HasSuffix ( providerName , "." ) {
providerName = providerName + "."
}
if serverName == nil {
serverName = & providerName
}
2019-12-11 14:02:56 +01:00
query := dns . Msg { }
2018-04-01 16:35:32 +02:00
query . SetQuestion ( providerName , dns . TypeTXT )
2019-10-20 19:40:03 +02:00
if ! strings . HasPrefix ( providerName , "2.dnscrypt-cert." ) {
2020-07-03 12:59:51 +02:00
if relayUDPAddr != nil && ! proxy . anonDirectCertFallback {
2020-07-03 12:58:36 +02:00
dlog . Warnf ( "[%v] uses a non-standard provider name, enable direct cert fallback to use with a relay ('%v' doesn't start with '2.dnscrypt-cert.')" , * serverName , providerName )
} else {
dlog . Warnf ( "[%v] uses a non-standard provider name ('%v' doesn't start with '2.dnscrypt-cert.')" , * serverName , providerName )
relayUDPAddr , relayTCPAddr = nil , nil
}
2019-10-20 19:40:03 +02:00
}
2020-03-13 19:45:25 +01:00
tryFragmentsSupport := true
2020-03-25 20:09:46 +01:00
if knownBugs . fragmentsBlocked {
2020-03-13 19:45:25 +01:00
tryFragmentsSupport = false
}
in , rtt , fragmentsBlocked , err := dnsExchange ( proxy , proto , & query , serverAddress , relayUDPAddr , relayTCPAddr , serverName , tryFragmentsSupport )
2018-04-01 16:35:32 +02:00
if err != nil {
dlog . Noticef ( "[%s] TIMEOUT" , * serverName )
2020-03-13 19:45:25 +01:00
return CertInfo { } , 0 , fragmentsBlocked , err
2018-04-01 16:35:32 +02:00
}
now := uint32 ( time . Now ( ) . Unix ( ) )
certInfo := CertInfo { CryptoConstruction : UndefinedConstruction }
highestSerial := uint32 ( 0 )
2018-04-02 20:55:42 +02:00
var certCountStr string
2018-04-01 16:35:32 +02:00
for _ , answerRr := range in . Answer {
2019-10-17 17:29:08 +02:00
var txt string
if t , ok := answerRr . ( * dns . TXT ) ; ! ok {
2019-11-17 20:37:55 +01:00
dlog . Noticef ( "[%v] Extra record of type [%v] found in certificate" , * serverName , answerRr . Header ( ) . Rrtype )
2019-10-17 17:29:08 +02:00
continue
} else {
txt = strings . Join ( t . Txt , "" )
}
2019-11-01 16:37:33 +01:00
binCert := packTxtString ( txt )
2018-04-01 16:35:32 +02:00
if len ( binCert ) < 124 {
2019-11-17 20:37:55 +01:00
dlog . Warnf ( "[%v] Certificate too short" , * serverName )
2018-04-01 16:35:32 +02:00
continue
}
if ! bytes . Equal ( binCert [ : 4 ] , CertMagic [ : 4 ] ) {
2019-11-17 20:37:55 +01:00
dlog . Warnf ( "[%v] Invalid cert magic" , * serverName )
2018-04-01 16:35:32 +02:00
continue
}
cryptoConstruction := CryptoConstruction ( 0 )
switch esVersion := binary . BigEndian . Uint16 ( binCert [ 4 : 6 ] ) ; esVersion {
case 0x0001 :
cryptoConstruction = XSalsa20Poly1305
case 0x0002 :
cryptoConstruction = XChacha20Poly1305
default :
2019-11-17 20:37:55 +01:00
dlog . Noticef ( "[%v] Unsupported crypto construction" , * serverName )
2018-04-01 16:35:32 +02:00
continue
}
signature := binCert [ 8 : 72 ]
signed := binCert [ 72 : ]
if ! ed25519 . Verify ( pk , signed , signature ) {
2019-11-17 20:40:59 +01:00
dlog . Warnf ( "[%v] Incorrect signature for provider name: [%v]" , * serverName , providerName )
2018-04-01 16:35:32 +02:00
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 {
2019-11-17 20:40:59 +01:00
dlog . Warnf ( "[%v] certificate ends before it starts (%v >= %v)" , * serverName , tsBegin , tsEnd )
2018-04-01 16:35:32 +02:00
continue
}
ttl := tsEnd - tsBegin
if ttl > 86400 * 7 {
2019-11-17 20:40:59 +01:00
dlog . Infof ( "[%v] the key validity period for this server is excessively long (%d days), significantly reducing reliability and forward security." , * serverName , ttl / 86400 )
2018-04-01 16:35:32 +02:00
daysLeft := ( tsEnd - now ) / 86400
if daysLeft < 1 {
2019-11-17 20:40:59 +01:00
dlog . Criticalf ( "[%v] certificate will expire today -- Switch to a different resolver as soon as possible" , * serverName )
2018-04-01 16:35:32 +02:00
} else if daysLeft <= 7 {
2019-11-17 20:40:59 +01:00
dlog . Warnf ( "[%v] certificate is about to expire -- if you don't manage this server, tell the server operator about it" , * serverName )
2018-04-01 16:35:32 +02:00
} else if daysLeft <= 30 {
2019-11-17 20:40:59 +01:00
dlog . Infof ( "[%v] certificate will expire in %d days" , * serverName , daysLeft )
2018-04-01 16:35:32 +02:00
}
certInfo . ForwardSecurity = false
} else {
certInfo . ForwardSecurity = true
}
if ! proxy . certIgnoreTimestamp {
if now > tsEnd || now < tsBegin {
2019-11-17 20:40:59 +01:00
dlog . Debugf ( "[%v] Certificate not valid at the current date (now: %v is not in [%v..%v])" , * serverName , now , tsBegin , tsEnd )
2018-04-01 16:35:32 +02:00
continue
}
}
if serial < highestSerial {
2019-11-17 20:40:59 +01:00
dlog . Debugf ( "[%v] Superseded by a previous certificate" , * serverName )
2018-04-01 16:35:32 +02:00
continue
}
if serial == highestSerial {
if cryptoConstruction < certInfo . CryptoConstruction {
2019-11-17 20:40:59 +01:00
dlog . Debugf ( "[%v] Keeping the previous, preferred crypto construction" , * serverName )
2018-04-01 16:35:32 +02:00
continue
} else {
2019-11-17 20:40:59 +01:00
dlog . Debugf ( "[%v] Upgrading the construction from %v to %v" , * serverName , certInfo . CryptoConstruction , cryptoConstruction )
2018-04-01 16:35:32 +02:00
}
}
if cryptoConstruction != XChacha20Poly1305 && cryptoConstruction != XSalsa20Poly1305 {
2019-11-17 20:40:59 +01:00
dlog . Noticef ( "[%v] Cryptographic construction %v not supported" , * serverName , cryptoConstruction )
2018-04-01 16:35:32 +02:00
continue
}
var serverPk [ 32 ] byte
copy ( serverPk [ : ] , binCert [ 72 : 104 ] )
2018-04-09 03:12:34 +02:00
sharedKey := ComputeSharedKey ( cryptoConstruction , & proxy . proxySecretKey , & serverPk , & providerName )
2018-04-01 16:35:32 +02:00
certInfo . SharedKey = sharedKey
highestSerial = serial
certInfo . CryptoConstruction = cryptoConstruction
copy ( certInfo . ServerPk [ : ] , serverPk [ : ] )
copy ( certInfo . MagicQuery [ : ] , binCert [ 104 : 112 ] )
if isNew {
2019-04-08 08:29:59 +02:00
dlog . Noticef ( "[%s] OK (DNSCrypt) - rtt: %dms%s" , * serverName , rtt . Nanoseconds ( ) / 1000000 , certCountStr )
2018-04-01 16:35:32 +02:00
} else {
2019-04-08 08:29:59 +02:00
dlog . Infof ( "[%s] OK (DNSCrypt) - rtt: %dms%s" , * serverName , rtt . Nanoseconds ( ) / 1000000 , certCountStr )
2018-04-01 16:35:32 +02:00
}
2018-04-02 20:55:42 +02:00
certCountStr = " - additional certificate"
2018-04-01 16:35:32 +02:00
}
if certInfo . CryptoConstruction == UndefinedConstruction {
2020-03-13 19:45:25 +01:00
return certInfo , 0 , fragmentsBlocked , errors . New ( "No useable certificate found" )
2018-04-01 16:35:32 +02:00
}
2020-03-13 19:45:25 +01:00
return certInfo , int ( rtt . Nanoseconds ( ) / 1000000 ) , fragmentsBlocked , nil
2018-04-01 16:35:32 +02:00
}
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' ) )
}
2019-11-01 16:37:33 +01:00
func packTxtString ( s string ) [ ] byte {
2018-04-01 16:35:32 +02:00
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 ] )
}
}
2019-11-01 16:37:33 +01:00
return msg
2018-04-01 16:35:32 +02:00
}
2019-10-19 22:08:02 +02:00
2020-03-13 19:45:25 +01:00
type dnsExchangeResponse struct {
response * dns . Msg
rtt time . Duration
priority int
fragmentsBlocked bool
err error
}
func dnsExchange ( proxy * Proxy , proto string , query * dns . Msg , serverAddress string , relayUDPAddr * net . UDPAddr , relayTCPAddr * net . TCPAddr , serverName * string , tryFragmentsSupport bool ) ( * dns . Msg , time . Duration , bool , error ) {
for {
2020-03-26 12:53:22 +01:00
cancelChannel := make ( chan struct { } )
2020-03-13 19:45:25 +01:00
channel := make ( chan dnsExchangeResponse )
var err error
options := 0
2020-03-26 12:53:22 +01:00
for tries := 0 ; tries < 3 ; tries ++ {
2020-03-24 12:38:23 +01:00
if tryFragmentsSupport {
2020-03-26 10:37:56 +01:00
queryCopy := query . Copy ( )
queryCopy . Id += uint16 ( options )
2020-03-20 21:45:09 +01:00
go func ( query * dns . Msg , delay time . Duration ) {
2020-03-13 19:45:25 +01:00
option := _dnsExchange ( proxy , proto , query , serverAddress , relayUDPAddr , relayTCPAddr , 1500 )
option . fragmentsBlocked = false
option . priority = 0
channel <- option
2020-03-26 12:53:22 +01:00
time . Sleep ( delay )
select {
case <- cancelChannel :
return
default :
}
2020-03-26 10:37:56 +01:00
} ( queryCopy , time . Duration ( 200 * tries ) * time . Millisecond )
2020-03-13 19:45:25 +01:00
options ++
}
2020-03-26 10:37:56 +01:00
queryCopy := query . Copy ( )
queryCopy . Id += uint16 ( options )
2020-03-20 21:45:09 +01:00
go func ( query * dns . Msg , delay time . Duration ) {
2020-03-13 19:45:25 +01:00
option := _dnsExchange ( proxy , proto , query , serverAddress , relayUDPAddr , relayTCPAddr , 480 )
option . fragmentsBlocked = true
option . priority = 1
channel <- option
2020-03-26 12:53:22 +01:00
time . Sleep ( delay )
select {
case <- cancelChannel :
return
default :
}
2020-03-26 10:37:56 +01:00
} ( queryCopy , time . Duration ( 250 * tries ) * time . Millisecond )
2020-03-13 19:45:25 +01:00
options ++
}
var bestOption * dnsExchangeResponse
for i := 0 ; i < options ; i ++ {
if dnsExchangeResponse := <- channel ; dnsExchangeResponse . err == nil {
if bestOption == nil || dnsExchangeResponse . priority < bestOption . priority ||
( dnsExchangeResponse . priority == bestOption . priority && dnsExchangeResponse . rtt < bestOption . rtt ) {
bestOption = & dnsExchangeResponse
if bestOption . priority == 0 {
2020-03-26 12:53:22 +01:00
close ( cancelChannel )
2020-03-13 19:45:25 +01:00
break
}
}
} else {
err = dnsExchangeResponse . err
}
}
if bestOption != nil {
if bestOption . fragmentsBlocked {
dlog . Debugf ( "Certificate retrieval for [%v] succeeded but server is blocking fragments" , * serverName )
} else {
dlog . Debugf ( "Certificate retrieval for [%v] succeeded" , * serverName )
}
return bestOption . response , bestOption . rtt , bestOption . fragmentsBlocked , nil
}
2020-07-03 12:59:51 +02:00
if relayUDPAddr == nil || ! proxy . anonDirectCertFallback {
2020-03-13 19:45:25 +01:00
if err == nil {
err = errors . New ( "Unable to reach the server" )
}
return nil , 0 , false , err
2019-10-20 19:11:54 +02:00
}
2020-03-13 19:45:25 +01:00
dlog . Infof ( "Unable to get a certificate for [%v] via relay [%v], retrying over a direct connection" , * serverName , relayUDPAddr . IP )
relayUDPAddr , relayTCPAddr = nil , nil
2019-10-20 12:26:12 +02:00
}
}
2020-03-13 19:45:25 +01:00
func _dnsExchange ( proxy * Proxy , proto string , query * dns . Msg , serverAddress string , relayUDPAddr * net . UDPAddr , relayTCPAddr * net . TCPAddr , paddedLen int ) dnsExchangeResponse {
2019-10-19 22:08:02 +02:00
var packet [ ] byte
var rtt time . Duration
2020-03-26 10:37:56 +01:00
2019-10-19 22:08:02 +02:00
if proto == "udp" {
qNameLen , padding := len ( query . Question [ 0 ] . Name ) , 0
2020-03-13 19:45:25 +01:00
if qNameLen < paddedLen {
padding = paddedLen - qNameLen
2019-10-19 22:08:02 +02:00
}
if padding > 0 {
opt := new ( dns . OPT )
opt . Hdr . Name = "."
ext := new ( dns . EDNS0_PADDING )
ext . Padding = make ( [ ] byte , padding )
opt . Option = append ( opt . Option , ext )
query . Extra = [ ] dns . RR { opt }
}
binQuery , err := query . Pack ( )
if err != nil {
2020-03-13 19:45:25 +01:00
return dnsExchangeResponse { err : err }
2019-10-19 22:08:02 +02:00
}
udpAddr , err := net . ResolveUDPAddr ( "udp" , serverAddress )
if err != nil {
2020-03-13 19:45:25 +01:00
return dnsExchangeResponse { err : err }
2019-10-19 22:08:02 +02:00
}
2019-10-19 23:50:05 +02:00
upstreamAddr := udpAddr
if relayUDPAddr != nil {
proxy . prepareForRelay ( udpAddr . IP , udpAddr . Port , & binQuery )
upstreamAddr = relayUDPAddr
}
2019-10-19 22:08:02 +02:00
now := time . Now ( )
2019-10-19 23:50:05 +02:00
pc , err := net . DialUDP ( "udp" , nil , upstreamAddr )
2019-10-19 22:08:02 +02:00
if err != nil {
2020-03-13 19:45:25 +01:00
return dnsExchangeResponse { err : err }
2019-10-19 22:08:02 +02:00
}
defer pc . Close ( )
2020-03-13 18:44:30 +01:00
if err := pc . SetDeadline ( time . Now ( ) . Add ( proxy . timeout ) ) ; err != nil {
2020-03-13 19:45:25 +01:00
return dnsExchangeResponse { err : err }
2019-12-09 09:49:33 +01:00
}
2020-03-13 18:44:30 +01:00
if _ , err := pc . Write ( binQuery ) ; err != nil {
2020-03-13 19:45:25 +01:00
return dnsExchangeResponse { err : err }
2019-12-09 09:49:33 +01:00
}
2019-10-19 22:08:02 +02:00
packet = make ( [ ] byte , MaxDNSPacketSize )
length , err := pc . Read ( packet )
if err != nil {
2020-03-13 19:45:25 +01:00
return dnsExchangeResponse { err : err }
2019-10-19 22:08:02 +02:00
}
rtt = time . Since ( now )
packet = packet [ : length ]
} else {
binQuery , err := query . Pack ( )
if err != nil {
2020-03-13 19:45:25 +01:00
return dnsExchangeResponse { err : err }
2019-10-19 22:08:02 +02:00
}
tcpAddr , err := net . ResolveTCPAddr ( "tcp" , serverAddress )
if err != nil {
2020-03-13 19:45:25 +01:00
return dnsExchangeResponse { err : err }
2019-10-19 22:08:02 +02:00
}
2019-10-20 21:45:19 +02:00
upstreamAddr := tcpAddr
2019-11-01 10:07:57 +01:00
if relayTCPAddr != nil {
2019-10-20 21:45:19 +02:00
proxy . prepareForRelay ( tcpAddr . IP , tcpAddr . Port , & binQuery )
upstreamAddr = relayTCPAddr
}
2019-10-19 22:08:02 +02:00
now := time . Now ( )
var pc net . Conn
proxyDialer := proxy . xTransport . proxyDialer
if proxyDialer == nil {
2019-10-20 21:45:19 +02:00
pc , err = net . DialTCP ( "tcp" , nil , upstreamAddr )
2019-10-19 22:08:02 +02:00
} else {
pc , err = ( * proxyDialer ) . Dial ( "tcp" , tcpAddr . String ( ) )
}
if err != nil {
2020-03-13 19:45:25 +01:00
return dnsExchangeResponse { err : err }
2019-10-19 22:08:02 +02:00
}
defer pc . Close ( )
2020-03-13 18:44:30 +01:00
if err := pc . SetDeadline ( time . Now ( ) . Add ( proxy . timeout ) ) ; err != nil {
2020-03-13 19:45:25 +01:00
return dnsExchangeResponse { err : err }
2019-12-09 09:49:33 +01:00
}
2019-10-19 22:08:02 +02:00
binQuery , err = PrefixWithSize ( binQuery )
if err != nil {
2020-03-13 19:45:25 +01:00
return dnsExchangeResponse { err : err }
2019-10-19 22:08:02 +02:00
}
2020-03-13 18:44:30 +01:00
if _ , err := pc . Write ( binQuery ) ; err != nil {
2020-03-13 19:45:25 +01:00
return dnsExchangeResponse { err : err }
2019-12-09 09:49:33 +01:00
}
2019-10-19 22:08:02 +02:00
packet , err = ReadPrefixed ( & pc )
if err != nil {
2020-03-13 19:45:25 +01:00
return dnsExchangeResponse { err : err }
2019-10-19 22:08:02 +02:00
}
rtt = time . Since ( now )
}
msg := dns . Msg { }
if err := msg . Unpack ( packet ) ; err != nil {
2020-03-13 19:45:25 +01:00
return dnsExchangeResponse { err : err }
2019-10-19 22:08:02 +02:00
}
2020-03-13 19:45:25 +01:00
return dnsExchangeResponse { response : & msg , rtt : rtt , err : nil }
2019-10-19 22:08:02 +02:00
}