From edd51a8355a8ccafb88706c81352c24f30db1b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 29 Aug 2023 19:28:26 +0800 Subject: [PATCH] Add ECH keypair generator --- cmd/sing-box/cmd_generate_ech.go | 39 +++++++ common/tls/ech_keygen.go | 169 +++++++++++++++++++++++++++++++ common/tls/ech_stub.go | 10 +- common/tls/reality_server.go | 4 +- common/tls/std_server.go | 9 +- go.mod | 2 +- test/ech_test.go | 91 +++++++++++++++++ test/go.sum | 25 ++--- 8 files changed, 323 insertions(+), 26 deletions(-) create mode 100644 cmd/sing-box/cmd_generate_ech.go create mode 100644 common/tls/ech_keygen.go create mode 100644 test/ech_test.go diff --git a/cmd/sing-box/cmd_generate_ech.go b/cmd/sing-box/cmd_generate_ech.go new file mode 100644 index 00000000..deb066d7 --- /dev/null +++ b/cmd/sing-box/cmd_generate_ech.go @@ -0,0 +1,39 @@ +package main + +import ( + "os" + + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/log" + + "github.com/spf13/cobra" +) + +var pqSignatureSchemesEnabled bool + +var commandGenerateECHKeyPair = &cobra.Command{ + Use: "ech-keypair ", + Short: "Generate TLS ECH key pair", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + err := generateECHKeyPair(args[0]) + if err != nil { + log.Fatal(err) + } + }, +} + +func init() { + commandGenerateECHKeyPair.Flags().BoolVar(&pqSignatureSchemesEnabled, "pq-signature-schemes-enabled", false, "Enable PQ signature schemes") + commandGenerate.AddCommand(commandGenerateECHKeyPair) +} + +func generateECHKeyPair(host string) error { + configPem, keyPem, err := tls.ECHKeygenDefault(host, pqSignatureSchemesEnabled) + if err != nil { + return err + } + os.Stdout.WriteString(configPem) + os.Stdout.WriteString(keyPem) + return nil +} diff --git a/common/tls/ech_keygen.go b/common/tls/ech_keygen.go new file mode 100644 index 00000000..b5856dd6 --- /dev/null +++ b/common/tls/ech_keygen.go @@ -0,0 +1,169 @@ +//go:build with_ech + +package tls + +import ( + "bytes" + "encoding/binary" + "encoding/pem" + + cftls "github.com/sagernet/cloudflare-tls" + E "github.com/sagernet/sing/common/exceptions" + + "github.com/cloudflare/circl/hpke" + "github.com/cloudflare/circl/kem" +) + +func ECHKeygenDefault(host string, pqSignatureSchemesEnabled bool) (configPem string, keyPem string, err error) { + cipherSuites := []echCipherSuite{ + { + kdf: hpke.KDF_HKDF_SHA256, + aead: hpke.AEAD_AES128GCM, + }, { + kdf: hpke.KDF_HKDF_SHA256, + aead: hpke.AEAD_ChaCha20Poly1305, + }, + } + + keyConfig := []myECHKeyConfig{ + {id: 0, kem: hpke.KEM_X25519_HKDF_SHA256}, + } + if pqSignatureSchemesEnabled { + keyConfig = append(keyConfig, myECHKeyConfig{id: 1, kem: hpke.KEM_X25519_KYBER768_DRAFT00}) + } + + keyPairs, err := echKeygen(0xfe0d, host, keyConfig, cipherSuites) + if err != nil { + return + } + + var configBuffer bytes.Buffer + var totalLen uint16 + for _, keyPair := range keyPairs { + totalLen += uint16(len(keyPair.rawConf)) + } + binary.Write(&configBuffer, binary.BigEndian, totalLen) + for _, keyPair := range keyPairs { + configBuffer.Write(keyPair.rawConf) + } + + var keyBuffer bytes.Buffer + for _, keyPair := range keyPairs { + keyBuffer.Write(keyPair.rawKey) + } + + configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer.Bytes()})) + keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer.Bytes()})) + return +} + +type echKeyConfigPair struct { + id uint8 + key cftls.EXP_ECHKey + rawKey []byte + conf myECHKeyConfig + rawConf []byte +} + +type echCipherSuite struct { + kdf hpke.KDF + aead hpke.AEAD +} + +type myECHKeyConfig struct { + id uint8 + kem hpke.KEM + seed []byte +} + +func echKeygen(version uint16, host string, conf []myECHKeyConfig, suite []echCipherSuite) ([]echKeyConfigPair, error) { + be := binary.BigEndian + // prepare for future update + if version != 0xfe0d { + return nil, E.New("unsupported ECH version", version) + } + + suiteBuf := make([]byte, 0, len(suite)*4+2) + suiteBuf = be.AppendUint16(suiteBuf, uint16(len(suite))*4) + for _, s := range suite { + if !s.kdf.IsValid() || !s.aead.IsValid() { + return nil, E.New("invalid HPKE cipher suite") + } + suiteBuf = be.AppendUint16(suiteBuf, uint16(s.kdf)) + suiteBuf = be.AppendUint16(suiteBuf, uint16(s.aead)) + } + + pairs := []echKeyConfigPair{} + for _, c := range conf { + pair := echKeyConfigPair{} + pair.id = c.id + pair.conf = c + + if !c.kem.IsValid() { + return nil, E.New("invalid HPKE KEM") + } + + kpGenerator := c.kem.Scheme().GenerateKeyPair + if len(c.seed) > 0 { + kpGenerator = func() (kem.PublicKey, kem.PrivateKey, error) { + pub, sec := c.kem.Scheme().DeriveKeyPair(c.seed) + return pub, sec, nil + } + if len(c.seed) < c.kem.Scheme().PrivateKeySize() { + return nil, E.New("HPKE KEM seed too short") + } + } + + pub, sec, err := kpGenerator() + if err != nil { + return nil, E.Cause(err, "generate ECH config key pair") + } + b := []byte{} + b = be.AppendUint16(b, version) + b = be.AppendUint16(b, 0) // length field + // contents + // key config + b = append(b, c.id) + b = be.AppendUint16(b, uint16(c.kem)) + pubBuf, err := pub.MarshalBinary() + if err != nil { + return nil, E.Cause(err, "serialize ECH public key") + } + b = be.AppendUint16(b, uint16(len(pubBuf))) + b = append(b, pubBuf...) + + b = append(b, suiteBuf...) + // end key config + // max name len, not supported + b = append(b, 0) + // server name + b = append(b, byte(len(host))) + b = append(b, []byte(host)...) + // extensions, not supported + b = be.AppendUint16(b, 0) + + be.PutUint16(b[2:], uint16(len(b)-4)) + + pair.rawConf = b + + secBuf, err := sec.MarshalBinary() + sk := []byte{} + sk = be.AppendUint16(sk, uint16(len(secBuf))) + sk = append(sk, secBuf...) + sk = be.AppendUint16(sk, uint16(len(b))) + sk = append(sk, b...) + + cfECHKeys, err := cftls.EXP_UnmarshalECHKeys(sk) + if err != nil { + return nil, E.Cause(err, "bug: can't parse generated ECH server key") + } + if len(cfECHKeys) != 1 { + return nil, E.New("bug: unexpected server key count") + } + pair.key = cfECHKeys[0] + pair.rawKey = sk + + pairs = append(pairs, pair) + } + return pairs, nil +} diff --git a/common/tls/ech_stub.go b/common/tls/ech_stub.go index 0aab700e..4ee4272c 100644 --- a/common/tls/ech_stub.go +++ b/common/tls/ech_stub.go @@ -10,10 +10,16 @@ import ( E "github.com/sagernet/sing/common/exceptions" ) +var errECHNotIncluded = E.New(`ECH is not included in this build, rebuild with -tags with_ech`) + func NewECHServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { - return nil, E.New(`ECH is not included in this build, rebuild with -tags with_ech`) + return nil, errECHNotIncluded } func NewECHClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { - return nil, E.New(`ECH is not included in this build, rebuild with -tags with_ech`) + return nil, errECHNotIncluded +} + +func ECHKeygenDefault(host string, pqSignatureSchemesEnabled bool) (configPem string, keyPem string, err error) { + return "", "", errECHNotIncluded } diff --git a/common/tls/reality_server.go b/common/tls/reality_server.go index f6e30827..a9318798 100644 --- a/common/tls/reality_server.go +++ b/common/tls/reality_server.go @@ -67,10 +67,10 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb return nil, E.New("unknown cipher_suite: ", cipherSuite) } } - if options.Certificate != "" || options.CertificatePath != "" { + if len(options.Certificate) > 0 || options.CertificatePath != "" { return nil, E.New("certificate is unavailable in reality") } - if options.Key != "" || options.KeyPath != "" { + if len(options.Key) > 0 || options.KeyPath != "" { return nil, E.New("key is unavailable in reality") } diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 33d92232..36ce67b6 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "net" "os" + "strings" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/log" @@ -212,8 +213,8 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound var certificate []byte var key []byte if acmeService == nil { - if options.Certificate != "" { - certificate = []byte(options.Certificate) + if len(options.Certificate) > 0 { + certificate = []byte(strings.Join(options.Certificate, "\n")) } else if options.CertificatePath != "" { content, err := os.ReadFile(options.CertificatePath) if err != nil { @@ -221,8 +222,8 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound } certificate = content } - if options.Key != "" { - key = []byte(options.Key) + if len(options.Key) > 0 { + key = []byte(strings.Join(options.Key, "\n")) } else if options.KeyPath != "" { content, err := os.ReadFile(options.KeyPath) if err != nil { diff --git a/go.mod b/go.mod index 73700183..08333c14 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( berty.tech/go-libtor v1.0.385 github.com/Dreamacro/clash v1.17.0 github.com/caddyserver/certmagic v0.19.2 + github.com/cloudflare/circl v1.3.3 github.com/cretz/bine v0.2.0 github.com/dustin/go-humanize v1.0.1 github.com/fsnotify/fsnotify v1.6.0 @@ -59,7 +60,6 @@ require ( github.com/Dreamacro/protobytes v0.0.0-20230617041236-6500a9f4f158 // indirect github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.5 // indirect - github.com/cloudflare/circl v1.3.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect diff --git a/test/ech_test.go b/test/ech_test.go new file mode 100644 index 00000000..b05351b4 --- /dev/null +++ b/test/ech_test.go @@ -0,0 +1,91 @@ +package main + +import ( + "net/netip" + "testing" + + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" +) + +func TestECH(t *testing.T) { + _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") + echConfig, echKey := common.Must2(tls.ECHKeygenDefault("example.org", false)) + startInstance(t, option.Options{ + Inbounds: []option.Inbound{ + { + Type: C.TypeMixed, + Tag: "mixed-in", + MixedOptions: option.HTTPMixedInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.NewListenAddress(netip.IPv4Unspecified()), + ListenPort: clientPort, + }, + }, + }, + { + Type: C.TypeTrojan, + TrojanOptions: option.TrojanInboundOptions{ + ListenOptions: option.ListenOptions{ + Listen: option.NewListenAddress(netip.IPv4Unspecified()), + ListenPort: serverPort, + }, + Users: []option.TrojanUser{ + { + Name: "sekai", + Password: "password", + }, + }, + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + KeyPath: keyPem, + ECH: &option.InboundECHOptions{ + Enabled: true, + Key: []string{echKey}, + }, + }, + }, + }, + }, + Outbounds: []option.Outbound{ + { + Type: C.TypeDirect, + }, + { + Type: C.TypeTrojan, + Tag: "trojan-out", + TrojanOptions: option.TrojanOutboundOptions{ + ServerOptions: option.ServerOptions{ + Server: "127.0.0.1", + ServerPort: serverPort, + }, + Password: "password", + TLS: &option.OutboundTLSOptions{ + Enabled: true, + ServerName: "example.org", + CertificatePath: certPem, + ECH: &option.OutboundECHOptions{ + Enabled: true, + Config: []string{echConfig}, + }, + }, + }, + }, + }, + Route: &option.RouteOptions{ + Rules: []option.Rule{ + { + DefaultOptions: option.DefaultRule{ + Inbound: []string{"mixed-in"}, + Outbound: "trojan-out", + }, + }, + }, + }, + }) + testSuit(t, clientPort, testPort) +} diff --git a/test/go.sum b/test/go.sum index 02893533..bb28f7d7 100644 --- a/test/go.sum +++ b/test/go.sum @@ -17,8 +17,7 @@ github.com/caddyserver/certmagic v0.19.2/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cloudflare/circl v1.2.1-0.20221019164342-6ab4dfed8f3c h1:K1VdSnBZiGapczwcUKnE1qcsMBclA84DUOD2NG/78VY= -github.com/cloudflare/circl v1.2.1-0.20221019164342-6ab4dfed8f3c/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= @@ -114,11 +113,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI= -github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM= github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= -github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt1Jtp5vW2ohNvstvQffTOQ/s5vENuGXzdA+TM= -github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I= +github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a h1:wZHruBxZCsQLXHAozWpnJBL3wJ/XufDpz0qKtgpSnA4= github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a/go.mod h1:dNV1ZP9y3qx5ltULeKaQZTZWTLHflgW5DES+Ses7cMI= github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA= github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms= @@ -126,22 +123,17 @@ github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 h1:dnkKrzapqtAwjTS github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2/go.mod h1:1JUiV7nGuf++YFm9eWZ8q2lrwHmhcUGzptMl/vL1+LA= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/quic-go v0.0.0-20230824033040-30ef72e3be3e h1:URg7GQT9Mp0b6m/LXH1NgVT3P/ybVomvy2kkfA6QIHQ= -github.com/sagernet/quic-go v0.0.0-20230824033040-30ef72e3be3e/go.mod h1:7DXnweBVxZ7CQWsCdc7QAAQ65dFPEtenfz+w6WDESlI= +github.com/sagernet/quic-go v0.0.0-20230825040534-0cd917b2ddda h1:7J/hnOFqCThiCrVpvr0wKO+Dic/XPSulPr5yI8FVJMs= github.com/sagernet/quic-go v0.0.0-20230825040534-0cd917b2ddda/go.mod h1:Iw8Tt3dMqC/61cMHa0nN5i/958oYuuMnQCMOSPx+xcg= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= -github.com/sagernet/sing v0.2.10-0.20230821073500-620f3a3b882d h1:4kgoOCE48CuQcBUcoRnE0QTPXkl8yM8i7Nipmzp/978= -github.com/sagernet/sing v0.2.10-0.20230821073500-620f3a3b882d/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= -github.com/sagernet/sing v0.2.10-0.20230824115837-8d731e68853a/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= +github.com/sagernet/sing v0.2.10-0.20230830132630-30bf19f2833c h1:J2ptRncTNy+ZHfcFYSBfTmpvmgNlSEUZz6sDjh1np/Y= github.com/sagernet/sing v0.2.10-0.20230830132630-30bf19f2833c/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= -github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659 h1:1DAKccGNqTYJ8nsBR765FS0LVBVXfuFlFAHqKsGN3EI= -github.com/sagernet/sing-dns v0.1.9-0.20230731012726-ad50da89b659/go.mod h1:W7GHTZFS8RkoLI3bA2LFY27/0E+uoQESWtMFLepO/JA= +github.com/sagernet/sing-dns v0.1.9-0.20230824120133-4d5cbceb40c1 h1:5w+jXz8y/8UQAxO74TjftN5okYkpg5mGvVxXunlKdqI= github.com/sagernet/sing-dns v0.1.9-0.20230824120133-4d5cbceb40c1/go.mod h1:Kg98PBJEg/08jsNFtmZWmPomhskn9Ausn50ecNm4M+8= -github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c h1:35/FowAvt3Z62mck0TXzVc4jS5R5CWq62qcV2P1cp0I= -github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY= +github.com/sagernet/sing-mux v0.1.3-0.20230830095209-2a10ebd53ba8 h1:UyUkEUEGqfIGqzOJ7OuJry4slgcT/qb0etDJ+89LTAs= github.com/sagernet/sing-mux v0.1.3-0.20230830095209-2a10ebd53ba8/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY= github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY= github.com/sagernet/sing-shadowsocks v0.2.4/go.mod h1:80fNKP0wnqlu85GZXV1H1vDPC/2t+dQbFggOw4XuFUM= @@ -194,8 +186,7 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= -go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28 h1:zLxFnORHDFTSkJPawMU7LzsuGQJ4MUFS653jJHpORow= -go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y= +go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ= go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=