pax_global_header00006660000000000000000000000064137646635760014541gustar00rootroot0000000000000052 comment=8d2deeae02b9f5a53dc51d9f8bd7e0101395a9ed go-dnsstamps-0.1.3/000077500000000000000000000000001376466357600141615ustar00rootroot00000000000000go-dnsstamps-0.1.3/.gitignore000066400000000000000000000004231376466357600161500ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ go-dnsstamps-0.1.3/LICENSE000066400000000000000000000020611376466357600151650ustar00rootroot00000000000000MIT License Copyright (c) 2018-2020 Frank Denis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. go-dnsstamps-0.1.3/README.md000066400000000000000000000001641376466357600154410ustar00rootroot00000000000000# go-dnsstamps DNS Stamps library for Go. ## [Documentation](https://pkg.go.dev/github.com/jedisct1/go-dnsstamps) go-dnsstamps-0.1.3/dnsstamps.go000066400000000000000000000362531376466357600165350ustar00rootroot00000000000000package dnsstamps import ( "encoding/base64" "encoding/binary" "encoding/hex" "errors" "fmt" "net" "strconv" "strings" ) const DefaultPort = 443 type ServerInformalProperties uint64 const ( ServerInformalPropertyDNSSEC = ServerInformalProperties(1) << 0 ServerInformalPropertyNoLog = ServerInformalProperties(1) << 1 ServerInformalPropertyNoFilter = ServerInformalProperties(1) << 2 ) type StampProtoType uint8 const ( StampProtoTypePlain = StampProtoType(0x00) StampProtoTypeDNSCrypt = StampProtoType(0x01) StampProtoTypeDoH = StampProtoType(0x02) StampProtoTypeTLS = StampProtoType(0x03) StampProtoTypeDoQ = StampProtoType(0x04) StampProtoTypeODoHTarget = StampProtoType(0x05) StampProtoTypeDNSCryptRelay = StampProtoType(0x81) StampProtoTypeODoHRelay = StampProtoType(0x85) ) func (stampProtoType *StampProtoType) String() string { switch *stampProtoType { case StampProtoTypePlain: return "Plain" case StampProtoTypeDNSCrypt: return "DNSCrypt" case StampProtoTypeDoH: return "DoH" case StampProtoTypeTLS: return "TLS" case StampProtoTypeDoQ: return "QUIC" case StampProtoTypeODoHTarget: return "oDoH target" case StampProtoTypeDNSCryptRelay: return "DNSCrypt relay" case StampProtoTypeODoHRelay: return "oDoH relay" default: return "(unknown)" } } type ServerStamp struct { ServerAddrStr string ServerPk []uint8 Hashes [][]uint8 ProviderName string Path string Props ServerInformalProperties Proto StampProtoType } func NewDNSCryptServerStampFromLegacy(serverAddrStr string, serverPkStr string, providerName string, props ServerInformalProperties) (ServerStamp, error) { if net.ParseIP(serverAddrStr) != nil { serverAddrStr = fmt.Sprintf("%s:%d", serverAddrStr, DefaultPort) } serverPk, err := hex.DecodeString(strings.Replace(serverPkStr, ":", "", -1)) if err != nil || len(serverPk) != 32 { return ServerStamp{}, fmt.Errorf("Unsupported public key: [%s]", serverPkStr) } return ServerStamp{ ServerAddrStr: serverAddrStr, ServerPk: serverPk, ProviderName: providerName, Props: props, Proto: StampProtoTypeDNSCrypt, }, nil } func NewServerStampFromString(stampStr string) (ServerStamp, error) { if !strings.HasPrefix(stampStr, "sdns:") { return ServerStamp{}, errors.New("Stamps are expected to start with \"sdns:\"") } stampStr = stampStr[5:] stampStr = strings.TrimPrefix(stampStr, "//") bin, err := base64.RawURLEncoding.Strict().DecodeString(stampStr) if err != nil { return ServerStamp{}, err } if len(bin) < 1 { return ServerStamp{}, errors.New("Stamp is too short") } if bin[0] == uint8(StampProtoTypeDNSCrypt) { return newDNSCryptServerStamp(bin) } else if bin[0] == uint8(StampProtoTypeDoH) { return newDoHServerStamp(bin) } else if bin[0] == uint8(StampProtoTypeODoHTarget) { return newODoHTargetStamp(bin) } else if bin[0] == uint8(StampProtoTypeDNSCryptRelay) { return newDNSCryptRelayStamp(bin) } else if bin[0] == uint8(StampProtoTypeODoHRelay) { return newODoHRelayStamp(bin) } return ServerStamp{}, errors.New("Unsupported stamp version or protocol") } // id(u8)=0x01 props addrLen(1) serverAddr pkStrlen(1) pkStr providerNameLen(1) providerName func newDNSCryptServerStamp(bin []byte) (ServerStamp, error) { stamp := ServerStamp{Proto: StampProtoTypeDNSCrypt} if len(bin) < 66 { return stamp, errors.New("Stamp is too short") } stamp.Props = ServerInformalProperties(binary.LittleEndian.Uint64(bin[1:9])) binLen := len(bin) pos := 9 length := int(bin[pos]) if 1+length >= binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ stamp.ServerAddrStr = string(bin[pos : pos+length]) pos += length colIndex := strings.LastIndex(stamp.ServerAddrStr, ":") bracketIndex := strings.LastIndex(stamp.ServerAddrStr, "]") if colIndex < bracketIndex { colIndex = -1 } if colIndex < 0 { colIndex = len(stamp.ServerAddrStr) stamp.ServerAddrStr = fmt.Sprintf("%s:%d", stamp.ServerAddrStr, DefaultPort) } if colIndex >= len(stamp.ServerAddrStr)-1 { return stamp, errors.New("Invalid stamp (empty port)") } ipOnly := stamp.ServerAddrStr[:colIndex] portOnly := stamp.ServerAddrStr[colIndex+1:] if _, err := strconv.ParseUint(portOnly, 10, 16); err != nil { return stamp, errors.New("Invalid stamp (port range)") } if net.ParseIP(strings.TrimRight(strings.TrimLeft(ipOnly, "["), "]")) == nil { return stamp, errors.New("Invalid stamp (IP address)") } length = int(bin[pos]) if 1+length >= binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ stamp.ServerPk = bin[pos : pos+length] pos += length length = int(bin[pos]) if length >= binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ stamp.ProviderName = string(bin[pos : pos+length]) pos += length if pos != binLen { return stamp, errors.New("Invalid stamp (garbage after end)") } return stamp, nil } // id(u8)=0x02 props addrLen(1) serverAddr hashLen(1) hash hostNameLen(1) hostName pathLen(1) path func newDoHServerStamp(bin []byte) (ServerStamp, error) { stamp := ServerStamp{Proto: StampProtoTypeDoH} if len(bin) < 22 { return stamp, errors.New("Stamp is too short") } stamp.Props = ServerInformalProperties(binary.LittleEndian.Uint64(bin[1:9])) binLen := len(bin) pos := 9 length := int(bin[pos]) if 1+length >= binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ stamp.ServerAddrStr = string(bin[pos : pos+length]) pos += length for { vlen := int(bin[pos]) length = vlen & ^0x80 if 1+length >= binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ if length > 0 { stamp.Hashes = append(stamp.Hashes, bin[pos:pos+length]) } pos += length if vlen&0x80 != 0x80 { break } } length = int(bin[pos]) if 1+length >= binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ stamp.ProviderName = string(bin[pos : pos+length]) pos += length length = int(bin[pos]) if length >= binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ stamp.Path = string(bin[pos : pos+length]) pos += length if pos != binLen { return stamp, errors.New("Invalid stamp (garbage after end)") } if len(stamp.ServerAddrStr) > 0 { colIndex := strings.LastIndex(stamp.ServerAddrStr, ":") bracketIndex := strings.LastIndex(stamp.ServerAddrStr, "]") if colIndex < bracketIndex { colIndex = -1 } if colIndex < 0 { colIndex = len(stamp.ServerAddrStr) stamp.ServerAddrStr = fmt.Sprintf("%s:%d", stamp.ServerAddrStr, DefaultPort) } if colIndex >= len(stamp.ServerAddrStr)-1 { return stamp, errors.New("Invalid stamp (empty port)") } ipOnly := stamp.ServerAddrStr[:colIndex] portOnly := stamp.ServerAddrStr[colIndex+1:] if _, err := strconv.ParseUint(portOnly, 10, 16); err != nil { return stamp, errors.New("Invalid stamp (port range)") } if net.ParseIP(strings.TrimRight(strings.TrimLeft(ipOnly, "["), "]")) == nil { return stamp, errors.New("Invalid stamp (IP address)") } } return stamp, nil } // id(u8)=0x05 props hostNameLen(1) hostName pathLen(1) path func newODoHTargetStamp(bin []byte) (ServerStamp, error) { stamp := ServerStamp{Proto: StampProtoTypeODoHTarget} if len(bin) < 12 { return stamp, errors.New("Stamp is too short") } stamp.Props = ServerInformalProperties(binary.LittleEndian.Uint64(bin[1:9])) binLen := len(bin) pos := 9 length := int(bin[pos]) if 1+length >= binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ stamp.ProviderName = string(bin[pos : pos+length]) pos += length length = int(bin[pos]) if length >= binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ stamp.Path = string(bin[pos : pos+length]) pos += length if pos != binLen { return stamp, errors.New("Invalid stamp (garbage after end)") } return stamp, nil } // id(u8)=0x81 addrLen(1) serverAddr func newDNSCryptRelayStamp(bin []byte) (ServerStamp, error) { stamp := ServerStamp{Proto: StampProtoTypeDNSCryptRelay} if len(bin) < 13 { return stamp, errors.New("Stamp is too short") } binLen := len(bin) pos := 1 length := int(bin[pos]) if 1+length > binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ stamp.ServerAddrStr = string(bin[pos : pos+length]) pos += length colIndex := strings.LastIndex(stamp.ServerAddrStr, ":") bracketIndex := strings.LastIndex(stamp.ServerAddrStr, "]") if colIndex < bracketIndex { colIndex = -1 } if colIndex < 0 { colIndex = len(stamp.ServerAddrStr) stamp.ServerAddrStr = fmt.Sprintf("%s:%d", stamp.ServerAddrStr, DefaultPort) } if colIndex >= len(stamp.ServerAddrStr)-1 { return stamp, errors.New("Invalid stamp (empty port)") } ipOnly := stamp.ServerAddrStr[:colIndex] portOnly := stamp.ServerAddrStr[colIndex+1:] if _, err := strconv.ParseUint(portOnly, 10, 16); err != nil { return stamp, errors.New("Invalid stamp (port range)") } if net.ParseIP(strings.TrimRight(strings.TrimLeft(ipOnly, "["), "]")) == nil { return stamp, errors.New("Invalid stamp (IP address)") } if pos != binLen { return stamp, errors.New("Invalid stamp (garbage after end)") } return stamp, nil } // id(u8)=0x85 props addrLen(1) serverAddr hashLen(1) hash hostNameLen(1) hostName pathLen(1) path func newODoHRelayStamp(bin []byte) (ServerStamp, error) { stamp := ServerStamp{Proto: StampProtoTypeODoHRelay} if len(bin) < 13 { return stamp, errors.New("Stamp is too short") } stamp.Props = ServerInformalProperties(binary.LittleEndian.Uint64(bin[1:9])) binLen := len(bin) pos := 9 length := int(bin[pos]) if 1+length >= binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ stamp.ServerAddrStr = string(bin[pos : pos+length]) pos += length for { vlen := int(bin[pos]) length = vlen & ^0x80 if 1+length >= binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ if length > 0 { stamp.Hashes = append(stamp.Hashes, bin[pos:pos+length]) } pos += length if vlen&0x80 != 0x80 { break } } length = int(bin[pos]) if 1+length >= binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ stamp.ProviderName = string(bin[pos : pos+length]) pos += length length = int(bin[pos]) if length >= binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ stamp.Path = string(bin[pos : pos+length]) pos += length if pos != binLen { return stamp, errors.New("Invalid stamp (garbage after end)") } if len(stamp.ServerAddrStr) > 0 { colIndex := strings.LastIndex(stamp.ServerAddrStr, ":") bracketIndex := strings.LastIndex(stamp.ServerAddrStr, "]") if colIndex < bracketIndex { colIndex = -1 } if colIndex < 0 { colIndex = len(stamp.ServerAddrStr) stamp.ServerAddrStr = fmt.Sprintf("%s:%d", stamp.ServerAddrStr, DefaultPort) } if colIndex >= len(stamp.ServerAddrStr)-1 { return stamp, errors.New("Invalid stamp (empty port)") } ipOnly := stamp.ServerAddrStr[:colIndex] portOnly := stamp.ServerAddrStr[colIndex+1:] if _, err := strconv.ParseUint(portOnly, 10, 16); err != nil { return stamp, errors.New("Invalid stamp (port range)") } if net.ParseIP(strings.TrimRight(strings.TrimLeft(ipOnly, "["), "]")) == nil { return stamp, errors.New("Invalid stamp (IP address)") } } return stamp, nil } func (stamp *ServerStamp) String() string { if stamp.Proto == StampProtoTypeDNSCrypt { return stamp.dnsCryptString() } else if stamp.Proto == StampProtoTypeDoH { return stamp.dohString() } else if stamp.Proto == StampProtoTypeODoHTarget { return stamp.oDohTargetString() } else if stamp.Proto == StampProtoTypeDNSCryptRelay { return stamp.dnsCryptRelayString() } else if stamp.Proto == StampProtoTypeODoHRelay { return stamp.oDohRelayString() } panic("Unsupported protocol") } func (stamp *ServerStamp) dnsCryptString() string { bin := make([]uint8, 9) bin[0] = uint8(StampProtoTypeDNSCrypt) binary.LittleEndian.PutUint64(bin[1:9], uint64(stamp.Props)) serverAddrStr := stamp.ServerAddrStr if strings.HasSuffix(serverAddrStr, ":"+strconv.Itoa(DefaultPort)) { serverAddrStr = serverAddrStr[:len(serverAddrStr)-1-len(strconv.Itoa(DefaultPort))] } bin = append(bin, uint8(len(serverAddrStr))) bin = append(bin, []uint8(serverAddrStr)...) bin = append(bin, uint8(len(stamp.ServerPk))) bin = append(bin, stamp.ServerPk...) bin = append(bin, uint8(len(stamp.ProviderName))) bin = append(bin, []uint8(stamp.ProviderName)...) str := base64.RawURLEncoding.EncodeToString(bin) return "sdns://" + str } func (stamp *ServerStamp) dohString() string { bin := make([]uint8, 9) bin[0] = uint8(StampProtoTypeDoH) binary.LittleEndian.PutUint64(bin[1:9], uint64(stamp.Props)) serverAddrStr := stamp.ServerAddrStr if strings.HasSuffix(serverAddrStr, ":"+strconv.Itoa(DefaultPort)) { serverAddrStr = serverAddrStr[:len(serverAddrStr)-1-len(strconv.Itoa(DefaultPort))] } bin = append(bin, uint8(len(serverAddrStr))) bin = append(bin, []uint8(serverAddrStr)...) if len(stamp.Hashes) == 0 { bin = append(bin, uint8(0)) } else { last := len(stamp.Hashes) - 1 for i, hash := range stamp.Hashes { vlen := len(hash) if i < last { vlen |= 0x80 } bin = append(bin, uint8(vlen)) bin = append(bin, hash...) } } bin = append(bin, uint8(len(stamp.ProviderName))) bin = append(bin, []uint8(stamp.ProviderName)...) bin = append(bin, uint8(len(stamp.Path))) bin = append(bin, []uint8(stamp.Path)...) str := base64.RawURLEncoding.EncodeToString(bin) return "sdns://" + str } func (stamp *ServerStamp) oDohTargetString() string { bin := make([]uint8, 9) bin[0] = uint8(StampProtoTypeODoHTarget) binary.LittleEndian.PutUint64(bin[1:9], uint64(stamp.Props)) bin = append(bin, uint8(len(stamp.ProviderName))) bin = append(bin, []uint8(stamp.ProviderName)...) bin = append(bin, uint8(len(stamp.Path))) bin = append(bin, []uint8(stamp.Path)...) str := base64.RawURLEncoding.EncodeToString(bin) return "sdns://" + str } func (stamp *ServerStamp) dnsCryptRelayString() string { bin := make([]uint8, 1) bin[0] = uint8(StampProtoTypeDNSCryptRelay) serverAddrStr := stamp.ServerAddrStr if strings.HasSuffix(serverAddrStr, ":"+strconv.Itoa(DefaultPort)) { serverAddrStr = serverAddrStr[:len(serverAddrStr)-1-len(strconv.Itoa(DefaultPort))] } bin = append(bin, uint8(len(serverAddrStr))) bin = append(bin, []uint8(serverAddrStr)...) str := base64.RawURLEncoding.EncodeToString(bin) return "sdns://" + str } func (stamp *ServerStamp) oDohRelayString() string { bin := make([]uint8, 9) bin[0] = uint8(StampProtoTypeODoHRelay) binary.LittleEndian.PutUint64(bin[1:9], uint64(stamp.Props)) serverAddrStr := stamp.ServerAddrStr if strings.HasSuffix(serverAddrStr, ":"+strconv.Itoa(DefaultPort)) { serverAddrStr = serverAddrStr[:len(serverAddrStr)-1-len(strconv.Itoa(DefaultPort))] } bin = append(bin, uint8(len(serverAddrStr))) bin = append(bin, []uint8(serverAddrStr)...) if len(stamp.Hashes) == 0 { bin = append(bin, uint8(0)) } else { last := len(stamp.Hashes) - 1 for i, hash := range stamp.Hashes { vlen := len(hash) if i < last { vlen |= 0x80 } bin = append(bin, uint8(vlen)) bin = append(bin, hash...) } } bin = append(bin, uint8(len(stamp.ProviderName))) bin = append(bin, []uint8(stamp.ProviderName)...) bin = append(bin, uint8(len(stamp.Path))) bin = append(bin, []uint8(stamp.Path)...) str := base64.RawURLEncoding.EncodeToString(bin) return "sdns://" + str } go-dnsstamps-0.1.3/dnsstamps_test.go000066400000000000000000000062231376466357600175660ustar00rootroot00000000000000package dnsstamps import ( "encoding/hex" "strings" "testing" ) var ( pk1 []byte ) func init() { var err error // generated with: // openssl x509 -noout -fingerprint -sha256 -inform pem -in /etc/ssl/certs/Go_Daddy_Class_2_CA.pem pkStr := "C3:84:6B:F2:4B:9E:93:CA:64:27:4C:0E:C6:7C:1E:CC:5E:02:4F:FC:AC:D2:D7:40:19:35:0E:81:FE:54:6A:E4" pk1, err = hex.DecodeString(strings.Replace(pkStr, ":", "", -1)) if err != nil { panic(err) } if len(pk1) != 32 { panic("invalid public key fingerprint") } } func TestDnscryptStamp(t *testing.T) { // same as exampleStamp in dnscrypt-stamper const expected = `sdns://AQcAAAAAAAAACTEyNy4wLjAuMSDDhGvyS56TymQnTA7GfB7MXgJP_KzS10AZNQ6B_lRq5BkyLmRuc2NyeXB0LWNlcnQubG9jYWxob3N0` var stamp ServerStamp stamp.Props |= ServerInformalPropertyDNSSEC stamp.Props |= ServerInformalPropertyNoLog stamp.Props |= ServerInformalPropertyNoFilter stamp.Proto = StampProtoTypeDNSCrypt stamp.ServerAddrStr = "127.0.0.1" stamp.ProviderName = "2.dnscrypt-cert.localhost" stamp.ServerPk = pk1 stampStr := stamp.String() if stampStr != expected { t.Errorf("expected stamp %q but got instead %q", expected, stampStr) } parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverHTTP_NoHashes(t *testing.T) { const expected = `sdns://AgcAAAAAAAAACTEyNy4wLjAuMSDDhGvyS56TymQnTA7GfB7MXgJP_KzS10AZNQ6B_lRq5AtleGFtcGxlLmNvbQovZG5zLXF1ZXJ5` var stamp ServerStamp stamp.Props |= ServerInformalPropertyDNSSEC stamp.Props |= ServerInformalPropertyNoLog stamp.Props |= ServerInformalPropertyNoFilter stamp.ServerAddrStr = "127.0.0.1" stamp.Proto = StampProtoTypeDoH stamp.ProviderName = "example.com" stamp.Hashes = [][]uint8{pk1} stamp.Path = "/dns-query" stampStr := stamp.String() if stampStr != expected { t.Errorf("expected stamp %q but got instead %q", expected, stampStr) } parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverHTTP2_2(t *testing.T) { const q9 = `sdns://AgYAAAAAAAAACDkuOS45LjEwABJkbnM5LnF1YWQ5Lm5ldDo0NDMKL2Rucy1xdWVyeQ` parsedStamp, err := NewServerStampFromString(q9) if err != nil { t.Fatal(err) } ps := parsedStamp.String() if ps != q9 { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, q9) } } func TestODoHTarget(t *testing.T) { const stamp = `sdns://BQcAAAAAAAAAEG9kb2guZXhhbXBsZS5jb20HL3RhcmdldA` parsedStamp, err := NewServerStampFromString(stamp) if err != nil { t.Fatal(err) } ps := parsedStamp.String() if ps != stamp { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stamp) } } func TestODoHRelay(t *testing.T) { const stamp = `sdns://hQcAAAAAAAAAB1s6OjFdOjGCq80CASMPZG9oLmV4YW1wbGUuY29tBi9yZWxheQ` parsedStamp, err := NewServerStampFromString(stamp) if err != nil { t.Fatal(err) } ps := parsedStamp.String() if ps != stamp { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stamp) } } go-dnsstamps-0.1.3/go.mod000066400000000000000000000000611376466357600152640ustar00rootroot00000000000000module github.com/jedisct1/go-dnsstamps go 1.15