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