]>
Commit | Line | Data |
---|---|---|
882358c8 | 1 | /* |
12471842 PL |
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 | |
882358c8 BH |
25 | #include "dnssecinfra.hh" |
26 | #include "namespaces.hh" | |
fa8fd4d2 | 27 | |
bfcdbc13 | 28 | #include "digests.hh" |
882358c8 | 29 | #include "dnsseckeeper.hh" |
ff1040bf | 30 | #include "dns_random.hh" |
882358c8 | 31 | #include "lock.hh" |
bba84134 | 32 | #include "arguments.hh" |
c681ff64 | 33 | #include "statbag.hh" |
ec47cf6f FM |
34 | #include "sha.hh" |
35 | ||
c681ff64 | 36 | extern StatBag S; |
882358c8 | 37 | |
ec47cf6f | 38 | using signaturecache_t = map<pair<string, string>, string>; |
4f4b8ed8 | 39 | static SharedLockGuarded<signaturecache_t> g_signatures; |
8455425c RG |
40 | static int g_cacheweekno; |
41 | ||
e335c490 | 42 | const static std::set<uint16_t> g_KSKSignedQTypes {QType::DNSKEY, QType::CDS, QType::CDNSKEY}; |
8455425c RG |
43 | AtomicCounter* g_signatureCount; |
44 | ||
b60fb4a9 | 45 | static std::string getLookupKeyFromMessage(const std::string& msg) |
bfcdbc13 RG |
46 | { |
47 | try { | |
f2e4e85a | 48 | return pdns::md5(msg); |
bfcdbc13 RG |
49 | } |
50 | catch(const std::runtime_error& e) { | |
f2e4e85a | 51 | return pdns::sha1(msg); |
bfcdbc13 RG |
52 | } |
53 | } | |
54 | ||
b60fb4a9 | 55 | static std::string getLookupKeyFromPublicKey(const std::string& pubKey) |
ab5df144 RG |
56 | { |
57 | /* arbitrarily cut off at 64 bytes, the main idea is to save space | |
b60fb4a9 | 58 | for very large keys like RSA ones (1024+ bits so 128+ bytes) by storing a 20 bytes hash |
ab5df144 RG |
59 | instead */ |
60 | if (pubKey.size() <= 64) { | |
61 | return pubKey; | |
62 | } | |
ec47cf6f | 63 | return pdns::sha1sum(pubKey); |
ab5df144 RG |
64 | } |
65 | ||
c1e7b833 | 66 | static void fillOutRRSIG(DNSSECPrivateKey& dpk, const DNSName& signQName, RRSIGRecordContent& rrc, const sortedRecords_t& toSign) |
8455425c RG |
67 | { |
68 | if(!g_signatureCount) | |
69 | g_signatureCount = S.getPointer("signatures"); | |
70 | ||
71 | DNSKEYRecordContent drc = dpk.getDNSKEY(); | |
ca23a01d | 72 | const std::shared_ptr<DNSCryptoKeyEngine>& rc = dpk.getKey(); |
8455425c RG |
73 | rrc.d_tag = drc.getTag(); |
74 | rrc.d_algorithm = drc.d_algorithm; | |
75 | ||
ab5df144 | 76 | string msg = getMessageForRRSET(signQName, rrc, toSign); // this is what we will hash & sign |
b60fb4a9 | 77 | pair<string, string> lookup(getLookupKeyFromPublicKey(drc.d_key), getLookupKeyFromMessage(msg)); // this hash is a memory saving exercise |
8455425c | 78 | |
b60fb4a9 RG |
79 | bool doCache = true; |
80 | if (doCache) { | |
4f4b8ed8 RG |
81 | auto signatures = g_signatures.read_lock(); |
82 | signaturecache_t::const_iterator iter = signatures->find(lookup); | |
83 | if (iter != signatures->end()) { | |
8455425c RG |
84 | rrc.d_signature=iter->second; |
85 | return; | |
86 | } | |
87 | // else cerr<<"Miss!"<<endl; | |
88 | } | |
89 | ||
90 | rrc.d_signature = rc->sign(msg); | |
91 | (*g_signatureCount)++; | |
92 | if(doCache) { | |
c02c999b | 93 | /* we add some jitter here so not all your secondaries start pruning their caches at the very same millisecond */ |
4646277d | 94 | int weekno = (time(nullptr) - dns_random(3600)) / (86400*7); // we just spent milliseconds doing a signature, microsecond more won't kill us |
8455425c RG |
95 | const static int maxcachesize=::arg().asNum("max-signature-cache-entries", INT_MAX); |
96 | ||
4f4b8ed8 RG |
97 | auto signatures = g_signatures.write_lock(); |
98 | if (g_cacheweekno < weekno || signatures->size() >= (uint) maxcachesize) { // blunt but effective (C) Habbie, mind04 | |
e6a9dde5 | 99 | g_log<<Logger::Warning<<"Cleared signature cache."<<endl; |
4f4b8ed8 | 100 | signatures->clear(); |
8455425c RG |
101 | g_cacheweekno = weekno; |
102 | } | |
4f4b8ed8 | 103 | (*signatures)[lookup] = rrc.d_signature; |
8455425c RG |
104 | } |
105 | } | |
106 | ||
d3e7090c | 107 | /* this is where the RRSIGs begin, keys are retrieved, |
882358c8 | 108 | but the actual signing happens in fillOutRRSIG */ |
8de7d2a0 | 109 | static int getRRSIGsForRRSET(DNSSECKeeper& dk, const DNSName& signer, const DNSName& signQName, uint16_t signQType, uint32_t signTTL, |
c1e7b833 | 110 | const sortedRecords_t& toSign, vector<RRSIGRecordContent>& rrcs) |
882358c8 BH |
111 | { |
112 | if(toSign.empty()) | |
113 | return -1; | |
b61e407d | 114 | uint32_t startOfWeek = getStartOfWeek(); |
882358c8 BH |
115 | RRSIGRecordContent rrc; |
116 | rrc.d_type=signQType; | |
117 | ||
8aa5a28c | 118 | rrc.d_labels=signQName.countLabels()-signQName.isWildcard(); |
bb85386d | 119 | rrc.d_originalttl=signTTL; |
b61e407d KM |
120 | rrc.d_siginception=startOfWeek - 7*86400; // XXX should come from zone metadata |
121 | rrc.d_sigexpire=startOfWeek + 14*86400; | |
675fa24c | 122 | rrc.d_signer = signer; |
882358c8 | 123 | rrc.d_tag = 0; |
7dc95f99 | 124 | |
b6bd795c | 125 | DNSSECKeeper::keyset_t keys = dk.getKeys(signer); |
7dc95f99 | 126 | |
b6bd795c | 127 | for(DNSSECKeeper::keyset_t::value_type& keymeta : keys) { |
7dc95f99 | 128 | if(!keymeta.second.active) |
882358c8 | 129 | continue; |
8a95b04c | 130 | |
e335c490 CHB |
131 | bool signWithKSK = g_KSKSignedQTypes.count(signQType) != 0; |
132 | // Do not sign DNSKEY RRsets with the ZSK | |
b6bd795c | 133 | if((signQType == QType::DNSKEY && keymeta.second.keyType == DNSSECKeeper::ZSK) || |
e335c490 CHB |
134 | // Do not sign any other RRset than DNSKEY, CDS and CDNSKEY with a KSK |
135 | (!signWithKSK && keymeta.second.keyType == DNSSECKeeper::KSK)) { | |
b6bd795c | 136 | continue; |
7dc95f99 KM |
137 | } |
138 | ||
b6bd795c | 139 | fillOutRRSIG(keymeta.first, signQName, rrc, toSign); |
882358c8 BH |
140 | rrcs.push_back(rrc); |
141 | } | |
142 | return 0; | |
143 | } | |
144 | ||
145 | // this is the entrypoint from DNSPacket | |
8de7d2a0 | 146 | static void addSignature(DNSSECKeeper& dk, UeberBackend& db, const DNSName& signer, const DNSName& signQName, const DNSName& wildcardname, uint16_t signQType, |
8455425c | 147 | uint32_t signTTL, DNSResourceRecord::Place signPlace, |
c1e7b833 | 148 | sortedRecords_t& toSign, vector<DNSZoneRecord>& outsigned, uint32_t origTTL) |
882358c8 | 149 | { |
51a3a4d4 | 150 | //cerr<<"Asked to sign '"<<signQName<<"'|"<<DNSRecordContent::NumberToType(signQType)<<", "<<toSign.size()<<" records\n"; |
882358c8 BH |
151 | if(toSign.empty()) |
152 | return; | |
d3e7090c BH |
153 | vector<RRSIGRecordContent> rrcs; |
154 | if(dk.isPresigned(signer)) { | |
51a3a4d4 | 155 | //cerr<<"Doing presignatures"<<endl; |
e6fb0c9b | 156 | dk.getPreRRSIGs(db, outsigned, origTTL); // does it all |
d3e7090c | 157 | } |
6b35b541 | 158 | else { |
675fa24c | 159 | if(getRRSIGsForRRSET(dk, signer, wildcardname.countLabels() ? wildcardname : signQName, signQType, signTTL, toSign, rrcs) < 0) { |
6b35b541 BH |
160 | // cerr<<"Error signing a record!"<<endl; |
161 | return; | |
bb85386d FM |
162 | } |
163 | ||
90ba52e0 | 164 | DNSZoneRecord rr; |
165 | rr.dr.d_name=signQName; | |
166 | rr.dr.d_type=QType::RRSIG; | |
794c2f92 | 167 | if(origTTL) |
90ba52e0 | 168 | rr.dr.d_ttl=origTTL; |
794c2f92 | 169 | else |
90ba52e0 | 170 | rr.dr.d_ttl=signTTL; |
6b35b541 | 171 | rr.auth=false; |
90ba52e0 | 172 | rr.dr.d_place = signPlace; |
ef7cd021 | 173 | for(RRSIGRecordContent& rrc : rrcs) { |
d06dcda4 | 174 | rr.dr.setContent(std::make_shared<RRSIGRecordContent>(rrc)); |
6b35b541 BH |
175 | outsigned.push_back(rr); |
176 | } | |
882358c8 | 177 | } |
882358c8 BH |
178 | toSign.clear(); |
179 | } | |
180 | ||
d73de874 | 181 | uint64_t signatureCacheSize(const std::string& /* str */) |
e903706d | 182 | { |
4f4b8ed8 | 183 | return g_signatures.read_lock()->size(); |
e903706d | 184 | } |
185 | ||
90ba52e0 | 186 | static bool rrsigncomp(const DNSZoneRecord& a, const DNSZoneRecord& b) |
e02d0a59 | 187 | { |
905dae56 | 188 | return std::tie(a.dr.d_place, a.dr.d_type) < std::tie(b.dr.d_place, b.dr.d_type); |
e02d0a59 BH |
189 | } |
190 | ||
675fa24c | 191 | static bool getBestAuthFromSet(const set<DNSName>& authSet, const DNSName& name, DNSName& auth) |
8d3cbffa | 192 | { |
675fa24c PD |
193 | auth.trimToLabels(0); |
194 | DNSName sname(name); | |
8d3cbffa BH |
195 | do { |
196 | if(authSet.find(sname) != authSet.end()) { | |
197 | auth = sname; | |
198 | return true; | |
199 | } | |
200 | } | |
675fa24c | 201 | while(sname.chopOff()); |
bb85386d | 202 | |
8d3cbffa BH |
203 | return false; |
204 | } | |
205 | ||
90ba52e0 | 206 | void addRRSigs(DNSSECKeeper& dk, UeberBackend& db, const set<DNSName>& authSet, vector<DNSZoneRecord>& rrs) |
e02d0a59 | 207 | { |
e02d0a59 | 208 | stable_sort(rrs.begin(), rrs.end(), rrsigncomp); |
bb85386d | 209 | |
edacfa5d | 210 | DNSName authQName, signQName, wildcardQName; |
e02d0a59 BH |
211 | uint16_t signQType=0; |
212 | uint32_t signTTL=0; | |
794c2f92 | 213 | uint32_t origTTL=0; |
bb85386d | 214 | |
e693ff5a | 215 | DNSResourceRecord::Place signPlace=DNSResourceRecord::ANSWER; |
c1e7b833 | 216 | sortedRecords_t toSign; |
e02d0a59 | 217 | |
90ba52e0 | 218 | vector<DNSZoneRecord> signedRecords; |
d42896fb | 219 | signedRecords.reserve(rrs.size()*1.5); |
90ba52e0 | 220 | // cout<<rrs.size()<<", "<<sizeof(DNSZoneRecord)<<endl; |
675fa24c | 221 | DNSName signer; |
90ba52e0 | 222 | for(auto pos = rrs.cbegin(); pos != rrs.cend(); ++pos) { |
223 | if(pos != rrs.cbegin() && (signQType != pos->dr.d_type || signQName != pos->dr.d_name)) { | |
edacfa5d | 224 | if (getBestAuthFromSet(authSet, authQName, signer)) |
794c2f92 | 225 | addSignature(dk, db, signer, signQName, wildcardQName, signQType, signTTL, signPlace, toSign, signedRecords, origTTL); |
e02d0a59 | 226 | } |
8e9b7d99 | 227 | signedRecords.push_back(*pos); |
edacfa5d KM |
228 | signQName = pos->dr.d_name.makeLowerCase(); |
229 | if (pos->dr.d_type == QType::NSEC) { | |
230 | authQName = signQName.getCommonLabels(getRR<NSECRecordContent>(pos->dr)->d_next); | |
edacfa5d KM |
231 | } |
232 | else { | |
233 | authQName = signQName; | |
234 | } | |
e1a9ab9f | 235 | if(!pos->wildcardname.empty()) |
d42896fb | 236 | wildcardQName = pos->wildcardname.makeLowerCase(); |
07338ade KM |
237 | else |
238 | wildcardQName.clear(); | |
90ba52e0 | 239 | signQType = pos->dr.d_type; |
794c2f92 PD |
240 | if(pos->signttl) |
241 | signTTL = pos->signttl; | |
242 | else | |
90ba52e0 | 243 | signTTL = pos->dr.d_ttl; |
244 | origTTL = pos->dr.d_ttl; | |
245 | signPlace = pos->dr.d_place; | |
246 | if(pos->auth || pos->dr.d_type == QType::DS) { | |
d06dcda4 | 247 | toSign.insert(pos->dr.getContent()); // so ponder.. should this be a deep copy perhaps? |
e02d0a59 BH |
248 | } |
249 | } | |
edacfa5d | 250 | if (getBestAuthFromSet(authSet, authQName, signer)) |
794c2f92 | 251 | addSignature(dk, db, signer, signQName, wildcardQName, signQType, signTTL, signPlace, toSign, signedRecords, origTTL); |
e02d0a59 BH |
252 | rrs.swap(signedRecords); |
253 | } |