// TrimDomainName trims origin from s if s is a subdomain. // This function will never return "", but returns "@" instead (@ represents the apex (bare) domain). func TrimDomainName(s, origin string) string { // An apex (bare) domain is always returned as "@". // If the return value ends in a ".", the domain was not the suffix. // origin can end in "." or not. Either way the results should be the same. if len(s) == 0 { return "@" // Return the apex (@) rather than "". } // Someone is using TrimDomainName(s, ".") to remove a dot if it exists. if origin == "." { return strings.TrimSuffix(s, origin) } // Dude, you aren't even if the right subdomain! if !dns.IsSubDomain(origin, s) { return s } slabels := dns.Split(s) olabels := dns.Split(origin) m := dns.CompareDomainName(s, origin) if len(olabels) == m { if len(olabels) == len(slabels) { return "@" // origin == s } if (s[0] == '.') && (len(slabels) == (len(olabels) + 1)) { return "@" // TrimDomainName(".foo.", "foo.") } } // Return the first (len-m) labels: return s[:slabels[len(slabels)-m]-1] }
// Denial creates (if needed) NSEC3 records that are included in the reply. func (s *server) Denial(m *dns.Msg) { if m.Rcode == dns.RcodeNameError { // qname nsec nsec1 := s.NewNSEC3(m.Question[0].Name) m.Ns = append(m.Ns, nsec1) // wildcard nsec idx := dns.Split(m.Question[0].Name) wildcard := "*." + m.Question[0].Name[idx[0]:] nsec2 := s.NewNSEC3(wildcard) if nsec1.Hdr.Name != nsec2.Hdr.Name || nsec1.NextDomain != nsec2.NextDomain { // different NSEC3, add it m.Ns = append(m.Ns, nsec2) } } if m.Rcode == dns.RcodeSuccess && len(m.Ns) == 1 { if _, ok := m.Ns[0].(*dns.SOA); ok { m.Ns = append(m.Ns, s.NewNSEC3(m.Question[0].Name)) } } }
func (s *server) Denial(m *dns.Msg) { if m.Rcode == dns.RcodeNameError { // ce is qname minus the left label idx := dns.Split(m.Question[0].Name) ce := m.Question[0].Name[idx[1]:] nsec3ce, nsec3wildcard := newNSEC3CEandWildcard(s.config.Domain, ce, s.config.MinTtl) // Add ce and wildcard m.Ns = append(m.Ns, nsec3ce) m.Ns = append(m.Ns, nsec3wildcard) // Deny Qname nsec3 m.Ns = append(m.Ns, s.newNSEC3NameError(m.Question[0].Name)) } if m.Rcode == dns.RcodeSuccess && len(m.Ns) == 1 { // NODATA if _, ok := m.Ns[0].(*dns.SOA); ok { m.Ns = append(m.Ns, s.newNSEC3NoData(m.Question[0].Name)) } } }
// FindZoneByFqdn determines the zone apex for the given fqdn by recursing up the // domain labels until the nameserver returns a SOA record in the answer section. func FindZoneByFqdn(fqdn string, nameservers []string) (string, error) { // Do we have it cached? if zone, ok := fqdnToZone[fqdn]; ok { return zone, nil } labelIndexes := dns.Split(fqdn) for _, index := range labelIndexes { domain := fqdn[index:] // Give up if we have reached the TLD if isTLD(domain) { break } in, err := dnsQuery(domain, dns.TypeSOA, nameservers, true) if err != nil { return "", err } // Any response code other than NOERROR and NXDOMAIN is treated as error if in.Rcode != dns.RcodeNameError && in.Rcode != dns.RcodeSuccess { return "", fmt.Errorf("Unexpected response code '%s' for %s", dns.RcodeToString[in.Rcode], domain) } // Check if we got a SOA RR in the answer section if in.Rcode == dns.RcodeSuccess { for _, ans := range in.Answer { if soa, ok := ans.(*dns.SOA); ok { zone := soa.Hdr.Name fqdnToZone[fqdn] = zone return zone, nil } } } } return "", fmt.Errorf("Could not find the start of authority") }
// NSEC3 Helper func verifyNameError3(nsec3 []dns.RR, qname string, qtype uint16) error { indx := dns.Split(qname) ce := "" // Closest Encloser nc := "" // Next Closer wc := "" // Source of Synthesis (wildcard) ClosestEncloser: for i := 0; i < len(indx); i++ { for j := 0; j < len(nsec3); j++ { if nsec3[j].(*dns.NSEC3).Match(qname[indx[i]:]) { ce = qname[indx[i]:] wc = "*." + ce if i == 0 { nc = qname } else { nc = qname[indx[i-1]:] } break ClosestEncloser } } } if ce == "" { return dns.ErrSig // ErrNoMatchingNSEC3 } covered := 0 // Both nc and wc must be covered for i := 0; i < len(nsec3); i++ { if nsec3[i].(*dns.NSEC3).Cover(nc) { covered++ } if nsec3[i].(*dns.NSEC3).Cover(wc) { covered++ } } if covered != 2 { return dns.ErrSig } return nil }
// NSEC3 Helper func denial3(nsec3 []dns.RR, in *dns.Msg) { qname := in.Question[0].Name qtype := in.Question[0].Qtype switch in.Rcode { case dns.RcodeSuccess: // qname should match nsec3, type should not be in bitmap match := nsec3[0].(*dns.NSEC3).Match(qname) if !match { fmt.Printf(";- Denial, owner name does not match qname\n") fmt.Printf(";- Denial, failed authenticated denial of existence proof for no data\n") return } for _, t := range nsec3[0].(*dns.NSEC3).TypeBitMap { if t == qtype { fmt.Printf(";- Denial, found type, %d, in bitmap\n", qtype) fmt.Printf(";- Denial, failed authenticated denial of existence proof for no data\n") return } if t > qtype { // ordered list, bail out, because not found break } } // Some success data printed here fmt.Printf(";+ Denial, matching record, %s, (%s) found and type %s denied\n", qname, strings.ToLower(dns.HashName(qname, nsec3[0].(*dns.NSEC3).Hash, nsec3[0].(*dns.NSEC3).Iterations, nsec3[0].(*dns.NSEC3).Salt)), dns.TypeToString[qtype]) fmt.Printf(";+ Denial, secure authenticated denial of existence proof for no data\n") return case dns.RcodeNameError: // NXDOMAIN Proof indx := dns.Split(qname) ce := "" // Closest Encloser nc := "" // Next Closer wc := "" // Source of Synthesis (wildcard) ClosestEncloser: for i := 0; i < len(indx); i++ { for j := 0; j < len(nsec3); j++ { if nsec3[j].(*dns.NSEC3).Match(qname[indx[i]:]) { ce = qname[indx[i]:] wc = "*." + ce if i == 0 { nc = qname } else { nc = qname[indx[i-1]:] } break ClosestEncloser } } } if ce == "" { fmt.Printf(";- Denial, closest encloser not found\n") return } fmt.Printf(";+ Denial, closest encloser, %s (%s)\n", ce, strings.ToLower(dns.HashName(ce, nsec3[0].(*dns.NSEC3).Hash, nsec3[0].(*dns.NSEC3).Iterations, nsec3[0].(*dns.NSEC3).Salt))) covered := 0 // Both nc and wc must be covered for i := 0; i < len(nsec3); i++ { if nsec3[i].(*dns.NSEC3).Cover(nc) { fmt.Printf(";+ Denial, next closer %s (%s), covered by %s -> %s\n", nc, nsec3[i].Header().Name, nsec3[i].(*dns.NSEC3).NextDomain, strings.ToLower(dns.HashName(ce, nsec3[0].(*dns.NSEC3).Hash, nsec3[0].(*dns.NSEC3).Iterations, nsec3[0].(*dns.NSEC3).Salt))) covered++ } if nsec3[i].(*dns.NSEC3).Cover(wc) { fmt.Printf(";+ Denial, source of synthesis %s (%s), covered by %s -> %s\n", wc, nsec3[i].Header().Name, nsec3[i].(*dns.NSEC3).NextDomain, strings.ToLower(dns.HashName(ce, nsec3[0].(*dns.NSEC3).Hash, nsec3[0].(*dns.NSEC3).Iterations, nsec3[0].(*dns.NSEC3).Salt))) covered++ } } if covered != 2 { fmt.Printf(";- Denial, too many, %d, covering records\n", covered) fmt.Printf(";- Denial, failed authenticated denial of existence proof for name error\n") return } fmt.Printf(";+ Denial, secure authenticated denial of existence proof for name error\n") return } }