Compare commits

...

2 commits

Author SHA1 Message Date
DataHoarder 4bc524dd78
Add Cache-Control header, bump MI
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-07 00:20:18 +02:00
DataHoarder 8e8e2eff0f
Generalize signature and server types 2022-06-06 20:14:34 +02:00
4 changed files with 80 additions and 39 deletions

View file

@ -21,7 +21,6 @@ import (
"time" "time"
) )
var privateKey ed25519.PrivateKey
var publicKey ed25519.PublicKey var publicKey ed25519.PublicKey
var debugOutput = false var debugOutput = false
@ -57,14 +56,15 @@ func selectNextContentServer(skip []int) *content.Server {
return chooser.Pick().(*content.Server) return chooser.Pick().(*content.Server)
} }
func setOtherHeaders(ctx *httputils.RequestContext) { func setOtherHeaders(ctx httputils.RequestContext) {
ctx.SetResponseHeader("Cache-Control", "no-store")
ctx.SetResponseHeader("Server", "FinalCommander") ctx.SetResponseHeader("Server", "FinalCommander")
ctx.SetResponseHeader("Vary", "Content-Encoding") ctx.SetResponseHeader("Vary", "Content-Encoding")
ctx.SetResponseHeader("X-Content-Type-Options", "nosniff") ctx.SetResponseHeader("X-Content-Type-Options", "nosniff")
ctx.SetResponseHeader("X-Robots-Tags", "noindex, nofollow, notranslate") ctx.SetResponseHeader("X-Robots-Tags", "noindex, nofollow, notranslate")
ctx.SetResponseHeader("Referrer-Policy", "origin") ctx.SetResponseHeader("Referrer-Policy", "origin")
} }
func setCORSHeaders(ctx *httputils.RequestContext) { func setCORSHeaders(ctx httputils.RequestContext) {
ctx.SetResponseHeader("Access-Control-Allow-Credentials", "true") ctx.SetResponseHeader("Access-Control-Allow-Credentials", "true")
ctx.SetResponseHeader("Access-Control-Max-Age", "7200") //Firefox caps this to 86400, Chrome to 7200. Default is 5 seconds (!!!) ctx.SetResponseHeader("Access-Control-Max-Age", "7200") //Firefox caps this to 86400, Chrome to 7200. Default is 5 seconds (!!!)
ctx.SetResponseHeader("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS") ctx.SetResponseHeader("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS")
@ -78,7 +78,7 @@ func setCORSHeaders(ctx *httputils.RequestContext) {
ctx.SetResponseHeader("Cross-Origin-Opener-Policy", "unsafe-none") ctx.SetResponseHeader("Cross-Origin-Opener-Policy", "unsafe-none")
} }
func handleHexHash(pathElements []string, ctx *httputils.RequestContext, host string) { func handleHexHash(pathElements []string, ctx httputils.RequestContext, host string) {
if len(pathElements) < 3 { if len(pathElements) < 3 {
ctx.SetResponseCode(http.StatusBadRequest) ctx.SetResponseCode(http.StatusBadRequest)
@ -179,7 +179,7 @@ func handleHexHash(pathElements []string, ctx *httputils.RequestContext, host st
return return
} }
ctx.DoRedirect(contentServer.GetContentURL(entry, privateKey, skip)+host, http.StatusFound) ctx.DoRedirect(contentServer.GetContentURL(entry, skip)+host, http.StatusFound)
} else { } else {
contentServer := selectNextContentServer(skip) contentServer := selectNextContentServer(skip)
if contentServer == nil { if contentServer == nil {
@ -197,7 +197,7 @@ func handleHexHash(pathElements []string, ctx *httputils.RequestContext, host st
continue continue
} }
result, err := c.CheckEntryKey(key, privateKey) result, err := c.CheckEntryKey(key)
if result != nil { if result != nil {
if e == nil { if e == nil {
e = &content.Entry{ e = &content.Entry{
@ -232,11 +232,11 @@ func handleHexHash(pathElements []string, ctx *httputils.RequestContext, host st
} }
}() }()
ctx.DoRedirect(contentServer.GetHashURL(mh, privateKey, skip)+host, http.StatusFound) ctx.DoRedirect(contentServer.GetHashURL(mh, skip)+host, http.StatusFound)
} }
} }
func handle(ctx *httputils.RequestContext) { func handle(ctx httputils.RequestContext) {
if len(ctx.GetRequestHeader("Host")) > 0 && ctx.GetRequestHeader("Host") == ctx.GetTLSServerName() { //Prevents rebinding / DNS stuff if len(ctx.GetRequestHeader("Host")) > 0 && ctx.GetRequestHeader("Host") == ctx.GetTLSServerName() { //Prevents rebinding / DNS stuff
ctx.SetResponseCode(http.StatusNotFound) ctx.SetResponseCode(http.StatusNotFound)
return return
@ -307,7 +307,7 @@ func getContentEntry(key *content.HashIdentifier) *content.Entry {
continue continue
} }
h, err := c.CheckEntryKey(&e.Key, privateKey) h, err := c.CheckEntryKey(&e.Key)
if h == nil && err == nil { if h == nil && err == nil {
newInvalidList = append(newInvalidList, c.Index) newInvalidList = append(newInvalidList, c.Index)
} }
@ -328,14 +328,14 @@ func checkContentServers() {
} }
func main() { func main() {
//TODO: OCSP debugOption := flag.Bool("debug", false, "Enable debug output.")
certificatePath := flag.String("certificate", "", "Path to SSL certificate file.") certificatePath := flag.String("certificate", "", "Path to SSL certificate file.")
keypairPath := flag.String("keypair", "", "Path to SSL key file.") keypairPath := flag.String("keypair", "", "Path to SSL key file.")
databasePath := flag.String("dbpath", "database", "Path to key/value database.") databasePath := flag.String("dbpath", "database", "Path to key/value database.")
listenAddress := flag.String("listen", ":7777", "Address/port to lisent on.") listenAddress := flag.String("listen", ":7777", "address/port to listen on.")
weightedServerList := flag.String("servers", "", "Weighted list of servers to use. All use HTTPs. Format address:PORT/WEIGHT,[...]") weightedServerList := flag.String("servers", "", "Weighted list of servers to use. All will use HTTPs. Allowed protocols: orbt. Format [protocol=]address:PORT/WEIGHT,[...]")
sniAddressOption := flag.String("sni", "", "Define SNI address if desired. Empty will serve any requests regardless.") sniAddressOption := flag.String("sni", "", "Define SNI address if desired. Empty will serve any requests regardless.")
@ -343,6 +343,8 @@ func main() {
var err error var err error
debugOutput = *debugOption
privateKeyEnv := os.Getenv("PRIVATE_KEY") privateKeyEnv := os.Getenv("PRIVATE_KEY")
if privateKeyEnv != "" { if privateKeyEnv != "" {
@ -373,7 +375,7 @@ func main() {
log.Fatal("Wrong Private key length") log.Fatal("Wrong Private key length")
} }
privateKey = ed25519.NewKeyFromSeed(privateSeed) privateKey := ed25519.NewKeyFromSeed(privateSeed)
publicKey = make([]byte, ed25519.PublicKeySize) publicKey = make([]byte, ed25519.PublicKeySize)
copy(publicKey, privateKey[ed25519.PublicKeySize:]) copy(publicKey, privateKey[ed25519.PublicKeySize:])
log.Printf("Loaded Private Ed25519 key, Public %s", MakyuuIchaival.Bech32Encoding.EncodeToString(publicKey)) log.Printf("Loaded Private Ed25519 key, Public %s", MakyuuIchaival.Bech32Encoding.EncodeToString(publicKey))
@ -385,7 +387,7 @@ func main() {
defer db.Close() defer db.Close()
for i, s := range strings.Split(*weightedServerList, ",") { for i, s := range strings.Split(*weightedServerList, ",") {
cs, err := content.NewContentServerFromArgument(s, i) cs, err := content.NewContentServerFromArgument(s, i, privateKey)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View file

@ -16,17 +16,42 @@ import (
"time" "time"
) )
type ServerProtocol int
const (
ProtocolOrbitalBeatV1 ServerProtocol = iota
)
type Server struct { type Server struct {
Index int Index int
Address string Address string
Protocol ServerProtocol
key []byte
Weight uint Weight uint
LastCheckResult bool LastCheckResult bool
lastCheckMutex sync.RWMutex lastCheckMutex sync.RWMutex
} }
func NewContentServerFromArgument(arg string, index int) (*Server, error) { func NewContentServerFromArgument(arg string, index int, defaultKey []byte) (*Server, error) {
//Format address:PORT/WEIGHT[/publicKey], //Format Address:PORT/WEIGHT[/publicKey],
p := strings.Split(arg, "/")
protos := strings.Split(arg, "=")
serverProtocol := ProtocolOrbitalBeatV1
serverKey := defaultKey
if len(protos) > 1 {
switch protos[0] {
case "orbt":
serverProtocol = ProtocolOrbitalBeatV1
default:
return nil, fmt.Errorf("invalid server Protocol %s", arg)
}
}
p := strings.Split(protos[len(protos)-1], "/")
if len(p) < 2 { if len(p) < 2 {
return nil, fmt.Errorf("invalid weighted server %s", arg) return nil, fmt.Errorf("invalid weighted server %s", arg)
} }
@ -39,6 +64,8 @@ func NewContentServerFromArgument(arg string, index int) (*Server, error) {
cs := &Server{ cs := &Server{
Index: index, Index: index,
Address: p[0], Address: p[0],
Protocol: serverProtocol,
key: serverKey,
Weight: uint(weight), Weight: uint(weight),
LastCheckResult: false, LastCheckResult: false,
} }
@ -46,19 +73,29 @@ func NewContentServerFromArgument(arg string, index int) (*Server, error) {
return cs, nil return cs, nil
} }
func (s *Server) GetContentURL(content *Entry, key ed25519.PrivateKey, skip []int) string { func (s *Server) GetContentURL(content *Entry, skip []int) string {
message := contentmessage.NewContentMessageV1(content.Multihash(), key) switch s.Protocol {
skip = append(skip, s.Index) case ProtocolOrbitalBeatV1:
return s.getURL(MakyuuIchaival.Bech32Encoding.EncodeToString(message.Encode()), MakyuuIchaival.Bech32Encoding.EncodeToString(utilities.EncodeIntegerList(skip))) message := contentmessage.NewContentMessageV1(content.Multihash(), ed25519.PrivateKey(s.key))
skip = append(skip, s.Index)
return s.getBaseURL(MakyuuIchaival.Bech32Encoding.EncodeToString(message.Encode()), MakyuuIchaival.Bech32Encoding.EncodeToString(utilities.EncodeIntegerList(skip)))
default:
return ""
}
} }
func (s *Server) GetHashURL(mh multihash.Multihash, key ed25519.PrivateKey, skip []int) string { func (s *Server) GetHashURL(mh multihash.Multihash, skip []int) string {
message := contentmessage.NewContentMessageV1(mh, key) switch s.Protocol {
skip = append(skip, s.Index) case ProtocolOrbitalBeatV1:
return s.getURL(MakyuuIchaival.Bech32Encoding.EncodeToString(message.Encode()), MakyuuIchaival.Bech32Encoding.EncodeToString(utilities.EncodeIntegerList(skip))) message := contentmessage.NewContentMessageV1(mh, ed25519.PrivateKey(s.key))
skip = append(skip, s.Index)
return s.getBaseURL(MakyuuIchaival.Bech32Encoding.EncodeToString(message.Encode()), MakyuuIchaival.Bech32Encoding.EncodeToString(utilities.EncodeIntegerList(skip)))
default:
return ""
}
} }
func (s *Server) getURL(args ...string) string { func (s *Server) getBaseURL(args ...string) string {
return fmt.Sprintf("https://%s/%s", s.Address, strings.Join(args, "/")) return fmt.Sprintf("https://%s/%s", s.Address, strings.Join(args, "/"))
} }
@ -81,7 +118,7 @@ func (s *Server) Check() {
Transport: customTransport, Transport: customTransport,
Timeout: 5 * time.Second, Timeout: 5 * time.Second,
} }
response, err := client.Head(s.getURL()) response, err := client.Head(s.getBaseURL())
if err != nil { if err != nil {
s.setCheckResult(false) s.setCheckResult(false)
@ -97,14 +134,14 @@ func (s *Server) Check() {
s.setCheckResult(true) s.setCheckResult(true)
} }
func (s *Server) CheckEntryKey(key *HashIdentifier, privateKey ed25519.PrivateKey) (*HashIdentifier, error) { func (s *Server) CheckEntryKey(key *HashIdentifier) (*HashIdentifier, error) {
customTransport := http.DefaultTransport.(*http.Transport).Clone() customTransport := http.DefaultTransport.(*http.Transport).Clone()
customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
client := &http.Client{ client := &http.Client{
Transport: customTransport, Transport: customTransport,
Timeout: 5 * time.Second, Timeout: 5 * time.Second,
} }
response, err := client.Head(s.GetHashURL(key.Hash(), privateKey, []int{})) response, err := client.Head(s.GetHashURL(key.Hash(), []int{}))
if err != nil { if err != nil {
return nil, err return nil, err

8
go.mod
View file

@ -3,7 +3,7 @@ module git.gammaspectra.live/S.O.N.G/FinalCommander
go 1.18 go 1.18
require ( require (
git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220531073006-dc0db04fcaac git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220606212530-4821aa05b5d6
github.com/cloudflare/circl v1.1.0 github.com/cloudflare/circl v1.1.0
github.com/dgraph-io/badger/v3 v3.2103.2 github.com/dgraph-io/badger/v3 v3.2103.2
github.com/ipfs/go-cid v0.2.0 github.com/ipfs/go-cid v0.2.0
@ -27,13 +27,13 @@ require (
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/flatbuffers v2.0.6+incompatible // indirect github.com/google/flatbuffers v2.0.6+incompatible // indirect
github.com/klauspost/compress v1.15.5 // indirect github.com/klauspost/compress v1.15.6 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/lucas-clemente/quic-go v0.27.1 // indirect github.com/lucas-clemente/quic-go v0.27.1 // indirect
github.com/marten-seemann/qpack v0.2.1 // indirect github.com/marten-seemann/qpack v0.2.1 // indirect
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect

14
go.sum
View file

@ -7,8 +7,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220531073006-dc0db04fcaac h1:wAXYrT17ver91I1LPHr1e8sTSgmbISkiEPRGAfA5tsU= git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220606212530-4821aa05b5d6 h1:eTAOxKCXiEsP8v47OL9S/OAPWHH1KjXSAgsUIPeim7U=
git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220531073006-dc0db04fcaac/go.mod h1:h0vDSQpfuNGOclNiJ4ircFrmPwTzxgZU5+SyPo763GY= git.gammaspectra.live/S.O.N.G/MakyuuIchaival v0.0.0-20220606212530-4821aa05b5d6/go.mod h1:eWzPxHEC8uYNigoSqsjUOsML8cGScgwhVs5dY3HDinM=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
@ -134,8 +134,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.5 h1:qyCLMz2JCrKADihKOh9FxnW3houKeNsp2h5OEz0QSEA= github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY=
github.com/klauspost/compress v1.15.5/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE= github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
@ -153,10 +153,12 @@ github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0O
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ= github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
github.com/marten-seemann/qtls-go1-17 v0.1.1 h1:DQjHPq+aOzUeh9/lixAGunn6rIOQyWChPSI4+hgW7jc=
github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s= github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
github.com/marten-seemann/qtls-go1-18 v0.1.1 h1:qp7p7XXUFL7fpBvSS1sWD+uSqPvzNQK43DH+/qEkj0Y= github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR7s0bLKJeYlQ=
github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM=
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=