12 class DNSCryptResolverCertificate(object):
13 DNSCRYPT_CERT_MAGIC
= '\x44\x4e\x53\x43'
14 DNSCRYPT_ES_VERSION
= '\x00\x01'
15 DNSCRYPT_PROTOCOL_MIN_VERSION
= '\x00\x00'
17 def __init__(self
, serial
, validFrom
, validUntil
, publicKey
, clientMagic
):
19 self
.validFrom
= validFrom
20 self
.validUntil
= validUntil
21 self
.publicKey
= publicKey
22 self
.clientMagic
= clientMagic
26 return self
.validFrom
<= now
and self
.validUntil
>= now
29 def fromBinary(binary
, providerFP
):
30 if len(binary
) != 124:
31 raise Exception("Invalid binary certificate")
33 certMagic
= binary
[0:4]
34 esVersion
= binary
[4:6]
35 protocolMinVersion
= binary
[6:8]
37 if certMagic
!= DNSCryptResolverCertificate
.DNSCRYPT_CERT_MAGIC
or esVersion
!= DNSCryptResolverCertificate
.DNSCRYPT_ES_VERSION
or protocolMinVersion
!= DNSCryptResolverCertificate
.DNSCRYPT_PROTOCOL_MIN_VERSION
:
38 raise Exception("Invalid binary certificate")
40 orig
= libnacl
.crypto_sign_open(binary
[8:124], providerFP
)
42 resolverPK
= orig
[0:32]
43 clientMagic
= orig
[32:40]
44 serial
= struct
.unpack_from("!I", orig
[40:44])[0]
45 validFrom
= struct
.unpack_from("!I", orig
[44:48])[0]
46 validUntil
= struct
.unpack_from("!I", orig
[48:52])[0]
47 return DNSCryptResolverCertificate(serial
, validFrom
, validUntil
, resolverPK
, clientMagic
)
49 class DNSCryptClient(object):
50 DNSCRYPT_NONCE_SIZE
= 24
51 DNSCRYPT_MAC_SIZE
= 16
52 DNSCRYPT_PADDED_BLOCK_SIZE
= 64
53 DNSCRYPT_MIN_UDP_LENGTH
= 256
54 DNSCRYPT_RESOLVER_MAGIC
= '\x72\x36\x66\x6e\x76\x57\x6a\x38'
57 def _addrToSocketType(addr
):
60 socket
.inet_pton(socket
.AF_INET6
, addr
)
61 result
= socket
.AF_INET6
63 socket
.inet_pton(socket
.AF_INET
, addr
)
64 result
= socket
.AF_INET
68 def __init__(self
, providerName
, providerFingerprint
, resolverAddress
, resolverPort
=443, timeout
=2):
69 self
._providerName
= providerName
70 self
._providerFingerprint
= binascii
.unhexlify(providerFingerprint
.lower().replace(':', ''))
71 self
._resolverAddress
= resolverAddress
72 self
._resolverPort
= resolverPort
73 self
._resolverCertificates
= []
74 self
._publicKey
, self
._privateKey
= libnacl
.crypto_box_keypair()
75 self
._timeout
= timeout
77 addrType
= self
._addrToSocketType
(self
._resolverAddress
)
78 self
._sock
= socket
.socket(addrType
, socket
.SOCK_DGRAM
)
79 self
._sock
.settimeout(timeout
)
80 self
._sock
.connect((self
._resolverAddress
, self
._resolverPort
))
82 def _sendQuery(self
, queryContent
, tcp
=False):
84 addrType
= self
._addrToSocketType
(self
._resolverAddress
)
85 sock
= socket
.socket(addrType
, socket
.SOCK_STREAM
)
86 sock
.settimeout(self
._timeout
)
87 sock
.connect((self
._resolverAddress
, self
._resolverPort
))
88 sock
.send(struct
.pack("!H", len(queryContent
)))
92 sock
.send(queryContent
)
98 (rlen
,) = struct
.unpack("!H", got
)
99 data
= sock
.recv(rlen
)
101 data
= sock
.recv(4096)
105 def _hasValidResolverCertificate(self
):
107 for cert
in self
._resolverCertificates
:
113 def clearExpiredResolverCertificates(self
):
116 for cert
in self
._resolverCertificates
:
118 newCerts
.append(cert
)
120 self
._resolverCertificates
= newCerts
122 def refreshResolverCertificates(self
):
123 self
.clearExpiredResolverCertificates()
125 query
= dns
.message
.make_query(self
._providerName
, dns
.rdatatype
.TXT
, dns
.rdataclass
.IN
)
126 data
= self
._sendQuery
(query
.to_wire())
128 response
= dns
.message
.from_wire(data
)
129 if response
.rcode() != dns
.rcode
.NOERROR
or len(response
.answer
) != 1:
130 raise Exception("Invalid response to public key request")
132 an
= response
.answer
[0]
133 if an
.rdclass
!= dns
.rdataclass
.IN
or an
.rdtype
!= dns
.rdatatype
.TXT
or len(an
.items
) == 0:
134 raise Exception("Invalid response to public key request")
136 self
._resolverCertificates
= []
138 for item
in an
.items
:
139 if len(item
.strings
) != 1:
142 cert
= DNSCryptResolverCertificate
.fromBinary(item
.strings
[0], self
._providerFingerprint
)
144 self
._resolverCertificates
.append(cert
)
146 def getResolverCertificate(self
):
147 certs
= self
._resolverCertificates
151 if result
is None or cert
.serial
> result
.serial
:
156 def getAllResolverCertificates(self
, onlyValid
=False):
157 certs
= self
._resolverCertificates
160 if not onlyValid
or cert
.isValid():
166 def _generateNonce():
167 nonce
= libnacl
.utils
.rand_nonce()
168 return nonce
[:(DNSCryptClient
.DNSCRYPT_NONCE_SIZE
/ 2)]
170 def _encryptQuery(self
, queryContent
, resolverCert
, nonce
, tcp
=False):
171 header
= resolverCert
.clientMagic
+ self
._publicKey
+ nonce
172 requiredSize
= len(header
) + self
.DNSCRYPT_MAC_SIZE
+ len(queryContent
)
173 paddingSize
= self
.DNSCRYPT_PADDED_BLOCK_SIZE
- (len(queryContent
) % self
.DNSCRYPT_PADDED_BLOCK_SIZE
)
174 # padding size should be DNSCRYPT_PADDED_BLOCK_SIZE <= padding size <= 4096
175 if not tcp
and requiredSize
< self
.DNSCRYPT_MIN_UDP_LENGTH
:
176 paddingSize
+= self
.DNSCRYPT_MIN_UDP_LENGTH
- requiredSize
177 requiredSize
= self
.DNSCRYPT_MIN_UDP_LENGTH
181 while idx
< (paddingSize
- 1):
182 padding
= padding
+ '\x00'
185 data
= queryContent
+ padding
186 nonce
= nonce
+ ('\x00'*(self
.DNSCRYPT_NONCE_SIZE
/ 2))
187 box
= libnacl
.crypto_box(data
, nonce
, resolverCert
.publicKey
, self
._privateKey
)
190 def _decryptResponse(self
, encryptedResponse
, resolverCert
, clientNonce
):
191 resolverMagic
= encryptedResponse
[:8]
192 if resolverMagic
!= self
.DNSCRYPT_RESOLVER_MAGIC
:
193 raise Exception("Invalid encrypted response: bad resolver magic")
195 nonce
= encryptedResponse
[8:32]
196 if nonce
[0:self
.DNSCRYPT_NONCE_SIZE
/ 2] != clientNonce
:
197 raise Exception("Invalid encrypted response: bad nonce")
199 cleartext
= libnacl
.crypto_box_open(encryptedResponse
[32:], nonce
, resolverCert
.publicKey
, self
._privateKey
)
200 idx
= len(cleartext
) - 1
202 if cleartext
[idx
] != '\x00':
206 if idx
== 0 or cleartext
[idx
] != '\x80':
207 raise Exception("Invalid encrypted response: invalid padding")
210 paddingLen
= len(cleartext
) - idx
212 return cleartext
[:idx
+1]
214 def query(self
, queryContent
, tcp
=False):
216 if not self
._hasValidResolverCertificate
():
217 self
.refreshResolverCertificates()
219 nonce
= self
._generateNonce
()
220 resolverCert
= self
.getResolverCertificate()
221 if resolverCert
is None:
222 raise Exception("No valid certificate found")
223 encryptedQuery
= self
._encryptQuery
(queryContent
, resolverCert
, nonce
, tcp
)
224 encryptedResponse
= self
._sendQuery
(encryptedQuery
, tcp
)
225 response
= self
._decryptResponse
(encryptedResponse
, resolverCert
, nonce
)