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])
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()
74 addrType
= self
._addrToSocketType
(self
._resolverAddress
)
75 self
._sock
= socket
.socket(addrType
, socket
.SOCK_DGRAM
)
76 self
._sock
.settimeout(timeout
)
77 self
._sock
.connect((self
._resolverAddress
, self
._resolverPort
))
79 def _sendQuery(self
, queryContent
):
80 self
._sock
.send(queryContent
)
81 data
= self
._sock
.recv(4096)
84 def _hasValidResolverCertificate(self
):
86 for cert
in self
._resolverCertificates
:
92 def _getResolverCertificates(self
):
93 query
= dns
.message
.make_query(self
._providerName
, dns
.rdatatype
.TXT
, dns
.rdataclass
.IN
)
94 data
= self
._sendQuery
(query
.to_wire())
96 response
= dns
.message
.from_wire(data
)
97 if response
.rcode() != dns
.rcode
.NOERROR
or len(response
.answer
) != 1:
98 raise Exception("Invalid response to public key request")
100 an
= response
.answer
[0]
101 if an
.rdclass
!= dns
.rdataclass
.IN
or an
.rdtype
!= dns
.rdatatype
.TXT
or len(an
.items
) == 0:
102 raise Exception("Invalid response to public key request")
104 for item
in an
.items
:
105 if len(item
.strings
) != 1:
108 cert
= DNSCryptResolverCertificate
.fromBinary(item
.strings
[0], self
._providerFingerprint
)
110 self
._resolverCertificates
.append(cert
)
112 def _getResolverCertificate(self
):
113 certs
= self
._resolverCertificates
117 if result
is None or cert
.serial
> result
.serial
:
123 def _generateNonce():
124 nonce
= libnacl
.utils
.rand_nonce()
125 return nonce
[:(DNSCryptClient
.DNSCRYPT_NONCE_SIZE
/ 2)]
127 def _encryptQuery(self
, queryContent
, resolverCert
, nonce
):
128 header
= resolverCert
.clientMagic
+ self
._publicKey
+ nonce
129 requiredSize
= len(header
) + self
.DNSCRYPT_MAC_SIZE
+ len(queryContent
)
130 paddingSize
= self
.DNSCRYPT_PADDED_BLOCK_SIZE
- (len(queryContent
) % self
.DNSCRYPT_PADDED_BLOCK_SIZE
)
131 # padding size should be DNSCRYPT_PADDED_BLOCK_SIZE <= padding size <= 4096
132 if requiredSize
< self
.DNSCRYPT_MIN_UDP_LENGTH
:
133 paddingSize
+= self
.DNSCRYPT_MIN_UDP_LENGTH
- requiredSize
134 requiredSize
= self
.DNSCRYPT_MIN_UDP_LENGTH
138 while idx
< (paddingSize
- 1):
139 padding
= padding
+ '\x00'
142 data
= queryContent
+ padding
143 nonce
= nonce
+ ('\x00'*(self
.DNSCRYPT_NONCE_SIZE
/ 2))
144 box
= libnacl
.crypto_box(data
, nonce
, resolverCert
.publicKey
, self
._privateKey
)
147 def _decryptResponse(self
, encryptedResponse
, resolverCert
, clientNonce
):
148 resolverMagic
= encryptedResponse
[:8]
149 if resolverMagic
!= self
.DNSCRYPT_RESOLVER_MAGIC
:
150 raise Exception("Invalid encrypted response: bad resolver magic")
152 nonce
= encryptedResponse
[8:32]
153 if nonce
[0:self
.DNSCRYPT_NONCE_SIZE
/ 2] != clientNonce
:
154 raise Exception("Invalid encrypted response: bad nonce")
156 cleartext
= libnacl
.crypto_box_open(encryptedResponse
[32:], nonce
, resolverCert
.publicKey
, self
._privateKey
)
157 idx
= len(cleartext
) - 1
159 if cleartext
[idx
] != '\x00':
163 if idx
== 0 or cleartext
[idx
] != '\x80':
164 raise Exception("Invalid encrypted response: invalid padding")
167 paddingLen
= len(cleartext
) - idx
169 return cleartext
[:idx
+1]
171 def query(self
, queryContent
):
173 if not self
._hasValidResolverCertificate
():
174 self
._getResolverCertificates
()
176 nonce
= self
._generateNonce
()
177 resolverCert
= self
._getResolverCertificate
()
178 if resolverCert
is None:
179 raise Exception("No valid certificate found")
180 encryptedQuery
= self
._encryptQuery
(queryContent
, resolverCert
, nonce
)
181 encryptedResponse
= self
._sendQuery
(encryptedQuery
)
182 response
= self
._decryptResponse
(encryptedResponse
, resolverCert
, nonce
)