]>
Commit | Line | Data |
---|---|---|
12471842 PL |
1 | /* |
2 | * This file is part of PowerDNS or dnsdist. | |
3 | * Copyright -- PowerDNS.COM B.V. and its contributors | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of version 2 of the GNU General Public License as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | * In addition, for the avoidance of any doubt, permission is granted to | |
10 | * link this program with OpenSSL and to (re)distribute the binaries | |
11 | * produced as the result of such linking. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
21 | */ | |
870a0fe4 AT |
22 | #ifdef HAVE_CONFIG_H |
23 | #include "config.h" | |
24 | #endif | |
4691b2df BH |
25 | #include "dnsparser.hh" |
26 | #include "sstuff.hh" | |
27 | #include "misc.hh" | |
28 | #include "dnswriter.hh" | |
29 | #include "dnsrecords.hh" | |
acbfb1c1 | 30 | #ifndef RECURSOR |
4691b2df | 31 | #include "statbag.hh" |
acbfb1c1 | 32 | #endif |
4691b2df | 33 | #include "iputils.hh" |
fa8fd4d2 | 34 | |
4691b2df | 35 | #include <boost/algorithm/string.hpp> |
4691b2df BH |
36 | #include "dnssecinfra.hh" |
37 | #include "dnsseckeeper.hh" | |
5e8e902d CH |
38 | #include <openssl/hmac.h> |
39 | #include <openssl/sha.h> | |
673208a2 BH |
40 | #include <boost/assign/std/vector.hpp> // for 'operator+=()' |
41 | #include <boost/assign/list_inserter.hpp> | |
f309dacd | 42 | #include "base64.hh" |
9e04108d | 43 | #include "namespaces.hh" |
8daea594 AT |
44 | #ifdef HAVE_P11KIT1 |
45 | #include "pkcs11signers.hh" | |
46 | #endif | |
7f9ac49b | 47 | #include "gss_context.hh" |
da15912b | 48 | #include "misc.hh" |
8daea594 | 49 | |
673208a2 BH |
50 | using namespace boost::assign; |
51 | ||
e69c2dac | 52 | shared_ptr<DNSCryptoKeyEngine> DNSCryptoKeyEngine::makeFromISCFile(DNSKEYRecordContent& drc, const char* fname) |
4691b2df | 53 | { |
f309dacd | 54 | string sline, isc; |
5e1f23ca | 55 | auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(fname, "r"), fclose); |
699e6e37 BH |
56 | if(!fp) { |
57 | throw runtime_error("Unable to read file '"+string(fname)+"' for generating DNS Private Key"); | |
58 | } | |
f309dacd | 59 | |
5e1f23ca | 60 | while(stringfgets(fp.get(), sline)) { |
f309dacd | 61 | isc += sline; |
699e6e37 | 62 | } |
5e1f23ca RG |
63 | fp.reset(); |
64 | ||
e69c2dac | 65 | shared_ptr<DNSCryptoKeyEngine> dke = makeFromISCString(drc, isc); |
be135eeb PD |
66 | vector<string> checkKeyErrors; |
67 | ||
68 | if(!dke->checkKey(&checkKeyErrors)) { | |
69 | string reason; | |
70 | if(checkKeyErrors.size()) { | |
71 | reason = " ("+boost::algorithm::join(checkKeyErrors, ", ")+")"; | |
72 | } | |
73 | throw runtime_error("Invalid DNS Private Key in file '"+string(fname)+"'"+reason); | |
8ca3ea33 RG |
74 | } |
75 | return dke; | |
f309dacd | 76 | } |
4691b2df | 77 | |
e69c2dac | 78 | shared_ptr<DNSCryptoKeyEngine> DNSCryptoKeyEngine::makeFromISCString(DNSKEYRecordContent& drc, const std::string& content) |
f309dacd | 79 | { |
8daea594 | 80 | bool pkcs11=false; |
f309dacd BH |
81 | int algorithm = 0; |
82 | string sline, key, value, raw; | |
9e04108d | 83 | std::istringstream str(content); |
f309dacd | 84 | map<string, string> stormap; |
495155bf | 85 | |
9e04108d | 86 | while(std::getline(str, sline)) { |
f309dacd BH |
87 | tie(key,value)=splitField(sline, ':'); |
88 | trim(value); | |
89 | if(pdns_iequals(key,"algorithm")) { | |
335da0ba AT |
90 | algorithm = pdns_stou(value); |
91 | stormap["algorithm"]=std::to_string(algorithm); | |
f309dacd | 92 | continue; |
8daea594 AT |
93 | } else if (pdns_iequals(key,"pin")) { |
94 | stormap["pin"]=value; | |
95 | continue; | |
96 | } else if (pdns_iequals(key,"engine")) { | |
97 | stormap["engine"]=value; | |
98 | pkcs11=true; | |
99 | continue; | |
100 | } else if (pdns_iequals(key,"slot")) { | |
248d701f | 101 | stormap["slot"]=value; |
8daea594 AT |
102 | continue; |
103 | } else if (pdns_iequals(key,"label")) { | |
104 | stormap["label"]=value; | |
105 | continue; | |
9ee32859 AT |
106 | } else if (pdns_iequals(key,"publabel")) { |
107 | stormap["publabel"]=value; | |
108 | continue; | |
f309dacd BH |
109 | } |
110 | else if(pdns_iequals(key, "Private-key-format")) | |
111 | continue; | |
112 | raw.clear(); | |
113 | B64Decode(value, raw); | |
114 | stormap[toLower(key)]=raw; | |
115 | } | |
e69c2dac | 116 | shared_ptr<DNSCryptoKeyEngine> dpk; |
8daea594 AT |
117 | |
118 | if (pkcs11) { | |
119 | #ifdef HAVE_P11KIT1 | |
f0a0745b AT |
120 | if (stormap.find("slot") == stormap.end()) |
121 | throw PDNSException("Cannot load PKCS#11 key, no Slot specified"); | |
122 | // we need PIN to be at least empty | |
123 | if (stormap.find("pin") == stormap.end()) stormap["pin"] = ""; | |
8daea594 AT |
124 | dpk = PKCS11DNSCryptoKeyEngine::maker(algorithm); |
125 | #else | |
70f0f8c4 | 126 | throw PDNSException("Cannot load PKCS#11 key without support for it"); |
8daea594 AT |
127 | #endif |
128 | } else { | |
129 | dpk=make(algorithm); | |
130 | } | |
f309dacd | 131 | dpk->fromISCMap(drc, stormap); |
022e5e0b BH |
132 | return dpk; |
133 | } | |
134 | ||
189bb9d2 BH |
135 | std::string DNSCryptoKeyEngine::convertToISC() const |
136 | { | |
c99573a5 | 137 | storvector_t stormap = this->convertToISCVector(); |
189bb9d2 | 138 | ostringstream ret; |
c99573a5 | 139 | ret<<"Private-key-format: v1.2\n"; |
ef7cd021 | 140 | for(const stormap_t::value_type& value : stormap) { |
8daea594 AT |
141 | if(value.first != "Algorithm" && value.first != "PIN" && |
142 | value.first != "Slot" && value.first != "Engine" && | |
9ee32859 | 143 | value.first != "Label" && value.first != "PubLabel") |
c99573a5 BH |
144 | ret<<value.first<<": "<<Base64Encode(value.second)<<"\n"; |
145 | else | |
146 | ret<<value.first<<": "<<value.second<<"\n"; | |
189bb9d2 BH |
147 | } |
148 | return ret.str(); | |
149 | } | |
f309dacd | 150 | |
e69c2dac | 151 | shared_ptr<DNSCryptoKeyEngine> DNSCryptoKeyEngine::make(unsigned int algo) |
022e5e0b | 152 | { |
8455425c | 153 | const makers_t& makers = getMakers(); |
022e5e0b | 154 | makers_t::const_iterator iter = makers.find(algo); |
8455425c | 155 | if(iter != makers.cend()) |
022e5e0b BH |
156 | return (iter->second)(algo); |
157 | else { | |
335da0ba | 158 | throw runtime_error("Request to create key object for unknown algorithm number "+std::to_string(algo)); |
699e6e37 | 159 | } |
699e6e37 | 160 | } |
4691b2df | 161 | |
98d13a90 PL |
162 | /** |
163 | * Returns the supported DNSSEC algorithms with the name of the Crypto Backend used | |
164 | * | |
165 | * @return A vector with pairs of (algorithm-number (int), backend-name (string)) | |
166 | */ | |
167 | vector<pair<uint8_t, string>> DNSCryptoKeyEngine::listAllAlgosWithBackend() | |
168 | { | |
169 | vector<pair<uint8_t, string>> ret; | |
170 | for (auto const& value : getMakers()) { | |
171 | shared_ptr<DNSCryptoKeyEngine> dcke(value.second(value.first)); | |
172 | ret.push_back(make_pair(value.first, dcke->getName())); | |
173 | } | |
174 | return ret; | |
175 | } | |
176 | ||
8d9f38f2 | 177 | void DNSCryptoKeyEngine::report(unsigned int algo, maker_t* maker, bool fallback) |
022e5e0b | 178 | { |
189bb9d2 | 179 | getAllMakers()[algo].push_back(maker); |
f309dacd BH |
180 | if(getMakers().count(algo) && fallback) { |
181 | return; | |
4691b2df | 182 | } |
f309dacd | 183 | getMakers()[algo]=maker; |
699e6e37 BH |
184 | } |
185 | ||
166d8647 | 186 | bool DNSCryptoKeyEngine::testAll() |
189bb9d2 | 187 | { |
166d8647 KM |
188 | bool ret=true; |
189 | ||
ef7cd021 | 190 | for(const allmakers_t::value_type& value : getAllMakers()) |
189bb9d2 | 191 | { |
ef7cd021 | 192 | for(maker_t* creator : value.second) { |
530b4335 | 193 | |
ef7cd021 | 194 | for(maker_t* signer : value.second) { |
530b4335 PD |
195 | // multi_map<unsigned int, maker_t*> bestSigner, bestVerifier; |
196 | ||
ef7cd021 | 197 | for(maker_t* verifier : value.second) { |
530b4335 | 198 | try { |
12184d68 | 199 | /* pair<unsigned int, unsigned int> res=*/ testMakers(value.first, creator, signer, verifier); |
530b4335 PD |
200 | } |
201 | catch(std::exception& e) | |
202 | { | |
203 | cerr<<e.what()<<endl; | |
166d8647 | 204 | ret=false; |
530b4335 | 205 | } |
189bb9d2 BH |
206 | } |
207 | } | |
208 | } | |
209 | } | |
166d8647 | 210 | return ret; |
189bb9d2 BH |
211 | } |
212 | ||
166d8647 | 213 | bool DNSCryptoKeyEngine::testOne(int algo) |
cbb0025b | 214 | { |
166d8647 KM |
215 | bool ret=true; |
216 | ||
ef7cd021 | 217 | for(maker_t* creator : getAllMakers()[algo]) { |
530b4335 | 218 | |
ef7cd021 | 219 | for(maker_t* signer : getAllMakers()[algo]) { |
cbb0025b PD |
220 | // multi_map<unsigned int, maker_t*> bestSigner, bestVerifier; |
221 | ||
ef7cd021 | 222 | for(maker_t* verifier : getAllMakers()[algo]) { |
cbb0025b | 223 | try { |
12184d68 | 224 | /* pair<unsigned int, unsigned int> res=*/testMakers(algo, creator, signer, verifier); |
cbb0025b PD |
225 | } |
226 | catch(std::exception& e) | |
227 | { | |
228 | cerr<<e.what()<<endl; | |
166d8647 | 229 | ret=false; |
cbb0025b PD |
230 | } |
231 | } | |
232 | } | |
530b4335 | 233 | } |
166d8647 | 234 | return ret; |
cbb0025b | 235 | } |
12184d68 | 236 | // returns times it took to sign and verify |
530b4335 | 237 | pair<unsigned int, unsigned int> DNSCryptoKeyEngine::testMakers(unsigned int algo, maker_t* creator, maker_t* signer, maker_t* verifier) |
189bb9d2 | 238 | { |
530b4335 | 239 | shared_ptr<DNSCryptoKeyEngine> dckeCreate(creator(algo)); |
189bb9d2 BH |
240 | shared_ptr<DNSCryptoKeyEngine> dckeSign(signer(algo)); |
241 | shared_ptr<DNSCryptoKeyEngine> dckeVerify(verifier(algo)); | |
530b4335 PD |
242 | |
243 | cerr<<"Testing algorithm "<<algo<<": '"<<dckeCreate->getName()<<"' ->'"<<dckeSign->getName()<<"' -> '"<<dckeVerify->getName()<<"' "; | |
189bb9d2 BH |
244 | unsigned int bits; |
245 | if(algo <= 10) | |
246 | bits=1024; | |
902c4e9c CH |
247 | else if(algo == DNSSECKeeper::ECCGOST || algo == DNSSECKeeper::ECDSA256 || algo == DNSSECKeeper::ED25519) |
248 | bits = 256; | |
249 | else if(algo == DNSSECKeeper::ECDSA384) | |
45826dd7 | 250 | bits = 384; |
902c4e9c | 251 | else if(algo == DNSSECKeeper::ED448) |
21a8834a | 252 | bits = 456; |
45826dd7 | 253 | else |
335da0ba | 254 | throw runtime_error("Can't guess key size for algorithm "+std::to_string(algo)); |
45826dd7 | 255 | |
530b4335 PD |
256 | dckeCreate->create(bits); |
257 | ||
258 | { // FIXME: this block copy/pasted from makeFromISCString | |
259 | DNSKEYRecordContent dkrc; | |
260 | int algorithm = 0; | |
261 | string sline, key, value, raw; | |
262 | std::istringstream str(dckeCreate->convertToISC()); | |
263 | map<string, string> stormap; | |
264 | ||
265 | while(std::getline(str, sline)) { | |
266 | tie(key,value)=splitField(sline, ':'); | |
267 | trim(value); | |
268 | if(pdns_iequals(key,"algorithm")) { | |
335da0ba AT |
269 | algorithm = pdns_stou(value); |
270 | stormap["algorithm"]=std::to_string(algorithm); | |
530b4335 | 271 | continue; |
8daea594 AT |
272 | } else if (pdns_iequals(key,"pin")) { |
273 | stormap["pin"]=value; | |
274 | continue; | |
275 | } else if (pdns_iequals(key,"engine")) { | |
276 | stormap["engine"]=value; | |
277 | continue; | |
278 | } else if (pdns_iequals(key,"slot")) { | |
335da0ba AT |
279 | int slot = std::stoi(value); |
280 | stormap["slot"]=std::to_string(slot); | |
8daea594 AT |
281 | continue; |
282 | } else if (pdns_iequals(key,"label")) { | |
283 | stormap["label"]=value; | |
284 | continue; | |
530b4335 PD |
285 | } |
286 | else if(pdns_iequals(key, "Private-key-format")) | |
287 | continue; | |
288 | raw.clear(); | |
289 | B64Decode(value, raw); | |
290 | stormap[toLower(key)]=raw; | |
291 | } | |
292 | dckeSign->fromISCMap(dkrc, stormap); | |
45c2bc60 RG |
293 | if(!dckeSign->checkKey()) { |
294 | throw runtime_error("Verification of key with creator "+dckeCreate->getName()+" with signer "+dckeSign->getName()+" and verifier "+dckeVerify->getName()+" failed"); | |
8ca3ea33 | 295 | } |
530b4335 PD |
296 | } |
297 | ||
189bb9d2 BH |
298 | string message("Hi! How is life?"); |
299 | ||
300 | string signature; | |
301 | DTime dt; dt.set(); | |
f011d0ad BH |
302 | for(unsigned int n = 0; n < 100; ++n) |
303 | signature = dckeSign->sign(message); | |
304 | unsigned int udiffSign= dt.udiff()/100, udiffVerify; | |
189bb9d2 BH |
305 | |
306 | dckeVerify->fromPublicKeyString(dckeSign->getPublicKeyString()); | |
7fbe4163 CH |
307 | if (dckeVerify->getPublicKeyString().compare(dckeSign->getPublicKeyString())) { |
308 | throw runtime_error("Comparison of public key loaded into verifier produced by signer failed"); | |
309 | } | |
189bb9d2 BH |
310 | dt.set(); |
311 | if(dckeVerify->verify(message, signature)) { | |
312 | udiffVerify = dt.udiff(); | |
313 | cerr<<"Signature & verify ok, signature "<<udiffSign<<"usec, verify "<<udiffVerify<<"usec"<<endl; | |
314 | } | |
315 | else { | |
530b4335 | 316 | throw runtime_error("Verification of creator "+dckeCreate->getName()+" with signer "+dckeSign->getName()+" and verifier "+dckeVerify->getName()+" failed"); |
189bb9d2 BH |
317 | } |
318 | return make_pair(udiffSign, udiffVerify); | |
319 | } | |
320 | ||
e69c2dac | 321 | shared_ptr<DNSCryptoKeyEngine> DNSCryptoKeyEngine::makeFromPublicKeyString(unsigned int algorithm, const std::string& content) |
aa65a832 | 322 | { |
e69c2dac | 323 | shared_ptr<DNSCryptoKeyEngine> dpk=make(algorithm); |
189bb9d2 | 324 | dpk->fromPublicKeyString(content); |
aa65a832 BH |
325 | return dpk; |
326 | } | |
327 | ||
f0397b95 | 328 | |
e69c2dac | 329 | shared_ptr<DNSCryptoKeyEngine> DNSCryptoKeyEngine::makeFromPEMString(DNSKEYRecordContent& drc, const std::string& raw) |
ed3f8559 | 330 | { |
ed3f8559 | 331 | |
8455425c | 332 | for(const makers_t::value_type& val : getMakers()) |
022e5e0b | 333 | { |
e69c2dac | 334 | shared_ptr<DNSCryptoKeyEngine> ret=nullptr; |
022e5e0b BH |
335 | try { |
336 | ret = val.second(val.first); | |
337 | ret->fromPEMString(drc, raw); | |
338 | return ret; | |
339 | } | |
340 | catch(...) | |
341 | { | |
ed3f8559 BH |
342 | } |
343 | } | |
022e5e0b | 344 | return 0; |
ed3f8559 | 345 | } |
f0397b95 | 346 | |
4691b2df | 347 | |
8455425c | 348 | static bool sharedDNSSECCompare(const shared_ptr<DNSRecordContent>& a, const shared_ptr<DNSRecordContent>& b) |
4691b2df | 349 | { |
12c06211 | 350 | return a->serialize(g_rootdnsname, true, true) < b->serialize(g_rootdnsname, true, true); |
4691b2df BH |
351 | } |
352 | ||
125058a0 PL |
353 | /** |
354 | * Returns the string that should be hashed to create/verify the RRSIG content | |
355 | * | |
356 | * @param qname DNSName of the RRSIG's owner name. | |
357 | * @param rrc The RRSIGRecordContent we take the Type Covered and | |
358 | * original TTL fields from. | |
359 | * @param signRecords A vector of DNSRecordContent shared_ptr's that are covered | |
360 | * by the RRSIG, where we get the RDATA from. | |
361 | * @param processRRSIGLabels A boolean to trigger processing the RRSIG's "Labels" | |
362 | * field. This is usually only needed for validation | |
363 | * purposes, as the authoritative server correctly | |
364 | * sets qname to the wildcard. | |
365 | */ | |
366 | string getMessageForRRSET(const DNSName& qname, const RRSIGRecordContent& rrc, vector<shared_ptr<DNSRecordContent> >& signRecords, bool processRRSIGLabels) | |
4691b2df BH |
367 | { |
368 | sort(signRecords.begin(), signRecords.end(), sharedDNSSECCompare); | |
369 | ||
370 | string toHash; | |
12c06211 | 371 | toHash.append(const_cast<RRSIGRecordContent&>(rrc).serialize(g_rootdnsname, true, true)); |
ade1b1e9 | 372 | toHash.resize(toHash.size() - rrc.d_signature.length()); // chop off the end, don't sign the signature! |
4691b2df | 373 | |
125058a0 PL |
374 | string nameToHash(qname.toDNSStringLC()); |
375 | ||
376 | if (processRRSIGLabels) { | |
377 | unsigned int rrsig_labels = rrc.d_labels; | |
378 | unsigned int fqdn_labels = qname.countLabels(); | |
379 | ||
380 | if (rrsig_labels < fqdn_labels) { | |
381 | DNSName choppedQname(qname); | |
382 | while (choppedQname.countLabels() > rrsig_labels) | |
383 | choppedQname.chopOff(); | |
384 | nameToHash = "\x01*" + choppedQname.toDNSStringLC(); | |
385 | } else if (rrsig_labels > fqdn_labels) { | |
386 | // The RRSIG Labels field is a lie (or the qname is wrong) and the RRSIG | |
387 | // can never be valid | |
388 | return ""; | |
389 | } | |
390 | } | |
391 | ||
ef7cd021 | 392 | for(shared_ptr<DNSRecordContent>& add : signRecords) { |
125058a0 | 393 | toHash.append(nameToHash); |
4691b2df BH |
394 | uint16_t tmp=htons(rrc.d_type); |
395 | toHash.append((char*)&tmp, 2); | |
396 | tmp=htons(1); // class | |
397 | toHash.append((char*)&tmp, 2); | |
398 | uint32_t ttl=htonl(rrc.d_originalttl); | |
399 | toHash.append((char*)&ttl, 4); | |
feb53a77 | 400 | // for NSEC signatures, we should not lowercase the rdata section |
12c06211 | 401 | string rdata=add->serialize(g_rootdnsname, true, (add->getType() == QType::NSEC) ? false : true); // RFC 6840, 5.1 |
4691b2df BH |
402 | tmp=htons(rdata.length()); |
403 | toHash.append((char*)&tmp, 2); | |
404 | toHash.append(rdata); | |
405 | } | |
f96192e3 | 406 | |
f309dacd | 407 | return toHash; |
4691b2df BH |
408 | } |
409 | ||
8455425c RG |
410 | bool DNSCryptoKeyEngine::isAlgorithmSupported(unsigned int algo) |
411 | { | |
412 | const makers_t& makers = getMakers(); | |
413 | makers_t::const_iterator iter = makers.find(algo); | |
414 | return iter != makers.cend(); | |
415 | } | |
416 | ||
417 | static unsigned int digestToAlgorithmNumber(uint8_t digest) | |
418 | { | |
419 | switch(digest) { | |
420 | case DNSSECKeeper::SHA1: | |
421 | return DNSSECKeeper::RSASHA1; | |
422 | case DNSSECKeeper::SHA256: | |
423 | return DNSSECKeeper::RSASHA256; | |
424 | case DNSSECKeeper::GOST: | |
425 | return DNSSECKeeper::ECCGOST; | |
426 | case DNSSECKeeper::SHA384: | |
427 | return DNSSECKeeper::ECDSA384; | |
428 | default: | |
429 | throw std::runtime_error("Unknown digest type " + std::to_string(digest)); | |
430 | } | |
431 | return 0; | |
432 | } | |
433 | ||
434 | bool DNSCryptoKeyEngine::isDigestSupported(uint8_t digest) | |
435 | { | |
436 | try { | |
437 | unsigned int algo = digestToAlgorithmNumber(digest); | |
438 | return isAlgorithmSupported(algo); | |
439 | } | |
440 | catch(const std::exception& e) { | |
441 | return false; | |
442 | } | |
443 | } | |
444 | ||
445 | DSRecordContent makeDSFromDNSKey(const DNSName& qname, const DNSKEYRecordContent& drc, uint8_t digest) | |
4691b2df BH |
446 | { |
447 | string toHash; | |
0ba4e1ee | 448 | toHash.assign(qname.toDNSStringLC()); |
290a083d | 449 | toHash.append(const_cast<DNSKEYRecordContent&>(drc).serialize(DNSName(), true, true)); |
224778b0 | 450 | |
4691b2df | 451 | DSRecordContent dsrc; |
8455425c RG |
452 | try { |
453 | unsigned int algo = digestToAlgorithmNumber(digest); | |
454 | shared_ptr<DNSCryptoKeyEngine> dpk(DNSCryptoKeyEngine::make(algo)); | |
39315b97 BH |
455 | dsrc.d_digest = dpk->hash(toHash); |
456 | } | |
8455425c | 457 | catch(const std::exception& e) { |
73c9bf8c | 458 | throw std::runtime_error("Asked to create (C)DS record of unknown digest type " + std::to_string(digest)); |
8455425c | 459 | } |
224778b0 | 460 | |
8455425c RG |
461 | dsrc.d_algorithm = drc.d_algorithm; |
462 | dsrc.d_digesttype = digest; | |
463 | dsrc.d_tag = const_cast<DNSKEYRecordContent&>(drc).getTag(); | |
8daea594 | 464 | |
4691b2df BH |
465 | return dsrc; |
466 | } | |
467 | ||
4691b2df | 468 | |
7afd3f74 | 469 | static DNSKEYRecordContent makeDNSKEYFromDNSCryptoKeyEngine(const std::shared_ptr<DNSCryptoKeyEngine>& pk, uint8_t algorithm, uint16_t flags) |
699e6e37 BH |
470 | { |
471 | DNSKEYRecordContent drc; | |
8daea594 | 472 | |
4691b2df | 473 | drc.d_protocol=3; |
c3c89361 | 474 | drc.d_algorithm = algorithm; |
4691b2df | 475 | |
4c1474f3 | 476 | drc.d_flags=flags; |
699e6e37 | 477 | drc.d_key = pk->getPublicKeyString(); |
8daea594 | 478 | |
4691b2df BH |
479 | return drc; |
480 | } | |
481 | ||
b61e407d | 482 | uint32_t getStartOfWeek() |
4691b2df | 483 | { |
b61e407d | 484 | uint32_t now = time(0); |
4691b2df BH |
485 | now -= (now % (7*86400)); |
486 | return now; | |
487 | } | |
488 | ||
28e2e78e | 489 | string hashQNameWithSalt(const NSEC3PARAMRecordContent& ns3prc, const DNSName& qname) |
4691b2df | 490 | { |
e4805005 | 491 | return hashQNameWithSalt(ns3prc.d_salt, ns3prc.d_iterations, qname); |
492 | } | |
493 | ||
494 | string hashQNameWithSalt(const std::string& salt, unsigned int iterations, const DNSName& qname) | |
495 | { | |
496 | unsigned int times = iterations; | |
4691b2df | 497 | unsigned char hash[20]; |
0ba4e1ee | 498 | string toHash(qname.toDNSStringLC()); |
28e2e78e | 499 | |
4691b2df | 500 | for(;;) { |
e4805005 | 501 | toHash.append(salt); |
5e8e902d | 502 | SHA1((unsigned char*)toHash.c_str(), toHash.length(), hash); |
f96192e3 | 503 | toHash.assign((char*)hash, sizeof(hash)); |
28e2e78e KM |
504 | if(!times--) |
505 | break; | |
4691b2df | 506 | } |
28e2e78e | 507 | return toHash; |
4691b2df | 508 | } |
28e2e78e | 509 | |
95823c07 RG |
510 | void incrementHash(std::string& raw) // I wonder if this is correct, cmouse? ;-) |
511 | { | |
512 | if(raw.empty()) | |
513 | return; | |
514 | ||
515 | for(string::size_type pos=raw.size(); pos; ) { | |
516 | --pos; | |
517 | unsigned char c = (unsigned char)raw[pos]; | |
518 | ++c; | |
519 | raw[pos] = (char) c; | |
520 | if(c) | |
521 | break; | |
522 | } | |
523 | } | |
524 | ||
525 | void decrementHash(std::string& raw) // I wonder if this is correct, cmouse? ;-) | |
526 | { | |
527 | if(raw.empty()) | |
528 | return; | |
529 | ||
530 | for(string::size_type pos=raw.size(); pos; ) { | |
531 | --pos; | |
532 | unsigned char c = (unsigned char)raw[pos]; | |
533 | --c; | |
534 | raw[pos] = (char) c; | |
535 | if(c != 0xff) | |
536 | break; | |
537 | } | |
538 | } | |
539 | ||
673208a2 BH |
540 | DNSKEYRecordContent DNSSECPrivateKey::getDNSKEY() const |
541 | { | |
8d9f38f2 | 542 | return makeDNSKEYFromDNSCryptoKeyEngine(getKey(), d_algorithm, d_flags); |
673208a2 | 543 | } |
ed3f8559 BH |
544 | |
545 | class DEREater | |
546 | { | |
547 | public: | |
548 | DEREater(const std::string& str) : d_str(str), d_pos(0) | |
549 | {} | |
550 | ||
551 | struct eof{}; | |
552 | ||
553 | uint8_t getByte() | |
554 | { | |
555 | if(d_pos >= d_str.length()) { | |
556 | throw eof(); | |
557 | } | |
558 | return (uint8_t) d_str[d_pos++]; | |
559 | } | |
560 | ||
561 | uint32_t getLength() | |
562 | { | |
563 | uint8_t first = getByte(); | |
564 | if(first < 0x80) { | |
565 | return first; | |
566 | } | |
567 | first &= ~0x80; | |
568 | ||
569 | uint32_t len=0; | |
570 | for(int n=0; n < first; ++n) { | |
571 | len *= 0x100; | |
572 | len += getByte(); | |
573 | } | |
574 | return len; | |
575 | } | |
576 | ||
577 | std::string getBytes(unsigned int len) | |
578 | { | |
579 | std::string ret; | |
580 | for(unsigned int n=0; n < len; ++n) | |
581 | ret.append(1, (char)getByte()); | |
582 | return ret; | |
583 | } | |
584 | ||
585 | std::string::size_type getOffset() | |
586 | { | |
587 | return d_pos; | |
588 | } | |
589 | private: | |
590 | const std::string& d_str; | |
591 | std::string::size_type d_pos; | |
592 | }; | |
593 | ||
ea3816cf | 594 | static string calculateHMAC(const std::string& key, const std::string& text, TSIGHashEnum hasher) { |
78bcb858 | 595 | |
5e8e902d CH |
596 | const EVP_MD* md_type; |
597 | unsigned int outlen; | |
598 | unsigned char hash[EVP_MAX_MD_SIZE]; | |
599 | switch(hasher) { | |
600 | case TSIG_MD5: | |
601 | md_type = EVP_md5(); | |
602 | break; | |
603 | case TSIG_SHA1: | |
604 | md_type = EVP_sha1(); | |
605 | break; | |
606 | case TSIG_SHA224: | |
607 | md_type = EVP_sha224(); | |
608 | break; | |
609 | case TSIG_SHA256: | |
610 | md_type = EVP_sha256(); | |
611 | break; | |
612 | case TSIG_SHA384: | |
613 | md_type = EVP_sha384(); | |
614 | break; | |
615 | case TSIG_SHA512: | |
616 | md_type = EVP_sha512(); | |
617 | break; | |
618 | default: | |
60a1c204 | 619 | throw PDNSException("Unknown hash algorithm requested from calculateHMAC()"); |
5e8e902d CH |
620 | } |
621 | ||
622 | unsigned char* out = HMAC(md_type, reinterpret_cast<const unsigned char*>(key.c_str()), key.size(), reinterpret_cast<const unsigned char*>(text.c_str()), text.size(), hash, &outlen); | |
60a1c204 RG |
623 | if (out == NULL || outlen == 0) { |
624 | throw PDNSException("HMAC computation failed"); | |
5e8e902d | 625 | } |
5e8e902d | 626 | |
60a1c204 RG |
627 | return string((char*) hash, outlen); |
628 | } | |
629 | ||
ea3816cf | 630 | static bool constantTimeStringEquals(const std::string& a, const std::string& b) |
60a1c204 RG |
631 | { |
632 | if (a.size() != b.size()) { | |
633 | return false; | |
634 | } | |
635 | const size_t size = a.size(); | |
636 | #if OPENSSL_VERSION_NUMBER >= 0x0090819fL | |
637 | return CRYPTO_memcmp(a.c_str(), b.c_str(), size) == 0; | |
638 | #else | |
639 | const volatile unsigned char *_a = (const volatile unsigned char *) a.c_str(); | |
640 | const volatile unsigned char *_b = (const volatile unsigned char *) b.c_str(); | |
641 | unsigned char res = 0; | |
642 | ||
643 | for (size_t idx = 0; idx < size; idx++) { | |
644 | res |= _a[idx] ^ _b[idx]; | |
645 | } | |
646 | ||
647 | return res == 0; | |
648 | #endif | |
3213be1e AT |
649 | } |
650 | ||
ea3816cf | 651 | static string makeTSIGPayload(const string& previous, const char* packetBegin, size_t packetSize, const DNSName& tsigKeyName, const TSIGRecordContent& trc, bool timersonly) |
01cb2fe2 BH |
652 | { |
653 | string message; | |
c48dec72 | 654 | |
01cb2fe2 BH |
655 | if(!previous.empty()) { |
656 | uint16_t len = htons(previous.length()); | |
ea3816cf | 657 | message.append(reinterpret_cast<const char*>(&len), sizeof(len)); |
01cb2fe2 BH |
658 | message.append(previous); |
659 | } | |
ea3816cf RG |
660 | |
661 | message.append(packetBegin, packetSize); | |
01cb2fe2 BH |
662 | |
663 | vector<uint8_t> signVect; | |
290a083d | 664 | DNSPacketWriter dw(signVect, DNSName(), 0); |
57ddc8ba | 665 | auto pos=signVect.size(); |
01cb2fe2 | 666 | if(!timersonly) { |
ea3816cf | 667 | dw.xfrName(tsigKeyName, false); |
f4d26b4f | 668 | dw.xfr16BitInt(QClass::ANY); // class |
01cb2fe2 | 669 | dw.xfr32BitInt(0); // TTL |
68e9d647 | 670 | dw.xfrName(trc.d_algoName.makeLowerCase(), false); |
01cb2fe2 BH |
671 | } |
672 | ||
673 | uint32_t now = trc.d_time; | |
674 | dw.xfr48BitInt(now); | |
675 | dw.xfr16BitInt(trc.d_fudge); // fudge | |
676 | if(!timersonly) { | |
677 | dw.xfr16BitInt(trc.d_eRcode); // extended rcode | |
678 | dw.xfr16BitInt(trc.d_otherData.length()); // length of 'other' data | |
679 | // dw.xfrBlob(trc->d_otherData); | |
680 | } | |
57ddc8ba | 681 | message.append(signVect.begin()+pos, signVect.end()); |
01cb2fe2 BH |
682 | return message; |
683 | } | |
684 | ||
ea3816cf | 685 | static string makeTSIGMessageFromTSIGPacket(const string& opacket, unsigned int tsigOffset, const DNSName& keyname, const TSIGRecordContent& trc, const string& previous, bool timersonly, unsigned int dnsHeaderOffset=0) |
01cb2fe2 | 686 | { |
ea3816cf RG |
687 | string message; |
688 | string packet(opacket); | |
981fa489 | 689 | |
ea3816cf RG |
690 | packet.resize(tsigOffset); // remove the TSIG record at the end as per RFC2845 3.4.1 |
691 | packet[(dnsHeaderOffset + sizeof(struct dnsheader))-1]--; // Decrease ARCOUNT because we removed the TSIG RR in the previous line. | |
01cb2fe2 | 692 | |
ea3816cf RG |
693 | |
694 | // Replace the message ID with the original message ID from the TSIG record. | |
695 | // This is needed for forwarded DNS Update as they get a new ID when forwarding (section 6.1 of RFC2136). The TSIG record stores the original ID and the | |
696 | // signature was created with the original ID, so we replace it here to get the originally signed message. | |
697 | // If the message is not forwarded, we simply override it with the same id. | |
698 | uint16_t origID = htons(trc.d_origID); | |
699 | packet.replace(0, 2, (char*)&origID, 2); | |
700 | ||
701 | return makeTSIGPayload(previous, packet.data(), packet.size(), keyname, trc, timersonly); | |
702 | } | |
703 | ||
704 | void addTSIG(DNSPacketWriter& pw, TSIGRecordContent& trc, const DNSName& tsigkeyname, const string& tsigsecret, const string& tsigprevious, bool timersonly) | |
705 | { | |
706 | TSIGHashEnum algo; | |
707 | if (!getTSIGHashEnum(trc.d_algoName, algo)) { | |
86f1af1c | 708 | throw PDNSException(string("Unsupported TSIG HMAC algorithm ") + trc.d_algoName.toLogString()); |
01cb2fe2 | 709 | } |
ea3816cf RG |
710 | |
711 | string toSign = makeTSIGPayload(tsigprevious, reinterpret_cast<const char*>(pw.getContent().data()), pw.getContent().size(), tsigkeyname, trc, timersonly); | |
01cb2fe2 | 712 | |
7f9ac49b | 713 | if (algo == TSIG_GSS) { |
ea3816cf | 714 | if (!gss_add_signature(tsigkeyname, toSign, trc.d_mac)) { |
86f1af1c | 715 | throw PDNSException(string("Could not add TSIG signature with algorithm 'gss-tsig' and key name '")+tsigkeyname.toLogString()+string("'")); |
7f9ac49b AT |
716 | } |
717 | } else { | |
ea3816cf RG |
718 | trc.d_mac = calculateHMAC(tsigsecret, toSign, algo); |
719 | // trc.d_mac[0]++; // sabotage | |
7f9ac49b | 720 | } |
e693ff5a | 721 | pw.startRecord(tsigkeyname, QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false); |
ea3816cf | 722 | trc.toPacket(pw); |
01cb2fe2 BH |
723 | pw.commit(); |
724 | } | |
8daea594 | 725 | |
ea3816cf RG |
726 | bool validateTSIG(const std::string& packet, size_t sigPos, const TSIGTriplet& tt, const TSIGRecordContent& trc, const std::string& previousMAC, const std::string& theirMAC, bool timersOnly, unsigned int dnsHeaderOffset) |
727 | { | |
728 | uint64_t delta = std::abs((int64_t)trc.d_time - (int64_t)time(nullptr)); | |
729 | if(delta > trc.d_fudge) { | |
730 | throw std::runtime_error("Invalid TSIG time delta " + std::to_string(delta) + " > fudge " + std::to_string(trc.d_fudge)); | |
731 | } | |
732 | ||
733 | TSIGHashEnum algo; | |
734 | if (!getTSIGHashEnum(trc.d_algoName, algo)) { | |
86f1af1c | 735 | throw std::runtime_error("Unsupported TSIG HMAC algorithm " + trc.d_algoName.toLogString()); |
ea3816cf RG |
736 | } |
737 | ||
738 | TSIGHashEnum expectedAlgo; | |
739 | if (!getTSIGHashEnum(tt.algo, expectedAlgo)) { | |
86f1af1c | 740 | throw std::runtime_error("Unsupported TSIG HMAC algorithm expected " + tt.algo.toLogString()); |
ea3816cf RG |
741 | } |
742 | ||
743 | if (algo != expectedAlgo) { | |
86f1af1c | 744 | throw std::runtime_error("Signature with TSIG key '"+tt.name.toLogString()+"' does not match the expected algorithm (" + tt.algo.toLogString() + " / " + trc.d_algoName.toLogString() + ")"); |
ea3816cf RG |
745 | } |
746 | ||
747 | string tsigMsg; | |
748 | tsigMsg = makeTSIGMessageFromTSIGPacket(packet, sigPos, tt.name, trc, previousMAC, timersOnly, dnsHeaderOffset); | |
749 | ||
750 | if (algo == TSIG_GSS) { | |
751 | GssContext gssctx(tt.name); | |
752 | if (!gss_verify_signature(tt.name, tsigMsg, theirMAC)) { | |
86f1af1c | 753 | throw std::runtime_error("Signature with TSIG key '"+tt.name.toLogString()+"' failed to validate"); |
ea3816cf RG |
754 | } |
755 | } else { | |
756 | string ourMac = calculateHMAC(tt.secret, tsigMsg, algo); | |
757 | ||
758 | if(!constantTimeStringEquals(ourMac, theirMAC)) { | |
86f1af1c | 759 | throw std::runtime_error("Signature with TSIG key '"+tt.name.toLogString()+"' failed to validate"); |
ea3816cf RG |
760 | } |
761 | } | |
762 | ||
763 | return true; | |
764 | } |