From: Bert Hubert Date: Sun, 18 Apr 2010 11:58:27 +0000 (+0000) Subject: initial drop of dnssec keying infrastructure - mostly ceremonial X-Git-Tag: rec-3.3~151 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=4691b2df3db44be70c3e6b868cd0c224984c0dc2;p=pdns initial drop of dnssec keying infrastructure - mostly ceremonial git-svn-id: svn://svn.powerdns.com/pdns/trunk/pdns@1551 d19b8d6e-7fed-0310-83ef-9ca221ded41b --- diff --git a/pdns/base32.cc b/pdns/base32.cc new file mode 100644 index 000000000..27f4dfd60 --- /dev/null +++ b/pdns/base32.cc @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#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: '"< + +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 index 000000000..536e7d4e0 --- /dev/null +++ b/pdns/dnssecinfra.cc @@ -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 +#include +#include +#include +#include +#include +#include +#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 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 '"<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& a, const shared_ptr& b) +{ + return a->serialize("", true) < b->serialize("", true); +} + +string getSHA1HashForRRSET(const std::string& qname, const RRSIGRecordContent& rrc, vector >& signRecords) +{ + sort(signRecords.begin(), signRecords.end(), sharedDNSSECCompare); + + string toHash; + toHash.append(const_cast(rrc).serialize("", true)); + toHash.resize(toHash.size() - rrc.d_signature.length()); // chop off the end; + // cerr<<"toHash start size: "<& add, signRecords) { + // cerr<<"\t IN "<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: "<(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(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: "<, RRSIGRecordContent> g_rrsigs; + +void fillOutRRSIG(const std::string& keyrepodir, const std::string& signQName, RRSIGRecordContent& rrc, const std::string& hash, vector >& 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 !"< >& 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 '"< >& toSign, DNSPacketWriter& pw) +{ + cerr<<"Asked to sign '"< +#include +#include +#include +#include "misc.hh" + +struct CanonicalCompare: public binary_function +{ + bool operator()(const std::string& a, const std::string& b) { + std::vector 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& a, const shared_ptr& b); +string getSHA1HashForRRSET(const std::string& qname, const RRSIGRecordContent& rrc, std::vector >& 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 >& 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 >& toSign, DNSPacketWriter& pw); +int getRRSIGForRRSET(const std::string& keyrepodir, const std::string signQName, uint16_t signQType, uint32_t signTTL, + vector >& 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 index 000000000..2dca30f12 --- /dev/null +++ b/pdns/dnsseckeeper.cc @@ -0,0 +1,230 @@ +#include "dnsseckeeper.hh" +#include "dnssecinfra.hh" +#include "statbag.hh" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 > 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() <<"'"<leaf(),".isc")) { + // cerr<<"Hit!"<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()<leaf() <<"'"<leaf(),".isc")) { + //cerr<<"Hit!"<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()< +#include +#include +#include +#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(&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 > 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