// Copyright 2011 Miek Gieben. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package dns import ( "crypto/sha1" "hash" "io" "strings" ) type saltWireFmt struct { Salt string `dns:"size-hex"` } // HashName hashes a string (label) according to RFC 5155. It returns the hashed string. func HashName(label string, ha uint8, iter uint16, salt string) string { saltwire := new(saltWireFmt) saltwire.Salt = salt wire := make([]byte, DefaultMsgSize) n, err := PackStruct(saltwire, wire, 0) if err != nil { return "" } wire = wire[:n] name := make([]byte, 255) off, err := PackDomainName(strings.ToLower(label), name, 0, nil, false) if err != nil { return "" } name = name[:off] var s hash.Hash switch ha { case SHA1: s = sha1.New() default: return "" } // k = 0 name = append(name, wire...) io.WriteString(s, string(name)) nsec3 := s.Sum(nil) // k > 0 for k := uint16(0); k < iter; k++ { s.Reset() nsec3 = append(nsec3, wire...) io.WriteString(s, string(nsec3)) nsec3 = s.Sum(nil) } return unpackBase32(nsec3) } type Denialer interface { // Cover will check if the (unhashed) name is being covered by this NSEC or NSEC3. Cover(name string) bool // Match will check if the ownername matches the (unhashed) name for this NSEC3 or NSEC3. Match(name string) bool } // Cover implements the Denialer interface. func (rr *NSEC) Cover(name string) bool { return true } // Match implements the Denialer interface. func (rr *NSEC) Match(name string) bool { return true } // Cover implements the Denialer interface. func (rr *NSEC3) Cover(name string) bool { // FIXME(miek): check if the zones match // FIXME(miek): check if we're not dealing with parent nsec3 hname := HashName(name, rr.Hash, rr.Iterations, rr.Salt) labels := Split(rr.Hdr.Name) if len(labels) < 2 { return false } hash := strings.ToUpper(rr.Hdr.Name[labels[0] : labels[1]-1]) // -1 to remove the dot if hash == rr.NextDomain { return false // empty interval } if hash > rr.NextDomain { // last name, points to apex // hname > hash // hname > rr.NextDomain // TODO(miek) } if hname <= hash { return false } if hname >= rr.NextDomain { return false } return true } // Match implements the Denialer interface. func (rr *NSEC3) Match(name string) bool { // FIXME(miek): Check if we are in the same zone hname := HashName(name, rr.Hash, rr.Iterations, rr.Salt) labels := Split(rr.Hdr.Name) if len(labels) < 2 { return false } hash := strings.ToUpper(rr.Hdr.Name[labels[0] : labels[1]-1]) // -1 to remove the . if hash == hname { return true } return false } // Proof takes a slice of NSEC or NSEC3 RR, the qname and the qtype and tries // to proof the authenticated denial of existence. If nil is returned the proof // succeeded otherwise the error will indicated what was wrong. func Proof(nsecx []RR, qname string, qtype uint16) error { // TODO(miek): wildcard expanded reply nsec3 := 0 nsec := 0 for i := 0; i < len(nsecx); i++ { if _, ok := nsecx[0].(*NSEC3); ok { nsec3++ } if _, ok := nsecx[0].(*NSEC); ok { nsec++ } } if nsec3 == len(nsecx) { return proofNSEC3(nsecx, qname, qtype) } if nsec == len(nsecx) { return proofNSEC(nsecx, qname, qtype) } return ErrSig // ErrNotRRset? } // NSEC3 Helper func proofNSEC3(nsec3 []RR, qname string, qtype uint16) error { indx := 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].(*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 ErrSig // ErrNoMatchingNSEC3 } covered := 0 // Both nc and wc must be covered for i := 0; i < len(nsec3); i++ { if nsec3[i].(*NSEC3).Cover(nc) { covered++ } if nsec3[i].(*NSEC3).Cover(wc) { covered++ } } if covered != 2 { return ErrSig } return nil } // NSEC Helper func proofNSEC(nsecx []RR, qname string, qtype uint16) error { return nil }