diff --git a/docs/configuration/endpoint/openvpn-client.md b/docs/configuration/endpoint/openvpn-client.md index f82c1ff60..86d2a54e2 100644 --- a/docs/configuration/endpoint/openvpn-client.md +++ b/docs/configuration/endpoint/openvpn-client.md @@ -38,6 +38,8 @@ "crl_path": "", "remote_certificate_ku": [], "remote_certificate_eku": "", + "remote_certificate_tls": "", + "certificate_profile": "", "version_min": "1.2", "version_max": "", "cipher": "", @@ -251,15 +253,35 @@ Disabled by default. Required server certificate key usage masks, written as hexadecimal values in OpenVPN `remote-cert-ku` format. -Multiple values are combined, and all requested usages must be present. +The certificate must contain all bits from at least one configured mask. Disabled by default. ### tls.remote_certificate_eku -Required server certificate extended key usage, one of `server` or `client`. +Required server certificate extended key usage. -Disabled by default. The standard OpenVPN server certificate usage check still applies. +OpenSSL names, object identifiers, and the aliases `server` and `client` are accepted. + +When set, this field replaces the default `tls.remote_certificate_tls` check. + +Conflict with an explicitly configured `tls.remote_certificate_tls`. + +### tls.remote_certificate_tls + +Peer certificate purpose check, one of `server`, `client`, or `none`. + +`server` is used by default. + +`none` disables the certificate purpose check. + +Conflict with `tls.remote_certificate_eku`. + +### tls.certificate_profile + +Certificate profile, one of `insecure`, `legacy`, `preferred`, or `suiteb`. + +`legacy` is used by default. ### tls.version_min diff --git a/docs/configuration/endpoint/openvpn-client.zh.md b/docs/configuration/endpoint/openvpn-client.zh.md index f34631f16..db1c07120 100644 --- a/docs/configuration/endpoint/openvpn-client.zh.md +++ b/docs/configuration/endpoint/openvpn-client.zh.md @@ -38,6 +38,8 @@ "crl_path": "", "remote_certificate_ku": [], "remote_certificate_eku": "", + "remote_certificate_tls": "", + "certificate_profile": "", "version_min": "1.2", "version_max": "", "cipher": "", @@ -251,15 +253,35 @@ OpenVPN 控制通道 TLS 配置。 服务器证书所需的 Key Usage mask,使用 OpenVPN `remote-cert-ku` 格式的十六进制值。 -多个值会被组合,证书必须包含所有要求的用途。 +证书必须包含至少一个已配置 mask 中的所有 bit。 默认禁用。 ### tls.remote_certificate_eku -服务器证书所需的 Extended Key Usage,可选值为 `server` 或 `client`。 +服务器证书所需的 Extended Key Usage。 -默认禁用。标准 OpenVPN 服务器证书用途检查仍然生效。 +接受 OpenSSL 名称、Object Identifier 以及 `server` 和 `client` 别名。 + +设置后,该字段会替代默认的 `tls.remote_certificate_tls` 检查。 + +与显式配置的 `tls.remote_certificate_tls` 冲突。 + +### tls.remote_certificate_tls + +对端证书用途检查,可选值为 `server`、`client` 或 `none`。 + +默认使用 `server`。 + +`none` 禁用证书用途检查。 + +与 `tls.remote_certificate_eku` 冲突。 + +### tls.certificate_profile + +证书 profile,可选值为 `insecure`、`legacy`、`preferred` 或 `suiteb`。 + +默认使用 `legacy`。 ### tls.version_min diff --git a/docs/configuration/endpoint/openvpn-server.md b/docs/configuration/endpoint/openvpn-server.md index 43ee713fc..0cb029f05 100644 --- a/docs/configuration/endpoint/openvpn-server.md +++ b/docs/configuration/endpoint/openvpn-server.md @@ -33,6 +33,7 @@ "client_certificate": [], "client_certificate_path": "", "verify_client_certificate": "require", + "certificate_profile": "", "control_wrap": { "type": "tls_crypt", "key": [], @@ -214,6 +215,12 @@ If set to `none`, client certificates are not requested. This field does not replace `users`; when `users` is set, username/password authentication is still required. +### tls.certificate_profile + +Certificate profile, one of `insecure`, `legacy`, `preferred`, or `suiteb`. + +`legacy` is used by default. + ### tls.control_wrap OpenVPN control channel wrapping. diff --git a/docs/configuration/endpoint/openvpn-server.zh.md b/docs/configuration/endpoint/openvpn-server.zh.md index f9e6fdf52..b862908b4 100644 --- a/docs/configuration/endpoint/openvpn-server.zh.md +++ b/docs/configuration/endpoint/openvpn-server.zh.md @@ -33,6 +33,7 @@ "client_certificate": [], "client_certificate_path": "", "verify_client_certificate": "require", + "certificate_profile": "", "control_wrap": { "type": "tls_crypt", "key": [], @@ -214,6 +215,12 @@ OpenVPN 客户端证书策略,`require`、`optional` 或 `none` 之一。 该字段不替代 `users`;设置 `users` 后仍然要求用户名/密码认证。 +### tls.certificate_profile + +证书 profile,可选值为 `insecure`、`legacy`、`preferred` 或 `suiteb`。 + +默认使用 `legacy`。 + ### tls.control_wrap OpenVPN 控制信道包装。 diff --git a/go.mod b/go.mod index 2ee30e4de..7007ddfa1 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 github.com/sagernet/sing-mux v0.3.5 github.com/sagernet/sing-openconnect v0.0.0-20260720032640-bf28b6a6f10e - github.com/sagernet/sing-openvpn v0.0.0-20260719094204-c57d60b5c5a4 + github.com/sagernet/sing-openvpn v0.0.0-20260720132803-a5e407d00242 github.com/sagernet/sing-quic v0.6.4-0.20260720125339-63fa6a7ebc31 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 @@ -105,8 +105,9 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/btree v1.1.3 // indirect + github.com/google/certificate-transparency-go v1.3.2 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gopacket v1.1.19 // indirect diff --git a/go.sum b/go.sum index 5f6dfaef0..3ad3a153f 100644 --- a/go.sum +++ b/go.sum @@ -92,12 +92,14 @@ github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/certificate-transparency-go v1.3.2 h1:9ahSNZF2o7SYMaKaXhAumVEzXB2QaayzII9C8rv7v+A= +github.com/google/certificate-transparency-go v1.3.2/go.mod h1:H5FpMUaGa5Ab2+KCYsxg6sELw3Flkl7pGZzWdBoYLXs= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -287,8 +289,8 @@ github.com/sagernet/sing-mux v0.3.5 h1:RHnhVEc+SFqkrK4xMygYjDwwLhzp2Bj3lztSukONf github.com/sagernet/sing-mux v0.3.5/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= github.com/sagernet/sing-openconnect v0.0.0-20260720032640-bf28b6a6f10e h1:z0QFO/Bj3ikw4qDM7iwlEzSRdt/04Mh1s1pPeI/LRs0= github.com/sagernet/sing-openconnect v0.0.0-20260720032640-bf28b6a6f10e/go.mod h1:EIzh5HtImfQJxPKXFwS9lyMnmMy4aCQCx7ntQ4u41Gs= -github.com/sagernet/sing-openvpn v0.0.0-20260719094204-c57d60b5c5a4 h1:3H1pOsE5IRzr2U+oUTabPrvryj7xg1CEmOnMMwsQSQA= -github.com/sagernet/sing-openvpn v0.0.0-20260719094204-c57d60b5c5a4/go.mod h1:CmTGnS5ijVSqFQV1dTq4WvFLUoz7bk9xasBPsX8NcYo= +github.com/sagernet/sing-openvpn v0.0.0-20260720132803-a5e407d00242 h1:0ZvKyBmBIlAuZ9G+zAWdGh6SrmXIXN7NWENHDrptxtI= +github.com/sagernet/sing-openvpn v0.0.0-20260720132803-a5e407d00242/go.mod h1:mK4GzZyUIhG751Mt1MSvSqLGYOR7DFJd8q5QfgFjE2Y= github.com/sagernet/sing-quic v0.6.4-0.20260720125339-63fa6a7ebc31 h1:FuiTs45PaA1/f2Kj6E2QlctFBrJUUFB8QAHnV5im9c8= github.com/sagernet/sing-quic v0.6.4-0.20260720125339-63fa6a7ebc31/go.mod h1:9k+dzGsWMttUGldBzq3dU792YHXzW6NgfbOGltnXq+0= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= diff --git a/option/openvpn.go b/option/openvpn.go index 538598f32..7ad68006c 100644 --- a/option/openvpn.go +++ b/option/openvpn.go @@ -94,6 +94,8 @@ type OpenVPNOutboundTLSOptions struct { CRLPath string `json:"crl_path,omitempty"` RemoteCertificateKU badoption.Listable[string] `json:"remote_certificate_ku,omitempty"` RemoteCertificateEKU string `json:"remote_certificate_eku,omitempty"` + RemoteCertificateTLS string `json:"remote_certificate_tls,omitempty"` + CertificateProfile string `json:"certificate_profile,omitempty"` VersionMin string `json:"version_min,omitempty"` VersionMax string `json:"version_max,omitempty"` Cipher string `json:"cipher,omitempty"` @@ -109,6 +111,7 @@ type OpenVPNInboundTLSOptions struct { ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"` ClientCertificatePath string `json:"client_certificate_path,omitempty"` VerifyClientCertificate string `json:"verify_client_certificate,omitempty"` + CertificateProfile string `json:"certificate_profile,omitempty"` ControlWrap *OpenVPNInboundControlWrapOptions `json:"control_wrap,omitempty"` } diff --git a/protocol/openvpn/client.go b/protocol/openvpn/client.go index 339e6b106..9756ccaa2 100644 --- a/protocol/openvpn/client.go +++ b/protocol/openvpn/client.go @@ -237,6 +237,20 @@ func (c *ClientEndpoint) buildClientOptions(options option.OpenVPNClientEndpoint tunnelRoutes := common.Map(options.Routes, func(route netip.Prefix) ovpn.TunnelRoute { return ovpn.TunnelRoute{Prefix: route} }) + remoteCertificateTLS := options.TLS.RemoteCertificateTLS + switch remoteCertificateTLS { + case "", "server", "client", "none": + default: + return ovpn.ClientOptions{}, E.New("invalid `tls.remote_certificate_tls`: ", remoteCertificateTLS) + } + if options.TLS.RemoteCertificateEKU != "" && remoteCertificateTLS != "" { + return ovpn.ClientOptions{}, E.New("`tls.remote_certificate_eku` is conflict with `tls.remote_certificate_tls`") + } + if remoteCertificateTLS == "" && options.TLS.RemoteCertificateEKU == "" { + remoteCertificateTLS = "server" + } else if remoteCertificateTLS == "none" { + remoteCertificateTLS = "" + } clientTLSOptions := ovpn.ClientTLSOptions{ CertificateAuthority: certificateAuthority, Certificate: clientCertificate, @@ -249,9 +263,10 @@ func (c *ClientEndpoint) buildClientOptions(options option.OpenVPNClientEndpoint CRLVerify: options.TLS.CRLPath, RemoteCertificateKU: options.TLS.RemoteCertificateKU, RemoteCertificateEKU: options.TLS.RemoteCertificateEKU, - RemoteCertificateTLS: "server", + RemoteCertificateTLS: remoteCertificateTLS, VersionMin: options.TLS.VersionMin, VersionMax: options.TLS.VersionMax, + CertificateProfile: options.TLS.CertificateProfile, Cipher: options.TLS.Cipher, Groups: options.TLS.Groups, } diff --git a/protocol/openvpn/server.go b/protocol/openvpn/server.go index 1d0db1646..1492f0da0 100644 --- a/protocol/openvpn/server.go +++ b/protocol/openvpn/server.go @@ -336,6 +336,7 @@ func buildServerTLSOptions(options option.OpenVPNInboundTLSOptions) (ovpn.Server Certificate: certificate, Key: key, VerifyClientCertificate: options.VerifyClientCertificate, + CertificateProfile: options.CertificateProfile, } keyDirection := -1 controlWrap := options.ControlWrap diff --git a/test/go.mod b/test/go.mod index dc56e0206..41d2742db 100644 --- a/test/go.mod +++ b/test/go.mod @@ -13,10 +13,10 @@ require ( github.com/opencontainers/image-spec v1.1.0 github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 github.com/sagernet/sing v0.8.12-0.20260717153536-4f1ed45a99a5 - github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc + github.com/sagernet/sing-quic v0.6.4-0.20260720125339-63fa6a7ebc31 github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 - github.com/sagernet/sing-tun v0.8.12-0.20260719050255-7e7bd198288c + github.com/sagernet/sing-tun v0.8.12-0.20260719094150-557ca930fccd github.com/spyzhov/ajson v0.9.4 github.com/stretchr/testify v1.11.1 go.uber.org/goleak v1.3.0 @@ -67,8 +67,9 @@ require ( github.com/gobwas/pool v0.2.1 // indirect github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/btree v1.1.3 // indirect + github.com/google/certificate-transparency-go v1.3.2 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect @@ -153,10 +154,10 @@ require ( github.com/sagernet/nftables v0.3.0-mod.4 // indirect github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 // indirect github.com/sagernet/sing-mux v0.3.5 // indirect - github.com/sagernet/sing-openconnect v0.0.0-20260718163953-a1c7815e4f04 // indirect - github.com/sagernet/sing-openvpn v0.0.0-20260718163953-26ecbeb6352c // indirect + github.com/sagernet/sing-openconnect v0.0.0-20260720032640-bf28b6a6f10e // indirect + github.com/sagernet/sing-openvpn v0.0.0-20260720132803-a5e407d00242 // indirect github.com/sagernet/sing-shadowtls v0.2.1 // indirect - github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb // indirect + github.com/sagernet/sing-snell v0.0.0-20260719094200-c43fbee0e839 // indirect github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb // indirect github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 // indirect github.com/sagernet/smux v1.5.50-sing-box-mod.1 // indirect @@ -181,7 +182,7 @@ require ( github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/zeebo/blake3 v0.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect go.opentelemetry.io/otel v1.39.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect diff --git a/test/go.sum b/test/go.sum index 43099a4df..bb892735b 100644 --- a/test/go.sum +++ b/test/go.sum @@ -102,12 +102,14 @@ github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/certificate-transparency-go v1.3.2 h1:9ahSNZF2o7SYMaKaXhAumVEzXB2QaayzII9C8rv7v+A= +github.com/google/certificate-transparency-go v1.3.2/go.mod h1:H5FpMUaGa5Ab2+KCYsxg6sELw3Flkl7pGZzWdBoYLXs= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -117,8 +119,8 @@ github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdF github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= @@ -304,22 +306,22 @@ github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 h1:3y6 github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3/go.mod h1:XEqEDYRCAYLaoPjZ1ifVWJg5iWAJHL2gOAXe/PM28Cg= github.com/sagernet/sing-mux v0.3.5 h1:RHnhVEc+SFqkrK4xMygYjDwwLhzp2Bj3lztSukONfhI= github.com/sagernet/sing-mux v0.3.5/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-openconnect v0.0.0-20260718163953-a1c7815e4f04 h1:HIb3Tu19qqH5fD5xnyyHb6zJaETIsnXamb/hWTtxil8= -github.com/sagernet/sing-openconnect v0.0.0-20260718163953-a1c7815e4f04/go.mod h1:EIzh5HtImfQJxPKXFwS9lyMnmMy4aCQCx7ntQ4u41Gs= -github.com/sagernet/sing-openvpn v0.0.0-20260718163953-26ecbeb6352c h1:EhwLZF3IUyDj4uZ7vkUZAI7GymXCeOCwiseuOTsFjp8= -github.com/sagernet/sing-openvpn v0.0.0-20260718163953-26ecbeb6352c/go.mod h1:CmTGnS5ijVSqFQV1dTq4WvFLUoz7bk9xasBPsX8NcYo= -github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc h1:zdc0fj4JdAdgAmQIoh7ZF+B/wPTEF2X75lYDqTmvlaw= -github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc/go.mod h1:9k+dzGsWMttUGldBzq3dU792YHXzW6NgfbOGltnXq+0= +github.com/sagernet/sing-openconnect v0.0.0-20260720032640-bf28b6a6f10e h1:z0QFO/Bj3ikw4qDM7iwlEzSRdt/04Mh1s1pPeI/LRs0= +github.com/sagernet/sing-openconnect v0.0.0-20260720032640-bf28b6a6f10e/go.mod h1:EIzh5HtImfQJxPKXFwS9lyMnmMy4aCQCx7ntQ4u41Gs= +github.com/sagernet/sing-openvpn v0.0.0-20260720132803-a5e407d00242 h1:0ZvKyBmBIlAuZ9G+zAWdGh6SrmXIXN7NWENHDrptxtI= +github.com/sagernet/sing-openvpn v0.0.0-20260720132803-a5e407d00242/go.mod h1:mK4GzZyUIhG751Mt1MSvSqLGYOR7DFJd8q5QfgFjE2Y= +github.com/sagernet/sing-quic v0.6.4-0.20260720125339-63fa6a7ebc31 h1:FuiTs45PaA1/f2Kj6E2QlctFBrJUUFB8QAHnV5im9c8= +github.com/sagernet/sing-quic v0.6.4-0.20260720125339-63fa6a7ebc31/go.mod h1:9k+dzGsWMttUGldBzq3dU792YHXzW6NgfbOGltnXq+0= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1 h1:ZiHZdnEnP+YS73NMsxiZmIFCwNd0M4k7PkGCKNXhbaM= github.com/sagernet/sing-shadowtls v0.2.1/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb h1:VvU2/PZqP5tbKTDq0BxkhRO8ZnKI4UJzziakgBiP2Qg= -github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb/go.mod h1:PcwzX/Xvqky0EP3kGt8OCjYb3R1pydenPHNQZcPZmXY= -github.com/sagernet/sing-tun v0.8.12-0.20260719050255-7e7bd198288c h1:jS5eWD9PyXhHY89DueRDgenfcrfpyPOuuZIoNfnBmOs= -github.com/sagernet/sing-tun v0.8.12-0.20260719050255-7e7bd198288c/go.mod h1:F/gRq5VX1WN/OZtsvbN2JjXXuNl2ATJglHMSk1/iN9U= +github.com/sagernet/sing-snell v0.0.0-20260719094200-c43fbee0e839 h1:YL0oCb55moImUGvjhhVEYODGMo5i9dAf+RpmMFPCq9w= +github.com/sagernet/sing-snell v0.0.0-20260719094200-c43fbee0e839/go.mod h1:PcwzX/Xvqky0EP3kGt8OCjYb3R1pydenPHNQZcPZmXY= +github.com/sagernet/sing-tun v0.8.12-0.20260719094150-557ca930fccd h1:tH79/IieRjLx5DiVu3NpXc1hir0xL6Avmmist0aFHks= +github.com/sagernet/sing-tun v0.8.12-0.20260719094150-557ca930fccd/go.mod h1:F/gRq5VX1WN/OZtsvbN2JjXXuNl2ATJglHMSk1/iN9U= github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb h1:KEMbfexD4DvrQGYWwx6r+AwH9Veh8z6cnBZmtCS2G+0= github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb/go.mod h1:D4CnJX3MNAAANhbQUxfIRgBdnvlTEaV7h6ojedcs+pw= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= @@ -390,12 +392,12 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= @@ -406,8 +408,8 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -541,6 +543,7 @@ golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 h1:Pw6WnI9W/LIdRxqK7T6XGugGbHIRl5Q7q3BssH6xk4s= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= diff --git a/test/openvpn_test.go b/test/openvpn_test.go index 09f17c6c0..d67d6fe34 100644 --- a/test/openvpn_test.go +++ b/test/openvpn_test.go @@ -84,9 +84,11 @@ type openVPNDockerServerEnvironment struct { } type openVPNSelfCase struct { - name string - protocol string - tlsCrypt bool + name string + protocol string + tlsCrypt bool + omitServerKeyUsage bool + remoteCertificateTLS string } func TestOpenVPNSelfToSelf(t *testing.T) { @@ -104,6 +106,12 @@ func TestOpenVPNSelfToSelf(t *testing.T) { protocol: N.NetworkUDP, tlsCrypt: true, }, + { + name: "missing_server_key_usage", + protocol: N.NetworkUDP, + omitServerKeyUsage: true, + remoteCertificateTLS: "none", + }, } for i := range testCases { currentTestCase := testCases[i] @@ -131,6 +139,9 @@ func runOpenVPNSelfToSelf(t *testing.T, testCase openVPNSelfCase) { echoPort := reserveOpenVPNEchoPort(t) readinessPort := reserveOpenVPNEchoPort(t) certificates := createOpenVPNCertificateBundle(t) + if testCase.omitServerKeyUsage { + certificates = createOpenVPNCertificateBundleWithServerKeyUsage(t, 0) + } serverOptions := option.OpenVPNServerEndpointOptions{ ListenOptions: option.ListenOptions{ Listen: common.Ptr(badoption.Addr(netip.MustParseAddr("127.0.0.1"))), @@ -154,6 +165,7 @@ func runOpenVPNSelfToSelf(t *testing.T, testCase openVPNSelfCase) { serverOptions.UDPFiltering = option.UDPNATBehaviorAddressAndPortDependent serverOptions.UDPNATMax = 128 clientOptions := newOpenVPNTLSClientOptions(testCase.protocol, openVPNPort, certificates.caPath, certificates.clientCertPath, certificates.clientKeyPath) + clientOptions.TLS.RemoteCertificateTLS = testCase.remoteCertificateTLS clientOptions.UDPMapping = option.UDPNATBehaviorAddressDependent clientOptions.UDPFiltering = option.UDPNATBehaviorAddressAndPortDependent clientOptions.UDPNATMax = 128 @@ -1663,6 +1675,10 @@ func readOpenVPNPacketWithTimeout(packetConn net.PacketConn, buffer []byte) (int } func createOpenVPNCertificateBundle(t *testing.T) openVPNCertificateBundle { + return createOpenVPNCertificateBundleWithServerKeyUsage(t, x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature) +} + +func createOpenVPNCertificateBundleWithServerKeyUsage(t *testing.T, serverKeyUsage x509.KeyUsage) openVPNCertificateBundle { t.Helper() tempDir := t.TempDir() caKey, err := rsa.GenerateKey(rand.Reader, 3072) @@ -1694,8 +1710,8 @@ func createOpenVPNCertificateBundle(t *testing.T) openVPNCertificateBundle { require.NoError(t, err) caPath := filepath.Join(tempDir, "ca.crt") writePEMFile(t, caPath, "CERTIFICATE", caCertificate) - serverCertPath, serverKeyPath := createOpenVPNLeafCertificate(t, tempDir, "server", x509.ExtKeyUsageServerAuth, caTemplate, caKey) - clientCertPath, clientKeyPath := createOpenVPNLeafCertificate(t, tempDir, "client", x509.ExtKeyUsageClientAuth, caTemplate, caKey) + serverCertPath, serverKeyPath := createOpenVPNLeafCertificate(t, tempDir, "server", serverKeyUsage, x509.ExtKeyUsageServerAuth, caTemplate, caKey) + clientCertPath, clientKeyPath := createOpenVPNLeafCertificate(t, tempDir, "client", x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature, x509.ExtKeyUsageClientAuth, caTemplate, caKey) return openVPNCertificateBundle{ caPath: caPath, serverCertPath: serverCertPath, @@ -1705,7 +1721,7 @@ func createOpenVPNCertificateBundle(t *testing.T) openVPNCertificateBundle { } } -func createOpenVPNLeafCertificate(t *testing.T, tempDir string, commonName string, usage x509.ExtKeyUsage, caTemplate *x509.Certificate, caKey *rsa.PrivateKey) (string, string) { +func createOpenVPNLeafCertificate(t *testing.T, tempDir string, commonName string, keyUsage x509.KeyUsage, extendedKeyUsage x509.ExtKeyUsage, caTemplate *x509.Certificate, caKey *rsa.PrivateKey) (string, string) { t.Helper() leafKey, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err) @@ -1717,12 +1733,12 @@ func createOpenVPNLeafCertificate(t *testing.T, tempDir string, commonName strin }, NotBefore: time.Now().Add(-time.Minute), NotAfter: time.Now().AddDate(0, 1, 0), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + KeyUsage: keyUsage, ExtKeyUsage: []x509.ExtKeyUsage{ - usage, + extendedKeyUsage, }, } - if usage == x509.ExtKeyUsageServerAuth { + if extendedKeyUsage == x509.ExtKeyUsageServerAuth { leafTemplate.IPAddresses = append(leafTemplate.IPAddresses, net.ParseIP("127.0.0.1")) leafTemplate.DNSNames = append(leafTemplate.DNSNames, "localhost") }