pax_global_header00006660000000000000000000000064151051425240014511gustar00rootroot0000000000000052 comment=191fc465df3113e6897bcc44b8fa23e2fd607804 go-dnsstamps-0.1.5/000077500000000000000000000000001510514252400141335ustar00rootroot00000000000000go-dnsstamps-0.1.5/.github/000077500000000000000000000000001510514252400154735ustar00rootroot00000000000000go-dnsstamps-0.1.5/.github/dependabot.yml000066400000000000000000000002211510514252400203160ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: gomod directory: "/" schedule: interval: daily time: "04:00" open-pull-requests-limit: 10 go-dnsstamps-0.1.5/.gitignore000066400000000000000000000004231510514252400161220ustar00rootroot00000000000000# 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.5/LICENSE000066400000000000000000000020611510514252400151370ustar00rootroot00000000000000MIT License Copyright (c) 2018-2023 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.5/README.md000066400000000000000000000001641510514252400154130ustar00rootroot00000000000000# go-dnsstamps DNS Stamps library for Go. ## [Documentation](https://pkg.go.dev/github.com/jedisct1/go-dnsstamps) go-dnsstamps-0.1.5/dnsstamps.go000066400000000000000000000675251510514252400165150ustar00rootroot00000000000000package dnsstamps import ( "encoding/base64" "encoding/binary" "encoding/hex" "errors" "fmt" "net" "strconv" "strings" ) const ( DefaultPort = 443 DefaultDoTPort = 853 DefaultDNSPort = 53 StampScheme = "sdns://" ) 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 BootstrapIPs []string } 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(StampProtoTypePlain) { return newPlainDNSServerStamp(bin) } else if bin[0] == uint8(StampProtoTypeDNSCrypt) { return newDNSCryptServerStamp(bin) } else if bin[0] == uint8(StampProtoTypeDoH) { return newDoHServerStamp(bin) } else if bin[0] == uint8(StampProtoTypeTLS) { return newDoTServerStamp(bin) } else if bin[0] == uint8(StampProtoTypeDoQ) { return newDoQServerStamp(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") } func NewRelayAndServerStampFromString(stampStr string) (ServerStamp, ServerStamp, error) { if !strings.HasPrefix(stampStr, StampScheme) { return ServerStamp{}, ServerStamp{}, errors.New("Stamps are expected to start with \"sdns://\"") } stampStr = stampStr[7:] parts := strings.Split(stampStr, "/") if len(parts) != 2 { return ServerStamp{}, ServerStamp{}, errors.New("This is not a relay+server stamp") } relayStamp, err := NewServerStampFromString(StampScheme + parts[0]) if err != nil { return ServerStamp{}, ServerStamp{}, err } serverStamp, err := NewServerStampFromString(StampScheme + parts[1]) if err != nil { return ServerStamp{}, ServerStamp{}, err } if relayStamp.Proto != StampProtoTypeDNSCryptRelay && relayStamp.Proto != StampProtoTypeODoHRelay { return ServerStamp{}, ServerStamp{}, errors.New("First stamp is not a relay") } if !(serverStamp.Proto != StampProtoTypeDNSCryptRelay && serverStamp.Proto != StampProtoTypeODoHRelay) { return ServerStamp{}, ServerStamp{}, errors.New("Second stamp is a relay") } return relayStamp, serverStamp, nil } // id(u8)=0x00 props 0x00 addrLen(1) serverAddr func newPlainDNSServerStamp(bin []byte) (ServerStamp, error) { stamp := ServerStamp{Proto: StampProtoTypePlain} if len(bin) < 1+8+1+1 { 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) // DefaultDNSPort stamp.ServerAddrStr = fmt.Sprintf("%s:%d", stamp.ServerAddrStr, DefaultDNSPort) } if colIndex >= len(stamp.ServerAddrStr)-1 { return stamp, errors.New("Invalid stamp (empty port)") } ipOnly := stamp.ServerAddrStr[:colIndex] if err := validatePort(stamp.ServerAddrStr[colIndex+1:]); 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)=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] if err := validatePort(stamp.ServerAddrStr[colIndex+1:]); 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) < 15 { 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 { if length != 32 { return stamp, errors.New("Invalid stamp (certificate hash must be 32 bytes)") } 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 // Parse optional bootstrap IP addresses (VLP format) if pos < binLen { for { if pos >= binLen { break } vlen := int(bin[pos]) length = vlen & ^0x80 if 1+length > binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ if length > 0 { bootstrapIP := string(bin[pos : pos+length]) stamp.BootstrapIPs = append(stamp.BootstrapIPs, bootstrapIP) } pos += length if vlen&0x80 != 0x80 { break } } } 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] if err := validatePort(stamp.ServerAddrStr[colIndex+1:]); 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)=0x03 props addrLen(1) serverAddr hashLen(1) hash hostNameLen(1) hostName [ bootstrapLen(1) bootstrap ] func newDoTServerStamp(bin []byte) (ServerStamp, error) { stamp := ServerStamp{Proto: StampProtoTypeTLS} 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 { if length != 32 { return stamp, errors.New("Invalid stamp (certificate hash must be 32 bytes)") } stamp.Hashes = append(stamp.Hashes, bin[pos:pos+length]) } pos += length if vlen&0x80 != 0x80 { break } } length = int(bin[pos]) if length >= binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ stamp.ProviderName = string(bin[pos : pos+length]) pos += length // Parse optional bootstrap IP addresses (VLP format) if pos < binLen { for { if pos >= binLen { break } vlen := int(bin[pos]) length = vlen & ^0x80 if 1+length > binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ if length > 0 { bootstrapIP := string(bin[pos : pos+length]) stamp.BootstrapIPs = append(stamp.BootstrapIPs, bootstrapIP) } pos += length if vlen&0x80 != 0x80 { break } } } 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, DefaultDoTPort) } if colIndex >= len(stamp.ServerAddrStr)-1 { return stamp, errors.New("Invalid stamp (empty port)") } ipOnly := stamp.ServerAddrStr[:colIndex] if err := validatePort(stamp.ServerAddrStr[colIndex+1:]); err != nil { return stamp, errors.New("Invalid stamp (port range)") } if ipOnly != "" && net.ParseIP(strings.TrimRight(strings.TrimLeft(ipOnly, "["), "]")) == nil { return stamp, errors.New("Invalid stamp (IP address)") } } return stamp, nil } // id(u8)=0x04 props addrLen(1) serverAddr hashLen(1) hash hostNameLen(1) hostName [ bootstrapLen(1) bootstrap ] func newDoQServerStamp(bin []byte) (ServerStamp, error) { stamp := ServerStamp{Proto: StampProtoTypeDoQ} 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 { if length != 32 { return stamp, errors.New("Invalid stamp (certificate hash must be 32 bytes)") } stamp.Hashes = append(stamp.Hashes, bin[pos:pos+length]) } pos += length if vlen&0x80 != 0x80 { break } } length = int(bin[pos]) if length >= binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ stamp.ProviderName = string(bin[pos : pos+length]) pos += length // Parse optional bootstrap IP addresses (VLP format) if pos < binLen { for { if pos >= binLen { break } vlen := int(bin[pos]) length = vlen & ^0x80 if 1+length > binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ if length > 0 { bootstrapIP := string(bin[pos : pos+length]) stamp.BootstrapIPs = append(stamp.BootstrapIPs, bootstrapIP) } pos += length if vlen&0x80 != 0x80 { break } } } 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, DefaultDoTPort) } if colIndex >= len(stamp.ServerAddrStr)-1 { return stamp, errors.New("Invalid stamp (empty port)") } ipOnly := stamp.ServerAddrStr[:colIndex] if err := validatePort(stamp.ServerAddrStr[colIndex+1:]); err != nil { return stamp, errors.New("Invalid stamp (port range)") } if ipOnly != "" && 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) < 9 { 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] if err := validatePort(stamp.ServerAddrStr[colIndex+1:]); 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 { if length != 32 { return stamp, errors.New("Invalid stamp (certificate hash must be 32 bytes)") } 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 // Parse optional bootstrap IP addresses (VLP format) if pos < binLen { for { if pos >= binLen { break } vlen := int(bin[pos]) length = vlen & ^0x80 if 1+length > binLen-pos { return stamp, errors.New("Invalid stamp") } pos++ if length > 0 { bootstrapIP := string(bin[pos : pos+length]) stamp.BootstrapIPs = append(stamp.BootstrapIPs, bootstrapIP) } pos += length if vlen&0x80 != 0x80 { break } } } 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] if err := validatePort(stamp.ServerAddrStr[colIndex+1:]); 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 validatePort(port string) error { p, err := strconv.ParseUint(port, 10, 16) if err != nil || p == 0 { return errors.New("Invalid port") } return nil } func (stamp *ServerStamp) String() string { if stamp.Proto == StampProtoTypePlain { return stamp.plainStrng() } else if stamp.Proto == StampProtoTypeDNSCrypt { return stamp.dnsCryptString() } else if stamp.Proto == StampProtoTypeDoH { return stamp.dohString() } else if stamp.Proto == StampProtoTypeTLS { return stamp.dotString() } else if stamp.Proto == StampProtoTypeDoQ { return stamp.doqString() } 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) plainStrng() string { bin := make([]uint8, 9) bin[0] = uint8(StampProtoTypePlain) binary.LittleEndian.PutUint64(bin[1:9], uint64(stamp.Props)) serverAddrStr := stamp.ServerAddrStr if strings.HasSuffix(serverAddrStr, ":"+strconv.Itoa(DefaultDNSPort)) { serverAddrStr = serverAddrStr[:len(serverAddrStr)-1-len(strconv.Itoa(DefaultDNSPort))] } bin = append(bin, uint8(len(serverAddrStr))) bin = append(bin, []uint8(serverAddrStr)...) str := base64.RawURLEncoding.EncodeToString(bin) return StampScheme + str } 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 StampScheme + 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)...) // Serialize optional bootstrap IP addresses (VLP format) if len(stamp.BootstrapIPs) > 0 { last := len(stamp.BootstrapIPs) - 1 for i, bootstrapIP := range stamp.BootstrapIPs { vlen := len(bootstrapIP) if i < last { vlen |= 0x80 } bin = append(bin, uint8(vlen)) bin = append(bin, []uint8(bootstrapIP)...) } } str := base64.RawURLEncoding.EncodeToString(bin) return StampScheme + str } func (stamp *ServerStamp) dotString() string { bin := make([]uint8, 9) bin[0] = uint8(StampProtoTypeTLS) binary.LittleEndian.PutUint64(bin[1:9], uint64(stamp.Props)) serverAddrStr := stamp.ServerAddrStr if strings.HasSuffix(serverAddrStr, ":"+strconv.Itoa(DefaultDoTPort)) { serverAddrStr = serverAddrStr[:len(serverAddrStr)-1-len(strconv.Itoa(DefaultDoTPort))] } 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)...) // Serialize optional bootstrap IP addresses (VLP format) if len(stamp.BootstrapIPs) > 0 { last := len(stamp.BootstrapIPs) - 1 for i, bootstrapIP := range stamp.BootstrapIPs { vlen := len(bootstrapIP) if i < last { vlen |= 0x80 } bin = append(bin, uint8(vlen)) bin = append(bin, []uint8(bootstrapIP)...) } } str := base64.RawURLEncoding.EncodeToString(bin) return StampScheme + str } func (stamp *ServerStamp) doqString() string { bin := make([]uint8, 9) bin[0] = uint8(StampProtoTypeDoQ) binary.LittleEndian.PutUint64(bin[1:9], uint64(stamp.Props)) serverAddrStr := stamp.ServerAddrStr if strings.HasSuffix(serverAddrStr, ":"+strconv.Itoa(DefaultDoTPort)) { serverAddrStr = serverAddrStr[:len(serverAddrStr)-1-len(strconv.Itoa(DefaultDoTPort))] } 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)...) // Serialize optional bootstrap IP addresses (VLP format) if len(stamp.BootstrapIPs) > 0 { last := len(stamp.BootstrapIPs) - 1 for i, bootstrapIP := range stamp.BootstrapIPs { vlen := len(bootstrapIP) if i < last { vlen |= 0x80 } bin = append(bin, uint8(vlen)) bin = append(bin, []uint8(bootstrapIP)...) } } str := base64.RawURLEncoding.EncodeToString(bin) return StampScheme + 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 StampScheme + 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 StampScheme + 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)...) // Serialize optional bootstrap IP addresses (VLP format) if len(stamp.BootstrapIPs) > 0 { last := len(stamp.BootstrapIPs) - 1 for i, bootstrapIP := range stamp.BootstrapIPs { vlen := len(bootstrapIP) if i < last { vlen |= 0x80 } bin = append(bin, uint8(vlen)) bin = append(bin, []uint8(bootstrapIP)...) } } str := base64.RawURLEncoding.EncodeToString(bin) return StampScheme + str } go-dnsstamps-0.1.5/dnsstamps_test.go000066400000000000000000001002761510514252400175430ustar00rootroot00000000000000package 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://hQcAAAAAAAAAB1s6OjFdOjEgw4Rr8kuek8pkJ0wOxnwezF4CT_ys0tdAGTUOgf5UauQPZG9oLmV4YW1wbGUuY29tBi9yZWxheQ` 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 TestRelayServerPair(t *testing.T) { const stamp = `sdns://hQcAAAAAAAAAB1s6OjFdOjEgw4Rr8kuek8pkJ0wOxnwezF4CT_ys0tdAGTUOgf5UauQPZG9oLmV4YW1wbGUuY29tBi9yZWxheQ/BQEAAAAAAAAAEG9kb2guZXhhbXBsZS5jb20HL3RhcmdldA` _, _, err := NewRelayAndServerStampFromString(stamp) if err != nil { t.Fatal(err) } } func TestPlainOldDNS(t *testing.T) { // [DNSSEC|No Filter|No Log] + 8.8.8.8 (no port) const stamp = `sdns://AAcAAAAAAAAABzguOC44Ljg` parsedStamp, err := NewServerStampFromString(stamp) if err != nil { t.Fatal(err) } if parsedStamp.ServerAddrStr != "8.8.8.8:53" { t.Errorf("expected server address 8.8.8.8 but got %q", parsedStamp.ServerAddrStr) } ps := parsedStamp.String() if ps != stamp { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stamp) } } func TestPlainOldDNSWithPort(t *testing.T) { // [DNSSEC|No Filter|No Log] + 8.8.8.8:8053 const stamp = `sdns://AAcAAAAAAAAADDguOC44Ljg6ODA1Mw` parsedStamp, err := NewServerStampFromString(stamp) if err != nil { t.Fatal(err) } if parsedStamp.ServerAddrStr != "8.8.8.8:8053" { t.Errorf("expected server address 8.8.8.8 but got %q", parsedStamp.ServerAddrStr) } ps := parsedStamp.String() if ps != stamp { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stamp) } } func TestDNSOverTLS_Basic(t *testing.T) { // [DNSSEC|No Filter|No Log] + 127.0.0.1 + hash + dns.example.com const expected = `sdns://AwcAAAAAAAAACTEyNy4wLjAuMSDDhGvyS56TymQnTA7GfB7MXgJP_KzS10AZNQ6B_lRq5A9kbnMuZXhhbXBsZS5jb20` var stamp ServerStamp stamp.Props |= ServerInformalPropertyDNSSEC stamp.Props |= ServerInformalPropertyNoLog stamp.Props |= ServerInformalPropertyNoFilter stamp.Proto = StampProtoTypeTLS stamp.ServerAddrStr = "127.0.0.1" stamp.ProviderName = "dns.example.com" stamp.Hashes = [][]uint8{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 TestDNSOverTLS_DefaultPort(t *testing.T) { // Test that default port 853 is added correctly var stamp ServerStamp stamp.Proto = StampProtoTypeTLS stamp.ServerAddrStr = "1.1.1.1" stamp.ProviderName = "cloudflare-dns.com" stamp.Hashes = [][]uint8{pk1} stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Should have default port 853 added if parsedStamp.ServerAddrStr != "1.1.1.1:853" { t.Errorf("expected server address 1.1.1.1:853 but got %q", parsedStamp.ServerAddrStr) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverTLS_CustomPort(t *testing.T) { // Test with custom port 8853 const expected = `sdns://AwcAAAAAAAAADDEuMS4xLjE6ODg1MyDDhGvyS56TymQnTA7GfB7MXgJP_KzS10AZNQ6B_lRq5BJjbG91ZGZsYXJlLWRucy5jb20` var stamp ServerStamp stamp.Props |= ServerInformalPropertyDNSSEC stamp.Props |= ServerInformalPropertyNoLog stamp.Props |= ServerInformalPropertyNoFilter stamp.Proto = StampProtoTypeTLS stamp.ServerAddrStr = "1.1.1.1:8853" stamp.ProviderName = "cloudflare-dns.com" stamp.Hashes = [][]uint8{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) } if parsedStamp.ServerAddrStr != "1.1.1.1:8853" { t.Errorf("expected server address 1.1.1.1:8853 but got %q", parsedStamp.ServerAddrStr) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverTLS_NoHashes(t *testing.T) { // Test DoT stamp without certificate hashes var stamp ServerStamp stamp.Proto = StampProtoTypeTLS stamp.ServerAddrStr = "9.9.9.9" stamp.ProviderName = "dns.quad9.net" stamp.Props = ServerInformalPropertyDNSSEC | ServerInformalPropertyNoFilter stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify parsed stamp matches original if parsedStamp.Proto != StampProtoTypeTLS { t.Errorf("expected proto TLS but got %v", parsedStamp.Proto) } if parsedStamp.ServerAddrStr != "9.9.9.9:853" { t.Errorf("expected server address 9.9.9.9:853 but got %q", parsedStamp.ServerAddrStr) } if parsedStamp.ProviderName != "dns.quad9.net" { t.Errorf("expected provider name dns.quad9.net but got %q", parsedStamp.ProviderName) } if len(parsedStamp.Hashes) != 0 { t.Errorf("expected no hashes but got %d", len(parsedStamp.Hashes)) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverTLS_MultipleHashes(t *testing.T) { // Test with multiple certificate hashes hash1 := pk1 hash2 := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20} var stamp ServerStamp stamp.Proto = StampProtoTypeTLS stamp.ServerAddrStr = "8.8.8.8" stamp.ProviderName = "dns.google" stamp.Hashes = [][]uint8{hash1, hash2} stamp.Props = ServerInformalPropertyDNSSEC stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify multiple hashes were parsed correctly if len(parsedStamp.Hashes) != 2 { t.Errorf("expected 2 hashes but got %d", len(parsedStamp.Hashes)) } if len(parsedStamp.Hashes[0]) != 32 { t.Errorf("expected first hash to be 32 bytes but got %d", len(parsedStamp.Hashes[0])) } if len(parsedStamp.Hashes[1]) != 32 { t.Errorf("expected second hash to be 32 bytes but got %d", len(parsedStamp.Hashes[1])) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverTLS_IPv6(t *testing.T) { // Test with IPv6 address var stamp ServerStamp stamp.Proto = StampProtoTypeTLS stamp.ServerAddrStr = "[2001:4860:4860::8888]" stamp.ProviderName = "dns.google" stamp.Hashes = [][]uint8{pk1} stamp.Props = ServerInformalPropertyDNSSEC stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Should have default port 853 added if parsedStamp.ServerAddrStr != "[2001:4860:4860::8888]:853" { t.Errorf("expected server address [2001:4860:4860::8888]:853 but got %q", parsedStamp.ServerAddrStr) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverTLS_AllProps(t *testing.T) { // Test with all properties set var stamp ServerStamp stamp.Props = ServerInformalPropertyDNSSEC | ServerInformalPropertyNoLog | ServerInformalPropertyNoFilter stamp.Proto = StampProtoTypeTLS stamp.ServerAddrStr = "1.0.0.1" stamp.ProviderName = "one.one.one.one" stamp.Hashes = [][]uint8{pk1} stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify all properties were preserved if parsedStamp.Props != stamp.Props { t.Errorf("expected props %v but got %v", stamp.Props, parsedStamp.Props) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverQUIC_Basic(t *testing.T) { // [DNSSEC|No Filter|No Log] + 127.0.0.1 + hash + dns.example.com const expected = `sdns://BAcAAAAAAAAACTEyNy4wLjAuMSDDhGvyS56TymQnTA7GfB7MXgJP_KzS10AZNQ6B_lRq5A9kbnMuZXhhbXBsZS5jb20` var stamp ServerStamp stamp.Props |= ServerInformalPropertyDNSSEC stamp.Props |= ServerInformalPropertyNoLog stamp.Props |= ServerInformalPropertyNoFilter stamp.Proto = StampProtoTypeDoQ stamp.ServerAddrStr = "127.0.0.1" stamp.ProviderName = "dns.example.com" stamp.Hashes = [][]uint8{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 TestDNSOverQUIC_DefaultPort(t *testing.T) { // Test that default port 853 is added correctly var stamp ServerStamp stamp.Proto = StampProtoTypeDoQ stamp.ServerAddrStr = "1.1.1.1" stamp.ProviderName = "cloudflare-dns.com" stamp.Hashes = [][]uint8{pk1} stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Should have default port 853 added if parsedStamp.ServerAddrStr != "1.1.1.1:853" { t.Errorf("expected server address 1.1.1.1:853 but got %q", parsedStamp.ServerAddrStr) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverQUIC_CustomPort(t *testing.T) { // Test with custom port 8853 const expected = `sdns://BAcAAAAAAAAADDEuMS4xLjE6ODg1MyDDhGvyS56TymQnTA7GfB7MXgJP_KzS10AZNQ6B_lRq5BJjbG91ZGZsYXJlLWRucy5jb20` var stamp ServerStamp stamp.Props |= ServerInformalPropertyDNSSEC stamp.Props |= ServerInformalPropertyNoLog stamp.Props |= ServerInformalPropertyNoFilter stamp.Proto = StampProtoTypeDoQ stamp.ServerAddrStr = "1.1.1.1:8853" stamp.ProviderName = "cloudflare-dns.com" stamp.Hashes = [][]uint8{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) } if parsedStamp.ServerAddrStr != "1.1.1.1:8853" { t.Errorf("expected server address 1.1.1.1:8853 but got %q", parsedStamp.ServerAddrStr) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverQUIC_NoHashes(t *testing.T) { // Test DoQ stamp without certificate hashes var stamp ServerStamp stamp.Proto = StampProtoTypeDoQ stamp.ServerAddrStr = "9.9.9.9" stamp.ProviderName = "dns.quad9.net" stamp.Props = ServerInformalPropertyDNSSEC | ServerInformalPropertyNoFilter stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify parsed stamp matches original if parsedStamp.Proto != StampProtoTypeDoQ { t.Errorf("expected proto DoQ but got %v", parsedStamp.Proto) } if parsedStamp.ServerAddrStr != "9.9.9.9:853" { t.Errorf("expected server address 9.9.9.9:853 but got %q", parsedStamp.ServerAddrStr) } if parsedStamp.ProviderName != "dns.quad9.net" { t.Errorf("expected provider name dns.quad9.net but got %q", parsedStamp.ProviderName) } if len(parsedStamp.Hashes) != 0 { t.Errorf("expected no hashes but got %d", len(parsedStamp.Hashes)) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverQUIC_MultipleHashes(t *testing.T) { // Test with multiple certificate hashes hash1 := pk1 hash2 := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20} var stamp ServerStamp stamp.Proto = StampProtoTypeDoQ stamp.ServerAddrStr = "8.8.8.8" stamp.ProviderName = "dns.google" stamp.Hashes = [][]uint8{hash1, hash2} stamp.Props = ServerInformalPropertyDNSSEC stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify multiple hashes were parsed correctly if len(parsedStamp.Hashes) != 2 { t.Errorf("expected 2 hashes but got %d", len(parsedStamp.Hashes)) } if len(parsedStamp.Hashes[0]) != 32 { t.Errorf("expected first hash to be 32 bytes but got %d", len(parsedStamp.Hashes[0])) } if len(parsedStamp.Hashes[1]) != 32 { t.Errorf("expected second hash to be 32 bytes but got %d", len(parsedStamp.Hashes[1])) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverQUIC_IPv6(t *testing.T) { // Test with IPv6 address var stamp ServerStamp stamp.Proto = StampProtoTypeDoQ stamp.ServerAddrStr = "[2001:4860:4860::8888]" stamp.ProviderName = "dns.google" stamp.Hashes = [][]uint8{pk1} stamp.Props = ServerInformalPropertyDNSSEC stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Should have default port 853 added if parsedStamp.ServerAddrStr != "[2001:4860:4860::8888]:853" { t.Errorf("expected server address [2001:4860:4860::8888]:853 but got %q", parsedStamp.ServerAddrStr) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverQUIC_AllProps(t *testing.T) { // Test with all properties set var stamp ServerStamp stamp.Props = ServerInformalPropertyDNSSEC | ServerInformalPropertyNoLog | ServerInformalPropertyNoFilter stamp.Proto = StampProtoTypeDoQ stamp.ServerAddrStr = "1.0.0.1" stamp.ProviderName = "one.one.one.one" stamp.Hashes = [][]uint8{pk1} stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify all properties were preserved if parsedStamp.Props != stamp.Props { t.Errorf("expected props %v but got %v", stamp.Props, parsedStamp.Props) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } // Bootstrap IP address tests for DoT func TestDNSOverTLS_SingleBootstrapIP(t *testing.T) { // Test DoT stamp with single bootstrap IP address var stamp ServerStamp stamp.Props = ServerInformalPropertyDNSSEC stamp.Proto = StampProtoTypeTLS stamp.ServerAddrStr = "1.1.1.1" stamp.ProviderName = "cloudflare-dns.com" stamp.Hashes = [][]uint8{pk1} stamp.BootstrapIPs = []string{"1.1.1.1"} stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify bootstrap IPs were parsed correctly if len(parsedStamp.BootstrapIPs) != 1 { t.Errorf("expected 1 bootstrap IP but got %d", len(parsedStamp.BootstrapIPs)) } if parsedStamp.BootstrapIPs[0] != "1.1.1.1" { t.Errorf("expected bootstrap IP 1.1.1.1 but got %q", parsedStamp.BootstrapIPs[0]) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverTLS_MultipleBootstrapIPs(t *testing.T) { // Test with multiple bootstrap IP addresses (IPv4 and IPv6) var stamp ServerStamp stamp.Props = ServerInformalPropertyDNSSEC | ServerInformalPropertyNoLog stamp.Proto = StampProtoTypeTLS stamp.ServerAddrStr = "9.9.9.9" stamp.ProviderName = "dns.quad9.net" stamp.Hashes = [][]uint8{pk1} stamp.BootstrapIPs = []string{"9.9.9.9", "149.112.112.112", "2620:fe::fe"} stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify all bootstrap IPs were parsed correctly if len(parsedStamp.BootstrapIPs) != 3 { t.Errorf("expected 3 bootstrap IPs but got %d", len(parsedStamp.BootstrapIPs)) } expectedIPs := []string{"9.9.9.9", "149.112.112.112", "2620:fe::fe"} for i, expectedIP := range expectedIPs { if parsedStamp.BootstrapIPs[i] != expectedIP { t.Errorf("expected bootstrap IP %q at index %d but got %q", expectedIP, i, parsedStamp.BootstrapIPs[i]) } } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverTLS_BootstrapIPsWithAllProps(t *testing.T) { // Test with bootstrap IPs and all properties var stamp ServerStamp stamp.Props = ServerInformalPropertyDNSSEC | ServerInformalPropertyNoLog | ServerInformalPropertyNoFilter stamp.Proto = StampProtoTypeTLS stamp.ServerAddrStr = "8.8.8.8:8853" stamp.ProviderName = "dns.google" stamp.Hashes = [][]uint8{pk1} stamp.BootstrapIPs = []string{"8.8.8.8", "8.8.4.4"} stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify all fields were preserved if parsedStamp.Props != stamp.Props { t.Errorf("expected props %v but got %v", stamp.Props, parsedStamp.Props) } if parsedStamp.ServerAddrStr != "8.8.8.8:8853" { t.Errorf("expected server address 8.8.8.8:8853 but got %q", parsedStamp.ServerAddrStr) } if len(parsedStamp.BootstrapIPs) != 2 { t.Errorf("expected 2 bootstrap IPs but got %d", len(parsedStamp.BootstrapIPs)) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverTLS_NoBootstrapIPs_BackwardCompatibility(t *testing.T) { // Test that DoT stamps without bootstrap IPs still work (backward compatibility) var stamp ServerStamp stamp.Props = ServerInformalPropertyDNSSEC stamp.Proto = StampProtoTypeTLS stamp.ServerAddrStr = "1.0.0.1" stamp.ProviderName = "one.one.one.one" stamp.Hashes = [][]uint8{pk1} // No bootstrap IPs set stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify no bootstrap IPs if len(parsedStamp.BootstrapIPs) != 0 { t.Errorf("expected 0 bootstrap IPs but got %d", len(parsedStamp.BootstrapIPs)) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverTLS_BootstrapIPv6Only(t *testing.T) { // Test with IPv6-only bootstrap addresses var stamp ServerStamp stamp.Proto = StampProtoTypeTLS stamp.ServerAddrStr = "[2606:4700:4700::1111]" stamp.ProviderName = "cloudflare-dns.com" stamp.Hashes = [][]uint8{pk1} stamp.BootstrapIPs = []string{"2606:4700:4700::1111", "2606:4700:4700::1001"} stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify IPv6 bootstrap IPs if len(parsedStamp.BootstrapIPs) != 2 { t.Errorf("expected 2 bootstrap IPs but got %d", len(parsedStamp.BootstrapIPs)) } if parsedStamp.BootstrapIPs[0] != "2606:4700:4700::1111" { t.Errorf("expected first bootstrap IP 2606:4700:4700::1111 but got %q", parsedStamp.BootstrapIPs[0]) } if parsedStamp.BootstrapIPs[1] != "2606:4700:4700::1001" { t.Errorf("expected second bootstrap IP 2606:4700:4700::1001 but got %q", parsedStamp.BootstrapIPs[1]) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } // Bootstrap IP address tests for DoQ func TestDNSOverQUIC_SingleBootstrapIP(t *testing.T) { // Test DoQ stamp with single bootstrap IP address var stamp ServerStamp stamp.Props = ServerInformalPropertyDNSSEC stamp.Proto = StampProtoTypeDoQ stamp.ServerAddrStr = "1.1.1.1" stamp.ProviderName = "cloudflare-dns.com" stamp.Hashes = [][]uint8{pk1} stamp.BootstrapIPs = []string{"1.1.1.1"} stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify bootstrap IPs were parsed correctly if len(parsedStamp.BootstrapIPs) != 1 { t.Errorf("expected 1 bootstrap IP but got %d", len(parsedStamp.BootstrapIPs)) } if parsedStamp.BootstrapIPs[0] != "1.1.1.1" { t.Errorf("expected bootstrap IP 1.1.1.1 but got %q", parsedStamp.BootstrapIPs[0]) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverQUIC_MultipleBootstrapIPs(t *testing.T) { // Test with multiple bootstrap IP addresses (IPv4 and IPv6) var stamp ServerStamp stamp.Props = ServerInformalPropertyDNSSEC | ServerInformalPropertyNoLog stamp.Proto = StampProtoTypeDoQ stamp.ServerAddrStr = "9.9.9.9" stamp.ProviderName = "dns.quad9.net" stamp.Hashes = [][]uint8{pk1} stamp.BootstrapIPs = []string{"9.9.9.9", "149.112.112.112", "2620:fe::fe"} stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify all bootstrap IPs were parsed correctly if len(parsedStamp.BootstrapIPs) != 3 { t.Errorf("expected 3 bootstrap IPs but got %d", len(parsedStamp.BootstrapIPs)) } expectedIPs := []string{"9.9.9.9", "149.112.112.112", "2620:fe::fe"} for i, expectedIP := range expectedIPs { if parsedStamp.BootstrapIPs[i] != expectedIP { t.Errorf("expected bootstrap IP %q at index %d but got %q", expectedIP, i, parsedStamp.BootstrapIPs[i]) } } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverQUIC_BootstrapIPsWithAllProps(t *testing.T) { // Test with bootstrap IPs and all properties var stamp ServerStamp stamp.Props = ServerInformalPropertyDNSSEC | ServerInformalPropertyNoLog | ServerInformalPropertyNoFilter stamp.Proto = StampProtoTypeDoQ stamp.ServerAddrStr = "8.8.8.8:8853" stamp.ProviderName = "dns.google" stamp.Hashes = [][]uint8{pk1} stamp.BootstrapIPs = []string{"8.8.8.8", "8.8.4.4"} stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify all fields were preserved if parsedStamp.Props != stamp.Props { t.Errorf("expected props %v but got %v", stamp.Props, parsedStamp.Props) } if parsedStamp.ServerAddrStr != "8.8.8.8:8853" { t.Errorf("expected server address 8.8.8.8:8853 but got %q", parsedStamp.ServerAddrStr) } if len(parsedStamp.BootstrapIPs) != 2 { t.Errorf("expected 2 bootstrap IPs but got %d", len(parsedStamp.BootstrapIPs)) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverQUIC_NoBootstrapIPs_BackwardCompatibility(t *testing.T) { // Test that DoQ stamps without bootstrap IPs still work (backward compatibility) var stamp ServerStamp stamp.Props = ServerInformalPropertyDNSSEC stamp.Proto = StampProtoTypeDoQ stamp.ServerAddrStr = "1.0.0.1" stamp.ProviderName = "one.one.one.one" stamp.Hashes = [][]uint8{pk1} // No bootstrap IPs set stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify no bootstrap IPs if len(parsedStamp.BootstrapIPs) != 0 { t.Errorf("expected 0 bootstrap IPs but got %d", len(parsedStamp.BootstrapIPs)) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } func TestDNSOverQUIC_BootstrapIPv6Only(t *testing.T) { // Test with IPv6-only bootstrap addresses var stamp ServerStamp stamp.Proto = StampProtoTypeDoQ stamp.ServerAddrStr = "[2606:4700:4700::1111]" stamp.ProviderName = "cloudflare-dns.com" stamp.Hashes = [][]uint8{pk1} stamp.BootstrapIPs = []string{"2606:4700:4700::1111", "2606:4700:4700::1001"} stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatal(err) } // Verify IPv6 bootstrap IPs if len(parsedStamp.BootstrapIPs) != 2 { t.Errorf("expected 2 bootstrap IPs but got %d", len(parsedStamp.BootstrapIPs)) } if parsedStamp.BootstrapIPs[0] != "2606:4700:4700::1111" { t.Errorf("expected first bootstrap IP 2606:4700:4700::1111 but got %q", parsedStamp.BootstrapIPs[0]) } if parsedStamp.BootstrapIPs[1] != "2606:4700:4700::1001" { t.Errorf("expected second bootstrap IP 2606:4700:4700::1001 but got %q", parsedStamp.BootstrapIPs[1]) } ps := parsedStamp.String() if ps != stampStr { t.Errorf("re-parsed stamp string is %q, but %q expected", ps, stampStr) } } // Hash length validation tests func TestDoH_InvalidHashLength_TooShort(t *testing.T) { // Create a DoH stamp with 16-byte hash (should be rejected) var stamp ServerStamp stamp.Proto = StampProtoTypeDoH stamp.ServerAddrStr = "1.1.1.1" stamp.ProviderName = "cloudflare-dns.com" stamp.Path = "/dns-query" // 16-byte hash instead of required 32 bytes stamp.Hashes = [][]uint8{{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}} stampStr := stamp.String() _, err := NewServerStampFromString(stampStr) if err == nil { t.Error("expected error for 16-byte hash, but got none") } if err != nil && err.Error() != "Invalid stamp (certificate hash must be 32 bytes)" { t.Errorf("expected certificate hash error, got: %v", err) } } func TestDoH_InvalidHashLength_TooLong(t *testing.T) { // Create a DoH stamp with 48-byte hash (should be rejected) var stamp ServerStamp stamp.Proto = StampProtoTypeDoH stamp.ServerAddrStr = "8.8.8.8" stamp.ProviderName = "dns.google" stamp.Path = "/dns-query" // 48-byte hash (oversized) stamp.Hashes = [][]uint8{{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, }} stampStr := stamp.String() _, err := NewServerStampFromString(stampStr) if err == nil { t.Error("expected error for 48-byte hash, but got none") } if err != nil && err.Error() != "Invalid stamp (certificate hash must be 32 bytes)" { t.Errorf("expected certificate hash error, got: %v", err) } } func TestDoT_InvalidHashLength(t *testing.T) { // Test DoT with invalid hash length var stamp ServerStamp stamp.Proto = StampProtoTypeTLS stamp.ServerAddrStr = "9.9.9.9" stamp.ProviderName = "dns.quad9.net" // 20-byte hash (wrong size) stamp.Hashes = [][]uint8{{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, }} stampStr := stamp.String() _, err := NewServerStampFromString(stampStr) if err == nil { t.Error("expected error for 20-byte hash, but got none") } if err != nil && err.Error() != "Invalid stamp (certificate hash must be 32 bytes)" { t.Errorf("expected certificate hash error, got: %v", err) } } func TestDoQ_InvalidHashLength(t *testing.T) { // Test DoQ with 1-byte hash var stamp ServerStamp stamp.Proto = StampProtoTypeDoQ stamp.ServerAddrStr = "1.0.0.1" stamp.ProviderName = "cloudflare-dns.com" stamp.Hashes = [][]uint8{{0xFF}} // Just 1 byte stampStr := stamp.String() _, err := NewServerStampFromString(stampStr) if err == nil { t.Error("expected error for 1-byte hash, but got none") } if err != nil && err.Error() != "Invalid stamp (certificate hash must be 32 bytes)" { t.Errorf("expected certificate hash error, got: %v", err) } } func TestODoHRelay_InvalidHashLength(t *testing.T) { // Test ODoH Relay with invalid hash length var stamp ServerStamp stamp.Proto = StampProtoTypeODoHRelay stamp.ServerAddrStr = "relay.example.com" stamp.ProviderName = "relay.example.com" stamp.Path = "/proxy" // 31-byte hash (one byte short) stamp.Hashes = [][]uint8{{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, }} stampStr := stamp.String() _, err := NewServerStampFromString(stampStr) if err == nil { t.Error("expected error for 31-byte hash, but got none") } if err != nil && err.Error() != "Invalid stamp (certificate hash must be 32 bytes)" { t.Errorf("expected certificate hash error, got: %v", err) } } func TestDoH_ValidHashLength_MultipleHashes(t *testing.T) { // Test that valid 32-byte hashes still work, including multiple hashes hash1 := pk1 // pk1 is 32 bytes from existing tests hash2 := []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, } var stamp ServerStamp stamp.Proto = StampProtoTypeDoH stamp.ServerAddrStr = "8.8.8.8" stamp.ProviderName = "dns.google" stamp.Path = "/dns-query" stamp.Hashes = [][]uint8{hash1, hash2} stampStr := stamp.String() parsedStamp, err := NewServerStampFromString(stampStr) if err != nil { t.Fatalf("unexpected error for valid 32-byte hashes: %v", err) } if len(parsedStamp.Hashes) != 2 { t.Errorf("expected 2 hashes but got %d", len(parsedStamp.Hashes)) } for i, hash := range parsedStamp.Hashes { if len(hash) != 32 { t.Errorf("hash %d: expected 32 bytes but got %d", i, len(hash)) } } } func TestDoT_InvalidHashLength_MultipleHashes_OneInvalid(t *testing.T) { // Test that if one hash in a multi-hash set is invalid, parsing fails hash1 := pk1 // Valid 32-byte hash hash2 := []byte{0x01, 0x02, 0x03} // Invalid 3-byte hash var stamp ServerStamp stamp.Proto = StampProtoTypeTLS stamp.ServerAddrStr = "1.1.1.1" stamp.ProviderName = "cloudflare-dns.com" stamp.Hashes = [][]uint8{hash1, hash2} stampStr := stamp.String() _, err := NewServerStampFromString(stampStr) if err == nil { t.Error("expected error when one of multiple hashes is invalid, but got none") } if err != nil && err.Error() != "Invalid stamp (certificate hash must be 32 bytes)" { t.Errorf("expected certificate hash error, got: %v", err) } } go-dnsstamps-0.1.5/go.mod000066400000000000000000000000611510514252400152360ustar00rootroot00000000000000module github.com/jedisct1/go-dnsstamps go 1.18