2018-04-01 16:35:32 +02:00
package main
import (
"bytes"
"encoding/binary"
"errors"
"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
}
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 )
2018-04-02 20:55:42 +02:00
var certCountStr string
2018-04-01 16:35:32 +02:00
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 {
2019-10-01 08:02:27 +02:00
dlog . Debugf ( "[%v] Certificate not valid at the current date (now: %v is not in [%v..%v])" , providerName , now , tsBegin , tsEnd )
2018-04-01 16:35:32 +02:00
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 ] )
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' ) )
}
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
}