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
}
2020-12-12 21:18:32 +01:00
func FetchCurrentDNSCryptCert ( proxy * Proxy , serverName * string , proto string , pk ed25519 . PublicKey , serverAddress string , providerName string , isNew bool , relay * DNSCryptRelay , 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-12-12 21:18:32 +01:00
if relay != 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 )
2020-12-12 21:18:32 +01:00
relay = nil
2020-07-03 12:58:36 +02:00
}
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
}
2020-12-12 23:09:15 +01:00
in , rtt , fragmentsBlocked , err := DNSExchange ( proxy , proto , & query , serverAddress , relay , 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 , "" )
}
2020-12-12 23:09:15 +01:00
binCert := PackTXTRR ( 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 )
2020-10-12 17:58:08 +02:00
} else {
dlog . Debugf ( "[%v] certificate still valid for %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
}