Merge branch 'v2'

This commit is contained in:
Miek Gieben 2012-09-19 21:01:39 +02:00
commit 4ebfc6b220
14 changed files with 390 additions and 103 deletions

View File

@ -4,6 +4,10 @@
* make example from chaos * make example from chaos
* Support for on-the-fly-signing * Support for on-the-fly-signing
* (Re)sign zonefiles * (Re)sign zonefiles
* TLSA support
* create record from PEM(?) files
* sign
* verify
## Nice to have ## Nice to have

View File

@ -202,7 +202,6 @@ func (k *RR_DNSKEY) ToDS(h int) *RR_DS {
// the values: Inception, Expiration, KeyTag, SignerName and Algorithm. // the values: Inception, Expiration, KeyTag, SignerName and Algorithm.
// The rest is copied from the RRset. Sign returns true when the signing went OK, // The rest is copied from the RRset. Sign returns true when the signing went OK,
// otherwise false. // otherwise false.
// The signature data in the RRSIG is filled by this method.
// There is no check if RRSet is a proper (RFC 2181) RRSet. // There is no check if RRSet is a proper (RFC 2181) RRSet.
func (rr *RR_RRSIG) Sign(k PrivateKey, rrset []RR) error { func (rr *RR_RRSIG) Sign(k PrivateKey, rrset []RR) error {
if k == nil { if k == nil {
@ -255,9 +254,6 @@ func (rr *RR_RRSIG) Sign(k PrivateKey, rrset []RR) error {
switch rr.Algorithm { switch rr.Algorithm {
case DSA, DSANSEC3SHA1: case DSA, DSANSEC3SHA1:
// Implicit in the ParameterSizes // Implicit in the ParameterSizes
case RSAMD5:
h = md5.New()
ch = crypto.MD5
case RSASHA1, RSASHA1NSEC3SHA1: case RSASHA1, RSASHA1NSEC3SHA1:
h = sha1.New() h = sha1.New()
ch = crypto.SHA1 ch = crypto.SHA1
@ -269,6 +265,8 @@ func (rr *RR_RRSIG) Sign(k PrivateKey, rrset []RR) error {
case RSASHA512: case RSASHA512:
h = sha512.New() h = sha512.New()
ch = crypto.SHA512 ch = crypto.SHA512
case RSAMD5:
fallthrough // Deprecated in RFC 6725
default: default:
return ErrAlg return ErrAlg
} }
@ -286,7 +284,8 @@ func (rr *RR_RRSIG) Sign(k PrivateKey, rrset []RR) error {
signature = append(signature, s1.Bytes()...) signature = append(signature, s1.Bytes()...)
rr.Signature = unpackBase64(signature) rr.Signature = unpackBase64(signature)
case *rsa.PrivateKey: case *rsa.PrivateKey:
signature, err := rsa.SignPKCS1v15(rand.Reader, p, ch, sighash) // We can use nil as rand.Reader here (says AGL)
signature, err := rsa.SignPKCS1v15(nil, p, ch, sighash)
if err != nil { if err != nil {
return err return err
} }

View File

@ -157,7 +157,7 @@ func main() {
defer pprof.StopCPUProfile() defer pprof.StopCPUProfile()
} }
dns.HandleFunc(".", handleReflect) dns.HandleFunc("miek.nl.", handleReflect)
dns.HandleFunc("authors.bind.", dns.HandleAuthors) dns.HandleFunc("authors.bind.", dns.HandleAuthors)
dns.HandleFunc("authors.server.", dns.HandleAuthors) dns.HandleFunc("authors.server.", dns.HandleAuthors)
dns.HandleFunc("version.bind.", dns.HandleVersion) dns.HandleFunc("version.bind.", dns.HandleVersion)

3
msg.go
View File

@ -45,8 +45,7 @@ var (
ErrSecret error = &Error{Err: "dns: no secrets defined"} ErrSecret error = &Error{Err: "dns: no secrets defined"}
ErrSigGen error = &Error{Err: "dns: bad signature generation"} ErrSigGen error = &Error{Err: "dns: bad signature generation"}
ErrAuth error = &Error{Err: "dns: bad authentication"} ErrAuth error = &Error{Err: "dns: bad authentication"}
ErrXfrSoa error = &Error{Err: "dns: no SOA seen"} ErrSoa error = &Error{Err: "dns: no SOA"}
ErrXfrType error = &Error{Err: "dns: no ixfr, nor axfr"}
ErrHandle error = &Error{Err: "dns: handle is nil"} ErrHandle error = &Error{Err: "dns: handle is nil"}
ErrChan error = &Error{Err: "dns: channel is nil"} ErrChan error = &Error{Err: "dns: channel is nil"}
ErrName error = &Error{Err: "dns: type not found for name"} ErrName error = &Error{Err: "dns: type not found for name"}

View File

@ -102,8 +102,8 @@ PrivateKey: WURgWHCcYIYUPWgeLmiPY2DJJk02vgrmTfitxgqcL4vwW7BOrbawVmVe0d9V94SR`
a, _ := NewRR("www.example.net. 3600 IN A 192.0.2.1") a, _ := NewRR("www.example.net. 3600 IN A 192.0.2.1")
sig := new(RR_RRSIG) sig := new(RR_RRSIG)
sig.Hdr = RR_Header{"example.net.", TypeRRSIG, ClassINET, 14400, 0} sig.Hdr = RR_Header{"example.net.", TypeRRSIG, ClassINET, 14400, 0}
sig.Expiration, _ = DateToTime("20100909102025") sig.Expiration, _ = StringToTime("20100909102025")
sig.Inception, _ = DateToTime("20100812102025") sig.Inception, _ = StringToTime("20100812102025")
sig.KeyTag = eckey.(*RR_DNSKEY).KeyTag() sig.KeyTag = eckey.(*RR_DNSKEY).KeyTag()
sig.SignerName = eckey.(*RR_DNSKEY).Hdr.Name sig.SignerName = eckey.(*RR_DNSKEY).Hdr.Name
sig.Algorithm = eckey.(*RR_DNSKEY).Algorithm sig.Algorithm = eckey.(*RR_DNSKEY).Algorithm
@ -521,8 +521,8 @@ func TestRfc1982(t *testing.T) {
// fall in the current 68 year span // fall in the current 68 year span
strtests := []string{"20120525134203", "19700101000000", "20380119031408"} strtests := []string{"20120525134203", "19700101000000", "20380119031408"}
for _, v := range strtests { for _, v := range strtests {
if x, _ := DateToTime(v); v != TimeToDate(x) { if x, _ := StringToTime(v); v != TimeToString(x) {
t.Logf("1982 arithmetic string failure %s (%s:%d)", v, TimeToDate(x), x) t.Logf("1982 arithmetic string failure %s (%s:%d)", v, TimeToString(x), x)
t.Fail() t.Fail()
} }
} }
@ -532,8 +532,8 @@ func TestRfc1982(t *testing.T) {
1<<32 - 1: "21060207062815", 1<<32 - 1: "21060207062815",
} }
for i, v := range inttests { for i, v := range inttests {
if TimeToDate(i) != v { if TimeToString(i) != v {
t.Logf("1982 arithmetic int failure %d:%s (%s)", i, v, TimeToDate(i)) t.Logf("1982 arithmetic int failure %d:%s (%s)", i, v, TimeToString(i))
t.Fail() t.Fail()
} }
} }
@ -548,8 +548,8 @@ func TestRfc1982(t *testing.T) {
"29210101121212": "21040522212236", "29210101121212": "21040522212236",
} }
for from, to := range future { for from, to := range future {
x, _ := DateToTime(from) x, _ := StringToTime(from)
y := TimeToDate(x) y := TimeToString(x)
if y != to { if y != to {
t.Logf("1982 arithmetic future failure %s:%s (%s)", from, to, y) t.Logf("1982 arithmetic future failure %s:%s (%s)", from, to, y)
t.Fail() t.Fail()

View File

@ -174,27 +174,21 @@ func (mux *ServeMux) match(zone string, t uint16) Handler {
if h, e := mux.m.Find(zone); e { if h, e := mux.m.Find(zone); e {
// If we got queried for a DS record, we must see if we // If we got queried for a DS record, we must see if we
// if we also serve the parent. We then redirect the query to it. // if we also serve the parent. We then redirect the query to it.
// TODO(mg): grandparents works too?
if t != TypeDS { if t != TypeDS {
return h.Value.(Handler) return h.Value.(Handler)
} }
if d := h.Up(); d != nil { if d := h.Up(); d != nil {
return d.Value.(Handler) return d.Value.(Handler)
} }
// No parent zone found, let the original handler take care of it
return h.Value.(Handler)
} else { } else {
if h == nil { if h == nil {
return nil return nil
} }
// Not an exact match and h may be nil and h.Value may be nil
if h.Value != nil {
return h.Value.(Handler) return h.Value.(Handler)
} }
if d := h.Up(); d != nil { panic("dns: not reached")
return d.Value.(Handler)
}
}
// Nothing found at all
return nil
} }
// Handle adds a handler to the ServeMux for pattern. // Handle adds a handler to the ServeMux for pattern.
@ -220,9 +214,12 @@ func (mux *ServeMux) HandleRemove(pattern string) {
} }
// ServeDNS dispatches the request to the handler whose // ServeDNS dispatches the request to the handler whose
// pattern most closely matches the request message. For DS queries // pattern most closely matches the request message. If DefaultServeMux
// a parent zone is sought. // is used the correct thing for DS queries is done: a possible parent
// is sought.
// If no handler is found a standard SERVFAIL message is returned // If no handler is found a standard SERVFAIL message is returned
// If the request message does not have a single question in the
// question section a SERVFAIL is returned.
func (mux *ServeMux) ServeDNS(w ResponseWriter, request *Msg) { func (mux *ServeMux) ServeDNS(w ResponseWriter, request *Msg) {
var h Handler var h Handler
if len(request.Question) != 1 { if len(request.Question) != 1 {

View File

@ -70,3 +70,29 @@ func BenchmarkServing(b *testing.B) {
c.Exchange(m, "127.0.0.1:8053") c.Exchange(m, "127.0.0.1:8053")
} }
} }
func TestDotAsCatchAllWildcard(t *testing.T) {
mux := NewServeMux()
mux.Handle(".", HandlerFunc(HelloServer))
mux.Handle("example.com.", HandlerFunc(AnotherHelloServer))
handler := mux.match("www.miek.nl.", TypeTXT)
if handler == nil {
t.Error("wildcard match failed")
}
handler = mux.match("www.example.com.", TypeTXT)
if handler == nil {
t.Error("example.com match failed")
}
handler = mux.match("a.www.example.com.", TypeTXT)
if handler == nil {
t.Error("a.www.example.com match failed")
}
handler = mux.match("boe.", TypeTXT)
if handler == nil {
t.Error("boe. match failed")
}
}

View File

@ -95,7 +95,7 @@ func (rr *RR_TSIG) String() string {
s := "\n;; TSIG PSEUDOSECTION:\n" s := "\n;; TSIG PSEUDOSECTION:\n"
s += rr.Hdr.String() + s += rr.Hdr.String() +
" " + rr.Algorithm + " " + rr.Algorithm +
" " + tsigTimeToDate(rr.TimeSigned) + " " + tsigTimeToString(rr.TimeSigned) +
" " + strconv.Itoa(int(rr.Fudge)) + " " + strconv.Itoa(int(rr.Fudge)) +
" " + strconv.Itoa(int(rr.MACSize)) + " " + strconv.Itoa(int(rr.MACSize)) +
" " + strings.ToUpper(rr.MAC) + " " + strings.ToUpper(rr.MAC) +
@ -351,7 +351,7 @@ func stripTsig(msg []byte) ([]byte, *RR_TSIG, error) {
// Translate the TSIG time signed into a date. There is no // Translate the TSIG time signed into a date. There is no
// need for RFC1982 calculations as this date is 48 bits. // need for RFC1982 calculations as this date is 48 bits.
func tsigTimeToDate(t uint64) string { func tsigTimeToString(t uint64) string {
ti := time.Unix(int64(t), 0).UTC() ti := time.Unix(int64(t), 0).UTC()
return ti.Format("20060102150405") return ti.Format("20060102150405")
} }

View File

@ -844,8 +844,8 @@ func (rr *RR_RRSIG) String() string {
" " + strconv.Itoa(int(rr.Algorithm)) + " " + strconv.Itoa(int(rr.Algorithm)) +
" " + strconv.Itoa(int(rr.Labels)) + " " + strconv.Itoa(int(rr.Labels)) +
" " + strconv.FormatInt(int64(rr.OrigTtl), 10) + " " + strconv.FormatInt(int64(rr.OrigTtl), 10) +
" " + TimeToDate(rr.Expiration) + " " + TimeToString(rr.Expiration) +
" " + TimeToDate(rr.Inception) + " " + TimeToString(rr.Inception) +
" " + strconv.Itoa(int(rr.KeyTag)) + " " + strconv.Itoa(int(rr.KeyTag)) +
" " + rr.SignerName + " " + rr.SignerName +
" " + rr.Signature " " + rr.Signature
@ -1371,10 +1371,10 @@ func (rr *RR_WKS) Copy() RR {
return &RR_WKS{*rr.Hdr.CopyHeader(), rr.Address, rr.Protocol, rr.BitMap} return &RR_WKS{*rr.Hdr.CopyHeader(), rr.Address, rr.Protocol, rr.BitMap}
} }
// TimeToDate translates the RRSIG's incep. and expir. times to the // TimeToString translates the RRSIG's incep. and expir. times to the
// string representation used when printing the record. // string representation used when printing the record.
// It takes serial arithmetic (RFC 1982) into account. // It takes serial arithmetic (RFC 1982) into account.
func TimeToDate(t uint32) string { func TimeToString(t uint32) string {
mod := ((int64(t) - time.Now().Unix()) / year68) - 1 mod := ((int64(t) - time.Now().Unix()) / year68) - 1
if mod < 0 { if mod < 0 {
mod = 0 mod = 0
@ -1383,10 +1383,10 @@ func TimeToDate(t uint32) string {
return ti.Format("20060102150405") return ti.Format("20060102150405")
} }
// DateToTime translates the RRSIG's incep. and expir. times from // StringToTime translates the RRSIG's incep. and expir. times from
// string values like "20110403154150" to an 32 bit integer. // string values like "20110403154150" to an 32 bit integer.
// It takes serial arithmetic (RFC 1982) into account. // It takes serial arithmetic (RFC 1982) into account.
func DateToTime(s string) (uint32, error) { func StringToTime(s string) (uint32, error) {
t, e := time.Parse("20060102150405", s) t, e := time.Parse("20060102150405", s)
if e != nil { if e != nil {
return 0, e return 0, e

10
xfr.go
View File

@ -40,7 +40,7 @@ func (c *Client) XfrReceive(q *Msg, a string) (chan *XfrToken, error) {
go w.ixfrReceive(q, e) go w.ixfrReceive(q, e)
return e, nil return e, nil
default: default:
return nil, ErrXfrType return nil, nil
} }
panic("dns: not reached") panic("dns: not reached")
} }
@ -61,7 +61,7 @@ func (w *reply) axfrReceive(q *Msg, c chan *XfrToken) {
} }
if first { if first {
if !checkXfrSOA(in, true) { if !checkXfrSOA(in, true) {
c <- &XfrToken{in.Answer, ErrXfrSoa} c <- &XfrToken{in.Answer, ErrSoa}
return return
} }
first = !first first = !first
@ -103,7 +103,7 @@ func (w *reply) ixfrReceive(q *Msg, c chan *XfrToken) {
// Check if the returned answer is ok // Check if the returned answer is ok
if !checkXfrSOA(in, true) { if !checkXfrSOA(in, true) {
c <- &XfrToken{in.Answer, ErrXfrSoa} c <- &XfrToken{in.Answer, ErrSoa}
return return
} }
// This serial is important // This serial is important
@ -141,8 +141,6 @@ func checkXfrSOA(in *Msg, first bool) bool {
return false return false
} }
// XfrSend performs an outgoing [AI]xfr depending on the request message. The // XfrSend performs an outgoing [AI]xfr depending on the request message. The
// caller is responsible for sending the correct sequence of RR sets through // caller is responsible for sending the correct sequence of RR sets through
// the channel c. For reasons of symmetry XfrToken is re-used. // the channel c. For reasons of symmetry XfrToken is re-used.
@ -171,7 +169,7 @@ func XfrSend(w ResponseWriter, q *Msg, c chan *XfrToken, e *error) error {
go axfrSend(w, q, c, e) go axfrSend(w, q, c, e)
return nil return nil
default: default:
return ErrXfrType return nil
} }
panic("not reached") panic("not reached")
} }

352
zone.go
View File

@ -4,6 +4,9 @@ package dns
import ( import (
"github.com/miekg/radix" "github.com/miekg/radix"
"math/rand"
"runtime"
"sort"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -16,10 +19,18 @@ type Zone struct {
Wildcard int // Whenever we see a wildcard name, this is incremented Wildcard int // Whenever we see a wildcard name, this is incremented
*radix.Radix // Zone data *radix.Radix // Zone data
mutex *sync.RWMutex mutex *sync.RWMutex
// timemodified?
expired bool // Slave zone is expired expired bool // Slave zone is expired
// Do we need a timemodified?
} }
type uint16Slice []uint16
func (p uint16Slice) Len() int { return len(p) }
func (p uint16Slice) Less(i, j int) bool { return p[i] < p[j] }
func (p uint16Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
type signData struct{ node, next *ZoneData }
// SignatureConfig holds the parameters for zone (re)signing. This // SignatureConfig holds the parameters for zone (re)signing. This
// is copied from OpenDNSSEC. See: // is copied from OpenDNSSEC. See:
// https://wiki.opendnssec.org/display/DOCS/kasp.xml // https://wiki.opendnssec.org/display/DOCS/kasp.xml
@ -29,22 +40,29 @@ type SignatureConfig struct {
// When the end of the validity approaches, how much time should remain // When the end of the validity approaches, how much time should remain
// before we start to resign. Typical value is 3 days. // before we start to resign. Typical value is 3 days.
Refresh time.Duration Refresh time.Duration
// Jitter is an amount of time added or subtracted from the // Jitter is an random amount of time added or subtracted from the
// expiration time to ensure not all signatures expire a the same time. // expiration time to ensure not all signatures expire a the same time.
// Typical value is 12 hours. // Typical value is 12 hours, which means the actual jitter value is
// between -12..0..+12.
Jitter time.Duration Jitter time.Duration
// InceptionOffset is subtracted from the inception time to ensure badly // InceptionOffset is subtracted from the inception time to ensure badly
// calibrated clocks on the internet can still validate a signature. // calibrated clocks on the internet can still validate a signature.
// Typical value is 300 seconds. // Typical value is 300 seconds.
InceptionOffset time.Duration InceptionOffset time.Duration
// SignerRoutines specifies the number of signing goroutines, if not
// set runtime.NumCPU() + 1 is used as the value.
SignerRoutines int
// SOA MINTTL value used as the TTL on NSEC/NSEC3.
minttl uint32
} }
func newSignatureConfig() *SignatureConfig { func newSignatureConfig() *SignatureConfig {
return &SignatureConfig{time.Duration(4*7*24) * time.Hour, time.Duration(3*24) * time.Hour, time.Duration(12) * time.Hour, time.Duration(300) * time.Second} return &SignatureConfig{time.Duration(4*7*24) * time.Hour, time.Duration(3*24) * time.Hour, time.Duration(12) * time.Hour, time.Duration(300) * time.Second, runtime.NumCPU() + 1, 0}
} }
// DefaultSignaturePolicy has the following values. Validity is 4 weeks, // DefaultSignaturePolicy has the following values. Validity is 4 weeks,
// Refresh is set to 3 days, Jitter to 12 hours and InceptionOffset to 300 seconds. // Refresh is set to 3 days, Jitter to 12 hours and InceptionOffset to 300 seconds.
// SignerRoutines is set to runtime.NumCPU() + 1
var DefaultSignatureConfig = newSignatureConfig() var DefaultSignatureConfig = newSignatureConfig()
// NewZone creates an initialized zone with Origin set to origin. // NewZone creates an initialized zone with Origin set to origin.
@ -71,8 +89,8 @@ type ZoneData struct {
mutex *sync.RWMutex mutex *sync.RWMutex
} }
// newZoneData creates a new zone data element // NewZoneData creates a new zone data element.
func newZoneData(s string) *ZoneData { func NewZoneData(s string) *ZoneData {
zd := new(ZoneData) zd := new(ZoneData)
zd.Name = s zd.Name = s
zd.RR = make(map[uint16][]RR) zd.RR = make(map[uint16][]RR)
@ -83,7 +101,7 @@ func newZoneData(s string) *ZoneData {
// toRadixName reverses a domain name so that when we store it in the radix tree // toRadixName reverses a domain name so that when we store it in the radix tree
// we preserve the nsec ordering of the zone (this idea was stolen from NSD). // we preserve the nsec ordering of the zone (this idea was stolen from NSD).
// each label is also lowercased. // Each label is also lowercased.
func toRadixName(d string) string { func toRadixName(d string) string {
if d == "." { if d == "." {
return "." return "."
@ -96,12 +114,66 @@ func toRadixName(d string) string {
} }
s = strings.ToLower(l) + "." + s s = strings.ToLower(l) + "." + s
} }
return "." + s
}
// String returns a string representation of a ZoneData. There is no
// String for the entire zone, because this will (most likely) take up
// a huge amount of memory. Basic use pattern for printing an entire
// zone:
//
// // z contains the zone
// z.Radix.DoNext(func(i interface{}) {
// fmt.Printf("%s", i.(*dns.ZoneData).String()) })
func (zd *ZoneData) String() string {
var (
s string
t uint16
)
// Make sure SOA is first
// There is only one SOA, but it may have multiple sigs
if soa, ok := zd.RR[TypeSOA]; ok {
s += soa[0].String() + "\n"
if _, ok := zd.Signatures[TypeSOA]; ok {
for _, sig := range zd.Signatures[TypeSOA] {
s += sig.String() + "\n"
}
}
}
Types:
for _, rrset := range zd.RR {
for _, rr := range rrset {
t = rr.Header().Rrtype
if t == TypeSOA || t == TypeNSEC { // Done above or below
continue Types
}
s += rr.String() + "\n"
}
if _, ok := zd.Signatures[t]; ok {
for _, rr := range zd.Signatures[t] {
s += rr.String() + "\n"
}
}
}
// Make sure NSEC is last
// There is only one NSEC, but it may have multiple sigs
if soa, ok := zd.RR[TypeNSEC]; ok {
s += soa[0].String() + "\n"
if _, ok := zd.Signatures[TypeNSEC]; ok {
for _, sig := range zd.Signatures[TypeNSEC] {
s += sig.String() + "\n"
}
}
}
return s return s
} }
func (z *Zone) String() string { // Lock locks the zone z for writing.
return z.Radix.String() func (z *Zone) Lock() { z.mutex.Lock() }
}
// Unlock unlocks the zone z for writing.
func (z *Zone) Unlock() { z.mutex.Unlock() }
// Insert inserts an RR into the zone. There is no check for duplicate data, although // Insert inserts an RR into the zone. There is no check for duplicate data, although
// Remove will remove all duplicates. // Remove will remove all duplicates.
@ -112,16 +184,16 @@ func (z *Zone) Insert(r RR) error {
// TODO(mg): quick check for doubles? // TODO(mg): quick check for doubles?
key := toRadixName(r.Header().Name) key := toRadixName(r.Header().Name)
z.mutex.Lock() z.Lock()
zd, exact := z.Radix.Find(key) zd, exact := z.Radix.Find(key)
if !exact { if !exact {
// Not an exact match, so insert new value // Not an exact match, so insert new value
defer z.mutex.Unlock() defer z.Unlock()
// Check if it's a wildcard name // Check if it's a wildcard name
if len(r.Header().Name) > 1 && r.Header().Name[0] == '*' && r.Header().Name[1] == '.' { if len(r.Header().Name) > 1 && r.Header().Name[0] == '*' && r.Header().Name[1] == '.' {
z.Wildcard++ z.Wildcard++
} }
zd := newZoneData(r.Header().Name) zd := NewZoneData(r.Header().Name)
switch t := r.Header().Rrtype; t { switch t := r.Header().Rrtype; t {
case TypeRRSIG: case TypeRRSIG:
sigtype := r.(*RR_RRSIG).TypeCovered sigtype := r.(*RR_RRSIG).TypeCovered
@ -138,7 +210,7 @@ func (z *Zone) Insert(r RR) error {
z.Radix.Insert(key, zd) z.Radix.Insert(key, zd)
return nil return nil
} }
z.mutex.Unlock() z.Unlock()
zd.Value.(*ZoneData).mutex.Lock() zd.Value.(*ZoneData).mutex.Lock()
defer zd.Value.(*ZoneData).mutex.Unlock() defer zd.Value.(*ZoneData).mutex.Unlock()
// Name already there // Name already there
@ -161,13 +233,13 @@ func (z *Zone) Insert(r RR) error {
// this is a no-op. // this is a no-op.
func (z *Zone) Remove(r RR) error { func (z *Zone) Remove(r RR) error {
key := toRadixName(r.Header().Name) key := toRadixName(r.Header().Name)
z.mutex.Lock() z.Lock()
zd, exact := z.Radix.Find(key) zd, exact := z.Radix.Find(key)
if !exact { if !exact {
defer z.mutex.Unlock() defer z.Unlock()
return nil return nil
} }
z.mutex.Unlock() z.Unlock()
zd.Value.(*ZoneData).mutex.Lock() zd.Value.(*ZoneData).mutex.Lock()
defer zd.Value.(*ZoneData).mutex.Unlock() defer zd.Value.(*ZoneData).mutex.Unlock()
remove := false remove := false
@ -199,51 +271,243 @@ func (z *Zone) Remove(r RR) error {
} }
// Find looks up the ownername s in the zone and returns the // Find looks up the ownername s in the zone and returns the
// data when found or nil when nothing is found. // data and true when an exact match is found. If an exact find isn't
// We can do better here, and include NXDOMAIN also. Much more efficient, only // possible the first parent node with a non-nil Value is returned and
// 1 tree walk. // the boolean is false.
func (z *Zone) Find(s string) *ZoneData { func (z *Zone) Find(s string) (node *ZoneData, exact bool) {
z.mutex.RLock() z.mutex.RLock()
defer z.mutex.RUnlock() defer z.mutex.RUnlock()
zd, e := z.Radix.Find(toRadixName(s)) n, e := z.Radix.Find(toRadixName(s))
if !e { if n == nil {
return nil return nil, false
} }
return zd.Value.(*ZoneData) node = n.Value.(*ZoneData)
exact = e
return
} }
// Predecessor searches the zone for a name shorter than s. // FindFunc works like Find, but the function f is executed on
func (z *Zone) Predecessor(s string) *ZoneData { // each node which has a non-nil Value during the tree traversal.
// If f returns true, that node is returned.
func (z *Zone) FindFunc(s string, f func(interface{}) bool) (*ZoneData, bool, bool) {
z.mutex.RLock() z.mutex.RLock()
defer z.mutex.RUnlock() defer z.mutex.RUnlock()
zd := z.Radix.Predecessor(toRadixName(s)) zd, e, b := z.Radix.FindFunc(toRadixName(s), f)
if zd == nil { if zd == nil {
return nil return nil, false, false
} }
return zd.Value.(*ZoneData) return zd.Value.(*ZoneData), e, b
} }
// Sign (re)signes the zone z. It adds keys to the zone (if not already there) // Sign (re)signes the zone z with the given keys, it knows about ZSKs and KSKs.
// and signs the keys with the KSKs and the rest of the zone with the ZSKs. // NSECs and RRSIGs are added as needed. The public keys themselves are not added
// NSEC is used for authenticated denial // to the zone. If config is nil DefaultSignatureConfig is used.
// of existence. If config is nil DefaultSignatureConfig is used. // Basic use pattern for signing a zone with the default SignatureConfig:
// TODO(mg): allow interaction with hsm //
func (z *Zone) Sign(keys []*RR_DNSKEY, privkeys []PrivateKey, config *SignatureConfig) error { // // A signle PublicKey/PrivateKey have been read from disk.
// e := z.Sign(map[*dns.RR_DNSKEY]dns.PrivateKey{pubkey.(*dns.RR_DNSKEY): privkey}, nil)
// if e != nil {
// // signing error
// }
// // Admire your signed zone...
// TODO(mg): resigning is not implemented
func (z *Zone) Sign(keys map[*RR_DNSKEY]PrivateKey, config *SignatureConfig) error {
z.Lock()
defer z.Unlock()
if config == nil { if config == nil {
config = DefaultSignatureConfig config = DefaultSignatureConfig
} }
// TODO(mg): concurrently walk the zone and sign the rrsets // Pre-calc the key tag
// TODO(mg): nsec, or next pointer. Need to be a single tree-op keytags := make(map[*RR_DNSKEY]uint16)
for k, _ := range keys {
keytags[k] = k.KeyTag()
}
errChan := make(chan error)
radChan := make(chan *radix.Radix, config.SignerRoutines*10)
// Start the signer goroutines
wg := new(sync.WaitGroup)
wg.Add(config.SignerRoutines * 5)
for i := 0; i < config.SignerRoutines*5; i++ {
go signerRoutine(wg, keys, keytags, config, radChan, errChan)
}
apex, e := z.Radix.Find(toRadixName(z.Origin))
if !e {
return ErrSoa
}
config.minttl = apex.Value.(*ZoneData).RR[TypeSOA][0].(*RR_SOA).Minttl
next := apex.Next()
radChan <- apex
var err error
Sign:
for next.Value.(*ZoneData).Name != z.Origin {
select {
case err = <-errChan:
break Sign
default:
radChan <- next
next = next.Next()
}
}
close(radChan)
close(errChan)
if err != nil {
return err
}
wg.Wait()
return nil return nil
} }
// Sign each ZoneData in place. // signerRoutine is a small helper routine to make the concurrent signing work.
// TODO(mg): assume not signed func signerRoutine(wg *sync.WaitGroup, keys map[*RR_DNSKEY]PrivateKey, keytags map[*RR_DNSKEY]uint16, config *SignatureConfig, in chan *radix.Radix, err chan error) {
func signZoneData(zd *ZoneData, privkeys []PrivateKey, signername string, config *SignatureConfig) { defer wg.Done()
if zd.NonAuth == true { for {
select {
case data, ok := <-in:
if !ok {
return return
} }
//s := new(RR_RRSIG) e := data.Value.(*ZoneData).Sign(data.Next().Value.(*ZoneData), keys, keytags, config)
// signername if e != nil {
err <- e
return
}
}
}
}
// Sign signs a single ZoneData node. The zonedata itself is locked for writing,
// during the execution. It is important that the nodes' next record does not
// changes. The caller must take care that the zone itself is also locked for writing.
// It works just like the Sign method for zones.
func (node *ZoneData) Sign(next *ZoneData, keys map[*RR_DNSKEY]PrivateKey, keytags map[*RR_DNSKEY]uint16, config *SignatureConfig) error {
node.mutex.Lock()
defer node.mutex.Unlock()
nsec := new(RR_NSEC)
nsec.Hdr.Rrtype = TypeNSEC
nsec.Hdr.Ttl = config.minttl // SOA's minimum value
nsec.Hdr.Name = node.Name
nsec.NextDomain = next.Name // Only thing I need from next, actually
nsec.Hdr.Class = ClassINET
if node.NonAuth == true {
for t, _ := range node.RR {
nsec.TypeBitMap = append(nsec.TypeBitMap, t)
}
nsec.TypeBitMap = append(nsec.TypeBitMap, TypeRRSIG) // Add sig too
nsec.TypeBitMap = append(nsec.TypeBitMap, TypeNSEC) // Add me too!
sort.Sort(uint16Slice(nsec.TypeBitMap))
node.RR[TypeNSEC] = []RR{nsec}
for k, p := range keys {
if k.Flags&SEP == SEP {
// only sign keys with SEP keys
continue
}
s := new(RR_RRSIG)
s.SignerName = k.Hdr.Name
s.Hdr.Ttl = k.Hdr.Ttl
s.Algorithm = k.Algorithm
s.KeyTag = keytags[k]
s.Inception = timeToUint32(time.Now().UTC().Add(-config.InceptionOffset))
s.Expiration = timeToUint32(time.Now().UTC().Add(jitterDuration(config.Jitter)).Add(config.Validity))
e := s.Sign(p, []RR{nsec})
if e != nil {
return e
}
node.Signatures[TypeNSEC] = append(node.Signatures[TypeNSEC], s)
// DS
if ds, ok := node.RR[TypeDS]; ok {
s := new(RR_RRSIG)
s.SignerName = k.Hdr.Name
s.Hdr.Ttl = k.Hdr.Ttl
s.Algorithm = k.Algorithm
s.KeyTag = keytags[k]
s.Inception = timeToUint32(time.Now().UTC().Add(-config.InceptionOffset))
s.Expiration = timeToUint32(time.Now().UTC().Add(jitterDuration(config.Jitter)).Add(config.Validity))
e := s.Sign(p, ds)
if e != nil {
return e
}
node.Signatures[TypeDS] = append(node.Signatures[TypeDS], s)
}
}
return nil
}
for k, p := range keys {
for t, rrset := range node.RR {
if k.Flags&SEP == SEP {
if _, ok := rrset[0].(*RR_DNSKEY); !ok {
// only sign keys with SEP keys
continue
}
}
s := new(RR_RRSIG)
s.SignerName = k.Hdr.Name
s.Hdr.Ttl = k.Hdr.Ttl
s.Hdr.Class = ClassINET
s.Algorithm = k.Algorithm
s.KeyTag = keytags[k]
s.Inception = timeToUint32(time.Now().UTC().Add(-config.InceptionOffset))
s.Expiration = timeToUint32(time.Now().UTC().Add(jitterDuration(config.Jitter)).Add(config.Validity))
e := s.Sign(p, rrset)
if e != nil {
return e
}
node.Signatures[t] = append(node.Signatures[t], s)
nsec.TypeBitMap = append(nsec.TypeBitMap, t)
}
nsec.TypeBitMap = append(nsec.TypeBitMap, TypeRRSIG) // Add sig too
nsec.TypeBitMap = append(nsec.TypeBitMap, TypeNSEC) // Add me too!
sort.Sort(uint16Slice(nsec.TypeBitMap))
node.RR[TypeNSEC] = []RR{nsec}
// NSEC
s := new(RR_RRSIG)
s.SignerName = k.Hdr.Name
s.Hdr.Ttl = k.Hdr.Ttl
s.Algorithm = k.Algorithm
s.KeyTag = keytags[k]
s.Inception = timeToUint32(time.Now().UTC().Add(-config.InceptionOffset))
s.Expiration = timeToUint32(time.Now().UTC().Add(jitterDuration(config.Jitter)).Add(config.Validity))
e := s.Sign(p, []RR{nsec})
if e != nil {
return e
}
node.Signatures[TypeNSEC] = append(node.Signatures[TypeNSEC], s)
}
return nil
}
// timeToUint32 translates a time.Time to a 32 bit value which
// can be used as the RRSIG's inception or expiration times.
func timeToUint32(t time.Time) uint32 {
mod := (t.Unix() / year68) - 1
if mod < 0 {
mod = 0
}
return uint32(t.Unix() - (mod * year68))
}
// uint32ToTime translates a uint32 to a time.Time
func uint32ToTime(t uint32) time.Time {
// uint32 to duration and then add it to epoch(0)
mod := (time.Now().Unix() / year68) - 1
if mod < 0 {
mod = 0
}
duration := time.Duration((mod * year68) * int64(t))
return time.Unix(0,0).Add(duration)
}
// jitterTime returns a random +/- jitter
func jitterDuration(d time.Duration) time.Duration {
jitter := rand.Intn(int(d))
if rand.Intn(1) == 1 {
return time.Duration(jitter)
}
return -time.Duration(jitter)
} }

View File

@ -915,14 +915,14 @@ func setRRSIG(h RR_Header, c chan lex, o, f string) (RR, *ParseError) {
} }
<-c // _BLANK <-c // _BLANK
l = <-c l = <-c
if i, err := DateToTime(l.token); err != nil { if i, err := StringToTime(l.token); err != nil {
return nil, &ParseError{f, "bad RRSIG Expiration", l} return nil, &ParseError{f, "bad RRSIG Expiration", l}
} else { } else {
rr.Expiration = i rr.Expiration = i
} }
<-c // _BLANK <-c // _BLANK
l = <-c l = <-c
if i, err := DateToTime(l.token); err != nil { if i, err := StringToTime(l.token); err != nil {
return nil, &ParseError{f, "bad RRSIG Inception", l} return nil, &ParseError{f, "bad RRSIG Inception", l}
} else { } else {
rr.Inception = i rr.Inception = i