From f41bf79426ba3bb0d13fe6548d4e42c154e0e377 Mon Sep 17 00:00:00 2001 From: Trevor Ackerman Date: Thu, 20 Oct 2016 11:43:03 -0600 Subject: [PATCH] Added some basic DNSSEC tests --- test/router/dnssec/Readme.md | 23 ++++ test/router/dnssec/dnssec.go | 139 ++++++++++++++++++++++++ test/router/dnssec/dnssec_suite_test.go | 36 ++++++ test/router/dnssec/dnssec_test.go | 109 +++++++++++++++++++ 4 files changed, 307 insertions(+) create mode 100644 test/router/dnssec/Readme.md create mode 100644 test/router/dnssec/dnssec.go create mode 100644 test/router/dnssec/dnssec_suite_test.go create mode 100644 test/router/dnssec/dnssec_test.go diff --git a/test/router/dnssec/Readme.md b/test/router/dnssec/Readme.md new file mode 100644 index 0000000000..139824bb0a --- /dev/null +++ b/test/router/dnssec/Readme.md @@ -0,0 +1,23 @@ +DNSSEC Tests +============ + +Running the test + +`ginkgo -- -ns=router-01.thecdn.example.com:53 -ds=ds-01.thecdn.example.com.` + +Sample Output +``` +Running Suite: Dnssec Suite +=========================== +Random Seed: 1476984556 +Will run 4 of 4 specs + +2016/10/20 11:29:17 Nameserver router-01.thecdn.example.com:53 +2016/10/20 11:29:17 DeliveryService ds-01.thecdn.example.com. +•••• +Ran 4 of 4 Specs in 0.110 seconds +SUCCESS! -- 4 Passed | 0 Failed | 0 Pending | 0 Skipped PASS + +Ginkgo ran 1 suite in 825.345359ms +Test Suite Passed +``` \ No newline at end of file diff --git a/test/router/dnssec/dnssec.go b/test/router/dnssec/dnssec.go new file mode 100644 index 0000000000..c57ffda691 --- /dev/null +++ b/test/router/dnssec/dnssec.go @@ -0,0 +1,139 @@ +package dnssec + +import ( +. "github.com/miekg/dns" +. "github.com/onsi/gomega" +"log" +) + +type DnssecClient struct { + *Client +} + +type SignedRRSet struct { + RRSIG RRSIG + RRSet []RR +} + +type SignedKeys struct { + SignedZsks []SignedRRSet + SignedKsks []SignedRRSet +} + +func MakeLabelHierarchy(label string) []string { + labels := []string{} + done := false + i := 0 + for !done { + label = label[i:] + labels = append([]string{label}, labels...) + i, done = NextLabel(label,i) + } + + return append([]string{"."}, labels...) +} + +func (d *DnssecClient) GetRecords(nameserver string, name string, t uint16) (*Msg) { + m := new(Msg) + m.Id = Id() + m.RecursionDesired = true + m.SetEdns0(4096, true) + m.Question = []Question{{name, t, ClassINET}} + r, _, err := d.Exchange(m, nameserver) + + Expect(err).Should(BeNil()) + Expect(len(r.Answer)).ToNot(Equal(0), "Received no answers from %v for query of records type %d for zone %v", nameserver, t, name) + return r +} + +func sigCovers(s RRSIG, rr RR) bool { + return s.TypeCovered == rr.Header().Rrtype && + s.Hdr.Class == rr.Header().Class && + s.Hdr.Ttl == rr.Header().Ttl +} + +func (d *DnssecClient) GetSignedRRSets(nameserver string, name string, t uint16) ([]SignedRRSet) { + records := []RR{} + rrsigs := []RR{} + + answers := d.GetRecords(nameserver, name, t).Answer + for _, ans := range answers { + if ans.Header().Rrtype == TypeRRSIG { + rrsigs = append(rrsigs, ans) + } else { + records = append(records, ans) + } + } + + rrsets := []SignedRRSet{} + for _, sig := range rrsigs { + switch s := sig.(type) { + case *RRSIG: + rs := RRSIG{ + Hdr: s.Hdr, + Signature: s.Signature, + Algorithm: s.Algorithm, + Expiration: s.Expiration, + Inception: s.Inception, + KeyTag: s.KeyTag, + Labels: s.Labels, + OrigTtl: s.OrigTtl, + SignerName: s.SignerName, + TypeCovered: s.TypeCovered, + } + + signedSet := SignedRRSet{ + RRSIG: rs, + } + + for _, rr := range records { + if sigCovers(*s,rr) { + signedSet.RRSet = append(signedSet.RRSet, rr) + } else { + log.Println("rrsig does not cover record") + log.Println(s.Header(),s.TypeCovered) + log.Println(rr.Header(),rr.Header().Rrtype) + } + } + + rrsets = append(rrsets, signedSet) + } + + } + + return rrsets +} + +func (d *DnssecClient) DelegationSignerData(nameserver string, name string) ([]SignedRRSet) { + return d.GetSignedRRSets(nameserver, name, TypeDS) +} + +func (d *DnssecClient) SigningData(nameserver string, name string) SignedKeys { + var signedKeys = SignedKeys{ + SignedZsks: []SignedRRSet{}, + SignedKsks: []SignedRRSet{}, + } + + signedRrsets := d.GetSignedRRSets(nameserver, name, TypeDNSKEY) + + for _, signedRRset := range signedRrsets { + if len(signedRRset.RRSet) < 1 { + log.Println("****** no rrset") + continue; + } + + for _, rr := range signedRRset.RRSet { + switch k := rr.(type) { + case *DNSKEY: + if k.Flags & 1 == 1 { + signedKeys.SignedKsks = append(signedKeys.SignedKsks, signedRRset) + } else { + signedKeys.SignedZsks = append(signedKeys.SignedZsks, signedRRset) + } + } + } + } + + return signedKeys +} + diff --git a/test/router/dnssec/dnssec_suite_test.go b/test/router/dnssec/dnssec_suite_test.go new file mode 100644 index 0000000000..50a29b4bd8 --- /dev/null +++ b/test/router/dnssec/dnssec_suite_test.go @@ -0,0 +1,36 @@ +package dnssec_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" + "github.com/apache/incubator-trafficcontrol/test/router/dnssec" + "github.com/miekg/dns" + "flag" + "log" +) + +var d *dnssec.DnssecClient +var nameserver string +var deliveryService string + +func init() { + flag.StringVar(&nameserver,"ns","changeit","ns is used to direct dns queries to a traffic router") + flag.StringVar(&deliveryService,"ds","changeit","ds is used to target some dns DS and DNS queries made by traffic router") +} + +var _ = BeforeSuite(func() { + d = &dnssec.DnssecClient{new(dns.Client)} + d.Net = "udp" + + Expect(nameserver).ToNot(Equal("changeit"), "Pass in a ns flag with the hostname of the traffic router") + Expect(deliveryService).ToNot(Equal("changeit"), "Pass in a ds flag with the dns label for a DNS delivery service") + log.Println("Nameserver",nameserver) + log.Println("DeliveryService", deliveryService) +}) + +func TestDnssec(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Dnssec Suite") +} diff --git a/test/router/dnssec/dnssec_test.go b/test/router/dnssec/dnssec_test.go new file mode 100644 index 0000000000..f933544c50 --- /dev/null +++ b/test/router/dnssec/dnssec_test.go @@ -0,0 +1,109 @@ +package dnssec_test + +import ( + "github.com/apache/incubator-trafficcontrol/test/router/dnssec" + "github.com/miekg/dns" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Dnssec", func() { + Context("The Interwebs", func() { + It("Makes Label Hierarchy", func() { + Expect(dnssec.MakeLabelHierarchy("example.com.")).To(Equal([]string{".", "com.", "example.com."})) + }) + + It("Uses Parent Zone Key to validate DS", func() { + signedDSSets := d.DelegationSignerData(nameserver, deliveryService) + + Expect(len(signedDSSets)).ToNot(Equal(0)) + Expect(len(signedDSSets[0].RRSet)).ToNot(Equal(0)) + + verifiedCount := 0 + for _, signedDSSet := range signedDSSets { + + signedKeys := d.SigningData(nameserver, signedDSSet.RRSIG.SignerName) + + Expect(len(signedKeys.SignedKsks)).ToNot(Equal(0)) + Expect(len(signedKeys.SignedZsks)).ToNot(Equal(0)) + + for _, sk := range signedKeys.SignedZsks { + for _, k := range sk.RRSet { + switch kk := k.(type) { + case *dns.DNSKEY: + if kk.KeyTag() == signedDSSet.RRSIG.KeyTag { + Expect(signedDSSet.RRSIG.Verify(kk, signedDSSet.RRSet)).To(BeNil()) + verifiedCount++ + } + } + } + } + + for _, sk := range signedKeys.SignedKsks { + for _, k := range sk.RRSet { + switch kk := k.(type) { + case *dns.DNSKEY: + if kk.KeyTag() == signedDSSet.RRSIG.KeyTag { + Expect(signedDSSet.RRSIG.Verify(kk, signedDSSet.RRSet)).To(BeNil()) + verifiedCount++ + } + } + } + } + } + + Expect(verifiedCount).ToNot(Equal(0)) + }) + + It("Uses DS to validate Public Key", func() { + signedKeys := d.SigningData(nameserver, deliveryService) + signedDSSets := d.DelegationSignerData(nameserver, deliveryService) + + Expect(len(signedDSSets)).ToNot(Equal(0)) + + count := 0 + for _, signedZsk := range signedKeys.SignedZsks { + for _, zsk := range signedZsk.RRSet { + switch z := zsk.(type) { + case *dns.DNSKEY: + for _, signedDs := range signedDSSets { + for _, ds := range signedDs.RRSet { + switch d := ds.(type) { + case *dns.DS: + if d.KeyTag == z.KeyTag() { + computedDS := z.ToDS(d.DigestType) + Expect(d.Digest).To(Equal(computedDS.Digest)) + count++ + } + } + } + } + } + } + } + + Expect(count).ToNot(Equal(0)) + }) + + It("Uses KSK public key to verify ZSK RRSig", func() { + signedKeys := d.SigningData(nameserver, deliveryService) + + + count := 0 + for _, signedZsk := range signedKeys.SignedZsks { + for _, signedKsk := range signedKeys.SignedKsks { + for _, ksk := range signedKsk.RRSet { + switch k := ksk.(type) { + case *dns.DNSKEY: + if k.KeyTag() == signedZsk.RRSIG.KeyTag { + Expect(signedZsk.RRSIG.Verify(k, signedZsk.RRSet)).To(BeNil()) + count++ + } + } + } + } + } + Expect(count).ToNot(Equal(0)) + }) + }) +})