From c78c938b264e1232df0fe5e2b75d282fff696b7a Mon Sep 17 00:00:00 2001 From: Shulhan Date: Wed, 19 Sep 2018 05:25:07 +0700 Subject: lib/dns: implement client and server for DNS over HTTPS The implementation is based on latest draft [1]. [1] https://tools.ietf.org/html/draft-ietf-doh-dns-over-https-14 --- lib/dns/dns.go | 4 + lib/dns/dns_test.go | 13 +- lib/dns/dohclient.go | 188 +++++++++++++++++++++ lib/dns/dohclient_test.go | 366 +++++++++++++++++++++++++++++++++++++++++ lib/dns/example_server_test.go | 3 +- lib/dns/request.go | 8 +- lib/dns/server.go | 138 +++++++++++++++- lib/dns/testdata/domain.crt | 24 +++ lib/dns/testdata/domain.key | 28 ++++ 9 files changed, 757 insertions(+), 15 deletions(-) create mode 100644 lib/dns/dohclient.go create mode 100644 lib/dns/dohclient_test.go create mode 100644 lib/dns/testdata/domain.crt create mode 100644 lib/dns/testdata/domain.key diff --git a/lib/dns/dns.go b/lib/dns/dns.go index c1680f72..52211ef5 100644 --- a/lib/dns/dns.go +++ b/lib/dns/dns.go @@ -36,6 +36,10 @@ const ( rdataIPv6Size = 16 // sectionHeaderSize define the size of section header in DNS message. sectionHeaderSize = 12 + + dohHeaderKeyAccept = "accept" + dohHeaderKeyContentType = "content-type" + dohHeaderValDNSMessage = "application/dns-message" ) // diff --git a/lib/dns/dns_test.go b/lib/dns/dns_test.go index 01185428..cdaa875b 100644 --- a/lib/dns/dns_test.go +++ b/lib/dns/dns_test.go @@ -167,9 +167,13 @@ func (h *serverHandler) ServeDNS(req *Request) { res.SetID(req.Message.Header.ID) } - _, err = req.Sender.Send(res, req.UDPAddr) - if err != nil { - log.Println("ServeDNS: ", err) + if req.Sender != nil { + _, err = req.Sender.Send(res, req.UDPAddr) + if err != nil { + log.Println("ServeDNS: ", err) + } + } else if req.ChanMessage != nil { + req.ChanMessage <- res } _testServer.FreeRequest(req) @@ -188,7 +192,8 @@ func TestMain(m *testing.M) { } go func() { - err := _testServer.ListenAndServe(testServerAddress) + err := _testServer.ListenAndServe(testServerAddress, + "testdata/domain.crt", "testdata/domain.key", true) if err != nil { log.Fatal("ListenAndServe: ", err) } diff --git a/lib/dns/dohclient.go b/lib/dns/dohclient.go new file mode 100644 index 00000000..98ebac2f --- /dev/null +++ b/lib/dns/dohclient.go @@ -0,0 +1,188 @@ +// Copyright 2018, Shulhan . 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 ( + "bytes" + "crypto/tls" + "encoding/base64" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "time" +) + +// +// DoHClient client for DNS over HTTPS. +// +type DoHClient struct { + addr *url.URL + headers http.Header + req *http.Request + query url.Values + conn *http.Client +} + +// +// NewDoHClient will create new DNS client with HTTP connection. +// +func NewDoHClient(nameserver string, allowInsecure bool) (*DoHClient, error) { + nsURL, err := url.Parse(nameserver) + if err != nil { + return nil, err + } + + if nsURL.Scheme != "https" { + err = fmt.Errorf("DoH name server must be HTTPS") + return nil, err + } + + tr := &http.Transport{ + MaxIdleConns: 1, + IdleConnTimeout: 30 * time.Second, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: allowInsecure, + }, + } + + cl := &DoHClient{ + addr: nsURL, + headers: http.Header{ + "accept": []string{ + "application/dns-message", + }, + }, + query: nsURL.Query(), + conn: &http.Client{ + Transport: tr, + Timeout: clientTimeout, + }, + } + + cl.req = &http.Request{ + Method: http.MethodGet, + URL: nsURL, + Proto: "HTTP/2", + ProtoMajor: 2, + ProtoMinor: 0, + Header: cl.headers, + Body: nil, + Host: nsURL.Hostname(), + } + + return cl, nil +} + +func (cl *DoHClient) Lookup(qtype, qclass uint16, qname []byte) (*Message, error) { + if len(qname) == 0 { + return nil, nil + } + if qtype == 0 { + qtype = QueryTypeA + } + if qclass == 0 { + qclass = QueryClassIN + } + + msg := NewMessage() + + msg.Question.Type = qtype + msg.Question.Class = qclass + msg.Question.Name = append(msg.Question.Name, qname...) + + _, err := msg.Pack() + if err != nil { + return nil, err + } + + res, err := cl.Get(msg) + if err != nil { + return nil, err + } + + return res, err +} + +// +// Post send query to name server using HTTP POST and return the response +// as unpacked message. +// +func (cl *DoHClient) Post(msg *Message) (*Message, error) { + cl.req.Method = http.MethodPost + cl.req.Body = ioutil.NopCloser(bytes.NewReader(msg.Packet)) + cl.req.URL.RawQuery = "" + + httpRes, err := cl.conn.Do(cl.req) + if err != nil { + cl.req.Body.Close() + return nil, err + } + cl.req.Body.Close() + + res := NewMessage() + + packet, err := ioutil.ReadAll(httpRes.Body) + if err != nil { + httpRes.Body.Close() + return nil, err + } + + res.Packet = append(res.Packet[:0], packet...) + + httpRes.Body.Close() + + err = res.Unpack() + + return res, err +} + +// +// Get send query to name server using HTTP GET and return the response as +// unpacked message. +// +func (cl *DoHClient) Get(msg *Message) (*Message, error) { + q := base64.RawURLEncoding.EncodeToString(msg.Packet) + + cl.query.Set("dns", q) + cl.req.Method = http.MethodGet + cl.req.Body = nil + cl.req.URL.RawQuery = cl.query.Encode() + + httpRes, err := cl.conn.Do(cl.req) + if err != nil { + return nil, err + } + + if httpRes.StatusCode != 200 { + body, err := ioutil.ReadAll(httpRes.Body) + if err != nil { + return nil, err + } + err = fmt.Errorf("%s", string(body)) + return nil, err + } + + res := NewMessage() + + packet, err := ioutil.ReadAll(httpRes.Body) + if err != nil { + httpRes.Body.Close() + return nil, err + } + + res.Packet = append(res.Packet[:0], packet...) + + httpRes.Body.Close() + + if len(res.Packet) > 20 { + err = res.Unpack() + if err != nil { + return nil, err + } + } + + return res, err +} diff --git a/lib/dns/dohclient_test.go b/lib/dns/dohclient_test.go new file mode 100644 index 00000000..a94ac4c9 --- /dev/null +++ b/lib/dns/dohclient_test.go @@ -0,0 +1,366 @@ +// Copyright 2018, Shulhan . 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 ( + "testing" + + "github.com/shuLhan/share/lib/test" +) + +func TestDoHClient_Lookup(t *testing.T) { + nameserver := "https://127.0.0.1:8443/dns-query" + + cl, err := NewDoHClient(nameserver, true) + if err != nil { + t.Fatal(err) + } + + cases := []struct { + desc string + qtype uint16 + qclass uint16 + qname []byte + exp *Message + }{{ + desc: "QType:A QClass:IN QName:kilabit.info", + qtype: QueryTypeA, + qclass: QueryClassIN, + qname: []byte("kilabit.info"), + exp: &Message{ + Header: &SectionHeader{ + ID: 0, + QDCount: 1, + ANCount: 1, + }, + Question: &SectionQuestion{ + Name: []byte("kilabit.info"), + Type: QueryTypeA, + Class: QueryClassIN, + }, + Answer: []*ResourceRecord{{ + Name: []byte("kilabit.info"), + Type: QueryTypeA, + Class: QueryClassIN, + TTL: 3600, + rdlen: 4, + Text: &RDataText{ + Value: []byte("127.0.0.1"), + }, + }}, + Authority: []*ResourceRecord{}, + Additional: []*ResourceRecord{}, + }, + }, { + desc: "QType:SOA QClass:IN QName:kilabit.info", + qtype: QueryTypeSOA, + qclass: QueryClassIN, + qname: []byte("kilabit.info"), + exp: &Message{ + Header: &SectionHeader{ + ID: 0, + QDCount: 1, + ANCount: 1, + }, + Question: &SectionQuestion{ + Name: []byte("kilabit.info"), + Type: QueryTypeSOA, + Class: QueryClassIN, + }, + Answer: []*ResourceRecord{{ + Name: []byte("kilabit.info"), + Type: QueryTypeSOA, + Class: QueryClassIN, + TTL: 3600, + SOA: &RDataSOA{ + MName: []byte("kilabit.info"), + RName: []byte("admin.kilabit.info"), + Serial: 20180832, + Refresh: 3600, + Retry: 60, + Expire: 3600, + Minimum: 3600, + }, + }}, + Authority: []*ResourceRecord{}, + Additional: []*ResourceRecord{}, + }, + }, { + desc: "QType:TXT QClass:IN QName:kilabit.info", + qtype: QueryTypeTXT, + qclass: QueryClassIN, + qname: []byte("kilabit.info"), + exp: &Message{ + Header: &SectionHeader{ + ID: 0, + QDCount: 1, + ANCount: 1, + }, + Question: &SectionQuestion{ + Name: []byte("kilabit.info"), + Type: QueryTypeTXT, + Class: QueryClassIN, + }, + Answer: []*ResourceRecord{{ + Name: []byte("kilabit.info"), + Type: QueryTypeTXT, + Class: QueryClassIN, + TTL: 3600, + Text: &RDataText{ + Value: []byte("This is a test server"), + }, + }}, + Authority: []*ResourceRecord{}, + Additional: []*ResourceRecord{}, + }, + }, { + desc: "QType:AAAA QClass:IN QName:kilabit.info", + qtype: QueryTypeAAAA, + qclass: QueryClassIN, + qname: []byte("kilabit.info"), + exp: &Message{ + Header: &SectionHeader{ + ID: 0, + QDCount: 1, + }, + Question: &SectionQuestion{ + Name: []byte("kilabit.info"), + Type: QueryTypeAAAA, + Class: QueryClassIN, + }, + Answer: []*ResourceRecord{}, + Authority: []*ResourceRecord{}, + Additional: []*ResourceRecord{}, + }, + }} + + for _, c := range cases { + t.Log(c.desc) + + got, err := cl.Lookup(c.qtype, c.qclass, c.qname) + if err != nil { + t.Fatal(err) + } + + _, err = c.exp.Pack() + if err != nil { + t.Fatal(err) + } + + test.Assert(t, "Packet", c.exp.Packet, got.Packet, true) + } +} + +func TestDoHClient_Post(t *testing.T) { + nameserver := "https://127.0.0.1:8443/dns-query" + + cl, err := NewDoHClient(nameserver, true) + if err != nil { + t.Fatal(err) + } + + cases := []struct { + desc string + qtype uint16 + qclass uint16 + qname []byte + exp *Message + }{{ + desc: "QType:A QClass:IN QName:kilabit.info", + qtype: QueryTypeA, + qclass: QueryClassIN, + qname: []byte("kilabit.info"), + exp: &Message{ + Header: &SectionHeader{ + ID: 0, + QDCount: 1, + ANCount: 1, + }, + Question: &SectionQuestion{ + Name: []byte("kilabit.info"), + Type: QueryTypeA, + Class: QueryClassIN, + }, + Answer: []*ResourceRecord{{ + Name: []byte("kilabit.info"), + Type: QueryTypeA, + Class: QueryClassIN, + TTL: 3600, + rdlen: 4, + Text: &RDataText{ + Value: []byte("127.0.0.1"), + }, + }}, + Authority: []*ResourceRecord{}, + Additional: []*ResourceRecord{}, + }, + }, { + desc: "QType:SOA QClass:IN QName:kilabit.info", + qtype: QueryTypeSOA, + qclass: QueryClassIN, + qname: []byte("kilabit.info"), + exp: &Message{ + Header: &SectionHeader{ + ID: 0, + QDCount: 1, + ANCount: 1, + }, + Question: &SectionQuestion{ + Name: []byte("kilabit.info"), + Type: QueryTypeSOA, + Class: QueryClassIN, + }, + Answer: []*ResourceRecord{{ + Name: []byte("kilabit.info"), + Type: QueryTypeSOA, + Class: QueryClassIN, + TTL: 3600, + SOA: &RDataSOA{ + MName: []byte("kilabit.info"), + RName: []byte("admin.kilabit.info"), + Serial: 20180832, + Refresh: 3600, + Retry: 60, + Expire: 3600, + Minimum: 3600, + }, + }}, + Authority: []*ResourceRecord{}, + Additional: []*ResourceRecord{}, + }, + }, { + desc: "QType:TXT QClass:IN QName:kilabit.info", + qtype: QueryTypeTXT, + qclass: QueryClassIN, + qname: []byte("kilabit.info"), + exp: &Message{ + Header: &SectionHeader{ + ID: 0, + QDCount: 1, + ANCount: 1, + }, + Question: &SectionQuestion{ + Name: []byte("kilabit.info"), + Type: QueryTypeTXT, + Class: QueryClassIN, + }, + Answer: []*ResourceRecord{{ + Name: []byte("kilabit.info"), + Type: QueryTypeTXT, + Class: QueryClassIN, + TTL: 3600, + Text: &RDataText{ + Value: []byte("This is a test server"), + }, + }}, + Authority: []*ResourceRecord{}, + Additional: []*ResourceRecord{}, + }, + }, { + desc: "QType:AAAA QClass:IN QName:kilabit.info", + qtype: QueryTypeAAAA, + qclass: QueryClassIN, + qname: []byte("kilabit.info"), + exp: &Message{ + Header: &SectionHeader{ + ID: 0, + QDCount: 1, + }, + Question: &SectionQuestion{ + Name: []byte("kilabit.info"), + Type: QueryTypeAAAA, + Class: QueryClassIN, + }, + Answer: []*ResourceRecord{}, + Authority: []*ResourceRecord{}, + Additional: []*ResourceRecord{}, + }, + }} + + for _, c := range cases { + t.Log(c.desc) + + msg := NewMessage() + + msg.Question.Type = c.qtype + msg.Question.Class = c.qclass + msg.Question.Name = append(msg.Question.Name, c.qname...) + + _, err := msg.Pack() + if err != nil { + t.Fatal("msg.Pack:", err) + } + + got, err := cl.Post(msg) + if err != nil { + t.Fatal(err) + } + + _, err = c.exp.Pack() + if err != nil { + t.Fatal(err) + } + + test.Assert(t, "Packet", c.exp.Packet, got.Packet, true) + } +} + +func TestDoHClient_Get(t *testing.T) { + nameserver := "https://127.0.0.1:8443/dns-invalid" + + cl, err := NewDoHClient(nameserver, true) + if err != nil { + t.Fatal(err) + } + + cases := []struct { + desc string + qtype uint16 + qclass uint16 + qname []byte + exp *Message + expErr string + }{{ + desc: "QType:A QClass:IN QName:kilabit.info", + qtype: QueryTypeA, + qclass: QueryClassIN, + qname: []byte("kilabit.info"), + expErr: "404 page not found\n", + }, { + desc: "QType:A QClass:IN QName:kilabit.info", + qtype: QueryTypeA, + qclass: QueryClassIN, + qname: []byte("kilabit.info"), + expErr: "404 page not found\n", + }} + + for _, c := range cases { + t.Log(c.desc) + + msg := NewMessage() + + msg.Question.Type = c.qtype + msg.Question.Class = c.qclass + msg.Question.Name = append(msg.Question.Name, c.qname...) + + _, err := msg.Pack() + if err != nil { + t.Fatal("msg.Pack:", err) + } + + got, err := cl.Get(msg) + if err != nil { + test.Assert(t, "error", c.expErr, err.Error(), true) + continue + } + + _, err = c.exp.Pack() + if err != nil { + t.Fatal(err) + } + + test.Assert(t, "Packet", c.exp.Packet, got.Packet, true) + } +} diff --git a/lib/dns/example_server_test.go b/lib/dns/example_server_test.go index a6970bfe..020e7907 100644 --- a/lib/dns/example_server_test.go +++ b/lib/dns/example_server_test.go @@ -195,7 +195,8 @@ func ExampleServer() { } go func() { - err := server.ListenAndServe(serverAddress) + err := server.ListenAndServe(serverAddress, + "testdata/domain.crt", "testdata/domain.key", true) if err != nil { log.Fatal("ListenAndServe: ", err) } diff --git a/lib/dns/request.go b/lib/dns/request.go index 052fdc22..54a84d82 100644 --- a/lib/dns/request.go +++ b/lib/dns/request.go @@ -22,9 +22,10 @@ var _requestPool = sync.Pool{ // Request contains UDP address and DNS query message from client. // type Request struct { - Message *Message - UDPAddr *net.UDPAddr - Sender Sender + Message *Message + UDPAddr *net.UDPAddr + Sender Sender + ChanMessage chan *Message } // @@ -34,4 +35,5 @@ func (req *Request) Reset() { req.Message.Reset() req.UDPAddr = nil req.Sender = nil + req.ChanMessage = nil } diff --git a/lib/dns/server.go b/lib/dns/server.go index 06274e09..99a93cb8 100644 --- a/lib/dns/server.go +++ b/lib/dns/server.go @@ -5,9 +5,15 @@ package dns import ( + "crypto/tls" + "encoding/base64" "io" + "io/ioutil" "log" "net" + "net/http" + "strings" + "time" libnet "github.com/shuLhan/share/lib/net" ) @@ -19,34 +25,40 @@ type Server struct { Handler Handler udp *net.UDPConn tcp *net.TCPListener + doh *http.Server } -func parseAddress(address string) (*net.UDPAddr, *net.TCPAddr, error) { +func parseAddress(address string) (udp *net.UDPAddr, tcp, doh *net.TCPAddr, err error) { ip, port, err := libnet.ParseIPPort(address, DefaultPort) if err != nil { - return nil, nil, err + return } - udpAddr := &net.UDPAddr{ + udp = &net.UDPAddr{ IP: ip, Port: int(port), } - tcpAddr := &net.TCPAddr{ + tcp = &net.TCPAddr{ IP: ip, Port: int(port), } - return udpAddr, tcpAddr, nil + doh = &net.TCPAddr{ + IP: ip, + Port: 8443, + } + + return } // // ListenAndServe run DNS server, listening on UDP and TCP connection. // -func (srv *Server) ListenAndServe(address string) error { +func (srv *Server) ListenAndServe(address, certFile, keyFile string, allowInsecure bool) error { var err error - udpAddr, tcpAddr, err := parseAddress(address) + udpAddr, tcpAddr, dohAddr, err := parseAddress(address) if err != nil { return err } @@ -65,12 +77,124 @@ func (srv *Server) ListenAndServe(address string) error { cherr <- err } }() + if len(certFile) > 0 && len(keyFile) > 0 { + go func() { + err = srv.ListenAndServeDoH(dohAddr, certFile, keyFile, allowInsecure) + if err != nil { + cherr <- err + } + }() + } err = <-cherr return err } +// +// ListenAndServeDoH listen for request over HTTPS using certificate and key +// file in parameter. The path to request is static "/dns-query". +// +func (srv *Server) ListenAndServeDoH(address *net.TCPAddr, certFile, keyFile string, allowInsecure bool) error { + srv.doh = &http.Server{ + Addr: address.String(), + IdleTimeout: 120 * time.Second, + TLSConfig: &tls.Config{ + InsecureSkipVerify: allowInsecure, + }, + } + + http.Handle("/dns-query", srv) + + err := srv.doh.ListenAndServeTLS(certFile, keyFile) + + return err +} + +func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + hdr := w.Header() + hdr.Set(dohHeaderKeyContentType, dohHeaderValDNSMessage) + + hdrAcceptValue := r.Header.Get(dohHeaderKeyAccept) + if len(hdrAcceptValue) == 0 { + w.WriteHeader(http.StatusUnsupportedMediaType) + return + } + + hdrAcceptValue = strings.ToLower(hdrAcceptValue) + if hdrAcceptValue != dohHeaderValDNSMessage { + w.WriteHeader(http.StatusUnsupportedMediaType) + return + } + + if r.Method == http.MethodGet { + srv.handleDoHGet(w, r) + return + } + if r.Method == http.MethodPost { + srv.handleDoHPost(w, r) + return + } + + w.WriteHeader(http.StatusMethodNotAllowed) +} + +func (srv *Server) handleDoHGet(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + msgBase64 := q.Get("dns") + + if len(msgBase64) == 0 { + w.WriteHeader(http.StatusBadRequest) + return + } + + raw, err := base64.RawURLEncoding.DecodeString(msgBase64) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + srv.handleDoHRequest(raw, w) +} + +func (srv *Server) handleDoHPost(w http.ResponseWriter, r *http.Request) { + raw, err := ioutil.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + srv.handleDoHRequest(raw, w) +} + +func (srv *Server) handleDoHRequest(raw []byte, w http.ResponseWriter) { + req := _requestPool.Get().(*Request) + req.Reset() + req.ChanMessage = make(chan *Message, 1) + req.Message.Packet = append(req.Message.Packet[:0], raw...) + req.Message.UnpackHeaderQuestion() + + srv.Handler.ServeDNS(req) + + timeout := time.NewTicker(clientTimeout) + for { + select { + case res := <-req.ChanMessage: + _, err := w.Write(res.Packet) + if err != nil { + log.Printf("! handleDoHRequest: %s\n", err) + } + goto out + + case <-timeout.C: + w.WriteHeader(http.StatusGatewayTimeout) + goto out + } + } +out: + timeout.Stop() +} + // // ListenAndServeTCP listen for request with TCP socket. // diff --git a/lib/dns/testdata/domain.crt b/lib/dns/testdata/domain.crt new file mode 100644 index 00000000..01501086 --- /dev/null +++ b/lib/dns/testdata/domain.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgIUJYHUkLhtYbIcfjnslpHyKqOCxY0wDQYJKoZIhvcNAQEL +BQAwgZIxCzAJBgNVBAYTAklEMREwDwYDVQQIDAhXRVNUSkFWQTEQMA4GA1UEBwwH +QkFORFVORzEVMBMGA1UECgwMS0lMQUJJVC5JTkZPMRUwEwYDVQQLDAxLSUxBQklU +LklORk8xEDAOBgNVBAMMB1NIVUxIQU4xHjAcBgkqhkiG9w0BCQEWD21zQGtpbGFi +aXQuaW5mbzAeFw0xODA5MTgyMTEzMjNaFw0yODA5MTUyMTEzMjNaMIGSMQswCQYD +VQQGEwJJRDERMA8GA1UECAwIV0VTVEpBVkExEDAOBgNVBAcMB0JBTkRVTkcxFTAT +BgNVBAoMDEtJTEFCSVQuSU5GTzEVMBMGA1UECwwMS0lMQUJJVC5JTkZPMRAwDgYD +VQQDDAdTSFVMSEFOMR4wHAYJKoZIhvcNAQkBFg9tc0BraWxhYml0LmluZm8wggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCckWPz7mzmx8/TYm3tg2W/cVMc +clraj+Gkf6KPEeZp310iWnQNV/DVNe+a1W8qrCgKT48xpWu/vqwmsUqpPe/sSGl2 +FJPyJORwVq4aKZkv1UAqk/5t/2bH69Meg+m0SznrVw7egvw/syUInT0Z27KGWmp9 +duMibgGSSHoBbw4y+EPVXQnMI0kDsB9hRxddu/gTxSoia6vJf/IFwqAWvECzzUjJ +YbI2Lq6I3U11ejc7XIGXB23uKnas0/aOlWMV7OHnDKqEZ7FEohe1IXwOQU0cLJ94 +P82u1flzdh9ntjIH0LOfNrpYk5Kvll4Cm1u9HPLCC9lfUoMhDb2M3x24Q78XAgMB +AAGjUzBRMB0GA1UdDgQWBBTk99WkqIL8oDVQwtP46WXeXtJ4KzAfBgNVHSMEGDAW +gBTk99WkqIL8oDVQwtP46WXeXtJ4KzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQAWYPRf2F9eE4EPuNBd6bEEbLEceL56b1C11Sivf4DW2PJwYbvd +0Uky7Hc0FkBEVL1a0GA+sJ0mNsTVTs/O8cU5VLkKCmoEuewlDF2YliH/mY6A5NDV +sumblp0JrpH4L6DEX2ktZ+9tTzguvg6HGZOR+HgBP6xaSpm8Tb06iYfgYtehTNEW +s6Ws67+E59qQFMKTMgqudkixNsr2CkdjtBCdPRQ8Y/Mj3yl57npnLQi/ltpq1BBH +i6wfhTGQX4uo0XU7hSZw9Nx1BQ4TreO3kirnN7kNxxPBwZyg4+I7YAJ3SKDCEQ7O +g6zg8CQQHszwsso4BMwZfIIJtpocu8Qba81g +-----END CERTIFICATE----- diff --git a/lib/dns/testdata/domain.key b/lib/dns/testdata/domain.key new file mode 100644 index 00000000..4660b947 --- /dev/null +++ b/lib/dns/testdata/domain.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCckWPz7mzmx8/T +Ym3tg2W/cVMcclraj+Gkf6KPEeZp310iWnQNV/DVNe+a1W8qrCgKT48xpWu/vqwm +sUqpPe/sSGl2FJPyJORwVq4aKZkv1UAqk/5t/2bH69Meg+m0SznrVw7egvw/syUI +nT0Z27KGWmp9duMibgGSSHoBbw4y+EPVXQnMI0kDsB9hRxddu/gTxSoia6vJf/IF +wqAWvECzzUjJYbI2Lq6I3U11ejc7XIGXB23uKnas0/aOlWMV7OHnDKqEZ7FEohe1 +IXwOQU0cLJ94P82u1flzdh9ntjIH0LOfNrpYk5Kvll4Cm1u9HPLCC9lfUoMhDb2M +3x24Q78XAgMBAAECggEAbiKkEhKVDp5d5k+mDl7Q8yNvmGIk4Pw3ePTD0CqCT9Vs ++V5xpnVHF2RSgTNEeNsTa3VdwEmiCwbAqJMsdvL309l4PjTpgXtMKm3/GK5McOZs +tcbXQl9X2KheIWgfvNDyFEdwUTwI33JQScf6FDeEVJhDsrAvxKdneZR8JogNj/IU +Z88jI1Qfc4DamfVi3cEAfXn2i0Vo1nvfHFbxUZqQkmw4RjYum+keDKg0fLa907zH +yV6zcqxsqDktwX9QW176M9WZ7yB6H0XNjSAkSrOFgVgOd6tSbYeGWlVf5Axw0B5I +5C2iV0CmVMSWjnCwEe0D16H/DgT9/VXSbvEboTJ6YQKBgQDL1ScVc7+6PQLXsuLe +I80cQBQ7rSE/J0DPG5gIFZgGrJi4zM7lo6Dnyb0W8AvFPpSHuEsYO38p/NOuBiBi +uvqFxpiGQudxZtYFEWwDr1Ay0NOu89UQbxo/48Bgz/KRfYJ6JIgMUJ9xeB7YjZE2 +Mrxmu5DugY+5MFS9UFouxNL50QKBgQDEo4RDdlF5BXraNymeLfS10cGUTx6WkiC4 +G04Hjl9LEJ0BY1oSBg19l6AxVzfHPYgXVWVDq7TTo5okH6eHd7h7qdeXj3UlyQSl +47K1QU5s+sfwoqjuvm/awhU7qrQlY0GTHn/MXMEBX5DTHEuemT9m+jyTS4K3zWU2 +bf/QKGB8ZwKBgQC1NLtYMNyjnpWmWFujjERN9xGFs/Y4hJbzB97yYPAUDuB+eWT9 +dagYJ5q4h5KPOYEl3sqzskDsfN1aegvUedE5mEIEKfpDMF7XhpN1+yba5hcqE464 +22yEm95ssrE8ck3KdCuWdx4n69fQQJp1ikk/M0Q3JGs3ASZ0Xritl0DP4QKBgHRM +pchkrTEfrZZsc7/rPEVhBtXZqaSyTom1FIRhjzjNXZ7ZjQcF72qtiABGrmW3ncr3 +JcpNPsjBhUQCOMplY4Y4YJtyLH4pkwcuUZ7kPic0d5Z6DeIOXgeLLJW6k4tdVgZW +To2m+jv+sqA5pvvpdVdJfxQ639gnscnsaxVJHC/XAoGBAIx5chZZesECe7ij+2c5 +3sPM8ZcNzxUmOF2mZ3yLy+x42suairfXlAQpLTAnjW+Nx3QslC4eV89kjU/a5iyc +fAiwWSQGk972CY9h0CwO4ykj3oOyYi6ZfrkRaDf6A9QD5EMUBP4oPePEJbCmDIsJ +VITYrHkjJsDULGIq4JX+zxdd +-----END PRIVATE KEY----- -- cgit v1.3