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