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
}
2019-10-19 23:50:05 +02:00
func FetchCurrentDNSCryptCert ( proxy * Proxy , serverName * string , proto string , pk ed25519 . PublicKey , serverAddress string , providerName string , isNew bool , relayUDPAddr * net . UDPAddr , relayTCPAddr * net . TCPAddr ) ( CertInfo , int , error ) {
2018-04-01 16:35:32 +02:00
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 )
2019-10-20 19:40:03 +02:00
if ! strings . HasPrefix ( providerName , "2.dnscrypt-cert." ) {
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:11:54 +02:00
in , rtt , err := dnsExchange ( proxy , proto , query , serverAddress , relayUDPAddr , relayTCPAddr , serverName )
2018-04-01 16:35:32 +02:00
if err != nil {
dlog . Noticef ( "[%s] TIMEOUT" , * serverName )
return CertInfo { } , 0 , err
}
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 {
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' ) )
}
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
2019-10-20 19:11:54 +02:00
func dnsExchange ( proxy * Proxy , proto string , query * dns . Msg , serverAddress string , relayUDPAddr * net . UDPAddr , relayTCPAddr * net . TCPAddr , serverName * string ) ( * dns . Msg , time . Duration , error ) {
2019-10-20 12:26:12 +02:00
response , ttl , err := _dnsExchange ( proxy , proto , query , serverAddress , relayUDPAddr , relayTCPAddr )
if err != nil && relayUDPAddr != nil {
2019-10-20 19:11:54 +02:00
dlog . Debugf ( "Unable to get a certificate for [%v] via relay [%v], retrying over a direct connection" , * serverName , relayUDPAddr . IP )
2019-10-20 12:26:12 +02:00
response , ttl , err = _dnsExchange ( proxy , proto , query , serverAddress , nil , nil )
2019-10-20 19:11:54 +02:00
if err == nil {
dlog . Infof ( "Direct certificate retrieval for [%v] succeeded" , * serverName )
}
2019-10-20 12:26:12 +02:00
}
return response , ttl , err
}
func _dnsExchange ( proxy * Proxy , proto string , query * dns . Msg , serverAddress string , relayUDPAddr * net . UDPAddr , relayTCPAddr * net . TCPAddr ) ( * dns . Msg , time . Duration , error ) {
2019-10-19 22:08:02 +02:00
var packet [ ] byte
var rtt time . Duration
if proto == "udp" {
qNameLen , padding := len ( query . Question [ 0 ] . Name ) , 0
2019-10-20 18:02:14 +02:00
if qNameLen < 480 {
padding = 480 - 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 {
return nil , 0 , err
}
udpAddr , err := net . ResolveUDPAddr ( "udp" , serverAddress )
if err != nil {
return nil , 0 , err
}
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 {
return nil , 0 , err
}
defer pc . Close ( )
2019-12-09 10:07:05 +01:00
if err = pc . SetDeadline ( time . Now ( ) . Add ( proxy . timeout ) ) ; err != nil {
2019-12-09 09:49:33 +01:00
return nil , 0 , err
}
2019-12-09 10:07:05 +01:00
if _ , err = pc . Write ( binQuery ) ; err != nil {
2019-12-09 09:49:33 +01:00
return nil , 0 , err
}
2019-10-19 22:08:02 +02:00
packet = make ( [ ] byte , MaxDNSPacketSize )
length , err := pc . Read ( packet )
if err != nil {
return nil , 0 , err
}
rtt = time . Since ( now )
packet = packet [ : length ]
} else {
binQuery , err := query . Pack ( )
if err != nil {
return nil , 0 , err
}
tcpAddr , err := net . ResolveTCPAddr ( "tcp" , serverAddress )
if err != nil {
return nil , 0 , err
}
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 {
return nil , 0 , err
}
defer pc . Close ( )
2019-12-09 10:07:05 +01:00
if err = pc . SetDeadline ( time . Now ( ) . Add ( proxy . timeout ) ) ; err != nil {
2019-12-09 09:49:33 +01:00
return nil , 0 , err
}
2019-10-19 22:08:02 +02:00
binQuery , err = PrefixWithSize ( binQuery )
if err != nil {
return nil , 0 , err
}
2019-12-09 10:07:05 +01:00
if _ , err = pc . Write ( binQuery ) ; err != nil {
2019-12-09 09:49:33 +01:00
return nil , 0 , err
}
2019-10-19 22:08:02 +02:00
packet , err = ReadPrefixed ( & pc )
if err != nil {
return nil , 0 , err
}
rtt = time . Since ( now )
}
msg := dns . Msg { }
if err := msg . Unpack ( packet ) ; err != nil {
return nil , 0 , err
}
return & msg , rtt , nil
}