1 #define BOOST_TEST_DYN_LINK
2 #define BOOST_TEST_NO_MAIN
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
6 #include <boost/test/unit_test.hpp>
7 #include <boost/assign/list_of.hpp>
8 
9 #include <boost/tuple/tuple.hpp>
10 
11 #include "base32.hh"
12 #include "base64.hh"
13 #include "dnsseckeeper.hh"
14 #include "dnssecinfra.hh"
15 #include "misc.hh"
16 
17 BOOST_AUTO_TEST_SUITE(test_signers)
18 
19 static const std::string message = "Very good, young padawan.";
20 
21 static const struct signerParams
22 {
23   std::string iscMap;
24   std::string dsSHA1;
25   std::string dsSHA256;
26   std::string dsSHA384;
27   std::vector<uint8_t> signature;
28   std::string zoneRepresentation;
29   std::string name;
30   std::string rfcMsgDump;
31   std::string rfcB64Signature;
32   int bits;
33   uint16_t flags;
34   uint16_t rfcFlags;
35   uint8_t algorithm;
36   bool isDeterministic;
37 } signers[] = {
38   /* RSA from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sample_keys.h */
39   { "Algorithm: 8\n"
40     "Modulus: qtunSiHnYq4XRLBehKAw1Glxb+48oIpAC7w3Jhpj570bb2uHt6orWGqnuyRtK8oqUi2ABoV0PFm8+IPgDMEdCQ==\n"
41     "PublicExponent: AQAB\n"
42     "PrivateExponent: MiItniUAngXzMeaGdWgDq/AcpvlCtOCcFlVt4TJRKkfp8DNRSxIxG53NNlOFkp1W00iLHqYC2GrH1qkKgT9l+Q==\n"
43     "Prime1: 3sZmM+5FKFy5xaRt0n2ZQOZ2C+CoKzVil6/al9LmYVs=\n"
44     "Prime2: xFcNWSIW6v8dDL2JQ1kxFDm/8RVeUSs1BNXXnvCjBGs=\n"
45     "Exponent1: WuUwhjfN1+4djlrMxHmisixWNfpwI1Eg7Ss/UXsnrMk=\n"
46     "Exponent2: vfMqas1cNsXRqP3Fym6D2Pl2BRuTQBv5E1B/ZrmQPTk=\n"
47     "Coefficient: Q10z43cA3hkwOkKsj5T0W5jrX97LBwZoY5lIjDCa4+M=\n",
48     "1506 8 1 172a500b374158d1a64ba3073cdbbc319b2fdf2c",
49     "1506 8 2 253b099ff47b02c6ffa52695a30a94c6681c56befe0e71a5077d6f79514972f9",
50     "1506 8 4 22ea940600dc2d9a98b1126c26ac0dc5c91b31eb50fe784b36ad675e9eecfe6573c1f85c53b6bc94580f3ac443d13c4c",
51     /* from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sign.c */
52     { 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 },
53     "256 3 8 AwEAAarbp0oh52KuF0SwXoSgMNRpcW/uPKCKQAu8NyYaY+e9G29rh7eqK1hqp7skbSvKKlItgAaFdDxZvPiD4AzBHQk=",
54     "rsa.",
55     "",
56     "",
57     512,
58     256,
59     0,
60     DNSSECKeeper::RSASHA256,
61     true
62   },
63 #ifdef HAVE_LIBCRYPTO_ECDSA
64   /* ECDSA-P256-SHA256 from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sample_keys.h */
65   { "Algorithm: 13\n"
66     "PrivateKey: iyLIPdk3DOIxVmmSYlmTstbtUPiVlEyDX46psyCwNVQ=\n",
67     "5345 13 1 954103ac7c43810ce9f414e80f30ab1cbe49b236",
68     "5345 13 2 bac2107036e735b50f85006ce409a19a3438cab272e70769ebda032239a3d0ca",
69     "5345 13 4 a0ac6790483872be72a258314200a88ab75cdd70f66a18a09f0f414c074df0989fdb1df0e67d82d4312cda67b93a76c1",
70     /* from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sign.c */
71     { 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, },
72     "256 3 13 8uD7C4THTM/w7uhryRSToeE/jKT78/p853RX0L5EwrZrSLBubLPiBw7gbvUP6SsIga5ZQ4CSAxNmYA/gZsuXzA==",
73     "ecdsa.",
74     "",
75     "",
76     256,
77     256,
78     0,
79     DNSSECKeeper::ECDSA256,
80     false
81   },
82 #endif /* HAVE_LIBCRYPTO_ECDSA */
83 #if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED25519)
84   /* ed25519 from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sample_keys.h,
85      also from rfc8080 section 6.1 */
86   { "Algorithm: 15\n"
87     "PrivateKey: ODIyNjAzODQ2MjgwODAxMjI2NDUxOTAyMDQxNDIyNjI=\n",
88     "3612 15 1 501249721e1f09a79d30d5c6c4dca1dc1da4ed5d",
89     "3612 15 2 1b1c8766b2a96566ff196f77c0c4194af86aaa109c5346ff60231a27d2b07ac0",
90     "3612 15 4 d11831153af4985efbd0ae792c967eb4aff3c35488db95f7e2f85dcec74ae8f59f9a72641798c91c67c675db1d710c18",
91     /* from https://github.com/CZ-NIC/knot/blob/master/src/dnssec/tests/sign.c */
92     { 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 },
93     "256 3 15 l02Woi0iS8Aa25FQkUd9RMzZHJpBoRQwAQEX1SxZJA4=",
94     "ed25519.",
95     // vector extracted from https://gitlab.labs.nic.cz/labs/ietf/blob/master/dnskey.py (rev 476d6ded) by printing signature_data
96     "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 ",
97     // vector verified from dnskey.py as above, and confirmed with https://www.rfc-editor.org/errata_search.php?rfc=8080&eid=4935
98     "oL9krJun7xfBOIWcGHi7mag5/hdZrKWw15jPGrHpjQeRAvTdszaPD+QLs3fx8A4M3e23mRZ9VrbpMngwcrqNAg==",
99     256,
100     256,
101     257,
102     DNSSECKeeper::ED25519,
103     true
104   },
105 #endif /* defined(HAVE_LIBSODIUM) || defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED25519) */
106 };
107 
checkRR(const signerParams & signer)108 static void checkRR(const signerParams& signer)
109 {
110   DNSKEYRecordContent drc;
111   auto dcke = std::shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromISCString(drc, signer.iscMap));
112   DNSSECPrivateKey dpk;
113   dpk.setKey(dcke);
114   dpk.d_flags = signer.rfcFlags;
115 
116   sortedRecords_t rrs;
117   /* values taken from rfc8080 for ed25519 and ed448, rfc5933 for gost */
118   DNSName qname(dpk.d_algorithm == 12 ? "www.example.net." : "example.com.");
119 
120   reportBasicTypes();
121 
122   RRSIGRecordContent rrc;
123   uint32_t expire = 1440021600;
124   uint32_t inception = 1438207200;
125 
126   if (dpk.d_algorithm == 12) {
127     rrc.d_signer = DNSName("example.net.");
128     inception = 946684800;
129     expire = 1893456000;
130     rrs.insert(DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.1"));
131   }
132   else {
133     rrc.d_signer = qname;
134     rrs.insert(DNSRecordContent::mastermake(QType::MX, QClass::IN, "10 mail.example.com."));
135   }
136 
137   rrc.d_originalttl = 3600;
138   rrc.d_sigexpire = expire;
139   rrc.d_siginception = inception;
140   rrc.d_type = (*rrs.cbegin())->getType();
141   rrc.d_labels = qname.countLabels();
142   rrc.d_tag = dpk.getTag();
143   rrc.d_algorithm = dpk.d_algorithm;
144 
145   string msg = getMessageForRRSET(qname, rrc, rrs, false);
146 
147   BOOST_CHECK_EQUAL(makeHexDump(msg), signer.rfcMsgDump);
148 
149   string signature = dcke->sign(msg);
150 
151   BOOST_CHECK(dcke->verify(msg, signature));
152 
153   if (signer.isDeterministic) {
154     string b64 = Base64Encode(signature);
155     BOOST_CHECK_EQUAL(b64, signer.rfcB64Signature);
156   }
157   else {
158     std::string raw;
159     B64Decode(signer.rfcB64Signature, raw);
160     BOOST_CHECK(dcke->verify(msg, raw));
161   }
162 }
163 
BOOST_AUTO_TEST_CASE(test_generic_signers)164 BOOST_AUTO_TEST_CASE(test_generic_signers)
165 {
166   for (const auto& signer : signers) {
167     DNSKEYRecordContent drc;
168     auto dcke = std::shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromISCString(drc, signer.iscMap));
169 
170     BOOST_CHECK_EQUAL(dcke->getAlgorithm(), signer.algorithm);
171     BOOST_CHECK_EQUAL(dcke->getBits(), signer.bits);
172     BOOST_CHECK_EQUAL(dcke->checkKey(nullptr), true);
173 
174     BOOST_CHECK_EQUAL(drc.d_algorithm, signer.algorithm);
175 
176     DNSSECPrivateKey dpk;
177     dpk.setKey(dcke);
178     dpk.d_flags = signer.flags;
179     drc = dpk.getDNSKEY();
180 
181     BOOST_CHECK_EQUAL(drc.d_algorithm, signer.algorithm);
182     BOOST_CHECK_EQUAL(drc.d_protocol, 3);
183     BOOST_CHECK_EQUAL(drc.getZoneRepresentation(), signer.zoneRepresentation);
184 
185     DNSName name(signer.name);
186     auto ds1 = makeDSFromDNSKey(name, drc, DNSSECKeeper::DIGEST_SHA1);
187     if (!signer.dsSHA1.empty()) {
188       BOOST_CHECK_EQUAL(ds1.getZoneRepresentation(), signer.dsSHA1);
189     }
190 
191     auto ds2 = makeDSFromDNSKey(name, drc, DNSSECKeeper::DIGEST_SHA256);
192     if (!signer.dsSHA256.empty()) {
193       BOOST_CHECK_EQUAL(ds2.getZoneRepresentation(), signer.dsSHA256);
194     }
195 
196     auto ds4 = makeDSFromDNSKey(name, drc, DNSSECKeeper::DIGEST_SHA384);
197     if (!signer.dsSHA384.empty()) {
198       BOOST_CHECK_EQUAL(ds4.getZoneRepresentation(), signer.dsSHA384);
199     }
200 
201     auto signature = dcke->sign(message);
202     BOOST_CHECK(dcke->verify(message, signature));
203 
204     if (signer.isDeterministic) {
205       BOOST_CHECK_EQUAL(signature, std::string(signer.signature.begin(), signer.signature.end()));
206     } else {
207       /* since the signing process is not deterministic, we can't directly compare our signature
208          with the one we have. Still the one we have should also validate correctly. */
209       BOOST_CHECK(dcke->verify(message, std::string(signer.signature.begin(), signer.signature.end())));
210     }
211 
212     if (!signer.rfcMsgDump.empty() && !signer.rfcB64Signature.empty()) {
213       checkRR(signer);
214     }
215   }
216 }
217 
218 #if defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED448)
BOOST_AUTO_TEST_CASE(test_ed448_signer)219 BOOST_AUTO_TEST_CASE(test_ed448_signer) {
220     sortedRecords_t rrs;
221     DNSName qname("example.com.");
222     DNSKEYRecordContent drc;
223 
224     // TODO: make this a collection of inputs and resulting sigs for various algos
225     shared_ptr<DNSCryptoKeyEngine> engine = DNSCryptoKeyEngine::makeFromISCString(drc,
226 "Private-key-format: v1.2\n"
227 "Algorithm: 16 (ED448)\n"
228 "PrivateKey: xZ+5Cgm463xugtkY5B0Jx6erFTXp13rYegst0qRtNsOYnaVpMx0Z/c5EiA9x8wWbDDct/U3FhYWA\n");
229 
230     DNSSECPrivateKey dpk;
231     dpk.setKey(engine);
232 
233     reportBasicTypes();
234 
235     rrs.insert(DNSRecordContent::mastermake(QType::MX, 1, "10 mail.example.com."));
236 
237     RRSIGRecordContent rrc;
238     rrc.d_originalttl = 3600;
239     rrc.d_sigexpire = 1440021600;
240     rrc.d_siginception = 1438207200;
241     rrc.d_signer = qname;
242     rrc.d_type = QType::MX;
243     rrc.d_labels = 2;
244     // TODO: derive the next two from the key
245     rrc.d_tag = 9713;
246     rrc.d_algorithm = 16;
247 
248     string msg = getMessageForRRSET(qname, rrc, rrs, false);
249 
250     // vector extracted from https://gitlab.labs.nic.cz/labs/ietf/blob/master/dnskey.py (rev 476d6ded) by printing signature_data
251     BOOST_CHECK_EQUAL(makeHexDump(msg), "00 0f 10 02 00 00 0e 10 55 d4 fc 60 55 b9 4c e0 25 f1 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 ");
252 
253     string signature = engine->sign(msg);
254     string b64 = Base64Encode(signature);
255 
256     // vector verified from dnskey.py as above, and confirmed with https://www.rfc-editor.org/errata_search.php?rfc=8080&eid=4935
257     BOOST_CHECK_EQUAL(b64, "3cPAHkmlnxcDHMyg7vFC34l0blBhuG1qpwLmjInI8w1CMB29FkEAIJUA0amxWndkmnBZ6SKiwZSAxGILn/NBtOXft0+Gj7FSvOKxE/07+4RQvE581N3Aj/JtIyaiYVdnYtyMWbSNyGEY2213WKsJlwEA");
258 }
259 #endif /* defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED448) */
260 
BOOST_AUTO_TEST_CASE(test_hash_qname_with_salt)261 BOOST_AUTO_TEST_CASE(test_hash_qname_with_salt) {
262   {
263     // rfc5155 appendix A
264     const unsigned char salt[] = { 0xaa, 0xbb, 0xcc, 0xdd };
265     const unsigned int iterations{12};
266     const std::vector<std::pair<std::string, std::string>> namesToHashes = {
267       { "example", "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom" },
268       { "a.example", "35mthgpgcu1qg68fab165klnsnk3dpvl" },
269       { "ai.example", "gjeqe526plbf1g8mklp59enfd789njgi" },
270       { "ns1.example", "2t7b4g4vsa5smi47k61mv5bv1a22bojr" },
271       { "ns2.example", "q04jkcevqvmu85r014c7dkba38o0ji5r" },
272       { "w.example", "k8udemvp1j2f7eg6jebps17vp3n8i58h" },
273       { "*.w.example", "r53bq7cc2uvmubfu5ocmm6pers9tk9en" },
274       { "x.w.example", "b4um86eghhds6nea196smvmlo4ors995" },
275       { "y.w.example", "ji6neoaepv8b5o6k4ev33abha8ht9fgc" },
276       { "x.y.w.example", "2vptu5timamqttgl4luu9kg21e0aor3s" },
277       { "xx.example", "t644ebqk9bibcna874givr6joj62mlhv" },
278       { "2t7b4g4vsa5smi47k61mv5bv1a22bojr.example", "kohar7mbb8dc2ce8a9qvl8hon4k53uhi" },
279     };
280 
281     for (const auto& [name, expectedHash] : namesToHashes) {
282       auto hash = hashQNameWithSalt(std::string(reinterpret_cast<const char*>(salt), sizeof(salt)), iterations, DNSName(name));
283       BOOST_CHECK_EQUAL(toBase32Hex(hash), expectedHash);
284     }
285   }
286 
287   {
288     /* no additional iterations, very short salt */
289     const unsigned char salt[] = { 0xFF };
290     const unsigned int iterations{0};
291     const std::vector<std::pair<std::string, std::string>> namesToHashes = {
292       { "example", "s9dp8o2l6jgqgg26ecobtjooe7p019cs" },
293     };
294 
295     for (const auto& [name, expectedHash] : namesToHashes) {
296       auto hash = hashQNameWithSalt(std::string(reinterpret_cast<const char*>(salt), sizeof(salt)), iterations, DNSName(name));
297       BOOST_CHECK_EQUAL(toBase32Hex(hash), expectedHash);
298     }
299   }
300 
301   {
302     /* only one iteration */
303     const unsigned char salt[] = { 0xaa, 0xbb, 0xcc, 0xdd };
304     const unsigned int iterations{1};
305     const std::vector<std::pair<std::string, std::string>> namesToHashes = {
306       { "example", "ulddquehrj5jpf50ga76vgqr1oq40133" },
307     };
308 
309     for (const auto& [name, expectedHash] : namesToHashes) {
310       auto hash = hashQNameWithSalt(std::string(reinterpret_cast<const char*>(salt), sizeof(salt)), iterations, DNSName(name));
311       BOOST_CHECK_EQUAL(toBase32Hex(hash), expectedHash);
312     }
313   }
314 
315   {
316     /* 65535 iterations, long salt */
317     unsigned char salt[255];
318     for (unsigned char idx = 0; idx < 255; idx++) {
319       salt[idx] = idx;
320     };
321     const unsigned int iterations{65535};
322     const std::vector<std::pair<std::string, std::string>> namesToHashes = {
323       { "example", "no95j4cfile8avstr7bn4aj9he18trri" },
324     };
325 
326     for (const auto& [name, expectedHash] : namesToHashes) {
327       auto hash = hashQNameWithSalt(std::string(reinterpret_cast<const char*>(salt), sizeof(salt)), iterations, DNSName(name));
328       BOOST_CHECK_EQUAL(toBase32Hex(hash), expectedHash);
329     }
330   }
331 }
332 
333 BOOST_AUTO_TEST_SUITE_END()
334