├── cmd └── digcaa │ └── digcaa.go ├── README.md └── dnscaa.go /cmd/digcaa/digcaa.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/weppos/dnscaa" 9 | ) 10 | 11 | func init() { 12 | flag.Parse() 13 | } 14 | 15 | func main() { 16 | if flag.NArg() < 1 { 17 | flag.Usage() 18 | os.Exit(2) 19 | } 20 | 21 | hostname := flag.Arg(0) 22 | dig(hostname) 23 | } 24 | 25 | func dig(hostname string) { 26 | records, err := dnscaa.Lookup(hostname) 27 | 28 | if err != nil { 29 | fmt.Println(err) 30 | return 31 | } 32 | 33 | fmt.Printf("%d records found\n", len(records)) 34 | 35 | for _, record := range records { 36 | fmt.Println(record) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dnscaa - CAA DNS Library for Go 2 | 3 | The _dnscaa_ package provides a library (not yet) and command-line tool to fetch the DNS CAA (Certification Authority Authorization) records for a hostname. 4 | 5 | ## CLI usage 6 | 7 | #### Compile 8 | 9 | ``` 10 | $ go build cmd/digcaa/digcaa.go 11 | ``` 12 | 13 | #### Run 14 | 15 | ``` 16 | $ ./digcaa www.comodo.com 17 | 18 | 2 records found 19 | comodo.com. 1199 IN CAA 0 iodef "mailto:sslabuse@comodoca.com" 20 | comodo.com. 1199 IN CAA 0 issue "comodoca.com" 21 | ``` 22 | 23 | Or use `go run` to compile-and-run the command: 24 | 25 | ``` 26 | $ go run cmd/digcaa/digcaa.go www.comodo.com 27 | 28 | 2 records found 29 | comodo.com. 1199 IN CAA 0 iodef "mailto:sslabuse@comodoca.com" 30 | comodo.com. 1199 IN CAA 0 issue "comodoca.com" 31 | ``` 32 | -------------------------------------------------------------------------------- /dnscaa.go: -------------------------------------------------------------------------------- 1 | package dnscaa 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | "sync" 8 | 9 | "github.com/miekg/dns" 10 | ) 11 | 12 | var defaultResolver = NewResolver() 13 | 14 | // Resolver represents a DNS resolver that can be used to lookup the CAA records. 15 | type Resolver struct { 16 | dnsClient *dns.Client 17 | } 18 | 19 | // NewResolver constructs a new DNS resolver with an underlying DNS client. 20 | func NewResolver() *Resolver { 21 | r := new(Resolver) 22 | r.dnsClient = &dns.Client{} 23 | return r 24 | } 25 | 26 | // Lookup performs a DNS CAA lookup for the hostname using the default Resolver. 27 | // See Resolver.Lookup() for more information. 28 | func Lookup(hostname string) ([]*dns.CAA, error) { 29 | return defaultResolver.Lookup(hostname) 30 | } 31 | 32 | // Lookup performs a lookup of the CAA records for the hostname. 33 | // 34 | // When fetching the CAA records for an hostname, the client must walk the hostname up to the 35 | // root and collect all the CAA records for each names that compose the hostname. 36 | // 37 | // This method executes all the relevant DNS queries, and returns a single array of all the CAA 38 | // records associated to the given hostname (including parent names). 39 | // 40 | // For instance, the result of a Lookup() for www.example.com may contain records for 41 | // - www.example.com 42 | // - example.com 43 | // - com 44 | // 45 | // The DNS queries are executed in parallel to minimize the execution time. Lookup() returns 46 | // when all the DNS queries are completed. 47 | func (r *Resolver) Lookup(hostname string) ([]*dns.CAA, error) { 48 | var records []*dns.CAA 49 | labels := strings.Split(hostname, ".") 50 | 51 | var wg sync.WaitGroup 52 | ch := make(chan *dns.CAA, 1) 53 | 54 | // Current issues: 55 | // - no error checking 56 | // - it should probably quit on first error 57 | // See https://blog.golang.org/pipelines (select + range) 58 | for i := range labels { 59 | wg.Add(1) 60 | go func(name string) { 61 | defer wg.Done() 62 | 63 | caas, err := r.LookupCAA(name) 64 | if err != nil { 65 | fmt.Println(err) 66 | return 67 | } 68 | 69 | for _, caa := range caas { 70 | caa := caa 71 | ch <- caa 72 | } 73 | }(strings.Join(labels[i:], ".")) 74 | } 75 | 76 | go func() { 77 | wg.Wait() 78 | close(ch) 79 | }() 80 | 81 | for rr := range ch { 82 | records = append(records, rr) 83 | } 84 | 85 | return records, nil 86 | } 87 | 88 | // LookupCAA performs a DNS query to lookup all the CAA records for the given hostname, 89 | // and returns the array of records. 90 | func (r *Resolver) LookupCAA(name string) ([]*dns.CAA, error) { 91 | var rrs []*dns.CAA 92 | 93 | msg := new(dns.Msg) 94 | msg.SetQuestion(dns.Fqdn(name), dns.TypeCAA) 95 | 96 | rsp, _, err := r.dnsClient.Exchange(msg, "8.8.8.8:53") 97 | if err != nil { 98 | log.Println("CAA lookup failed", name, err) 99 | return nil, err 100 | } 101 | 102 | if rsp.Rcode != dns.RcodeSuccess { 103 | log.Println("CAA lookup not success", name, dns.RcodeToString[rsp.Rcode]) 104 | return nil, fmt.Errorf("lookup code %s", dns.RcodeToString[rsp.Rcode]) 105 | } 106 | 107 | for _, rr := range rsp.Answer { 108 | if cca, ok := rr.(*dns.CAA); ok { 109 | rrs = append(rrs, cca) 110 | } 111 | } 112 | 113 | return rrs, nil 114 | } 115 | --------------------------------------------------------------------------------