]> granicus.if.org Git - pdns/commitdiff
initial drop of dnssec keying infrastructure - mostly ceremonial
authorBert Hubert <bert.hubert@netherlabs.nl>
Sun, 18 Apr 2010 11:58:27 +0000 (11:58 +0000)
committerBert Hubert <bert.hubert@netherlabs.nl>
Sun, 18 Apr 2010 11:58:27 +0000 (11:58 +0000)
git-svn-id: svn://svn.powerdns.com/pdns/trunk/pdns@1551 d19b8d6e-7fed-0310-83ef-9ca221ded41b

pdns/base32.cc [new file with mode: 0644]
pdns/base32.hh [new file with mode: 0644]
pdns/dnssecinfra.cc [new file with mode: 0644]
pdns/dnssecinfra.hh [new file with mode: 0644]
pdns/dnsseckeeper.cc [new file with mode: 0644]
pdns/dnsseckeeper.hh [new file with mode: 0644]

diff --git a/pdns/base32.cc b/pdns/base32.cc
new file mode 100644 (file)
index 0000000..27f4dfd
--- /dev/null
@@ -0,0 +1,136 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <string>
+#include <cstring>
+#include <stdlib.h>
+#include <iostream>
+#include "base32.hh"
+using namespace std;
+
+/* based on freebsd:src/contrib/opie/libopie/btoe.c extract: get bit ranges from a char* */
+uint32_t extract_bits(const char *s, int start, int length)
+{
+  uint32_t x;
+  unsigned char cl, cc, cr;
+
+  cl = s[start / 8];
+  cc = s[start / 8 + 1];
+  cr = s[start / 8 + 2];
+  x = ((uint32_t) (cl << 8 | cc) << 8 | cr);
+  x = x >> (24 - (length + (start % 8)));
+  x = (x & (0xffff >> (16 - length)));
+  return (x);
+}
+
+/* same, set bit ranges in a char* */
+static void set_bits(char* s, int x, int start, int length)
+{
+  unsigned char cl, cc, cr;
+  uint32_t y;
+  int shift;
+
+  shift = ((8 - ((start + length) % 8)) % 8);
+  y = (uint32_t) x << shift;
+  cl = (y >> 16) & 0xff;
+  cc = (y >> 8) & 0xff;
+  cr = y & 0xff;
+  if (shift + length > 16) {
+    s[start / 8] |= cl;
+    s[start / 8 + 1] |= cc;
+    s[start / 8 + 2] |= cr;
+  } 
+  else {
+    if (shift + length > 8) {
+      s[start / 8] |= cc;
+      s[start / 8 + 1] |= cr;
+    } else {
+      s[start / 8] |= cr;
+    }
+  }
+}
+
+/* convert a base32 hex character to its decoded equivalent */
+static int unbase32hex(char c)
+{
+  if(c >= '0' && c<='9')
+    return c-'0';
+  if(c >= 'a' && c<='z') 
+    return 10 + (c-'a');
+  if(c >= 'A' && c<='Z') 
+    return 10 + (c-'A');
+  if(c=='=')
+    return '=';
+  return -1;
+}
+
+/* convert a binary string to base32hex */
+string toBase32Hex(const std::string& input)
+{
+  static const char base32hex[] = "0123456789ABCDEFGHIJKLMNOPQRSTUV=";
+  string ret;
+  ret.reserve(4+ 8*input.length()/5); // optimization
+  // process input in groups of 5 8-bit chunks, emit 8 5-bit chunks 
+  for(string::size_type offset = 0 ; offset < input.length(); offset+=5) {
+    int todo = input.length() - offset;
+    int stuffing; // how much '=' to add at the end
+    
+    switch(todo) {
+    case 1:
+      stuffing = 6; break;
+    case 2:
+      stuffing = 4; break;
+    case 3:
+      stuffing = 3; break;
+    case 4:
+      stuffing = 1; break;
+    default: // ->  0 or more than 5, no stuffing
+      stuffing = 0; break;
+    }
+   
+    for(int n=0; n < 8 - stuffing; ++n)
+      ret.append(1, base32hex[extract_bits(input.c_str()+offset, n*5, 5)]);
+    ret.append(stuffing, '=');
+  }
+
+  return ret;
+}
+
+// convert base32hex encoded string to normal string
+string fromBase32Hex(const std::string& input)
+{
+  string ret;
+  char block[5]={0,0,0,0,0};  // we process 5 8-bit chunks at a time
+  string::size_type n, toWrite=0;
+  for(n = 0; n < input.length(); ++n) {
+    int c=unbase32hex(input[n]);
+    if(c == '=' || c < 0) // stop at stuffing or error
+      break;
+    set_bits(block, c , (n % 8) * 5, 5);
+    if(++toWrite == 8) {
+      ret.append(block, sizeof(block));
+      memset(block, 0, sizeof(block));
+      toWrite = 0;
+    }
+  }
+  ret.append(block, (7+toWrite*5)/8); // round up
+
+  return ret;
+}
+
+#if 0
+int main(int argc, char **argv)
+{
+  if(argc!=3 || (argc==3 && strcmp(argv[1],"from") && strcmp(argv[1],"to"))) {
+    printf("syntax: base32 from|to string\n");
+    exit(0);
+  }
+  if(!strcmp(argv[1],"to")) {
+    printf("input: '%s'\noutput: '%s'\n",
+          argv[2], 
+          toBase32Hex(argv[2]).c_str());
+  }
+  else {
+    cout<<"input: '"<<argv[2]<<"'\noutput: '"<<fromBase32Hex(argv[2])<<"'\n";
+  }
+}
+#endif
diff --git a/pdns/base32.hh b/pdns/base32.hh
new file mode 100644 (file)
index 0000000..5b633ee
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef PDNS_BASE32_HH
+#define PDNS_BASE32_HH
+#include <string>
+
+std::string toBase32Hex(const std::string& input);
+std::string fromBase32Hex(const std::string& input);
+
+#endif
diff --git a/pdns/dnssecinfra.cc b/pdns/dnssecinfra.cc
new file mode 100644 (file)
index 0000000..536e7d4
--- /dev/null
@@ -0,0 +1,349 @@
+#include "dnsparser.hh"
+#include "sstuff.hh"
+#include "misc.hh"
+#include "dnswriter.hh"
+#include "dnsrecords.hh"
+#include "statbag.hh"
+#include "iputils.hh"
+#include <netinet/sctp.h>
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string.hpp>
+#include <polarssl/rsa.h>
+#include <polarssl/base64.h>
+#include <polarssl/sha1.h>
+#include <polarssl/sha2.h>
+#include "dnssecinfra.hh" 
+#include "dnsseckeeper.hh"
+
+using namespace boost;
+using namespace std;
+
+DNSKEYRecordContent getRSAKeyFromISC(rsa_context* rsa, const char* fname)
+{
+  char line[1024];
+
+  string sline;
+  string key,value;
+  map<string, mpi*> places;
+
+  FILE *fp=fopen(fname, "r");
+  if(!fp)
+    unixDie("opening file '"+string(fname)+"'");
+
+  rsa_init(rsa, RSA_PKCS_V15, 0, NULL, NULL );
+
+  places["Modulus"]=&rsa->N;
+  places["PublicExponent"]=&rsa->E;
+  places["PrivateExponent"]=&rsa->D;
+  places["Prime1"]=&rsa->P;
+  places["Prime2"]=&rsa->Q;
+  places["Exponent1"]=&rsa->DP;
+  places["Exponent2"]=&rsa->DQ;
+  places["Coefficient"]=&rsa->QP;
+
+  unsigned char decoded[1024];
+  DNSKEYRecordContent drc;
+  string modulus, exponent;
+  while(fgets(line, sizeof(line),fp)) {
+    sline.assign(line);
+    tie(key,value)=splitField(line, ':');
+    trim(value);
+
+    if(places.count(key)) {
+      if(places[key]) {
+
+       int len=sizeof(decoded);
+       if(base64_decode(decoded, &len, (unsigned char*)value.c_str(), value.length()) < 0) {
+         cerr<<"Error base64 decoding '"<<value<<"'\n";
+         exit(1);
+       }
+       //      B64Decode(value, decoded);
+       //      cerr<<key<<" decoded.length(): "<<8*len<<endl;
+       mpi_read_binary(places[key], decoded, len);
+       if(key=="Modulus")
+         modulus.assign((const char*)decoded,len);
+       if(key=="PublicExponent")
+         exponent.assign((const char*)decoded,len);
+      }
+    }
+    else {
+      if(key != "Private-key-format" && key != "Algorithm") 
+       cerr<<"Unknown field '"<<key<<"'\n";
+    }
+  }
+  rsa->len = ( mpi_msb( &rsa->N ) + 7 ) >> 3; // no clue what this does
+
+  if(exponent.length() < 255) 
+    drc.d_key.assign(1, (char) (unsigned int) exponent.length());
+  else {
+    drc.d_key.assign(1, 0);
+    uint16_t len=htons(exponent.length());
+    drc.d_key.append((char*)&len, 2);
+  }
+  drc.d_key.append(exponent);
+  drc.d_key.append(modulus);
+  drc.d_protocol=3;
+  drc.d_algorithm = 5;
+  fclose(fp);
+  return drc;
+}
+
+
+void makeRSAPublicKeyFromDNS(rsa_context* rc, const DNSKEYRecordContent& dkrc)
+{
+  rsa_init(rc, RSA_PKCS_V15, 0, NULL, NULL );
+
+  mpi_read_binary(&rc->E, (unsigned char*)dkrc.getExponent().c_str(), dkrc.getExponent().length());    // exponent
+  mpi_read_binary(&rc->N, (unsigned char*)dkrc.getModulus().c_str(), dkrc.getModulus().length());    // modulus
+  rc->len = ( mpi_msb( &rc->N ) + 7 ) >> 3; // no clue what this does
+}
+
+bool sharedDNSSECCompare(const shared_ptr<DNSRecordContent>& a, const shared_ptr<DNSRecordContent>& b)
+{
+  return a->serialize("", true) < b->serialize("", true);
+}
+
+string getSHA1HashForRRSET(const std::string& qname, const RRSIGRecordContent& rrc, vector<shared_ptr<DNSRecordContent> >& signRecords) 
+{
+  sort(signRecords.begin(), signRecords.end(), sharedDNSSECCompare);
+
+  string toHash;
+  toHash.append(const_cast<RRSIGRecordContent&>(rrc).serialize("", true));
+  toHash.resize(toHash.size() - rrc.d_signature.length()); // chop off the end;
+  //  cerr<<"toHash start size: "<<toHash.size()<<", signature length: "<<rrc.d_signature.length()<<endl;
+
+
+  BOOST_FOREACH(shared_ptr<DNSRecordContent>& add, signRecords) {
+    //  cerr<<"\t IN "<<rrc.d_originalttl<<"\t"<<add->getZoneRepresentation()<<"\n";
+    toHash.append(toLower(simpleCompress(qname, "")));
+    uint16_t tmp=htons(rrc.d_type);
+    toHash.append((char*)&tmp, 2);
+    tmp=htons(1); // class
+    toHash.append((char*)&tmp, 2);
+    uint32_t ttl=htonl(rrc.d_originalttl);
+    toHash.append((char*)&ttl, 4);
+    string rdata=add->serialize("", true);  // case issues hiding here..
+    tmp=htons(rdata.length());
+    toHash.append((char*)&tmp, 2);
+    toHash.append(rdata);
+  }
+  //  cerr<<"toHash: "<<makeHexDump(toHash)<<endl;
+  unsigned char hash[20];
+  sha1((unsigned char*)toHash.c_str(), toHash.length(), hash);
+  return string((char*)hash, 20);
+}
+
+DSRecordContent makeDSFromDNSKey(const std::string& qname, const DNSKEYRecordContent& drc, int digest)
+{
+  string toHash;
+  toHash.assign(toLower(simpleCompress(qname)));
+  toHash.append(const_cast<DNSKEYRecordContent&>(drc).serialize("", true));
+
+  unsigned char hash[32];
+  if(digest==1)
+    sha1((unsigned char*)toHash.c_str(), toHash.length(), hash);
+  else
+    sha2((unsigned char*)toHash.c_str(), toHash.length(), hash, 0);
+
+  DSRecordContent dsrc;
+  dsrc.d_algorithm=5;
+  dsrc.d_digesttype=digest;
+  dsrc.d_tag=const_cast<DNSKEYRecordContent&>(drc).getTag();
+  dsrc.d_digest.assign((const char*)hash, digest == 1 ? 20 : 32);
+  return dsrc;
+}
+
+DNSKEYRecordContent makeDNSKEYFromRSAKey(rsa_context* rc)
+{
+  DNSKEYRecordContent drc;
+  char tmp[256];
+
+  //  cerr<<"in makeDNSKEY rsa_check_pubkey: "<<rsa_check_pubkey(rc)<<", bits="<<mpi_size(&rc->N)*8<<endl;
+
+  mpi_write_binary(&rc->E, (unsigned char*)tmp, mpi_size(&rc->E) );
+  string exponent((char*)tmp, mpi_size(&rc->E));
+
+  mpi_write_binary(&rc->N, (unsigned char*)tmp, mpi_size(&rc->N) );
+  string modulus((char*)tmp, mpi_size(&rc->N));
+
+  if(exponent.length() < 255) 
+    drc.d_key.assign(1, (char) (unsigned int) exponent.length());
+  else {
+    drc.d_key.assign(1, 0);
+    uint16_t len=htons(exponent.length());
+    drc.d_key.append((char*)&len, 2);
+  }
+  drc.d_key.append(exponent);
+  drc.d_key.append(modulus);
+
+  drc.d_protocol=3;
+  drc.d_algorithm = 5;
+
+  drc.d_flags=256 + (modulus.length()>128);  // oops, I just made this up..
+
+  return drc;
+}
+
+bool getSignerFor(const std::string& keyRepositoryDir, const std::string& qname, std::string &signer)
+{
+  DNSSECKeeper dk(keyRepositoryDir); 
+
+  signer=qname;
+  do {
+    if(dk.haveKSKFor(signer)) 
+      return true;
+  } while(chopOff(signer));
+  return false;
+}
+
+int countLabels(const std::string& signQName)
+{
+  int count =1;
+  for(string::const_iterator pos = signQName.begin(); pos != signQName.end() ; ++pos) 
+    if(*pos == '.' && pos+1 != signQName.end()) 
+      count++;
+
+  if(starts_with(signQName, "*."))
+    count--;
+  return count;
+}
+
+DNSKEYRecordContent getDNSKEYFor(const std::string& keyRepositoryDir, const std::string& qname, bool withKSK, RSAContext* rc)
+{
+  DNSSECKeeper dk(keyRepositoryDir);
+  cerr<<"Asked for a DNSKEY for '"<<qname<<"', withKSK="<<withKSK<<"\n";
+  DNSSECPrivateKey dpk;
+
+  if(!withKSK) {
+    DNSSECKeeper::zskset_t zskset=dk.getZSKsFor(qname);
+    BOOST_FOREACH(DNSSECKeeper::zskset_t::value_type value, zskset) {
+      if(value.second) {
+       cerr<<"Found a ZSK for '"<<qname<<"', key tag = "<<value.first.getDNSKEY().getTag()<<endl;
+       *rc=value.first.d_key;
+       return value.first.getDNSKEY();
+      }
+      else 
+       cerr<<"Found an expired ZSK for '"<<qname<<"', key tag = "<<value.first.getDNSKEY().getTag()<<endl;
+    }
+    cerr<<"withKSK was not true, but found nothing!"<<endl;
+    exit(1);
+  }
+  else if(dk.haveKSKFor(qname, &dpk)) {
+    cerr<<"Found something"<<endl;
+    *rc=dpk.d_key;
+    return dpk.getDNSKEY();
+  } else {
+      cerr<<"DID NOT FIND A ZSK!"<<endl;
+      exit(1);
+  }
+}
+
+
+map<pair<string, uint16_t>, RRSIGRecordContent> g_rrsigs;
+
+void fillOutRRSIG(const std::string& keyrepodir, const std::string& signQName, RRSIGRecordContent& rrc, const std::string& hash, vector<shared_ptr<DNSRecordContent> >& toSign, bool withKSK) 
+{
+  RSAContext rc;
+
+  DNSKEYRecordContent drc =getDNSKEYFor(keyrepodir, rrc.d_signer, withKSK, &rc);
+  rrc.d_tag = drc.getTag();
+  
+  if(g_rrsigs.count(make_pair(hash, rrc.d_tag))) {
+    cerr<<"RRSIG cache hit !"<<endl;
+    rrc = g_rrsigs[make_pair(hash, rrc.d_tag)];
+    return;
+  }
+    
+  string realhash=getSHA1HashForRRSET(signQName, rrc, toSign);
+
+  unsigned char signature[mpi_size(&rc.getContext().N)];
+
+  int ret=rsa_pkcs1_sign(&rc.getContext(), RSA_PRIVATE, SIG_RSA_SHA1, 20, (unsigned char*) realhash.c_str(), signature);
+  
+  if(ret!=0) {
+    cerr<<"signing returned: "<<ret<<endl;
+    exit(1);
+  }
+  
+  rrc.d_signature.assign((char*)signature, sizeof(signature));
+  
+  g_rrsigs[make_pair(hash, rrc.d_tag)] = rrc;
+
+}
+
+uint32_t getCurrentInception()
+{
+  uint32_t now = time(0);
+  now -= (now % (7*86400));
+  return now;
+}
+
+
+int getRRSIGForRRSET(const std::string& keyrepodir, const std::string signQName, uint16_t signQType, uint32_t signTTL, 
+                    vector<shared_ptr<DNSRecordContent> >& toSign, RRSIGRecordContent& rrc, bool ksk)
+{
+  if(toSign.empty())
+    return -1;
+
+  rrc.d_type=signQType;
+  rrc.d_algorithm=5;      // rsasha1
+  rrc.d_labels=countLabels(signQName); 
+  rrc.d_originalttl=signTTL; 
+  rrc.d_siginception=getCurrentInception();;
+  rrc.d_sigexpire = rrc.d_siginception + 14*86400;
+
+  rrc.d_tag=0;
+  if(!getSignerFor(keyrepodir, signQName, rrc.d_signer)) {
+    cerr<<"No signer known for '"<<signQName<<"'\n";
+    return -1;
+  }
+    
+  string hash= getSHA1HashForRRSET(signQName,  rrc, toSign);
+  fillOutRRSIG(keyrepodir, signQName, rrc, hash, toSign, ksk);
+  return 0;
+}
+
+void addSignature(const std::string& keyrepodir, const std::string signQName, const std::string& wildcardname, uint16_t signQType, uint32_t signTTL, DNSPacketWriter::Place signPlace, vector<shared_ptr<DNSRecordContent> >& toSign, DNSPacketWriter& pw)
+{
+  cerr<<"Asked to sign '"<<signQName<<"'|"<<DNSRecordContent::NumberToType(signQType)<<", "<<toSign.size()<<" records\n";
+
+  RRSIGRecordContent rrc;
+  if(toSign.empty())
+    return;
+
+  for(int ksk = 0; ksk < 2; ++ksk) {
+    if(getRRSIGForRRSET(keyrepodir, wildcardname.empty() ? signQName : wildcardname, signQType, signTTL, toSign, rrc, ksk) < 0) {
+      cerr<<"Error signing a record!"<<endl;
+      return;
+    }
+    
+    pw.startRecord(signQName, QType::RRSIG, 3600, 1, 
+                  signQType==QType::DNSKEY ? DNSPacketWriter:: ANSWER : signPlace); 
+    rrc.toPacket(pw);
+    
+    pw.commit();
+    if(signQType != QType::DNSKEY)
+      break;
+  }
+
+  toSign.clear();
+}
+
+
+std::string hashQNameWithSalt(unsigned int times, const std::string& salt, const std::string& qname)
+{
+  string toHash;
+  toHash.assign(simpleCompress(toLower(qname)));
+  toHash.append(salt);
+
+  cerr<<makeHexDump(toHash)<<endl;
+  unsigned char hash[20];
+  for(;;) {
+    sha1((unsigned char*)toHash.c_str(), toHash.length(), hash);
+    if(!times--) 
+      break;
+    toHash.assign((char*)hash, 20);
+    toHash.append(salt);
+  }
+  return string((char*)hash, 20);
+}
diff --git a/pdns/dnssecinfra.hh b/pdns/dnssecinfra.hh
new file mode 100644 (file)
index 0000000..484865f
--- /dev/null
@@ -0,0 +1,48 @@
+#ifndef PDNS_DNSSECINFRA_HH
+#define PDNS_DNSSECINFRA_HH
+#include "dnsrecords.hh"
+#include <polarssl/rsa.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <vector>
+#include "misc.hh"
+
+struct CanonicalCompare: public binary_function<string, string, bool>  
+{
+  bool operator()(const std::string& a, const std::string& b) {
+    std::vector<std::string> avect, bvect;
+
+    stringtok(avect, a, ".");
+    stringtok(bvect, b, ".");
+    
+    reverse(avect.begin(), avect.end());
+    reverse(bvect.begin(), bvect.end());
+    
+    return avect < bvect;
+  }
+};
+
+
+DNSKEYRecordContent getRSAKeyFromISC(rsa_context* rsa, const char* fname);
+
+void makeRSAPublicKeyFromDNS(rsa_context* rc, const DNSKEYRecordContent& dkrc);
+bool sharedDNSSECCompare(const boost::shared_ptr<DNSRecordContent>& a, const shared_ptr<DNSRecordContent>& b);
+string getSHA1HashForRRSET(const std::string& qname, const RRSIGRecordContent& rrc, std::vector<boost::shared_ptr<DNSRecordContent> >& signRecords);
+DNSKEYRecordContent makeDNSKEYFromRSAKey(rsa_context* rc);
+DSRecordContent makeDSFromDNSKey(const std::string& qname, const DNSKEYRecordContent& drc, int digest=1);
+
+bool getSignerFor(const std::string& keyrepodir, const std::string& qname, std::string &signer);
+int countLabels(const std::string& signQName);
+
+class RSAContext;
+
+DNSKEYRecordContent getDNSKEYFor(const std::string& keyrepodir, const std::string& qname, bool withKSK, RSAContext* rc);
+void fillOutRRSIG(const std::string& keyrepodir, const std::string& signQName, RRSIGRecordContent& rrc, const std::string& hash, vector<shared_ptr<DNSRecordContent> >& toSign, bool withKSK=false);
+uint32_t getCurrentInception();
+void addSignature(const std::string& keyrepodir, const std::string signQName, const std::string& wildcardname, uint16_t signQType, uint32_t signTTL, DNSPacketWriter::Place signPlace, vector<shared_ptr<DNSRecordContent> >& toSign, DNSPacketWriter& pw);
+int getRRSIGForRRSET(const std::string& keyrepodir, const std::string signQName, uint16_t signQType, uint32_t signTTL, 
+                    vector<shared_ptr<DNSRecordContent> >& toSign, RRSIGRecordContent &rrc, bool ksk);
+
+std::string hashQNameWithSalt(unsigned int times, const std::string& salt, const std::string& qname);
+
+#endif
diff --git a/pdns/dnsseckeeper.cc b/pdns/dnsseckeeper.cc
new file mode 100644 (file)
index 0000000..2dca30f
--- /dev/null
@@ -0,0 +1,230 @@
+#include "dnsseckeeper.hh"
+#include "dnssecinfra.hh"
+#include "statbag.hh"
+#include <iostream>
+#include <boost/filesystem/operations.hpp>
+#include <boost/filesystem/path.hpp>
+#include <polarssl/havege.h>
+#include <polarssl/base64.h>
+#include <boost/foreach.hpp>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fstream>
+#include <boost/algorithm/string.hpp>
+#include <boost/format.hpp>
+
+namespace fs = boost::filesystem;
+
+using namespace std;
+using namespace boost;
+
+void RSAContext::create(unsigned int bits)
+{
+  havege_state hs;
+  havege_init( &hs );
+  
+  rsa_init(&d_context, RSA_PKCS_V15, 0, havege_rand, &hs ); // FIXME this leaks memory
+  int ret=rsa_gen_key(&d_context, bits, 65537);
+  if(ret < 0) 
+    throw runtime_error("Key generation failed");
+}
+
+std::string RSAContext::convertToISC()
+{
+  string ret;
+  typedef vector<pair<string, mpi*> > outputs_t;
+  outputs_t outputs;
+
+  outputs.push_back(make_pair("Modulus", &d_context.N));
+  outputs.push_back(make_pair("PublicExponent",&d_context.E));
+  outputs.push_back(make_pair("PrivateExponent",&d_context.D));
+  outputs.push_back(make_pair("Prime1",&d_context.P));
+  outputs.push_back(make_pair("Prime2",&d_context.Q));
+  outputs.push_back(make_pair("Exponent1",&d_context.DP));
+  outputs.push_back(make_pair("Exponent2",&d_context.DQ));
+  outputs.push_back(make_pair("Coefficient",&d_context.QP));
+
+  ret = "Private-key-format: v1.2\nAlgorithm: 5 (RSASHA1)\n";
+
+  BOOST_FOREACH(outputs_t::value_type value, outputs) {
+    ret += value.first;
+    ret += ": ";
+    unsigned char tmp[mpi_size(value.second)];
+    mpi_write_binary(value.second, tmp, sizeof(tmp));
+    unsigned char base64tmp[sizeof(tmp)*2];
+    int dlen=sizeof(base64tmp);
+    base64_encode(base64tmp, &dlen, tmp, sizeof(tmp));
+    ret.append((const char*)base64tmp, dlen);
+    ret.append(1, '\n');
+  }
+  return ret;
+}
+
+bool DNSSECKeeper::haveKSKFor(const std::string& zone, DNSSECPrivateKey* dpk)
+{
+  fs::path full_path = fs::system_complete( fs::path(d_dirname + "/" + zone + "/ksks/" ) );
+
+  if ( !fs::exists( full_path ) )
+    return false;
+
+  fs::directory_iterator end_iter;
+  for ( fs::directory_iterator dir_itr( full_path );
+       dir_itr != end_iter;
+       ++dir_itr )
+  {
+    //    cerr<<"Entry: '"<< dir_itr->leaf() <<"'"<<endl;
+    if(ends_with(dir_itr->leaf(),".isc")) {
+      //      cerr<<"Hit!"<<endl;
+
+      if(dpk) {
+       getRSAKeyFromISC(&dpk->d_key.getContext(), dir_itr->path().file_string().c_str());
+      }
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void DNSSECKeeper::addZSKFor(const std::string& name, bool next)
+{
+  DNSSECPrivateKey dpk;
+  dpk.d_key.create(1024); // for testing, 1024
+
+  string isc = dpk.d_key.convertToISC();
+  DNSKEYRecordContent drc = dpk.getDNSKEY();
+  drc.d_flags = 256; // KSK
+  
+  string iscName=d_dirname+"/"+name+"/zsks/";
+  time_t inception=getCurrentInception();
+  time_t end=inception+14*86400;
+
+  if(next) {
+    inception += 14*86400;
+    end += 14*86400;
+  }
+
+  struct tm ts;
+  gmtime_r(&inception, &ts);
+
+  iscName += (boost::format("%04d%02d%02d%02d%02d") 
+             % (1900+ts.tm_year) % (ts.tm_mon + 1)
+             % ts.tm_mday % ts.tm_hour % ts.tm_min).str();
+
+  iscName += "-";
+
+  gmtime_r(&end, &ts);
+  iscName += (boost::format("%04d%02d%02d%02d%02d.%u") 
+             % (1900+ts.tm_year) % (ts.tm_mon + 1)
+             % ts.tm_mday % ts.tm_hour % ts.tm_min % drc.getTag()).str();
+
+  {  
+    ofstream iscFile((iscName+".isc").c_str());
+    iscFile << isc;
+  }
+
+  {  
+    ofstream dnskeyFile((iscName+".dnskey").c_str());
+    dnskeyFile << name << " IN DNSKEY " << drc.getZoneRepresentation()<<endl;
+  }
+
+}
+
+bool zskSortByDates(const DNSSECKeeper::zskset_t::value_type& a, const DNSSECKeeper::zskset_t::value_type& b)
+{
+  return 
+    tie(a.first.beginValidity, a.first.endValidity) < 
+    tie(b.first.beginValidity, b.first.endValidity);
+}
+
+DNSSECKeeper::zskset_t DNSSECKeeper::getZSKsFor(const std::string& zone, bool all)
+{
+  zskset_t zskset;
+
+  fs::path full_path = fs::system_complete( fs::path(d_dirname + "/" + zone + "/zsks/" ) );
+
+  if ( !fs::exists( full_path ) )
+    return zskset;
+
+  fs::directory_iterator end_iter;
+  for ( fs::directory_iterator dir_itr( full_path );
+       dir_itr != end_iter;
+       ++dir_itr )
+  {
+    //    cerr<<"Entry: '"<< dir_itr->leaf() <<"'"<<endl;
+    if(ends_with(dir_itr->leaf(),".isc")) {
+      //cerr<<"Hit!"<<endl;
+      DNSSECPrivateKey dpk;
+      getRSAKeyFromISC(&dpk.d_key.getContext(), dir_itr->path().file_string().c_str());
+      
+      struct tm ts1, ts2;
+      
+      memset(&ts1, 0, sizeof(ts1));
+      memset(&ts2, 0, sizeof(ts2));
+      
+      sscanf(dir_itr->leaf().c_str(), "%04d%02d%02d%02d%02d-%04d%02d%02d%02d%02d",
+            &ts1.tm_year, 
+            &ts1.tm_mon, &ts1.tm_mday, &ts1.tm_hour, &ts1.tm_min,
+            &ts2.tm_year, 
+            &ts2.tm_mon, &ts2.tm_mday, &ts2.tm_hour, &ts2.tm_min);
+
+      ts1.tm_year -= 1900;
+      ts2.tm_year -= 1900;
+
+      ts1.tm_mon--;
+      ts2.tm_mon--;
+
+      dpk.beginValidity=timegm(&ts1);
+      dpk.endValidity=timegm(&ts2);
+      time_t now=time(0);
+
+      zskset.push_back(make_pair(dpk, now > dpk.beginValidity && now < dpk.endValidity));
+    }
+    sort(zskset.begin(), zskset.end(), zskSortByDates);
+  }
+
+  return zskset;
+}
+
+DNSKEYRecordContent DNSSECPrivateKey::getDNSKEY()
+{
+  return makeDNSKEYFromRSAKey(&d_key.getContext());
+}
+
+
+void DNSSECKeeper::addZone(const std::string& name)
+{
+  mkdir((d_dirname+"/"+name).c_str(), 0700);
+  mkdir((d_dirname+"/"+name+"/ksks").c_str(), 0700);
+  mkdir((d_dirname+"/"+name+"/zsks").c_str(), 0700);
+
+  DNSSECPrivateKey dpk;
+  dpk.d_key.create(2048); // for testing, 1024
+
+  string isc = dpk.d_key.convertToISC();
+  DNSKEYRecordContent drc = dpk.getDNSKEY();
+  drc.d_flags = 257; // ZSK
+  
+  string iscName=d_dirname+"/"+name+"/ksks/";
+
+  time_t now=time(0);
+  struct tm ts;
+  gmtime_r(&now, &ts);
+  iscName += (boost::format("%04d%02d%02d%02d%02d.%u") 
+             % (1900+ts.tm_year) % (ts.tm_mon + 1)
+             % ts.tm_mday % ts.tm_hour % ts.tm_min % drc.getTag()).str();
+
+
+  {  
+    ofstream iscFile((iscName+".isc").c_str());
+    iscFile << isc;
+  }
+
+  {  
+    ofstream dnskeyFile((iscName+".dnskey").c_str());
+    dnskeyFile << name << " IN DNSKEY " << drc.getZoneRepresentation()<<endl;
+  }
+
+}
+
diff --git a/pdns/dnsseckeeper.hh b/pdns/dnsseckeeper.hh
new file mode 100644 (file)
index 0000000..656044d
--- /dev/null
@@ -0,0 +1,98 @@
+#ifndef PDNSDNSSECKEEPER_HH
+#define PDNSDNSSECKEEPER_HH
+#include <string>
+#include <polarssl/rsa.h>
+#include <string.h>
+#include <vector>
+#include "dnsrecords.hh"
+
+#define PDNSSEC_MI(x) mpi_init(&d_context.x, 0)
+#define PDNSSEC_MC(x) PDNSSEC_MI(x); mpi_copy(&d_context.x, const_cast<mpi*>(&orig.d_context.x))
+#define PDNSSEC_MF(x) mpi_free(&d_context.x, 0)
+
+class RSAContext
+{
+public:
+  RSAContext()
+  {
+    memset(&d_context, 0, sizeof(d_context));
+    PDNSSEC_MI(N); 
+    PDNSSEC_MI(E); PDNSSEC_MI(D); PDNSSEC_MI(P); PDNSSEC_MI(Q); PDNSSEC_MI(DP); PDNSSEC_MI(DQ); PDNSSEC_MI(QP); PDNSSEC_MI(RN); PDNSSEC_MI(RP); PDNSSEC_MI(RQ);
+  }
+
+  ~RSAContext()
+  {
+    PDNSSEC_MF(N); 
+    PDNSSEC_MF(E); PDNSSEC_MF(D); PDNSSEC_MF(P); PDNSSEC_MF(Q); PDNSSEC_MF(DP); PDNSSEC_MF(DQ); PDNSSEC_MF(QP); PDNSSEC_MF(RN); PDNSSEC_MF(RP); PDNSSEC_MF(RQ);
+  }
+
+  RSAContext(const RSAContext& orig) 
+  {
+    d_context.ver = orig.d_context.ver;
+    d_context.len = orig.d_context.len;
+
+    d_context.padding = orig.d_context.padding;
+    d_context.hash_id = orig.d_context.hash_id;
+    d_context.f_rng = orig.d_context.f_rng;
+    d_context.p_rng = orig.d_context.p_rng;
+    PDNSSEC_MC(N); 
+    PDNSSEC_MC(E); PDNSSEC_MC(D); PDNSSEC_MC(P); PDNSSEC_MC(Q); PDNSSEC_MC(DP); PDNSSEC_MC(DQ); PDNSSEC_MC(QP); PDNSSEC_MC(RN); PDNSSEC_MC(RP); PDNSSEC_MC(RQ);
+  }
+
+  RSAContext& operator=(const RSAContext& orig) 
+  {
+    d_context.ver = orig.d_context.ver;
+    d_context.len = orig.d_context.len;
+
+    d_context.padding = orig.d_context.padding;
+    d_context.hash_id = orig.d_context.hash_id;
+    d_context.f_rng = orig.d_context.f_rng;
+    d_context.p_rng = orig.d_context.p_rng;
+
+    PDNSSEC_MC(N); 
+    PDNSSEC_MC(E); PDNSSEC_MC(D); PDNSSEC_MC(P); PDNSSEC_MC(Q); PDNSSEC_MC(DP); PDNSSEC_MC(DQ); PDNSSEC_MC(QP); PDNSSEC_MC(RN); PDNSSEC_MC(RP); PDNSSEC_MC(RQ);
+    return *this;
+  }
+
+  rsa_context& getContext()
+  {
+    return d_context;
+  }
+
+  void create(unsigned int bits);
+  std::string convertToISC();
+
+private:
+  rsa_context d_context;
+};
+
+#undef PDNSSEC_MC
+#undef PDNSSEC_MI
+#undef PDNSSEC_MF
+
+struct DNSSECPrivateKey
+{
+  uint16_t getTag();
+  
+  RSAContext d_key;
+  DNSKEYRecordContent getDNSKEY();
+  time_t beginValidity, endValidity; // wart
+};
+
+class DNSSECKeeper
+{
+public:
+  explicit DNSSECKeeper(const std::string& dirname) : d_dirname(dirname){}
+  bool haveKSKFor(const std::string& zone, DNSSECPrivateKey* ksk=0);
+  
+  typedef std::vector<std::pair<DNSSECPrivateKey, bool> > zskset_t;
+  zskset_t getZSKsFor(const std::string& zone, bool all=false);
+  void addZSKFor(const std::string& fname, bool next=false);
+
+  void addZone(const std::string& fname);
+
+private:
+  std::string d_dirname;
+};
+
+#endif