From 620d480118eada2259872a1803fe68514e8a5de6 Mon Sep 17 00:00:00 2001
From: Remi Gacogne <remi.gacogne@powerdns.com>
Date: Fri, 22 Sep 2017 16:59:07 +0200
Subject: [PATCH] Add DNSSEC test vectors for RSA, ECDSA, ed25519 and GOST

---
 pdns/Makefile.am                  |   6 +
 pdns/dnssecinfra.cc               |   2 +-
 pdns/dnssecinfra.hh               |   5 +-
 pdns/recursordist/Makefile.am     |   4 +
 pdns/recursordist/test-signers.cc |   1 +
 pdns/test-signers.cc              | 235 ++++++++++++++++++++++++++----
 6 files changed, 221 insertions(+), 32 deletions(-)
 create mode 120000 pdns/recursordist/test-signers.cc

diff --git a/pdns/Makefile.am b/pdns/Makefile.am
index 4a3a41e76..e0140eb5c 100644
--- a/pdns/Makefile.am
+++ b/pdns/Makefile.am
@@ -1154,6 +1154,7 @@ testrunner_SOURCES = \
 	misc.cc \
 	nameserver.cc \
 	nsecrecords.cc \
+	opensslsigners.cc opensslsigners.hh \
 	qtype.cc \
 	rcpgenerator.cc \
 	responsestats.cc \
@@ -1213,6 +1214,11 @@ testrunner_SOURCES += sodiumsigners.cc
 testrunner_LDADD += $(LIBSODIUM_LIBS)
 endif
 
+if BOTAN110
+testrunner_SOURCES += botan110signers.cc
+testrunner_LDADD += $(BOTAN110_LIBS)
+endif
+
 if LIBDECAF
 testrunner_SOURCES += decafsigners.cc
 testrunner_LDADD += $(LIBDECAF_LIBS)
diff --git a/pdns/dnssecinfra.cc b/pdns/dnssecinfra.cc
index 95cbc3bda..695179b61 100644
--- a/pdns/dnssecinfra.cc
+++ b/pdns/dnssecinfra.cc
@@ -457,7 +457,7 @@ DSRecordContent makeDSFromDNSKey(const DNSName& qname, const DNSKEYRecordContent
 }
 
 
