diff options
author | David Benjamin <davidben@google.com> | 2021-06-16 21:30:35 +0000 |
---|---|---|
committer | Boringssl LUCI CQ <boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2021-06-16 21:30:48 +0000 |
commit | 9a5abe05cc999177bc55949c9243b3bc360595cb (patch) | |
tree | b39e5f88fa111e370073d42b2263a3e87860a6b1 | |
parent | 160a8891ae9a1d03f29aec079a67d97bc773990e (diff) | |
download | boringssl-9a5abe05cc999177bc55949c9243b3bc360595cb.zip boringssl-9a5abe05cc999177bc55949c9243b3bc360595cb.tar.gz boringssl-9a5abe05cc999177bc55949c9243b3bc360595cb.tar.bz2 |
Revert "Add util/fetch_ech_config_list.go"
This reverts commit 160a8891ae9a1d03f29aec079a67d97bc773990e.
Reason for revert: This broke go.sum on CI for some reason. Will fix
and reland.
Original change's description:
> Add util/fetch_ech_config_list.go
>
> I wrote this tool to make it easier to test the ECH client against
> real-world servers with the bssl client tool. I found that manually
> extracting an ECHConfigList from a raw HTTPS record is unnecessarily
> painful.
>
> The tool queries DNS over UDP for HTTPS records. If it finds any HTTPS
> records in the response, it attempts to extract an ECHConfigList from
> the "ech" SvcParam. It can write each extracted ECHConfigList to a file
> in a given directory. Once the ECH client implementation lands, the bssl
> client tool should have a new flag that that takes the path to an
> ECHConfigList file.
>
> I am using golang.org/x/net/dns/dnsmessage to parse the DNS response. I
> recently added the |UnknownResource| type to this library to enable
> callers (like us) to extract the bytes of otherwise-unsupported records
> (like HTTPS). I updated the dependency with `go get -u golang.org/x/net`.
>
> Although the bssl client tool knows how to resolve the address of its
> "-connect" parameter, it is difficult to query HTTPS records in a
> platform-agnostic way. If we decide the bssl client should directly
> query HTTPS rather than leaning on fetch_ech_config_list.go, we should
> look into libresolv. Specifically, the |res_query| function enables the
> caller to query arbitrary record types. This may open its own can of
> cross-platform worms; macOS and Linux typically ship with different
> implementations and it is not available on Windows. For more info, see
> `man 3 resolver`.
>
> Bug: 275
> Change-Id: I705591658921f60a958164a18b68ffb697c2ea4b
> Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/44104
> Reviewed-by: David Benjamin <davidben@google.com>
TBR=davidben@google.com,dmcardle@google.com
Change-Id: Iec36265dfa3b7c59eb811ed708219bfebb07a710
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 275
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48105
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
-rw-r--r-- | go.mod | 5 | ||||
-rw-r--r-- | go.sum | 7 | ||||
-rw-r--r-- | util/fetch_ech_config_list.go | 389 |
3 files changed, 1 insertions, 400 deletions
@@ -2,7 +2,4 @@ module boringssl.googlesource.com/boringssl go 1.13 -require ( - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/net v0.0.0-20210525063256-abc453219eb5 // indirect -) +require golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 @@ -2,14 +2,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/util/fetch_ech_config_list.go b/util/fetch_ech_config_list.go deleted file mode 100644 index 03b2f87..0000000 --- a/util/fetch_ech_config_list.go +++ /dev/null @@ -1,389 +0,0 @@ -// Copyright (c) 2021, Google Inc. -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -package main - -import ( - "errors" - "flag" - "fmt" - "io/ioutil" - "log" - "net" - "os" - "path" - "strings" - - "golang.org/x/crypto/cryptobyte" - "golang.org/x/net/dns/dnsmessage" -) - -const ( - httpsType = 65 // RRTYPE for HTTPS records. - - // SvcParamKey codepoints defined in draft-ietf-dnsop-svcb-https-06. - httpsKeyMandatory = 0 - httpsKeyALPN = 1 - httpsKeyNoDefaultALPN = 2 - httpsKeyPort = 3 - httpsKeyIPV4Hint = 4 - httpsKeyECH = 5 - httpsKeyIPV6Hint = 6 -) - -var ( - name = flag.String("name", "", "The name to look up in DNS. Required.") - server = flag.String("server", "8.8.8.8:53", "Comma-separated host and UDP port that defines the DNS server to query.") - outDir = flag.String("out-dir", "", "The directory where ECHConfigList values will be written. If unspecified, bytes are hexdumped to stdout.") -) - -type httpsRecord struct { - priority uint16 - targetName string - - // SvcParams: - mandatory []uint16 - alpn []string - noDefaultALPN bool - hasPort bool - port uint16 - ipv4hint []net.IP - ech []byte - ipv6hint []net.IP - unknownParams map[uint16][]byte -} - -// String pretty-prints |h| as a multi-line string with bullet points. -func (h httpsRecord) String() string { - var b strings.Builder - fmt.Fprintf(&b, "HTTPS SvcPriority:%d TargetName:%q", h.priority, h.targetName) - - if len(h.mandatory) != 0 { - fmt.Fprintf(&b, "\n * mandatory: %v", h.mandatory) - } - if len(h.alpn) != 0 { - fmt.Fprintf(&b, "\n * alpn: %q", h.alpn) - } - if h.noDefaultALPN { - fmt.Fprint(&b, "\n * no-default-alpn") - } - if h.hasPort { - fmt.Fprintf(&b, "\n * port: %d", h.port) - } - if len(h.ipv4hint) != 0 { - fmt.Fprintf(&b, "\n * ipv4hint:") - for _, address := range h.ipv4hint { - fmt.Fprintf(&b, "\n - %s", address) - } - } - if len(h.ech) != 0 { - fmt.Fprintf(&b, "\n * ech: %x", h.ech) - } - if len(h.ipv6hint) != 0 { - fmt.Fprintf(&b, "\n * ipv6hint:") - for _, address := range h.ipv6hint { - fmt.Fprintf(&b, "\n - %s", address) - } - } - if len(h.unknownParams) != 0 { - fmt.Fprint(&b, "\n * unknown SvcParams:") - for key, value := range h.unknownParams { - fmt.Fprintf(&b, "\n - %d: %x", key, value) - } - } - return b.String() -} - -// dnsQueryForHTTPS queries the DNS server over UDP for any HTTPS records -// associated with |domain|. It scans the response's answers and returns all the -// HTTPS records it finds. It returns an error if any connection steps fail. -func dnsQueryForHTTPS(domain string) ([][]byte, error) { - udpAddr, err := net.ResolveUDPAddr("udp", *server) - if err != nil { - return nil, err - } - conn, err := net.DialUDP("udp", nil, udpAddr) - if err != nil { - return nil, fmt.Errorf("failed to dial: %s", err) - } - defer conn.Close() - - // Domain name must be canonical or message packing will fail. - if domain[len(domain)-1] != '.' { - domain += "." - } - dnsName, err := dnsmessage.NewName(domain) - if err != nil { - return nil, fmt.Errorf("failed to create DNS name from %q: %s", domain, err) - } - question := dnsmessage.Question{ - Name: dnsName, - Type: httpsType, - Class: dnsmessage.ClassINET, - } - msg := dnsmessage.Message{ - Header: dnsmessage.Header{ - RecursionDesired: true, - }, - Questions: []dnsmessage.Question{question}, - } - packedMsg, err := msg.Pack() - if err != nil { - return nil, fmt.Errorf("failed to pack msg: %s", err) - } - - if _, err = conn.Write(packedMsg); err != nil { - return nil, fmt.Errorf("failed to send the DNS query: %s", err) - } - - for { - response := make([]byte, 512) - n, err := conn.Read(response) - if err != nil { - return nil, fmt.Errorf("failed to read the DNS response: %s", err) - } - response = response[:n] - - var p dnsmessage.Parser - header, err := p.Start(response) - if err != nil { - return nil, err - } - if !header.Response { - return nil, errors.New("received DNS message is not a response") - } - if header.RCode != dnsmessage.RCodeSuccess { - return nil, fmt.Errorf("response from DNS has non-success RCode: %s", header.RCode.String()) - } - if header.ID != 0 { - return nil, errors.New("received a DNS response with the wrong ID") - } - if !header.RecursionAvailable { - return nil, errors.New("server does not support recursion") - } - // Verify that this response answers the question that we asked in the - // query. If the resolver encountered any CNAMEs, it's not guaranteed - // that the response will contain a question with the same QNAME as our - // query. However, RFC8499 Section 4 indicates that in general use, the - // response's QNAME should match the query, so we will make that - // assumption. - q, err := p.Question() - if err != nil { - return nil, err - } - if q != question { - return nil, fmt.Errorf("response answers the wrong question: %v", q) - } - if q, err = p.Question(); err != dnsmessage.ErrSectionDone { - return nil, fmt.Errorf("response contains an unexpected question: %v", q) - } - - var httpsRecords [][]byte - for { - h, err := p.AnswerHeader() - if err == dnsmessage.ErrSectionDone { - break - } - if err != nil { - return nil, err - } - - switch h.Type { - case httpsType: - // This should continue to work when golang.org/x/net/dns/dnsmessage - // adds support for HTTPS records. - r, err := p.UnknownResource() - if err != nil { - return nil, err - } - httpsRecords = append(httpsRecords, r.Data) - default: - if _, err := p.UnknownResource(); err != nil { - return nil, err - } - } - } - return httpsRecords, nil - } -} - -// parseHTTPSRecord parses an HTTPS record (draft-ietf-dnsop-svcb-https-06, -// Section 2.2) from |raw|. If there are syntax errors, it returns an error. -func parseHTTPSRecord(raw []byte) (httpsRecord, error) { - reader := cryptobyte.String(raw) - - var priority uint16 - if !reader.ReadUint16(&priority) { - return httpsRecord{}, errors.New("failed to parse HTTPS record priority") - } - - // Read the TargetName. - var dottedDomain string - for { - var label cryptobyte.String - if !reader.ReadUint8LengthPrefixed(&label) { - return httpsRecord{}, errors.New("failed to parse HTTPS record TargetName") - } - if label.Empty() { - break - } - dottedDomain += string(label) + "." - } - - if priority == 0 { - // TODO(dmcardle) Recursively follow AliasForm records. - return httpsRecord{}, fmt.Errorf("received an AliasForm HTTPS record with TargetName=%q", dottedDomain) - } - - record := httpsRecord{ - priority: priority, - targetName: dottedDomain, - unknownParams: make(map[uint16][]byte), - } - - // Read the SvcParams. - var lastSvcParamKey uint16 - for svcParamCount := 0; !reader.Empty(); svcParamCount++ { - var svcParamKey uint16 - var svcParamValue cryptobyte.String - if !reader.ReadUint16(&svcParamKey) || - !reader.ReadUint16LengthPrefixed(&svcParamValue) { - return httpsRecord{}, errors.New("failed to parse HTTPS record SvcParam") - } - if svcParamCount > 0 && svcParamKey <= lastSvcParamKey { - return httpsRecord{}, errors.New("malformed HTTPS record contains out-of-order SvcParamKey") - } - lastSvcParamKey = svcParamKey - - switch svcParamKey { - case httpsKeyMandatory: - if svcParamValue.Empty() { - return httpsRecord{}, errors.New("malformed mandatory SvcParamValue") - } - var lastKey uint16 - for !svcParamValue.Empty() { - // |httpsKeyMandatory| may not appear in the mandatory list. - // |httpsKeyMandatory| is zero, so checking against the initial - // value of |lastKey| handles ordering and the invalid code point. - var key uint16 - if !svcParamValue.ReadUint16(&key) || - key <= lastKey { - return httpsRecord{}, errors.New("malformed mandatory SvcParamValue") - } - lastKey = key - record.mandatory = append(record.mandatory, key) - } - case httpsKeyALPN: - if svcParamValue.Empty() { - return httpsRecord{}, errors.New("malformed alpn SvcParamValue") - } - for !svcParamValue.Empty() { - var alpn cryptobyte.String - if !svcParamValue.ReadUint8LengthPrefixed(&alpn) || alpn.Empty() { - return httpsRecord{}, errors.New("malformed alpn SvcParamValue") - } - record.alpn = append(record.alpn, string(alpn)) - } - case httpsKeyNoDefaultALPN: - if !svcParamValue.Empty() { - return httpsRecord{}, errors.New("malformed no-default-alpn SvcParamValue") - } - record.noDefaultALPN = true - case httpsKeyPort: - if !svcParamValue.ReadUint16(&record.port) || - !svcParamValue.Empty() { - return httpsRecord{}, errors.New("malformed port SvcParamValue") - } - record.hasPort = true - case httpsKeyIPV4Hint: - if svcParamValue.Empty() { - return httpsRecord{}, errors.New("malformed ipv4hint SvcParamValue") - } - for !svcParamValue.Empty() { - var address []byte - if !svcParamValue.ReadBytes(&address, 4) { - return httpsRecord{}, errors.New("malformed ipv4hint SvcParamValue") - } - record.ipv4hint = append(record.ipv4hint, address) - } - case httpsKeyECH: - if svcParamValue.Empty() { - return httpsRecord{}, errors.New("malformed ech SvcParamValue") - } - record.ech = svcParamValue - case httpsKeyIPV6Hint: - if svcParamValue.Empty() { - return httpsRecord{}, errors.New("malformed ipv6hint SvcParamValue") - } - for !svcParamValue.Empty() { - var address []byte - if !svcParamValue.ReadBytes(&address, 16) { - return httpsRecord{}, errors.New("malformed ipv6hint SvcParamValue") - } - record.ipv6hint = append(record.ipv6hint, address) - } - default: - record.unknownParams[svcParamKey] = svcParamValue - } - } - return record, nil -} - -func main() { - flag.Parse() - log.SetFlags(log.Lshortfile | log.LstdFlags) - - if len(*name) == 0 { - flag.Usage() - os.Exit(1) - } - - httpsRecords, err := dnsQueryForHTTPS(*name) - if err != nil { - log.Printf("Error querying %q: %s\n", *name, err) - os.Exit(1) - } - if len(httpsRecords) == 0 { - log.Println("No HTTPS records found in DNS response.") - os.Exit(1) - } - - if len(*outDir) > 0 { - if err = os.Mkdir(*outDir, 0755); err != nil && !os.IsExist(err) { - log.Printf("Failed to create out directory %q: %s\n", *outDir, err) - os.Exit(1) - } - } - - var echConfigListCount int - for _, httpsRecord := range httpsRecords { - record, err := parseHTTPSRecord(httpsRecord) - if err != nil { - log.Printf("Failed to parse HTTPS record: %s", err) - os.Exit(1) - } - fmt.Printf("%s\n", record) - if len(*outDir) == 0 { - continue - } - - outFile := path.Join(*outDir, fmt.Sprintf("ech-config-list-%d", echConfigListCount)) - if err = ioutil.WriteFile(outFile, record.ech, 0644); err != nil { - log.Printf("Failed to write file: %s\n", err) - os.Exit(1) - } - fmt.Printf("Wrote ECHConfigList to %q\n", outFile) - echConfigListCount++ - } -} |