10 class DNSCryptResolverCertificate(object):
11 DNSCRYPT_CERT_MAGIC
= '\x44\x4e\x53\x43'
12 DNSCRYPT_ES_VERSION
= '\x00\x01'
13 DNSCRYPT_PROTOCOL_MIN_VERSION
= '\x00\x00'
15 def __init__(self
, serial
, validFrom
, validUntil
, publicKey
, clientMagic
):
17 self
.validFrom
= validFrom
18 self
.validUntil
= validUntil
19 self
.publicKey
= publicKey
20 self
.clientMagic
= clientMagic
24 return self
.validFrom
<= now
and self
.validUntil
>= now
27 def fromBinary(binary
, providerFP
):
28 if len(binary
) != 124:
29 raise Exception("Invalid binary certificate")
31 certMagic
= binary
[0:4]
32 esVersion
= binary
[4:6]
33 protocolMinVersion
= binary
[6:8]
35 if certMagic
!= DNSCryptResolverCertificate
.DNSCRYPT_CERT_MAGIC
or esVersion
!= DNSCryptResolverCertificate
.DNSCRYPT_ES_VERSION
or protocolMinVersion
!= DNSCryptResolverCertificate
.DNSCRYPT_PROTOCOL_MIN_VERSION
:
36 raise Exception("Invalid binary certificate")
38 orig
= libnacl
.crypto_sign_open(binary
[8:124], providerFP
)
40 resolverPK
= orig
[0:32]
41 clientMagic
= orig
[32:40]
42 serial
= struct
.unpack_from("I", orig
[40:44])[0]
43 validFrom
= struct
.unpack_from("!I", orig
[44:48])[0]
44 validUntil
= struct
.unpack_from("!I", orig
[48:52])[0]
45 return DNSCryptResolverCertificate(serial
, validFrom
, validUntil
, resolverPK
, clientMagic
)
47 class DNSCryptClient(object):
48 DNSCRYPT_NONCE_SIZE
= 24
49 DNSCRYPT_MAC_SIZE
= 16
50 DNSCRYPT_PADDED_BLOCK_SIZE
= 64
51 DNSCRYPT_MIN_UDP_LENGTH
= 256
52 DNSCRYPT_RESOLVER_MAGIC
= '\x72\x36\x66\x6e\x76\x57\x6a\x38'
55 def _addrToSocketType(addr
):
58 socket
.inet_pton(socket
.AF_INET6
, addr
)
59 result
= socket
.AF_INET6
61 socket
.inet_pton(socket
.AF_INET
, addr
)
62 result
= socket
.AF_INET
66 def __init__(self
, providerName
, providerFingerprint
, resolverAddress
, resolverPort
=443, timeout
=2):
67 self
._providerName
= providerName
68 self
._providerFingerprint
= providerFingerprint
.lower().replace(':', '').decode('hex')
69 self
._resolverAddress
= resolverAddress
70 self
._resolverPort
= resolverPort
71 self
._resolverCertificates
= []
72 self
._publicKey
, self
._privateKey
= libnacl
.crypto_box_keypair()
73 self
._timeout
= timeout
75 addrType
= self
._addrToSocketType
(self
._resolverAddress
)
76 self
._sock
= socket
.socket(addrType
, socket
.SOCK_DGRAM
)
77 self
._sock
.settimeout(timeout
)
78 self
._sock
.connect((self
._resolverAddress
, self
._resolverPort
))
80 def _sendQuery(self
, queryContent
, tcp
=False):
82 addrType
= self
._addrToSocketType
(self
._resolverAddress
)
83 sock
= socket
.socket(addrType
, socket
.SOCK_STREAM
)
84 sock
.settimeout(self
._timeout
)
85 sock
.connect((self
._resolverAddress
, self
._resolverPort
))
86 sock
.send(struct
.pack("!H", len(queryContent
)))
90 sock
.send(queryContent
)
97 (rlen
,) = struct
.unpack("!H", got
)
98 data
= sock
.recv(rlen
)
100 data
= sock
.recv(4096)
104 def _hasValidResolverCertificate(self
):
106 for cert
in self
._resolverCertificates
:
112 def clearExpiredResolverCertificates(self
):
115 for cert
in self
._resolverCertificates
:
117 newCerts
.append(cert
)
119 self
._resolverCertificates
= newCerts
121 def refreshResolverCertificates(self
):
122 self
.clearExpiredResolverCertificates()
124 query
= dns
.message
.make_query(self
._providerName
, dns
.rdatatype
.TXT
, dns
.rdataclass
.IN
)
125 data
= self
._sendQuery
(query
.to_wire())
127 response
= dns
.message
.from_wire(data
)
128 if response
.rcode() != dns
.rcode
.NOERROR
or len(response
.answer
) != 1:
129 raise Exception("Invalid response to public key request")
131 an
= response
.answer
[0]
132 if an
.rdclass
!= dns
.rdataclass
.IN
or an
.rdtype
!= dns
.rdatatype
.TXT
or len(an
.items
) == 0:
133 raise Exception("Invalid response to public key request")
135 for item
in an
.items
:
136 if len(item
.strings
) != 1:
139 cert
= DNSCryptResolverCertificate
.fromBinary(item
.strings
[0], self
._providerFingerprint
)
141 self
._resolverCertificates
.append(cert
)
143 def getResolverCertificate(self
):
144 certs
= self
._resolverCertificates
148 if result
is None or cert
.serial
> result
.serial
:
154 def _generateNonce():
155 nonce
= libnacl
.utils
.rand_nonce()
156 return nonce
[:(DNSCryptClient
.DNSCRYPT_NONCE_SIZE
/ 2)]
158 def _encryptQuery(self
, queryContent
, resolverCert
, nonce
, tcp
=False):
159 header
= resolverCert
.clientMagic
+ self
._publicKey
+ nonce
160 requiredSize
= len(header
) + self
.DNSCRYPT_MAC_SIZE
+ len(queryContent
)
161 paddingSize
= self
.DNSCRYPT_PADDED_BLOCK_SIZE
- (len(queryContent
) % self
.DNSCRYPT_PADDED_BLOCK_SIZE
)
162 # padding size should be DNSCRYPT_PADDED_BLOCK_SIZE <= padding size <= 4096
163 if not tcp
and requiredSize
< self
.DNSCRYPT_MIN_UDP_LENGTH
:
164 paddingSize
+= self
.DNSCRYPT_MIN_UDP_LENGTH
- requiredSize
165 requiredSize
= self
.DNSCRYPT_MIN_UDP_LENGTH
169 while idx
< (paddingSize
- 1):
170 padding
= padding
+ '\x00'
173 data
= queryContent
+ padding
174 nonce
= nonce
+ ('\x00'*(self
.DNSCRYPT_NONCE_SIZE
/ 2))
175 box
= libnacl
.crypto_box(data
, nonce
, resolverCert
.publicKey
, self
._privateKey
)
178 def _decryptResponse(self
, encryptedResponse
, resolverCert
, clientNonce
):
179 resolverMagic
= encryptedResponse
[:8]
180 if resolverMagic
!= self
.DNSCRYPT_RESOLVER_MAGIC
:
181 raise Exception("Invalid encrypted response: bad resolver magic")
183 nonce
= encryptedResponse
[8:32]
184 if nonce
[0:self
.DNSCRYPT_NONCE_SIZE
/ 2] != clientNonce
:
185 raise Exception("Invalid encrypted response: bad nonce")
187 cleartext
= libnacl
.crypto_box_open(encryptedResponse
[32:], nonce
, resolverCert
.publicKey
, self
._privateKey
)
188 idx
= len(cleartext
) - 1
190 if cleartext
[idx
] != '\x00':
194 if idx
== 0 or cleartext
[idx
] != '\x80':
195 raise Exception("Invalid encrypted response: invalid padding")
198 paddingLen
= len(cleartext
) - idx
200 return cleartext
[:idx
+1]
202 def query(self
, queryContent
, tcp
=False):
204 if not self
._hasValidResolverCertificate
():
205 self
.refreshResolverCertificates()
207 nonce
= self
._generateNonce
()
208 resolverCert
= self
.getResolverCertificate()
209 if resolverCert
is None:
210 raise Exception("No valid certificate found")
211 encryptedQuery
= self
._encryptQuery
(queryContent
, resolverCert
, nonce
, tcp
)
212 encryptedResponse
= self
._sendQuery
(encryptedQuery
, tcp
)
213 response
= self
._decryptResponse
(encryptedResponse
, resolverCert
, nonce
)