-DNSKEYRecordContent makeDNSKEYFromDNSCryptoKeyEngine(const std::shared_ptr<DNSCryptoKeyEngine> pk, uint8_t algorithm, uint16_t flags)
+static DNSKEYRecordContent makeDNSKEYFromDNSCryptoKeyEngine(const std::shared_ptr<DNSCryptoKeyEngine> pk, uint8_t algorithm, uint16_t flags)
 {
   DNSKEYRecordContent drc;
 
diff --git a/pdns/dnssecinfra.hh b/pdns/dnssecinfra.hh
index d094fdb55..865777c05 100644
--- a/pdns/dnssecinfra.hh
+++ b/pdns/dnssecinfra.hh
@@ -106,7 +106,10 @@ class DNSCryptoKeyEngine
 
 struct DNSSECPrivateKey
 {
-  uint16_t getTag();
+  uint16_t getTag() const
+  {
+    return getDNSKEY().getTag();
+  }
   
   const shared_ptr<DNSCryptoKeyEngine> getKey() const
   {
diff --git a/pdns/recursordist/Makefile.am b/pdns/recursordist/Makefile.am
index 39555d72f..0775481e1 100644
--- a/pdns/recursordist/Makefile.am
+++ b/pdns/recursordist/Makefile.am
@@ -242,6 +242,7 @@ testrunner_SOURCES = \
 	test-rcpgenerator_cc.cc \
 	test-recpacketcache_cc.cc \
 	test-recursorcache_cc.cc \
+	test-signers.cc \
 	test-syncres_cc.cc \
 	test-tsig.cc \
 	testrunner.cc \
@@ -265,6 +266,9 @@ if BOTAN110
 pdns_recursor_SOURCES += \
 	botan110signers.cc
 pdns_recursor_LDADD += $(BOTAN110_LIBS)
+testrunner_SOURCES += \
+	botan110signers.cc
+testrunner_LDADD += $(BOTAN110_LIBS)
 endif
 
 if LIBSODIUM
diff --git a/pdns/recursordist/test-signers.cc b/pdns/recursordist/test-signers.cc
new file mode 120000
index 000000000..fea26b92b
--- /dev/null
+++ b/pdns/recursordist/test-signers.cc
@@ -0,0 +1 @@
+../test-signers.cc
\ No newline at end of file
diff --git a/pdns/test-signers.cc b/pdns/test-signers.cc
index 043b1ab70..60a878ee5 100644
--- a/pdns/test-signers.cc
+++ b/pdns/test-signers.cc
@@ -13,51 +13,226 @@
 #include "dnsseckeeper.hh"
 #include "dnssecinfra.hh"
 #include "misc.hh"
-
 BOOST_AUTO_TEST_SUITE(test_signers)
 
+static const std::string message = "Very good, young padawan.";
+
+static const struct signerParams
+{
+  std::string iscMap;
+  std::string dsSHA1;
+  std::string dsSHA256;
+  std::string dsSHA384;
+  std::vector<uint8_t> signature;
+  std::string zoneRepresentation;
+  std::string name;
+  std::string rfcMsgDump;
+  std::string rfcB64Signature;
+  unsigned int bits;
+  uint16_t flags;
+  uint16_t rfcFlags;
+  uint8_t algorithm;
+  bool isDeterministic;
+} signers[] = {
+  /* RSA from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sample_keys.h */
+  { "Algorithm: 8\n"
+    "Modulus: qtunSiHnYq4XRLBehKAw1Glxb+48oIpAC7w3Jhpj570bb2uHt6orWGqnuyRtK8oqUi2ABoV0PFm8+IPgDMEdCQ==\n"
+    "PublicExponent: AQAB\n"
+    "PrivateExponent: MiItniUAngXzMeaGdWgDq/AcpvlCtOCcFlVt4TJRKkfp8DNRSxIxG53NNlOFkp1W00iLHqYC2GrH1qkKgT9l+Q==\n"
+    "Prime1: 3sZmM+5FKFy5xaRt0n2ZQOZ2C+CoKzVil6/al9LmYVs=\n"
+    "Prime2: xFcNWSIW6v8dDL2JQ1kxFDm/8RVeUSs1BNXXnvCjBGs=\n"
+    "Exponent1: WuUwhjfN1+4djlrMxHmisixWNfpwI1Eg7Ss/UXsnrMk=\n"
+    "Exponent2: vfMqas1cNsXRqP3Fym6D2Pl2BRuTQBv5E1B/ZrmQPTk=\n"
+    "Coefficient: Q10z43cA3hkwOkKsj5T0W5jrX97LBwZoY5lIjDCa4+M=\n",
+    "1506 8 1 172a500b374158d1a64ba3073cdbbc319b2fdf2c",
+    "1506 8 2 253b099ff47b02c6ffa52695a30a94c6681c56befe0e71a5077d6f79514972f9",
+    "1506 8 4 22ea940600dc2d9a98b1126c26ac0dc5c91b31eb50fe784b36ad675e9eecfe6573c1f85c53b6bc94580f3ac443d13c4c",
+    /* from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sign.c */
+    { 0x93, 0x93, 0x5f, 0xd8, 0xa1, 0x2b, 0x4c, 0x0b, 0xf3, 0x67, 0x42, 0x13, 0x52, 0x00, 0x35, 0xdc, 0x09, 0xe0, 0xdf, 0xe0, 0x3e, 0xc2, 0xcf, 0x64, 0xab, 0x9f, 0x9f, 0x51, 0x5f, 0x5c, 0x27, 0xbe, 0x13, 0xd6, 0x17, 0x07, 0xa6, 0xe4, 0x3b, 0x63, 0x44, 0x85, 0x06, 0x13, 0xaa, 0x01, 0x3c, 0x58, 0x52, 0xa3, 0x98, 0x20, 0x65, 0x03, 0xd0, 0x40, 0xc8, 0xa0, 0xe9, 0xd2, 0xc0, 0x03, 0x5a, 0xab },
+    "256 3 8 AwEAAarbp0oh52KuF0SwXoSgMNRpcW/uPKCKQAu8NyYaY+e9G29rh7eqK1hqp7skbSvKKlItgAaFdDxZvPiD4AzBHQk=",
+    "rsa.",
+    "",
+    "",
+    512,
+    256,
+    0,
+    DNSSECKeeper::RSASHA256,
+    true
+  },
+#ifdef HAVE_BOTAN110
+  /* ECC-GOST from rfc5933 */
+  { "Algorithm: 12\n"
+    "GostAsn1: MEUCAQAwHAYGKoUDAgITMBIGByqFAwICIwEGByqFAwICHgEEIgQg/9MiXtXKg9FDXDN/R9CmVhJDyuzRAIgh4tPwCu4NHIs=\n",
+    "59732 12 1 794287b8033625ae938c0341fd800fd5ce45a728",
+    "59732 12 2 a7c24528480884ef4f5c0aaf85b3a20323a96722ccda26045aa7d304c9942868",
+    "59732 12 4 6f43cc67087875a5f2115adbc29604f0b5a43be6f28be0deaf71e08168967f7a1a8218d063a6f9137133a721e60eed4f",
+    { 0x1f, 0x3f, 0x2a, 0x2d, 0xc6, 0x72, 0x1d, 0xc8, 0xc4, 0x1f, 0x8b, 0xa1, 0xe8, 0x07, 0x83, 0x25, 0x9a, 0xbd, 0xc3, 0x80, 0xc1, 0x67, 0x80, 0xb7, 0x07, 0xed, 0xcb, 0xb0, 0x45, 0x5e, 0x46, 0x00, 0xcb, 0xa2, 0x7c, 0xf4, 0x7a, 0xa1, 0x81, 0x0c, 0xb2, 0xd1, 0xa1, 0xba, 0xb4, 0x53, 0xed, 0x8c, 0x10, 0x79, 0x12, 0x84, 0x9f, 0x9a, 0x69, 0xf5, 0x6d, 0x00, 0x4f, 0x06, 0x30, 0xba, 0xaa, 0xe6 },
+    "256 3 12 aRS/DcPWGQj2wVJydT8EcAVoC0kXn5pDVm2IMvDDPXeD32dsSKcmq8KNVzigjL4OXZTV+t/6w4X1gpNrZiC01g==",
+    "gost.",
+    "00 01 0c 03 00 00 0e 10 70 db d8 80 38 6d 43 80 e9 54 07 65 78 61 6d 70 6c 65 03 6e 65 74 00 03 77 77 77 07 65 78 61 6d 70 6c 65 03 6e 65 74 00 00 01 00 01 00 00 0e 10 00 04 c0 00 02 01 ",
+    /* from rfc5933 */
+    "7vzzz6iLOmvtjs5FjVjSHT8XnRKFY15ki6KpkNPkUnS8iIns0Kv4APT+D9ibmHhGri6Sfbyyzi67+wBbbW/jrA==",
+    256,
+    256,
+    256,
+    DNSSECKeeper::ECCGOST,
+    false
+  },
+#endif /* HAVE_BOTAN110 */
+#ifdef HAVE_LIBCRYPTO_ECDSA
+  /* ECDSA-P256-SHA256 from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sample_keys.h */
+  { "Algorithm: 13\n"
+    "PrivateKey: iyLIPdk3DOIxVmmSYlmTstbtUPiVlEyDX46psyCwNVQ=\n",
+    "5345 13 1 954103ac7c43810ce9f414e80f30ab1cbe49b236",
+    "5345 13 2 bac2107036e735b50f85006ce409a19a3438cab272e70769ebda032239a3d0ca",
+    "5345 13 4 a0ac6790483872be72a258314200a88ab75cdd70f66a18a09f0f414c074df0989fdb1df0e67d82d4312cda67b93a76c1",
+    /* from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sign.c */
+    { 0xa2, 0x95, 0x76, 0xb5, 0xf5, 0x7e, 0xbd, 0xdd, 0xf5, 0x62, 0xa2, 0xc3, 0xa4, 0x8d, 0xd4, 0x53, 0x5c, 0xba, 0x29, 0x71,	0x8c, 0xcc, 0x28, 0x7b, 0x58, 0xf3, 0x1e, 0x4e, 0x58, 0xe2, 0x36, 0x7e,	0xa0, 0x1a, 0xb6, 0xe6, 0x29, 0x71, 0x1b, 0xd3, 0x8c, 0x88, 0xc3, 0xee, 0x12, 0x0e, 0x69, 0x70, 0x55, 0x99, 0xec, 0xd5,	0xf6, 0x4f, 0x4b, 0xe2, 0x41, 0xd9, 0x10, 0x7e, 0x67, 0xe5, 0xad, 0x2f, },
+    "256 3 13 8uD7C4THTM/w7uhryRSToeE/jKT78/p853RX0L5EwrZrSLBubLPiBw7gbvUP6SsIga5ZQ4CSAxNmYA/gZsuXzA==",
+    "ecdsa.",
+    "",
+    "",
+    256,
+    256,
+    0,
+    DNSSECKeeper::ECDSA256,
+    false
+  },
+#endif /* HAVE_LIBCRYPTO_ECDSA */
 #if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBDECAF)
-BOOST_AUTO_TEST_CASE(test_ed25519_signer) {
-    vector<std::shared_ptr<DNSRecordContent> > rrs;
-    DNSName qname("example.com.");
-    DNSKEYRecordContent drc;
+  /* ed25519 from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sample_keys.h,
+     also from rfc8080 section 6.1 */
+  { "Algorithm: 15\n"
+    "PrivateKey: ODIyNjAzODQ2MjgwODAxMjI2NDUxOTAyMDQxNDIyNjI=\n",
+    "3612 15 1 501249721e1f09a79d30d5c6c4dca1dc1da4ed5d",
+    "3612 15 2 1b1c8766b2a96566ff196f77c0c4194af86aaa109c5346ff60231a27d2b07ac0",
+    "3612 15 4 d11831153af4985efbd0ae792c967eb4aff3c35488db95f7e2f85dcec74ae8f59f9a72641798c91c67c675db1d710c18",
+    /* from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sign.c */
+    { 0x0a, 0x9e, 0x51, 0x5f, 0x16, 0x89, 0x49, 0x27, 0x0e, 0x98, 0x34, 0xd3, 0x48, 0xef, 0x5a, 0x6e, 0x85, 0x2f, 0x7c, 0xd6, 0xd7, 0xc8, 0xd0, 0xf4, 0x2c, 0x68, 0x8c, 0x1f, 0xf7, 0xdf, 0xeb, 0x7c, 0x25, 0xd6, 0x1a, 0x76, 0x3e, 0xaf, 0x28, 0x1f, 0x1d, 0x08, 0x10, 0x20, 0x1c, 0x01, 0x77, 0x1b, 0x5a, 0x48, 0xd6, 0xe5, 0x1c, 0xf9, 0xe3, 0xe0, 0x70, 0x34, 0x5e, 0x02, 0x49, 0xfb, 0x9e, 0x05 },
+    "256 3 15 l02Woi0iS8Aa25FQkUd9RMzZHJpBoRQwAQEX1SxZJA4=",
+    "ed25519.",
+    // vector extracted from https://gitlab.labs.nic.cz/labs/ietf/blob/master/dnskey.py (rev 476d6ded) by printing signature_data
+    "00 0f 0f 02 00 00 0e 10 55 d4 fc 60 55 b9 4c e0 0e 1d 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 0f 00 01 00 00 0e 10 00 14 00 0a 04 6d 61 69 6c 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ",
+    // vector verified from dnskey.py as above, and confirmed with https://www.rfc-editor.org/errata_search.php?rfc=8080&eid=4935
+    "oL9krJun7xfBOIWcGHi7mag5/hdZrKWw15jPGrHpjQeRAvTdszaPD+QLs3fx8A4M3e23mRZ9VrbpMngwcrqNAg==",
+    256,
+    256,
+    257,
+    DNSSECKeeper::ED25519,
+    true
+  },
+#endif /* defined(HAVE_LIBSODIUM) || defined(HAVE_LIBDECAF) */
+};
 
-    // TODO: make this a collection of inputs and resulting sigs for various algos
-    shared_ptr<DNSCryptoKeyEngine> engine = DNSCryptoKeyEngine::makeFromISCString(drc,
-"Private-key-format: v1.2\n"
-"Algorithm: 15 (ED25519)\n"
-"PrivateKey: ODIyNjAzODQ2MjgwODAxMjI2NDUxOTAyMDQxNDIyNjI=");
+static void checkRR(const signerParams& signer)
+{
+  DNSKEYRecordContent drc;
+  auto dcke = DNSCryptoKeyEngine::makeFromISCString(drc, signer.iscMap);
+  DNSSECPrivateKey dpk;
+  dpk.setKey(dcke);
+  dpk.d_flags = signer.rfcFlags;
 
-    DNSSECPrivateKey dpk;
-    dpk.setKey(engine);
+  vector<std::shared_ptr<DNSRecordContent> > rrs;
+  /* values taken from rfc8080 for ed25519 and ed448, rfc5933 for gost */
+  DNSName qname(dpk.d_algorithm == 12 ? "www.example.net." : "example.com.");
 
-    reportBasicTypes();
+  reportBasicTypes();
 
-    rrs.push_back(DNSRecordContent::makeunique(QType::MX, 1, "10 mail.example.com."));
+  RRSIGRecordContent rrc;
+  uint32_t expire = 1440021600;
+  uint32_t inception = 1438207200;
 
-    RRSIGRecordContent rrc;
-    rrc.d_originalttl = 3600;
-    rrc.d_sigexpire = 1440021600;
-    rrc.d_siginception = 1438207200;
+  if (dpk.d_algorithm == 12) {
+    rrc.d_signer = DNSName("example.net.");
+    inception = 946684800;
+    expire = 1893456000;
+    rrs.push_back(DNSRecordContent::makeunique(QType::A, QClass::IN, "192.0.2.1"));
+  }
+  else {
     rrc.d_signer = qname;
-    rrc.d_type = QType::MX;
-    rrc.d_labels = 2;
-    // TODO: derive the next two from the key
-    rrc.d_tag = 3613;
-    rrc.d_algorithm = 15;
+    rrs.push_back(DNSRecordContent::makeunique(QType::MX, QClass::IN, "10 mail.example.com."));
+  }
 
-    string msg = getMessageForRRSET(qname, rrc, rrs, false);
+  rrc.d_originalttl = 3600;
+  rrc.d_sigexpire = expire;
+  rrc.d_siginception = inception;
+  rrc.d_type = rrs.at(0)->getType();
+  rrc.d_labels = qname.countLabels();
+  rrc.d_tag = dpk.getTag();
+  rrc.d_algorithm = dpk.d_algorithm;
 
-    // vector extracted from https://gitlab.labs.nic.cz/labs/ietf/blob/master/dnskey.py (rev 476d6ded) by printing signature_data
-    BOOST_CHECK_EQUAL(makeHexDump(msg), "00 0f 0f 02 00 00 0e 10 55 d4 fc 60 55 b9 4c e0 0e 1d 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 0f 00 01 00 00 0e 10 00 14 00 0a 04 6d 61 69 6c 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ");
+  string msg = getMessageForRRSET(qname, rrc, rrs, false);
 
-    string signature = engine->sign(msg);
+  BOOST_CHECK_EQUAL(makeHexDump(msg), signer.rfcMsgDump);
+
+  string signature = dcke->sign(msg);
+
+  BOOST_CHECK(dcke->verify(msg, signature));
+
+  if (signer.isDeterministic) {
     string b64 = Base64Encode(signature);
+    BOOST_CHECK_EQUAL(b64, signer.rfcB64Signature);
+  }
+  else {
+    std::string raw;
+    B64Decode(signer.rfcB64Signature, raw);
+    BOOST_CHECK(dcke->verify(msg, raw));
+  }
+}
 
-    // vector verified from dnskey.py as above, and confirmed with https://www.rfc-editor.org/errata_search.php?rfc=8080&eid=4935
-    BOOST_CHECK_EQUAL(b64, "oL9krJun7xfBOIWcGHi7mag5/hdZrKWw15jPGrHpjQeRAvTdszaPD+QLs3fx8A4M3e23mRZ9VrbpMngwcrqNAg==");
+BOOST_AUTO_TEST_CASE(test_generic_signers)
+{
+  for (const auto signer : signers) {
+    DNSKEYRecordContent drc;
+    auto dcke = DNSCryptoKeyEngine::makeFromISCString(drc, signer.iscMap);
+
+    BOOST_CHECK_EQUAL(dcke->getAlgorithm(), signer.algorithm);
+    BOOST_CHECK_EQUAL(dcke->getBits(), signer.bits);
+    BOOST_CHECK_EQUAL(dcke->checkKey(), true);
+
+    BOOST_CHECK_EQUAL(drc.d_algorithm, signer.algorithm);
+
+    DNSSECPrivateKey dpk;
+    dpk.setKey(dcke);
+    dpk.d_flags = signer.flags;
+    drc = dpk.getDNSKEY();
+
+    BOOST_CHECK_EQUAL(drc.d_algorithm, signer.algorithm);
+    BOOST_CHECK_EQUAL(drc.d_protocol, 3);
+    BOOST_CHECK_EQUAL(drc.getZoneRepresentation(), signer.zoneRepresentation);
+
+    DNSName name(signer.name);
+    auto ds1 = makeDSFromDNSKey(name, drc, DNSSECKeeper::SHA1);
+    if (!signer.dsSHA1.empty()) {
+      BOOST_CHECK_EQUAL(ds1.getZoneRepresentation(), signer.dsSHA1);
+    }
+
+    auto ds2 = makeDSFromDNSKey(name, drc, DNSSECKeeper::SHA256);
+    if (!signer.dsSHA256.empty()) {
+      BOOST_CHECK_EQUAL(ds2.getZoneRepresentation(), signer.dsSHA256);
+    }
+
+    auto ds4 = makeDSFromDNSKey(name, drc, DNSSECKeeper::SHA384);
+    if (!signer.dsSHA384.empty()) {
+      BOOST_CHECK_EQUAL(ds4.getZoneRepresentation(), signer.dsSHA384);
+    }
+
+    auto signature = dcke->sign(message);
+    BOOST_CHECK(dcke->verify(message, signature));
+
+    if (signer.isDeterministic) {
+      BOOST_CHECK_EQUAL(signature, std::string(signer.signature.begin(), signer.signature.end()));
+    } else {
+      /* since the signing process is not deterministic, we can't directly compare our signature
+         with the one we have. Still the one we have should also validate correctly. */
+      BOOST_CHECK(dcke->verify(message, std::string(signer.signature.begin(), signer.signature.end())));
+    }
+
+    if (!signer.rfcMsgDump.empty() && !signer.rfcB64Signature.empty()) {
+      checkRR(signer);
+    }
+  }
 }
-#endif
 
 #ifdef HAVE_LIBDECAF
 BOOST_AUTO_TEST_CASE(test_ed448_signer) {
-- 
2.40.0