root/trunk/pdns/pdns/fsdnsseckeeper.cc @ 1760

Revision 1760, 9.9 KB (checked in by ahu, 2 years ago)

align our key storage naming with the excellent ldns/nsd/unbound tools, which also found a few bugs in our storage

Line 
1#include "dnsseckeeper.hh"
2#include "dnssecinfra.hh"
3#include "statbag.hh"
4#include <iostream>
5#include <boost/filesystem/operations.hpp>
6#include <boost/filesystem/path.hpp>
7#include <polarssl/havege.h>
8#include <polarssl/base64.h>
9#include <boost/foreach.hpp>
10#include <sys/stat.h>
11#include <sys/types.h>
12#include <fstream>
13#include <boost/algorithm/string.hpp>
14#include <boost/format.hpp>
15#include <boost/assign/std/vector.hpp> // for 'operator+=()'
16#include <boost/assign/list_inserter.hpp>
17using namespace boost::assign;
18namespace fs = boost::filesystem;
19
20using namespace std;
21using namespace boost;
22
23void RSAContext::create(unsigned int bits)
24{
25  havege_state hs;
26  havege_init( &hs );
27 
28  rsa_init(&d_context, RSA_PKCS_V15, 0, havege_rand, &hs ); // FIXME this leaks memory
29  int ret=rsa_gen_key(&d_context, bits, 65537);
30  if(ret < 0) 
31    throw runtime_error("Key generation failed");
32}
33
34std::string RSAContext::convertToISC()
35{
36  string ret;
37  typedef vector<pair<string, mpi*> > outputs_t;
38  outputs_t outputs;
39  push_back(outputs)("Modulus", &d_context.N)("PublicExponent",&d_context.E)
40    ("PrivateExponent",&d_context.D)
41    ("Prime1",&d_context.P)
42    ("Prime2",&d_context.Q)
43    ("Exponent1",&d_context.DP)
44    ("Exponent2",&d_context.DQ)
45    ("Coefficient",&d_context.QP);
46
47  ret = "Private-key-format: v1.2\nAlgorithm: 5 (RSASHA1)\n";
48
49  BOOST_FOREACH(outputs_t::value_type value, outputs) {
50    ret += value.first;
51    ret += ": ";
52    unsigned char tmp[mpi_size(value.second)];
53    mpi_write_binary(value.second, tmp, sizeof(tmp));
54    unsigned char base64tmp[sizeof(tmp)*2];
55    int dlen=sizeof(base64tmp);
56    base64_encode(base64tmp, &dlen, tmp, sizeof(tmp));
57    ret.append((const char*)base64tmp, dlen);
58    ret.append(1, '\n');
59  }
60  return ret;
61}
62
63bool DNSSECKeeper::haveActiveKSKFor(const std::string& zone, DNSSECPrivateKey* dpk)
64{
65  keyset_t keys = getKeys(zone, true);
66  // need to get an *active* one!
67  if(dpk && !keys.empty()) {
68    *dpk = keys.begin()->first;
69  }
70  return !keys.empty();
71 
72  #if 0
73  fs::path full_path = fs::system_complete( fs::path(d_dirname + "/" + zone + "/keys/" ) );
74
75  if ( !fs::exists( full_path ) )
76    return false;
77
78  fs::directory_iterator end_iter;
79  for ( fs::directory_iterator dir_itr( full_path );
80        dir_itr != end_iter;
81        ++dir_itr )
82  {
83    //    cerr<<"Entry: '"<< dir_itr->leaf() <<"'"<<endl;
84    if(ends_with(dir_itr->leaf(),".private")) {
85      //      cerr<<"Hit!"<<endl;
86
87      if(dpk) {
88        getRSAKeyFromISC(&dpk->d_key.getContext(), dir_itr->path().file_string().c_str());
89       
90        if(getNSEC3PARAM(zone)) {
91          dpk->d_algorithm = 7;
92        }
93        else {
94          dpk->d_algorithm = 5;
95        }
96     
97      }
98      return true;
99    }
100  }
101
102  return false;
103  #endif
104}
105
106unsigned int DNSSECKeeper::getNextKeyIDFromDir(const std::string& dirname)
107{
108  fs::path full_path = fs::system_complete( fs::path(dirname));
109
110  if ( !fs::exists( full_path ) )
111    unixDie("Unable to get next free key id from '"+dirname+"'");
112
113  fs::directory_iterator end_iter;
114  unsigned int maxID=0;
115  for ( fs::directory_iterator dir_itr( full_path );
116        dir_itr != end_iter;
117        ++dir_itr )
118  {
119          if(ends_with(dir_itr->leaf(),".private")) {
120                  maxID = max(maxID, (unsigned int)atoi(dir_itr->leaf().c_str()));
121          }
122  }
123  return maxID+1;
124}
125
126std::string DNSSECKeeper::getKeyFilenameById(const std::string& dirname, unsigned int id)
127{
128  fs::path full_path = fs::system_complete( fs::path(dirname));
129
130  if ( !fs::exists( full_path ) )
131    unixDie("Unable to get free key id from '"+dirname+"'");
132
133  fs::directory_iterator end_iter;
134  pair<string, string> parts;
135  for ( fs::directory_iterator dir_itr( full_path );
136    dir_itr != end_iter;
137    ++dir_itr )
138  {
139    parts = splitField(dir_itr->leaf(), '-');
140          if(atoi(parts.first.c_str()) == (signed int)id) 
141      return dirname+"/"+dir_itr->leaf();
142  }
143  throw runtime_error("Could not get filename for key id '"+lexical_cast<string>(id)+"'");
144}
145
146
147void DNSSECKeeper::addKey(const std::string& name, bool keyOrZone, int algorithm, bool active)
148{
149  DNSSECPrivateKey dpk;
150  dpk.d_key.create(1024); // for testing, 1024
151
152  string isc = dpk.d_key.convertToISC();
153  DNSKEYRecordContent drc = dpk.getDNSKEY();
154  drc.d_flags = 256; // KSK
155  drc.d_algorithm = algorithm; 
156  string iscName=d_dirname+"/"+name+"/keys/";
157  unsigned int id = getNextKeyIDFromDir(iscName);
158  time_t inception=time(0);
159
160  struct tm ts;
161  gmtime_r(&inception, &ts);
162
163  iscName += (boost::format("%06d-%04d%02d%02d%02d%02d.%u") % id
164              % (1900+ts.tm_year) % (ts.tm_mon + 1)
165              % ts.tm_mday % ts.tm_hour % ts.tm_min % drc.getTag()).str();
166
167  iscName += keyOrZone ? ".ksk" : ".zsk";
168  iscName += active ? ".active" : ".passive";
169 
170  { 
171    ofstream iscFile((iscName+".private").c_str());
172    iscFile << isc;
173  }
174
175  { 
176    ofstream dnskeyFile((iscName+".key").c_str());
177    dnskeyFile << toCanonic("", name) << " IN DNSKEY " << drc.getZoneRepresentation()<<endl;
178  }
179
180}
181
182
183static bool keyCompareByKindAndID(const DNSSECKeeper::keyset_t::value_type& a, const DNSSECKeeper::keyset_t::value_type& b)
184{
185  return make_pair(!a.second.keyOrZone, a.second.id) <
186         make_pair(!b.second.keyOrZone, b.second.id);
187}
188
189void DNSSECKeeper::removeKey(const std::string& zname, unsigned int id)
190{
191  string fname = getKeyFilenameById(d_dirname+"/keys/", id);
192  if(unlink(fname.c_str()) < 0)
193    unixDie("removing key file '"+fname+"'");
194}
195
196void DNSSECKeeper::deactivateKey(const std::string& zname, unsigned int id)
197{
198  string fname = getKeyFilenameById(d_dirname+"/keys/", id);
199  string newname = boost::replace_last_copy(fname, ".active", ".passive");
200  if(rename(fname.c_str(), newname.c_str()) < 0)
201    unixDie("renaming file to deactivate key, from: '"+fname+"' to '"+newname+"'");
202}
203
204void DNSSECKeeper::activateKey(const std::string& zname, unsigned int id)
205{
206  string fname = getKeyFilenameById(d_dirname+"/keys/", id);
207  string newname = boost::replace_last_copy(fname, ".passive", ".active");
208  if(rename(fname.c_str(), newname.c_str()) < 0)
209    unixDie("renaming file to deactivate key, from: '"+fname+"' to '"+newname+"'");
210}
211
212bool DNSSECKeeper::getNSEC3PARAM(const std::string& zname, NSEC3PARAMRecordContent* ns3p)
213{
214  fs::path full_path = fs::system_complete( fs::path(d_dirname + "/" + zname + "/nsec3param" ) );
215  ifstream ifs(full_path.external_directory_string().c_str());
216  // cerr<<"called for nsec3param..."<<endl;
217  if(!ifs)
218    return false;
219   
220  if(ns3p) {
221    string descr;
222    getline(ifs, descr);
223    NSEC3PARAMRecordContent* tmp=dynamic_cast<NSEC3PARAMRecordContent*>(DNSRecordContent::mastermake(QType::NSEC3PARAM, 1, descr));
224    if(!tmp) {
225      cerr<<"Could not parse "<< full_path.external_directory_string() <<endl;
226      cerr<<"descr: '"<<descr<<"'\n";
227    }
228    *ns3p = *tmp;
229    delete tmp;
230   
231    cerr<<"hmm salt: "<<makeHexDump(ns3p->d_salt)<<endl;
232  }
233  return true;
234}
235
236void DNSSECKeeper::setNSEC3PARAM(const std::string& zname, const NSEC3PARAMRecordContent* ns3p)
237{
238  fs::path full_path = fs::system_complete( fs::path(d_dirname + "/" + zname + "/nsec3param" ) );
239  if(ns3p) {
240    string descr = ns3p->getZoneRepresentation();
241   
242   
243    ofstream of(full_path.external_directory_string().c_str());
244    of << descr;
245  }
246  else {
247    unlink(full_path.external_directory_string().c_str());
248  }
249}
250
251
252DNSSECKeeper::keyset_t DNSSECKeeper::getKeys(const std::string& zone, boost::tribool allOrKeyOrZone)
253{
254  keyset_t keyset;
255
256  fs::path full_path = fs::system_complete( fs::path(d_dirname + "/" + zone + "/keys/" ) );
257
258  if ( !fs::exists( full_path ) )
259    return keyset;
260
261  fs::directory_iterator end_iter;
262  for ( fs::directory_iterator dir_itr( full_path );
263        dir_itr != end_iter;
264        ++dir_itr )
265  {
266    //cerr<<"Entry: '"<< dir_itr->leaf() <<"'"<<endl;
267    if(ends_with(dir_itr->leaf(),".private")) {
268      DNSSECPrivateKey dpk;
269      getRSAKeyFromISC(&dpk.d_key.getContext(), dir_itr->path().file_string().c_str());
270
271      if(getNSEC3PARAM(zone)) {
272        dpk.d_algorithm = 7;
273      }
274      else {
275        dpk.d_algorithm = 5;
276      }
277     
278      struct tm ts1, ts2;
279     
280      memset(&ts1, 0, sizeof(ts1));
281      memset(&ts2, 0, sizeof(ts2));
282     
283      unsigned int id;
284      sscanf(dir_itr->leaf().c_str(), "%06u-%04d%02d%02d%02d%02d",
285        &id,
286        &ts1.tm_year, 
287        &ts1.tm_mon, &ts1.tm_mday, &ts1.tm_hour, &ts1.tm_min);
288             
289      ts1.tm_year -= 1900;
290      ts1.tm_mon--;
291     
292      KeyMetaData kmd;
293     
294      kmd.id = id;
295      kmd.fname = dir_itr->leaf();
296      kmd.active = kmd.fname.find(".active") != string::npos;
297      kmd.keyOrZone = kmd.fname.find(".ksk") != string::npos;
298      if(boost::indeterminate(allOrKeyOrZone) || allOrKeyOrZone == kmd.keyOrZone)
299        keyset.push_back(make_pair(dpk, kmd));
300    }
301    sort(keyset.begin(), keyset.end(), keyCompareByKindAndID);
302  }
303
304  return keyset;
305}
306
307DNSKEYRecordContent DNSSECPrivateKey::getDNSKEY()
308{
309  return makeDNSKEYFromRSAKey(&d_key.getContext(), d_algorithm);
310}
311
312
313void DNSSECKeeper::secureZone(const std::string& name, int algorithm)
314{
315  mkdir((d_dirname+"/"+name).c_str(), 0700);
316  if(mkdir((d_dirname+"/"+name+"/keys").c_str(), 0700) < 0)
317    unixDie("Making directory for keys in '"+d_dirname+"'");
318
319
320  // now add the KSK
321
322  addKey(name, true, algorithm);
323#if 0
324
325  DNSSECPrivateKey dpk;
326  dpk.d_key.create(2048); // for testing, 1024
327
328  string isc = dpk.d_key.convertToISC();
329  DNSKEYRecordContent drc = dpk.getDNSKEY();
330  drc.d_flags = 257; // ZSK (?? for a KSK?)
331  drc.d_algorithm = algorithm; 
332  string iscName=d_dirname+"/"+name+"/keys/";
333
334  time_t now=time(0);
335  struct tm ts;
336  gmtime_r(&now, &ts);
337  unsigned int id=1;
338  iscName += (boost::format("%06d-%04d%02d%02d%02d%02d.%u.%s.%s") % id
339              % (1900+ts.tm_year) % (ts.tm_mon + 1)
340              % ts.tm_mday % ts.tm_hour % ts.tm_min % drc.getTag() % "ksk" % "active").str();
341
342
343  { 
344    ofstream iscFile((iscName+".private").c_str());
345    iscFile << isc;
346  }
347
348  { 
349    ofstream dnskeyFile((iscName+".key").c_str());
350    dnskeyFile << toCanonic("", name) << " IN DNSKEY " << drc.getZoneRepresentation()<<endl;
351  }
352#endif
353}
354 
355
Note: See TracBrowser for help on using the browser.