plugin/dnssec; insert and sign DS records (#1153)

* plugin/dnssec; insert and sign DS records

Sign a delegation as well and insert DS records.

Fixes #698

* better
This commit is contained in:
Miek Gieben
2017-10-20 09:22:02 +01:00
committed by GitHub
parent 73d702c052
commit 11203e440d
6 changed files with 57 additions and 33 deletions

View File

@@ -15,9 +15,10 @@ import (
// DNSKEY holds a DNSSEC public and private key used for on-the-fly signing. // DNSKEY holds a DNSSEC public and private key used for on-the-fly signing.
type DNSKEY struct { type DNSKEY struct {
K *dns.DNSKEY K *dns.DNSKEY
s crypto.Signer D *dns.DS
keytag uint16 s crypto.Signer
tag uint16
} }
// ParseKeyFile read a DNSSEC keyfile as generated by dnssec-keygen or other // ParseKeyFile read a DNSSEC keyfile as generated by dnssec-keygen or other
@@ -36,18 +37,20 @@ func ParseKeyFile(pubFile, privFile string) (*DNSKEY, error) {
if e != nil { if e != nil {
return nil, e return nil, e
} }
p, e := k.(*dns.DNSKEY).ReadPrivateKey(f, privFile)
dk := k.(*dns.DNSKEY)
p, e := dk.ReadPrivateKey(f, privFile)
if e != nil { if e != nil {
return nil, e return nil, e
} }
if v, ok := p.(*rsa.PrivateKey); ok { if s, ok := p.(*rsa.PrivateKey); ok {
return &DNSKEY{k.(*dns.DNSKEY), v, k.(*dns.DNSKEY).KeyTag()}, nil return &DNSKEY{K: dk, D: dk.ToDS(dns.SHA256), s: s, tag: dk.KeyTag()}, nil
} }
if v, ok := p.(*ecdsa.PrivateKey); ok { if s, ok := p.(*ecdsa.PrivateKey); ok {
return &DNSKEY{k.(*dns.DNSKEY), v, k.(*dns.DNSKEY).KeyTag()}, nil return &DNSKEY{K: dk, D: dk.ToDS(dns.SHA256), s: s, tag: dk.KeyTag()}, nil
} }
return &DNSKEY{k.(*dns.DNSKEY), nil, 0}, errors.New("no known? private key found") return &DNSKEY{K: dk, D: dk.ToDS(dns.SHA256), s: nil, tag: 0}, errors.New("no known private key found")
} }
// getDNSKEY returns the correct DNSKEY to the client. Signatures are added when do is true. // getDNSKEY returns the correct DNSKEY to the client. Signatures are added when do is true.

View File

@@ -35,20 +35,30 @@ func New(zones []string, keys []*DNSKEY, next plugin.Handler, c *cache.Cache) Dn
} }
// Sign signs the message in state. it takes care of negative or nodata responses. It // Sign signs the message in state. it takes care of negative or nodata responses. It
// uses NSEC black lies for authenticated denial of existence. Signatures // uses NSEC black lies for authenticated denial of existence. For delegations it
// creates will be cached for a short while. By default we sign for 8 days, // will insert DS records and sign those.
// Signatures will be cached for a short while. By default we sign for 8 days,
// starting 3 hours ago. // starting 3 hours ago.
func (d Dnssec) Sign(state request.Request, zone string, now time.Time) *dns.Msg { func (d Dnssec) Sign(state request.Request, zone string, now time.Time) *dns.Msg {
req := state.Req req := state.Req
incep, expir := incepExpir(now)
mt, _ := response.Typify(req, time.Now().UTC()) // TODO(miek): need opt record here? mt, _ := response.Typify(req, time.Now().UTC()) // TODO(miek): need opt record here?
if mt == response.Delegation { if mt == response.Delegation {
// TODO(miek): uh, signing DS record?!?! ttl := req.Ns[0].Header().Ttl
ds := []dns.RR{}
for i := range d.keys {
ds = append(ds, d.keys[i].D)
}
if sigs, err := d.sign(ds, zone, ttl, incep, expir); err == nil {
req.Ns = append(req.Ns, ds...)
req.Ns = append(req.Ns, sigs...)
}
return req return req
} }
incep, expir := incepExpir(now)
if mt == response.NameError || mt == response.NoData { if mt == response.NameError || mt == response.NoData {
if req.Ns[0].Header().Rrtype != dns.TypeSOA || len(req.Ns) > 1 { if req.Ns[0].Header().Rrtype != dns.TypeSOA || len(req.Ns) > 1 {
return req return req

View File

@@ -21,10 +21,10 @@ func TestZoneSigning(t *testing.T) {
m = d.Sign(state, "miek.nl.", time.Now().UTC()) m = d.Sign(state, "miek.nl.", time.Now().UTC())
if !section(m.Answer, 1) { if !section(m.Answer, 1) {
t.Errorf("answer section should have 1 sig") t.Errorf("Answer section should have 1 RRSIG")
} }
if !section(m.Ns, 1) { if !section(m.Ns, 1) {
t.Errorf("authority section should have 1 sig") t.Errorf("Authority section should have 1 RRSIG")
} }
} }
@@ -40,7 +40,7 @@ func TestZoneSigningDouble(t *testing.T) {
key1, err := ParseKeyFile(fPub1, fPriv1) key1, err := ParseKeyFile(fPub1, fPriv1)
if err != nil { if err != nil {
t.Fatalf("failed to parse key: %v\n", err) t.Fatalf("Failed to parse key: %v\n", err)
} }
d.keys = append(d.keys, key1) d.keys = append(d.keys, key1)
@@ -48,10 +48,10 @@ func TestZoneSigningDouble(t *testing.T) {
state := request.Request{Req: m} state := request.Request{Req: m}
m = d.Sign(state, "miek.nl.", time.Now().UTC()) m = d.Sign(state, "miek.nl.", time.Now().UTC())
if !section(m.Answer, 2) { if !section(m.Answer, 2) {
t.Errorf("answer section should have 1 sig") t.Errorf("Answer section should have 1 RRSIG")
} }
if !section(m.Ns, 2) { if !section(m.Ns, 2) {
t.Errorf("authority section should have 1 sig") t.Errorf("Authority section should have 1 RRSIG")
} }
} }
@@ -64,7 +64,7 @@ func TestSigningDifferentZone(t *testing.T) {
key, err := ParseKeyFile(fPub, fPriv) key, err := ParseKeyFile(fPub, fPriv)
if err != nil { if err != nil {
t.Fatalf("failed to parse key: %v\n", err) t.Fatalf("Failed to parse key: %v\n", err)
} }
m := testMsgEx() m := testMsgEx()
@@ -73,11 +73,11 @@ func TestSigningDifferentZone(t *testing.T) {
d := New([]string{"example.org."}, []*DNSKEY{key}, nil, c) d := New([]string{"example.org."}, []*DNSKEY{key}, nil, c)
m = d.Sign(state, "example.org.", time.Now().UTC()) m = d.Sign(state, "example.org.", time.Now().UTC())
if !section(m.Answer, 1) { if !section(m.Answer, 1) {
t.Errorf("answer section should have 1 sig") t.Errorf("Answer section should have 1 RRSIG")
t.Logf("%+v\n", m) t.Logf("%+v\n", m)
} }
if !section(m.Ns, 1) { if !section(m.Ns, 1) {
t.Errorf("authority section should have 1 sig") t.Errorf("Authority section should have 1 RRSIG")
t.Logf("%+v\n", m) t.Logf("%+v\n", m)
} }
} }
@@ -91,7 +91,7 @@ func TestSigningCname(t *testing.T) {
state := request.Request{Req: m} state := request.Request{Req: m}
m = d.Sign(state, "miek.nl.", time.Now().UTC()) m = d.Sign(state, "miek.nl.", time.Now().UTC())
if !section(m.Answer, 1) { if !section(m.Answer, 1) {
t.Errorf("answer section should have 1 sig") t.Errorf("Answer section should have 1 RRSIG")
} }
} }
@@ -103,12 +103,24 @@ func TestZoneSigningDelegation(t *testing.T) {
m := testDelegationMsg() m := testDelegationMsg()
state := request.Request{Req: m} state := request.Request{Req: m}
m = d.Sign(state, "miek.nl.", time.Now().UTC()) m = d.Sign(state, "miek.nl.", time.Now().UTC())
if !section(m.Ns, 0) { if !section(m.Ns, 1) {
t.Errorf("authority section should have 0 sig") t.Errorf("Authority section should have 1 RRSIG")
t.Logf("%v\n", m) t.Logf("%v\n", m)
} }
ds := 0
for i := range m.Ns {
if _, ok := m.Ns[i].(*dns.DS); ok {
ds++
}
}
if ds != 1 {
t.Errorf("Authority section should have 1 DS")
t.Logf("%v\n", m)
}
if !section(m.Extra, 0) { if !section(m.Extra, 0) {
t.Errorf("answer section should have 0 sig") t.Errorf("Answer section should have 0 RRSIGs")
t.Logf("%v\n", m) t.Logf("%v\n", m)
} }
} }
@@ -123,7 +135,7 @@ func TestSigningDname(t *testing.T) {
// We sign *everything* we see, also the synthesized CNAME. // We sign *everything* we see, also the synthesized CNAME.
m = d.Sign(state, "miek.nl.", time.Now().UTC()) m = d.Sign(state, "miek.nl.", time.Now().UTC())
if !section(m.Answer, 3) { if !section(m.Answer, 3) {
t.Errorf("answer section should have 3 sig") t.Errorf("Answer section should have 3 RRSIGs")
} }
} }
@@ -137,7 +149,7 @@ func TestSigningEmpty(t *testing.T) {
state := request.Request{Req: m} state := request.Request{Req: m}
m = d.Sign(state, "miek.nl.", time.Now().UTC()) m = d.Sign(state, "miek.nl.", time.Now().UTC())
if !section(m.Ns, 2) { if !section(m.Ns, 2) {
t.Errorf("authority section should have 2 sig") t.Errorf("Authority section should have 2 RRSIGs")
} }
} }

View File

@@ -22,8 +22,7 @@ func (d *ResponseWriter) WriteMsg(res *dns.Msg) error {
// which zone it should be. // which zone it should be.
state := request.Request{W: d.ResponseWriter, Req: res} state := request.Request{W: d.ResponseWriter, Req: res}
qname := state.Name() zone := plugin.Zones(d.d.zones).Matches(state.Name())
zone := plugin.Zones(d.d.zones).Matches(qname)
if zone == "" { if zone == "" {
return d.ResponseWriter.WriteMsg(res) return d.ResponseWriter.WriteMsg(res)
} }

View File

@@ -8,7 +8,7 @@ func (k *DNSKEY) newRRSIG(signerName string, ttl, incep, expir uint32) *dns.RRSI
sig.Hdr.Rrtype = dns.TypeRRSIG sig.Hdr.Rrtype = dns.TypeRRSIG
sig.Algorithm = k.K.Algorithm sig.Algorithm = k.K.Algorithm
sig.KeyTag = k.keytag sig.KeyTag = k.tag
sig.SignerName = signerName sig.SignerName = signerName
sig.Hdr.Ttl = ttl sig.Hdr.Ttl = ttl
sig.OrigTtl = origTTL sig.OrigTtl = origTTL

View File

@@ -77,7 +77,7 @@ func dnssecParse(c *caddy.Controller) ([]string, []*DNSKEY, int, error) {
zones[i] = plugin.Host(zones[i]).Normalize() zones[i] = plugin.Host(zones[i]).Normalize()
} }
// Check if each keys owner name can actually sign the zones we want them to sign // Check if each keys owner name can actually sign the zones we want them to sign.
for _, k := range keys { for _, k := range keys {
kname := plugin.Name(k.K.Header().Name) kname := plugin.Name(k.K.Header().Name)
ok := false ok := false
@@ -88,7 +88,7 @@ func dnssecParse(c *caddy.Controller) ([]string, []*DNSKEY, int, error) {
} }
} }
if !ok { if !ok {
return zones, keys, capacity, fmt.Errorf("key %s (keyid: %d) can not sign any of the zones", string(kname), k.keytag) return zones, keys, capacity, fmt.Errorf("key %s (keyid: %d) can not sign any of the zones", string(kname), k.tag)
} }
} }