2016-04-26 17:57:11 +01:00
|
|
|
package dnssec
|
|
|
|
|
|
|
|
|
|
import (
|
2026-04-04 20:40:29 +03:00
|
|
|
"crypto"
|
|
|
|
|
"errors"
|
|
|
|
|
"io"
|
2016-04-26 17:57:11 +01:00
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
2017-09-14 09:36:06 +01:00
|
|
|
"github.com/coredns/coredns/plugin/pkg/cache"
|
|
|
|
|
"github.com/coredns/coredns/plugin/test"
|
2017-02-21 22:51:47 -08:00
|
|
|
"github.com/coredns/coredns/request"
|
2016-04-26 17:57:11 +01:00
|
|
|
|
|
|
|
|
"github.com/miekg/dns"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestZoneSigning(t *testing.T) {
|
|
|
|
|
d, rm1, rm2 := newDnssec(t, []string{"miek.nl."})
|
|
|
|
|
defer rm1()
|
|
|
|
|
defer rm2()
|
|
|
|
|
|
|
|
|
|
m := testMsg()
|
2018-01-03 11:11:56 +00:00
|
|
|
state := request.Request{Req: m, Zone: "miek.nl."}
|
2016-04-26 17:57:11 +01:00
|
|
|
|
2018-04-27 19:37:31 +01:00
|
|
|
m = d.Sign(state, time.Now().UTC(), server)
|
2016-04-26 17:57:11 +01:00
|
|
|
if !section(m.Answer, 1) {
|
2017-10-20 09:22:02 +01:00
|
|
|
t.Errorf("Answer section should have 1 RRSIG")
|
2016-04-26 17:57:11 +01:00
|
|
|
}
|
|
|
|
|
if !section(m.Ns, 1) {
|
2017-10-20 09:22:02 +01:00
|
|
|
t.Errorf("Authority section should have 1 RRSIG")
|
2016-04-26 17:57:11 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestZoneSigningDouble(t *testing.T) {
|
|
|
|
|
d, rm1, rm2 := newDnssec(t, []string{"miek.nl."})
|
|
|
|
|
defer rm1()
|
|
|
|
|
defer rm2()
|
|
|
|
|
|
2016-10-02 15:58:01 +01:00
|
|
|
fPriv1, rmPriv1, _ := test.TempFile(".", privKey1)
|
|
|
|
|
fPub1, rmPub1, _ := test.TempFile(".", pubKey1)
|
2016-04-26 17:57:11 +01:00
|
|
|
defer rmPriv1()
|
|
|
|
|
defer rmPub1()
|
|
|
|
|
|
|
|
|
|
key1, err := ParseKeyFile(fPub1, fPriv1)
|
|
|
|
|
if err != nil {
|
2017-10-20 09:22:02 +01:00
|
|
|
t.Fatalf("Failed to parse key: %v\n", err)
|
2016-04-26 17:57:11 +01:00
|
|
|
}
|
|
|
|
|
d.keys = append(d.keys, key1)
|
|
|
|
|
|
|
|
|
|
m := testMsg()
|
2018-01-03 11:11:56 +00:00
|
|
|
state := request.Request{Req: m, Zone: "miek.nl."}
|
2018-04-27 19:37:31 +01:00
|
|
|
m = d.Sign(state, time.Now().UTC(), server)
|
2016-04-26 17:57:11 +01:00
|
|
|
if !section(m.Answer, 2) {
|
2026-03-27 05:33:55 +02:00
|
|
|
t.Errorf("Answer section should have 2 RRSIGs")
|
2016-04-26 17:57:11 +01:00
|
|
|
}
|
|
|
|
|
if !section(m.Ns, 2) {
|
2026-03-27 05:33:55 +02:00
|
|
|
t.Errorf("Authority section should have 2 RRSIGs")
|
2016-04-26 17:57:11 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestSigningDifferentZone tests if a key for miek.nl and be used for example.org.
|
|
|
|
|
func TestSigningDifferentZone(t *testing.T) {
|
2016-10-02 15:58:01 +01:00
|
|
|
fPriv, rmPriv, _ := test.TempFile(".", privKey)
|
|
|
|
|
fPub, rmPub, _ := test.TempFile(".", pubKey)
|
2016-04-26 17:57:11 +01:00
|
|
|
defer rmPriv()
|
|
|
|
|
defer rmPub()
|
|
|
|
|
|
|
|
|
|
key, err := ParseKeyFile(fPub, fPriv)
|
|
|
|
|
if err != nil {
|
2017-10-20 09:22:02 +01:00
|
|
|
t.Fatalf("Failed to parse key: %v\n", err)
|
2016-04-26 17:57:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m := testMsgEx()
|
2018-01-03 11:11:56 +00:00
|
|
|
state := request.Request{Req: m, Zone: "example.org."}
|
2026-02-04 02:23:53 +01:00
|
|
|
c := cache.New[[]dns.RR](defaultCap)
|
2018-10-20 17:35:59 +02:00
|
|
|
d := New([]string{"example.org."}, []*DNSKEY{key}, false, nil, c)
|
2018-04-27 19:37:31 +01:00
|
|
|
m = d.Sign(state, time.Now().UTC(), server)
|
2016-04-26 17:57:11 +01:00
|
|
|
if !section(m.Answer, 1) {
|
2017-10-20 09:22:02 +01:00
|
|
|
t.Errorf("Answer section should have 1 RRSIG")
|
2016-10-08 15:22:31 +01:00
|
|
|
t.Logf("%+v\n", m)
|
2016-04-26 17:57:11 +01:00
|
|
|
}
|
|
|
|
|
if !section(m.Ns, 1) {
|
2017-10-20 09:22:02 +01:00
|
|
|
t.Errorf("Authority section should have 1 RRSIG")
|
2016-10-08 15:22:31 +01:00
|
|
|
t.Logf("%+v\n", m)
|
2016-04-26 17:57:11 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSigningCname(t *testing.T) {
|
|
|
|
|
d, rm1, rm2 := newDnssec(t, []string{"miek.nl."})
|
|
|
|
|
defer rm1()
|
|
|
|
|
defer rm2()
|
|
|
|
|
|
|
|
|
|
m := testMsgCname()
|
2018-01-03 11:11:56 +00:00
|
|
|
state := request.Request{Req: m, Zone: "miek.nl."}
|
2018-04-27 19:37:31 +01:00
|
|
|
m = d.Sign(state, time.Now().UTC(), server)
|
2016-04-26 17:57:11 +01:00
|
|
|
if !section(m.Answer, 1) {
|
2017-10-20 09:22:02 +01:00
|
|
|
t.Errorf("Answer section should have 1 RRSIG")
|
2016-04-26 17:57:11 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-02 17:18:58 +01:00
|
|
|
func TestSigningDname(t *testing.T) {
|
|
|
|
|
d, rm1, rm2 := newDnssec(t, []string{"miek.nl."})
|
|
|
|
|
defer rm1()
|
|
|
|
|
defer rm2()
|
|
|
|
|
|
|
|
|
|
m := testMsgDname()
|
2018-01-03 11:11:56 +00:00
|
|
|
state := request.Request{Req: m, Zone: "miek.nl."}
|
2017-06-02 17:18:58 +01:00
|
|
|
// We sign *everything* we see, also the synthesized CNAME.
|
2018-04-27 19:37:31 +01:00
|
|
|
m = d.Sign(state, time.Now().UTC(), server)
|
2017-06-02 17:18:58 +01:00
|
|
|
if !section(m.Answer, 3) {
|
2017-10-20 09:22:02 +01:00
|
|
|
t.Errorf("Answer section should have 3 RRSIGs")
|
2017-06-02 17:18:58 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-08 13:28:35 +02:00
|
|
|
func TestSigningEmpty(t *testing.T) {
|
|
|
|
|
d, rm1, rm2 := newDnssec(t, []string{"miek.nl."})
|
|
|
|
|
defer rm1()
|
|
|
|
|
defer rm2()
|
|
|
|
|
|
|
|
|
|
m := testEmptyMsg()
|
|
|
|
|
m.SetQuestion("a.miek.nl.", dns.TypeA)
|
2018-01-03 11:11:56 +00:00
|
|
|
state := request.Request{Req: m, Zone: "miek.nl."}
|
2018-04-27 19:37:31 +01:00
|
|
|
m = d.Sign(state, time.Now().UTC(), server)
|
2017-10-08 13:28:35 +02:00
|
|
|
if !section(m.Ns, 2) {
|
2017-10-20 09:22:02 +01:00
|
|
|
t.Errorf("Authority section should have 2 RRSIGs")
|
2017-10-08 13:28:35 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-22 22:32:01 +02:00
|
|
|
func TestDelegationSigned(t *testing.T) {
|
|
|
|
|
d, rm1, rm2 := newDnssec(t, []string{"miek.nl."})
|
|
|
|
|
defer rm1()
|
|
|
|
|
defer rm2()
|
|
|
|
|
|
|
|
|
|
m := testMsgDelegationSigned()
|
|
|
|
|
m.SetQuestion("sub.miek.nl.", dns.TypeNS)
|
|
|
|
|
state := request.Request{Req: m, Zone: "miek.nl."}
|
|
|
|
|
m = d.Sign(state, time.Now().UTC(), server)
|
|
|
|
|
if !section(m.Ns, 1) {
|
|
|
|
|
t.Errorf("Authority section should have 1 RRSIGs")
|
|
|
|
|
}
|
|
|
|
|
if !section(m.Extra, 0) {
|
|
|
|
|
t.Error("Extra section should not have RRSIGs")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestDelegationUnSigned(t *testing.T) {
|
|
|
|
|
d, rm1, rm2 := newDnssec(t, []string{"miek.nl."})
|
|
|
|
|
defer rm1()
|
|
|
|
|
defer rm2()
|
|
|
|
|
|
|
|
|
|
m := testMsgDelegationUnSigned()
|
|
|
|
|
m.SetQuestion("sub.miek.nl.", dns.TypeNS)
|
|
|
|
|
state := request.Request{Req: m, Zone: "miek.nl."}
|
|
|
|
|
m = d.Sign(state, time.Now().UTC(), server)
|
|
|
|
|
if !section(m.Ns, 1) {
|
|
|
|
|
t.Errorf("Authority section should have 1 RRSIG")
|
|
|
|
|
}
|
|
|
|
|
if !section(m.Extra, 0) {
|
|
|
|
|
t.Error("Extra section should not have RRSIG")
|
|
|
|
|
}
|
|
|
|
|
var nsec *dns.NSEC
|
|
|
|
|
var rrsig *dns.RRSIG
|
|
|
|
|
for _, r := range m.Ns {
|
|
|
|
|
if r.Header().Rrtype == dns.TypeNSEC {
|
|
|
|
|
nsec = r.(*dns.NSEC)
|
|
|
|
|
}
|
|
|
|
|
if r.Header().Rrtype == dns.TypeRRSIG {
|
|
|
|
|
rrsig = r.(*dns.RRSIG)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if nsec == nil {
|
|
|
|
|
t.Error("Authority section should hold a NSEC record")
|
|
|
|
|
}
|
|
|
|
|
if rrsig.TypeCovered != dns.TypeNSEC {
|
|
|
|
|
t.Errorf("RRSIG should cover type %s, got %s",
|
|
|
|
|
dns.TypeToString[dns.TypeNSEC], dns.TypeToString[rrsig.TypeCovered])
|
|
|
|
|
}
|
|
|
|
|
if !correctNsecForDS(nsec) {
|
|
|
|
|
t.Error("NSEC as invalid TypeBitMap for a DS")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-26 17:57:11 +01:00
|
|
|
func section(rss []dns.RR, nrSigs int) bool {
|
|
|
|
|
i := 0
|
|
|
|
|
for _, r := range rss {
|
|
|
|
|
if r.Header().Rrtype == dns.TypeRRSIG {
|
|
|
|
|
i++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nrSigs == i
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testMsg() *dns.Msg {
|
|
|
|
|
// don't care about the message header
|
|
|
|
|
return &dns.Msg{
|
|
|
|
|
Answer: []dns.RR{test.MX("miek.nl. 1703 IN MX 1 aspmx.l.google.com.")},
|
2023-04-22 22:32:01 +02:00
|
|
|
Ns: []dns.RR{test.NS("miek.nl. 1703 IN NS omval.tednet.nl.")},
|
2016-04-26 17:57:11 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
func testMsgEx() *dns.Msg {
|
|
|
|
|
return &dns.Msg{
|
|
|
|
|
Answer: []dns.RR{test.MX("example.org. 1703 IN MX 1 aspmx.l.google.com.")},
|
2023-04-22 22:32:01 +02:00
|
|
|
Ns: []dns.RR{test.NS("example.org. 1703 IN NS omval.tednet.nl.")},
|
2016-04-26 17:57:11 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testMsgCname() *dns.Msg {
|
|
|
|
|
return &dns.Msg{
|
|
|
|
|
Answer: []dns.RR{test.CNAME("www.miek.nl. 1800 IN CNAME a.miek.nl.")},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-02 17:18:58 +01:00
|
|
|
func testMsgDname() *dns.Msg {
|
|
|
|
|
return &dns.Msg{
|
|
|
|
|
Answer: []dns.RR{
|
|
|
|
|
test.CNAME("a.dname.miek.nl. 1800 IN CNAME a.test.miek.nl."),
|
|
|
|
|
test.A("a.test.miek.nl. 1800 IN A 139.162.196.78"),
|
|
|
|
|
test.DNAME("dname.miek.nl. 1800 IN DNAME test.miek.nl."),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-22 22:32:01 +02:00
|
|
|
func testMsgDelegationSigned() *dns.Msg {
|
|
|
|
|
return &dns.Msg{
|
|
|
|
|
Ns: []dns.RR{
|
|
|
|
|
test.NS("sub.miek.nl. 1800 IN NS ns1.sub.miek.nl."),
|
|
|
|
|
test.DS("sub." + dsKey),
|
|
|
|
|
},
|
|
|
|
|
Extra: []dns.RR{
|
|
|
|
|
test.A("ns1.sub.miek.nl. 1800 IN A 192.0.2.1"),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testMsgDelegationUnSigned() *dns.Msg {
|
|
|
|
|
return &dns.Msg{
|
|
|
|
|
Ns: []dns.RR{
|
|
|
|
|
test.NS("sub.miek.nl. 1800 IN NS ns1.sub.miek.nl."),
|
|
|
|
|
},
|
|
|
|
|
Extra: []dns.RR{
|
|
|
|
|
test.A("ns1.sub.miek.nl. 1800 IN A 192.0.2.1"),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-08 13:28:35 +02:00
|
|
|
func testEmptyMsg() *dns.Msg {
|
|
|
|
|
// don't care about the message header
|
|
|
|
|
return &dns.Msg{
|
|
|
|
|
Ns: []dns.RR{test.SOA("miek.nl. 1800 IN SOA ns.miek.nl. dnsmaster.miek.nl. 2017100301 200 100 604800 3600")},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 20:40:29 +03:00
|
|
|
// errSigner is a crypto.Signer that always returns an error. Used to simulate
|
|
|
|
|
// signing failures in tests without panicking on nil keys.
|
|
|
|
|
type errSigner struct {
|
|
|
|
|
pub crypto.PublicKey
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *errSigner) Public() crypto.PublicKey { return e.pub }
|
|
|
|
|
func (e *errSigner) Sign(_ io.Reader, _ []byte, _ crypto.SignerOpts) ([]byte, error) {
|
|
|
|
|
return nil, errors.New("simulated signing failure")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestSignReturnsNilOnError verifies that when a signing operation fails mid-way
|
|
|
|
|
// through multiple keys, sign() returns (nil, error) rather than (partial_sigs, error).
|
|
|
|
|
// Before the fix, the inflight function returned the partially-accumulated sigs slice
|
|
|
|
|
// alongside the error. While callers checked err before using the sigs, returning
|
|
|
|
|
// partial results from an error path is incorrect and could cause a nil-assertion
|
|
|
|
|
// panic if the error guard were ever removed.
|
|
|
|
|
func TestSignReturnsNilOnError(t *testing.T) {
|
|
|
|
|
// Get a valid key that will sign successfully.
|
|
|
|
|
k1, rm1, rm2 := newKey(t)
|
|
|
|
|
defer rm1()
|
|
|
|
|
defer rm2()
|
|
|
|
|
|
|
|
|
|
// Create a second key that will fail during signing.
|
|
|
|
|
brokenKey := &DNSKEY{
|
|
|
|
|
K: dns.Copy(k1.K).(*dns.DNSKEY),
|
|
|
|
|
s: &errSigner{pub: k1.s.Public()},
|
|
|
|
|
tag: k1.tag + 1,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c := cache.New[[]dns.RR](defaultCap)
|
|
|
|
|
// k1 succeeds, brokenKey fails — sign() should return nil, not k1's partial sig.
|
|
|
|
|
d := New([]string{"miek.nl."}, []*DNSKEY{k1, brokenKey}, false, nil, c)
|
|
|
|
|
|
|
|
|
|
m := testMsg()
|
|
|
|
|
incep, expir := incepExpir(time.Now().UTC())
|
|
|
|
|
sigs, err := d.sign(m.Answer, "miek.nl.", 1703, incep, expir, server)
|
|
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("Expected error from broken key, got nil")
|
|
|
|
|
}
|
|
|
|
|
if sigs != nil {
|
|
|
|
|
t.Errorf("Expected nil sigs on signing error, got %d sig(s)", len(sigs))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-26 17:57:11 +01:00
|
|
|
func newDnssec(t *testing.T, zones []string) (Dnssec, func(), func()) {
|
2025-06-05 00:36:04 +03:00
|
|
|
t.Helper()
|
2016-04-26 17:57:11 +01:00
|
|
|
k, rm1, rm2 := newKey(t)
|
2026-02-04 02:23:53 +01:00
|
|
|
c := cache.New[[]dns.RR](defaultCap)
|
2018-10-20 17:35:59 +02:00
|
|
|
d := New(zones, []*DNSKEY{k}, false, nil, c)
|
2016-04-26 17:57:11 +01:00
|
|
|
return d, rm1, rm2
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newKey(t *testing.T) (*DNSKEY, func(), func()) {
|
2025-06-05 00:36:04 +03:00
|
|
|
t.Helper()
|
2016-10-02 15:58:01 +01:00
|
|
|
fPriv, rmPriv, _ := test.TempFile(".", privKey)
|
|
|
|
|
fPub, rmPub, _ := test.TempFile(".", pubKey)
|
2016-04-26 17:57:11 +01:00
|
|
|
|
|
|
|
|
key, err := ParseKeyFile(fPub, fPriv)
|
|
|
|
|
if err != nil {
|
2018-05-07 22:47:25 +01:00
|
|
|
t.Fatalf("Failed to parse key: %v\n", err)
|
2016-04-26 17:57:11 +01:00
|
|
|
}
|
|
|
|
|
return key, rmPriv, rmPub
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
pubKey = `miek.nl. IN DNSKEY 257 3 13 0J8u0XJ9GNGFEBXuAmLu04taHG4BXPP3gwhetiOUMnGA+x09nqzgF5IY OyjWB7N3rXqQbnOSILhH1hnuyh7mmA==`
|
|
|
|
|
privKey = `Private-key-format: v1.3
|
|
|
|
|
Algorithm: 13 (ECDSAP256SHA256)
|
|
|
|
|
PrivateKey: /4BZk8AFvyW5hL3cOLSVxIp1RTqHSAEloWUxj86p3gs=
|
|
|
|
|
Created: 20160423195532
|
|
|
|
|
Publish: 20160423195532
|
|
|
|
|
Activate: 20160423195532
|
|
|
|
|
`
|
2023-04-22 22:32:01 +02:00
|
|
|
dsKey = `miek.nl. IN DS 18512 13 2 D4E806322598BC97A003EF1ACDFF352EEFF7B42DBB0D41B8224714C36AEF08D9`
|
2016-04-26 17:57:11 +01:00
|
|
|
pubKey1 = `example.org. IN DNSKEY 257 3 13 tVRWNSGpHZbCi7Pr7OmbADVUO3MxJ0Lb8Lk3o/HBHqCxf5K/J50lFqRa 98lkdAIiFOVRy8LyMvjwmxZKwB5MNw==`
|
|
|
|
|
privKey1 = `Private-key-format: v1.3
|
|
|
|
|
Algorithm: 13 (ECDSAP256SHA256)
|
|
|
|
|
PrivateKey: i8j4OfDGT8CQt24SDwLz2hg9yx4qKOEOh1LvbAuSp1c=
|
|
|
|
|
Created: 20160423211746
|
|
|
|
|
Publish: 20160423211746
|
|
|
|
|
Activate: 20160423211746
|
|
|
|
|
`
|
|
|
|
|
)
|