* `addDNSCryptBind("127.0.0.1:8443", "provider name", "/path/to/resolver.cert", "/path/to/resolver.key", [, {doTCP=true, reusePort=false, tcpFastOpenSize=0, interface=\"\"}]):` listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of "provider name", using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth parameter is the same optional table than the one described in `addLocal()`, except that TCP is always enabled
* `generateDNSCryptProviderKeys("/path/to/providerPublic.key", "/path/to/providerPrivate.key"):` generate a new provider keypair
* `generateDNSCryptCertificate("/path/to/providerPrivate.key", "/path/to/resolver.cert", "/path/to/resolver.key", serial, validFrom, validUntil):` generate a new resolver private key and related certificate, valid from the `validFrom` UNIX timestamp until the `validUntil` one, signed with the provider private key
+ * `getDNSCryptBind(n)`: return the `DNSCryptContext` object corresponding to the bind `n`
* `printDNSCryptProviderFingerprint("/path/to/providerPublic.key")`: display the fingerprint of the provided resolver public key
* `showDNSCryptBinds():`: display the currently configured DNSCrypt binds
* BPFFilter related:
* member `getStats()`: print the block tables
* member `unblock(ComboAddress)`: unblock this address
* member `unblockQName(DNSName [, qtype=255])`: remove this qname from the block list
+ * DNSCryptCert related:
+ * member `getClientMagic`: return this certificate's client magic value, as a string
+ * member `getEsVersion()`: return the cryptographic construction to use with this certificate, as a string
+ * member `getMagic()`: return the certificate magic number, as a string
+ * member `getProtocolMinorVersion()`: return this certificate's minor version, as a string
+ * member `getResolverPublicKey()`: return the public key corresponding to this certificate, as a string
+ * member `getSerial()`: return the certificate serial number
+ * member `getSignature()`: return this certificate's signature, as a string
+ * member `getTSEnd()`: return the date the certificate is valid from, as a Unix timestamp
+ * member `getTSStart()`: return the date the certificate is valid until (inclusive), as a Unix timestamp
+ * DNSCryptContext related:
+ * member `generateAndLoadInMemoryCertificate(path/to/provider/private/key/file, serial, begin, end)`: generate a new resolver key and the associated certificate in-memory, sign it with the provided provider key, and use the new certificate
+ * member `getCurrentCertificate()`: return the current certificate as a `DnsCryptCert` object
+ * member `getOldCertificate()`: return the previous certificate as a `DnsCryptCert` object
+ * member `getProviderName()`: return the provider name
+ * member `hasOldCertificate()`: return a boolean indicating if the context has a previous certificate, from a certificate rotation
+ * member `loadNewCertificate(path/to/certificate, path/to/key)`: load a new certificate and the corresponding private key, and use it
* DNSDistProtoBufMessage related:
* member `setBytes(bytes)`: set the size of the query
* member `setEDNSSubnet(Netmask)`: set the EDNS Subnet
void setNewCertificate(const DnsCryptCert& newCert, const DnsCryptPrivateKey& newKey);
const DnsCryptCert& getCurrentCertificate() const { return cert; };
const DnsCryptCert& getOldCertificate() const { return oldCert; };
- bool hadOldCertificate() const { return hasOldCert; };
+ bool hasOldCertificate() const { return hasOldCert; };
const std::string& getProviderName() const { return providerName; }
int encryptQuery(char* query, uint16_t queryLen, uint16_t querySize, const unsigned char clientPublicKey[DNSCRYPT_PUBLIC_KEY_SIZE], const DnsCryptPrivateKey& clientPrivateKey, const unsigned char clientNonce[DNSCRYPT_NONCE_SIZE / 2], bool tcp, uint16_t* encryptedResponseLen) const;
{ "firstAvailable", false, "", "picks the server with the lowest `order` that has not exceeded its QPS limit" },
{ "fixupCase", true, "bool", "if set (default to no), rewrite the first qname of the question part of the answer to match the one from the query. It is only useful when you have a downstream server that messes up the case of the question qname in the answer" },
{ "generateDNSCryptCertificate", true, "\"/path/to/providerPrivate.key\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", serial, validFrom, validUntil", "generate a new resolver private key and related certificate, valid from the `validFrom` timestamp until the `validUntil` one, signed with the provider private key" },
- { "generateDNSCryptProviderKeys", true, "\"/path/to/providerPublic.key\", \"/path/to/providerPrivate.key\"", "generate a new provider keypair"},
+ { "generateDNSCryptProviderKeys", true, "\"/path/to/providerPublic.key\", \"/path/to/providerPrivate.key\"", "generate a new provider keypair" },
+ { "getDNSCryptBind", true, "n", "return the `DNSCryptContext` object corresponding to the bind `n`" },
{ "getPoolServers", true, "pool", "return servers part of this pool" },
{ "getQueryCounters", true, "[max=10]", "show current buffer of query counters, limited by 'max' if provided" },
{ "getResponseRing", true, "", "return the current content of the response ring" },
});
}
+#ifdef HAVE_DNSCRYPT
+static bool generateDNSCryptCertificate(const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end, DnsCryptCert& certOut, DnsCryptPrivateKey& keyOut)
+{
+ bool success = false;
+ unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE];
+ sodium_mlock(providerPrivateKey, sizeof(providerPrivateKey));
+ sodium_memzero(providerPrivateKey, sizeof(providerPrivateKey));
+
+ try {
+ ifstream providerKStream(providerPrivateKeyFile);
+ providerKStream.read((char*) providerPrivateKey, sizeof(providerPrivateKey));
+ if (providerKStream.fail()) {
+ providerKStream.close();
+ throw std::runtime_error("Invalid DNSCrypt provider key file " + providerPrivateKeyFile);
+ }
+
+ DnsCryptContext::generateCertificate(serial, begin, end, providerPrivateKey, keyOut, certOut);
+ success = true;
+ }
+ catch(const std::exception& e) {
+ errlog(e.what());
+ }
+ sodium_memzero(providerPrivateKey, sizeof(providerPrivateKey));
+ sodium_munlock(providerPrivateKey, sizeof(providerPrivateKey));
+ return success;
+}
+#endif /* HAVE_DNSCRYPT */
void moreLua(bool client)
{
for (const auto& local : g_dnsCryptLocals) {
const DnsCryptContext& ctx = std::get<1>(local);
- bool const hasOldCert = ctx.hadOldCertificate();
+ bool const hasOldCert = ctx.hasOldCertificate();
const DnsCryptCert& cert = ctx.getCurrentCertificate();
const DnsCryptCert& oldCert = ctx.getOldCertificate();
#endif
});
+ g_lua.writeFunction("getDNSCryptBind", [client](size_t idx) {
+ setLuaNoSideEffect();
+#ifdef HAVE_DNSCRYPT
+ DnsCryptContext* ret = nullptr;
+ if (idx < g_dnsCryptLocals.size()) {
+ ret = &(std::get<1>(g_dnsCryptLocals.at(idx)));
+ }
+ return ret;
+#else
+ g_outputBuffer="Error: DNSCrypt support is not enabled.\n";
+#endif
+ });
+
+#ifdef HAVE_DNSCRYPT
+ /* DnsCryptContext bindings */
+ g_lua.registerFunction<std::string(DnsCryptContext::*)()>("getProviderName", [](const DnsCryptContext& ctx) { return ctx.getProviderName(); });
+ g_lua.registerFunction<DnsCryptCert(DnsCryptContext::*)()>("getCurrentCertificate", [](const DnsCryptContext& ctx) { return ctx.getCurrentCertificate(); });
+ g_lua.registerFunction<DnsCryptCert(DnsCryptContext::*)()>("getOldCertificate", [](const DnsCryptContext& ctx) { return ctx.getOldCertificate(); });
+ g_lua.registerFunction("hasOldCertificate", &DnsCryptContext::hasOldCertificate);
+ g_lua.registerFunction("loadNewCertificate", &DnsCryptContext::loadNewCertificate);
+ g_lua.registerFunction<void(DnsCryptContext::*)(const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end)>("generateAndLoadInMemoryCertificate", [](DnsCryptContext& ctx, const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end) {
+ DnsCryptPrivateKey privateKey;
+ DnsCryptCert cert;
+
+ try {
+ if (generateDNSCryptCertificate(providerPrivateKeyFile, serial, begin, end, cert, privateKey)) {
+ ctx.setNewCertificate(cert, privateKey);
+ }
+ }
+ catch(const std::exception& e) {
+ errlog(e.what());
+ g_outputBuffer="Error: "+string(e.what())+"\n";
+ }
+ });
+
+ /* DnsCryptCert */
+ g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getMagic", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.magic), sizeof(cert.magic)); });
+ g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getEsVersion", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.esVersion), sizeof(cert.esVersion)); });
+ g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getProtocolMinorVersion", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.protocolMinorVersion), sizeof(cert.protocolMinorVersion)); });
+ g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getSignature", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signature), sizeof(cert.signature)); });
+ g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getResolverPublicKey", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signedData.resolverPK), sizeof(cert.signedData.resolverPK)); });
+ g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getClientMagic", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signedData.clientMagic), sizeof(cert.signedData.clientMagic)); });
+ g_lua.registerFunction<uint32_t(DnsCryptCert::*)()>("getSerial", [](const DnsCryptCert& cert) { return cert.signedData.serial; });
+ g_lua.registerFunction<uint32_t(DnsCryptCert::*)()>("getTSStart", [](const DnsCryptCert& cert) { return cert.signedData.tsStart; });
+ g_lua.registerFunction<uint32_t(DnsCryptCert::*)()>("getTSEnd", [](const DnsCryptCert& cert) { return cert.signedData.tsEnd; });
+#endif
+
g_lua.writeFunction("generateDNSCryptProviderKeys", [](const std::string& publicKeyFile, const std::string privateKeyFile) {
setLuaNoSideEffect();
#ifdef HAVE_DNSCRYPT
g_lua.writeFunction("generateDNSCryptCertificate", [](const std::string& providerPrivateKeyFile, const std::string& certificateFile, const std::string privateKeyFile, uint32_t serial, time_t begin, time_t end) {
setLuaNoSideEffect();
#ifdef HAVE_DNSCRYPT
- unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE];
- sodium_mlock(providerPrivateKey, sizeof(providerPrivateKey));
- sodium_memzero(providerPrivateKey, sizeof(providerPrivateKey));
+ DnsCryptPrivateKey privateKey;
+ DnsCryptCert cert;
try {
- DnsCryptPrivateKey privateKey;
- DnsCryptCert cert;
- ifstream providerKStream(providerPrivateKeyFile);
- providerKStream.read((char*) providerPrivateKey, sizeof(providerPrivateKey));
- if (providerKStream.fail()) {
- providerKStream.close();
- throw std::runtime_error("Invalid DNSCrypt provider key file " + providerPrivateKeyFile);
+ if (generateDNSCryptCertificate(providerPrivateKeyFile, serial, begin, end, cert, privateKey)) {
+ privateKey.saveToFile(privateKeyFile);
+ DnsCryptContext::saveCertFromFile(cert, certificateFile);
}
-
- DnsCryptContext::generateCertificate(serial, begin, end, providerPrivateKey, privateKey, cert);
-
- privateKey.saveToFile(privateKeyFile);
- DnsCryptContext::saveCertFromFile(cert, certificateFile);
}
- catch(std::exception& e) {
+ catch(const std::exception& e) {
errlog(e.what());
g_outputBuffer="Error: "+string(e.what())+"\n";
}
-
- sodium_memzero(providerPrivateKey, sizeof(providerPrivateKey));
- sodium_munlock(providerPrivateKey, sizeof(providerPrivateKey));
#else
g_outputBuffer="Error: DNSCrypt support is not enabled.\n";
#endif
resolverPK = orig[0:32]
clientMagic = orig[32:40]
- serial = struct.unpack_from("I", orig[40:44])
+ serial = struct.unpack_from("I", orig[40:44])[0]
validFrom = struct.unpack_from("!I", orig[44:48])[0]
validUntil = struct.unpack_from("!I", orig[48:52])[0]
return DNSCryptResolverCertificate(serial, validFrom, validUntil, resolverPK, clientMagic)
return False
- def _getResolverCertificates(self):
+ def clearExpiredResolverCertificates(self):
+ newCerts = []
+
+ for cert in self._resolverCertificates:
+ if cert.isValid():
+ newCerts.append(cert)
+
+ self._resolverCertificates = newCerts
+
+ def refreshResolverCertificates(self):
+ self.clearExpiredResolverCertificates()
+
query = dns.message.make_query(self._providerName, dns.rdatatype.TXT, dns.rdataclass.IN)
data = self._sendQuery(query.to_wire())
if cert.isValid():
self._resolverCertificates.append(cert)
- def _getResolverCertificate(self):
+ def getResolverCertificate(self):
certs = self._resolverCertificates
result = None
for cert in certs:
def query(self, queryContent, tcp=False):
if not self._hasValidResolverCertificate():
- self._getResolverCertificates()
+ self.refreshResolverCertificates()
nonce = self._generateNonce()
- resolverCert = self._getResolverCertificate()
+ resolverCert = self.getResolverCertificate()
if resolverCert is None:
raise Exception("No valid certificate found")
encryptedQuery = self._encryptQuery(queryContent, resolverCert, nonce, tcp)
#!/usr/bin/env python
+import base64
import time
import dns
import dns.message
from dnsdisttests import DNSDistTest
import dnscrypt
-class TestDNSCrypt(DNSDistTest):
+class DNSCryptTest(DNSDistTest):
"""
dnsdist is configured to accept DNSCrypt queries on 127.0.0.1:_dnsDistPortDNSCrypt.
The provider's keys have been generated with:
_dnsDistPort = 5340
_dnsDistPortDNSCrypt = 8443
- _config_template = """
- generateDNSCryptCertificate("DNSCryptProviderPrivate.key", "DNSCryptResolver.cert", "DNSCryptResolver.key", %d, %d, %d)
- addDNSCryptBind("127.0.0.1:%d", "%s", "DNSCryptResolver.cert", "DNSCryptResolver.key")
- newServer{address="127.0.0.1:%s"}
- """
+
+ _consoleKey = DNSDistTest.generateConsoleKey()
+ _consoleKeyB64 = base64.b64encode(_consoleKey)
_providerFingerprint = 'E1D7:2108:9A59:BF8D:F101:16FA:ED5E:EA6A:9F6C:C78F:7F91:AF6B:027E:62F4:69C3:B1AA'
_providerName = "2.provider.name"
_resolverCertificateSerial = 42
+
# valid from 60s ago until 2h from now
_resolverCertificateValidFrom = time.time() - 60
_resolverCertificateValidUntil = time.time() + 7200
- _config_params = ['_resolverCertificateSerial', '_resolverCertificateValidFrom', '_resolverCertificateValidUntil', '_dnsDistPortDNSCrypt', '_providerName', '_testServerPort']
+
_dnsdistStartupDelay = 10
+ def doDNSCryptQuery(self, client, query, response, tcp):
+ self._toResponderQueue.put(response)
+ data = client.query(query.to_wire(), tcp=tcp)
+ receivedResponse = dns.message.from_wire(data)
+ receivedQuery = None
+ if not self._fromResponderQueue.empty():
+ receivedQuery = self._fromResponderQueue.get(query)
+
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+
+class TestDNSCrypt(DNSCryptTest):
+ _config_template = """
+ setKey("%s")
+ controlSocket("127.0.0.1:%s")
+ generateDNSCryptCertificate("DNSCryptProviderPrivate.key", "DNSCryptResolver.cert", "DNSCryptResolver.key", %d, %d, %d)
+ addDNSCryptBind("127.0.0.1:%d", "%s", "DNSCryptResolver.cert", "DNSCryptResolver.key")
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ _config_params = ['_consoleKeyB64', '_consolePort', '_resolverCertificateSerial', '_resolverCertificateValidFrom', '_resolverCertificateValidUntil', '_dnsDistPortDNSCrypt', '_providerName', '_testServerPort']
+
def testSimpleA(self):
"""
DNSCrypt: encrypted A query
'192.2.0.1')
response.answer.append(rrset)
- self._toResponderQueue.put(response)
- data = client.query(query.to_wire())
- receivedResponse = dns.message.from_wire(data)
- receivedQuery = None
- if not self._fromResponderQueue.empty():
- receivedQuery = self._fromResponderQueue.get(query)
-
- self.assertTrue(receivedQuery)
- self.assertTrue(receivedResponse)
- receivedQuery.id = query.id
- self.assertEquals(query, receivedQuery)
- self.assertEquals(response, receivedResponse)
-
- self._toResponderQueue.put(response)
- data = client.query(query.to_wire(), tcp=True)
- receivedResponse = dns.message.from_wire(data)
- receivedQuery = None
- if not self._fromResponderQueue.empty():
- receivedQuery = self._fromResponderQueue.get(query)
-
- self.assertTrue(receivedQuery)
- self.assertTrue(receivedResponse)
- receivedQuery.id = query.id
- self.assertEquals(query, receivedQuery)
- self.assertEquals(response, receivedResponse)
+ self.doDNSCryptQuery(client, query, response, False)
+ self.doDNSCryptQuery(client, query, response, True)
def testResponseLargerThanPaddedQuery(self):
"""
self.assertTrue(len(receivedResponse.authority) == 0)
self.assertTrue(len(receivedResponse.additional) == 0)
-class TestDNSCryptWithCache(DNSDistTest):
- _dnsDistPortDNSCrypt = 8443
- _providerFingerprint = 'E1D7:2108:9A59:BF8D:F101:16FA:ED5E:EA6A:9F6C:C78F:7F91:AF6B:027E:62F4:69C3:B1AA'
- _providerName = "2.provider.name"
- _resolverCertificateSerial = 42
- # valid from 60s ago until 2h from now
- _resolverCertificateValidFrom = time.time() - 60
- _resolverCertificateValidUntil = time.time() + 7200
+ def testCertRotation(self):
+ """
+ DNSCrypt: certificate rotation
+ """
+ client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+ client.refreshResolverCertificates()
+
+ cert = client.getResolverCertificate()
+ self.assertTrue(cert)
+ self.assertEquals(cert.serial, self._resolverCertificateSerial)
+
+ name = 'rotation.dnscrypt.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.2.0.1')
+ response.answer.append(rrset)
+
+ self.doDNSCryptQuery(client, query, response, False)
+ self.doDNSCryptQuery(client, query, response, True)
+
+ # generate a new certificate
+ self.sendConsoleCommand("generateDNSCryptCertificate('DNSCryptProviderPrivate.key', 'DNSCryptResolver.cert.2', 'DNSCryptResolver.key.2', {!s}, {:.0f}, {:.0f})".format(self._resolverCertificateSerial + 1, self._resolverCertificateValidFrom, self._resolverCertificateValidUntil))
+ # switch to that new certificate
+ self.sendConsoleCommand("getDNSCryptBind(0):loadNewCertificate('DNSCryptResolver.cert.2', 'DNSCryptResolver.key.2')")
+
+ # we should still be able to send queries with the previous certificate
+ self.doDNSCryptQuery(client, query, response, False)
+ self.doDNSCryptQuery(client, query, response, True)
+ cert = client.getResolverCertificate()
+ self.assertTrue(cert)
+ self.assertEquals(cert.serial, self._resolverCertificateSerial)
+
+ # but refreshing should get us the new one
+ client.refreshResolverCertificates()
+ cert = client.getResolverCertificate()
+ self.assertTrue(cert)
+ self.assertEquals(cert.serial, self._resolverCertificateSerial + 1)
+
+ # generate a third certificate, this time in memory
+ self.sendConsoleCommand("getDNSCryptBind(0):generateAndLoadInMemoryCertificate('DNSCryptProviderPrivate.key', {!s}, {:.0f}, {:.0f})".format(self._resolverCertificateSerial + 2, self._resolverCertificateValidFrom, self._resolverCertificateValidUntil))
+
+ # we should still be able to send queries with the previous certificate
+ self.doDNSCryptQuery(client, query, response, False)
+ self.doDNSCryptQuery(client, query, response, True)
+ cert = client.getResolverCertificate()
+ self.assertTrue(cert)
+ self.assertEquals(cert.serial, self._resolverCertificateSerial + 1)
+
+ # but refreshing should get us the new one
+ client.refreshResolverCertificates()
+ cert = client.getResolverCertificate()
+ self.assertTrue(cert)
+ self.assertEquals(cert.serial, self._resolverCertificateSerial + 2)
+
+class TestDNSCryptWithCache(DNSCryptTest):
+
_config_params = ['_resolverCertificateSerial', '_resolverCertificateValidFrom', '_resolverCertificateValidUntil', '_dnsDistPortDNSCrypt', '_providerName', '_testServerPort']
_config_template = """
generateDNSCryptCertificate("DNSCryptProviderPrivate.key", "DNSCryptResolver.cert", "DNSCryptResolver.key", %d, %d, %d)
for key in self._responsesCounter:
total += self._responsesCounter[key]
self.assertEquals(total, misses)
+
+class TestDNSCryptAutomaticRotation(DNSCryptTest):
+ _config_template = """
+ setKey("%s")
+ controlSocket("127.0.0.1:%s")
+ generateDNSCryptCertificate("DNSCryptProviderPrivate.key", "DNSCryptResolver.cert", "DNSCryptResolver.key", %d, %d, %d)
+ addDNSCryptBind("127.0.0.1:%d", "%s", "DNSCryptResolver.cert", "DNSCryptResolver.key")
+ newServer{address="127.0.0.1:%s"}
+
+ local last = 0
+ serial = %d
+ function maintenance()
+ local now = os.time()
+ if ((now - last) > 2) then
+ serial = serial + 1
+ getDNSCryptBind(0):generateAndLoadInMemoryCertificate('DNSCryptProviderPrivate.key', serial, now - 60, now + 120)
+ last = now
+ end
+ end
+ """
+
+ _config_params = ['_consoleKeyB64', '_consolePort', '_resolverCertificateSerial', '_resolverCertificateValidFrom', '_resolverCertificateValidUntil', '_dnsDistPortDNSCrypt', '_providerName', '_testServerPort', '_resolverCertificateSerial']
+
+ def testCertRotation(self):
+ """
+ DNSCrypt: automatic certificate rotation
+ """
+ client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+
+ client.refreshResolverCertificates()
+ cert = client.getResolverCertificate()
+ self.assertTrue(cert)
+ firstSerial = cert.serial
+ self.assertGreaterEqual(cert.serial, self._resolverCertificateSerial)
+
+ time.sleep(3)
+
+ client.refreshResolverCertificates()
+ cert = client.getResolverCertificate()
+ self.assertTrue(cert)
+ secondSerial = cert.serial
+ self.assertGreater(cert.serial, firstSerial)
+
+ name = 'automatic-rotation.dnscrypt.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.2.0.1')
+ response.answer.append(rrset)
+
+ self.doDNSCryptQuery(client, query, response, False)
+ self.doDNSCryptQuery(client, query, response, True)