mirror of
https://github.com/cmz0228/hysteria-dev.git
synced 2025-06-08 21:39:53 +00:00
feat: client TLS cert SHA256 pinning (pinSHA256)
This commit is contained in:
parent
b12bd74ac7
commit
3c3c2a51a8
@ -1,7 +1,9 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -72,6 +74,7 @@ type clientConfigObfs struct {
|
|||||||
type clientConfigTLS struct {
|
type clientConfigTLS struct {
|
||||||
SNI string `mapstructure:"sni"`
|
SNI string `mapstructure:"sni"`
|
||||||
Insecure bool `mapstructure:"insecure"`
|
Insecure bool `mapstructure:"insecure"`
|
||||||
|
PinSHA256 string `mapstructure:"pinSHA256"`
|
||||||
CA string `mapstructure:"ca"`
|
CA string `mapstructure:"ca"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,6 +177,20 @@ func (c *clientConfig) fillTLSConfig(hyConfig *client.Config) error {
|
|||||||
hyConfig.TLSConfig.ServerName = c.TLS.SNI
|
hyConfig.TLSConfig.ServerName = c.TLS.SNI
|
||||||
}
|
}
|
||||||
hyConfig.TLSConfig.InsecureSkipVerify = c.TLS.Insecure
|
hyConfig.TLSConfig.InsecureSkipVerify = c.TLS.Insecure
|
||||||
|
if c.TLS.PinSHA256 != "" {
|
||||||
|
nHash := normalizeCertHash(c.TLS.PinSHA256)
|
||||||
|
hyConfig.TLSConfig.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
||||||
|
for _, cert := range rawCerts {
|
||||||
|
hash := sha256.Sum256(cert)
|
||||||
|
hashHex := hex.EncodeToString(hash[:])
|
||||||
|
if hashHex == nHash {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No match
|
||||||
|
return errors.New("no certificate matches the pinned hash")
|
||||||
|
}
|
||||||
|
}
|
||||||
if c.TLS.CA != "" {
|
if c.TLS.CA != "" {
|
||||||
ca, err := os.ReadFile(c.TLS.CA)
|
ca, err := os.ReadFile(c.TLS.CA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -233,6 +250,7 @@ func (c *clientConfig) fillFastOpen(hyConfig *client.Config) error {
|
|||||||
// - obfuscation password
|
// - obfuscation password
|
||||||
// - TLS SNI
|
// - TLS SNI
|
||||||
// - TLS insecure
|
// - TLS insecure
|
||||||
|
// - TLS pinned SHA256 hash (normalized)
|
||||||
func (c *clientConfig) URI() string {
|
func (c *clientConfig) URI() string {
|
||||||
q := url.Values{}
|
q := url.Values{}
|
||||||
switch strings.ToLower(c.Obfs.Type) {
|
switch strings.ToLower(c.Obfs.Type) {
|
||||||
@ -246,6 +264,9 @@ func (c *clientConfig) URI() string {
|
|||||||
if c.TLS.Insecure {
|
if c.TLS.Insecure {
|
||||||
q.Set("insecure", "1")
|
q.Set("insecure", "1")
|
||||||
}
|
}
|
||||||
|
if c.TLS.PinSHA256 != "" {
|
||||||
|
q.Set("pinSHA256", normalizeCertHash(c.TLS.PinSHA256))
|
||||||
|
}
|
||||||
var user *url.Userinfo
|
var user *url.Userinfo
|
||||||
if c.Auth != "" {
|
if c.Auth != "" {
|
||||||
// We need to handle the special case of user:pass pairs
|
// We need to handle the special case of user:pass pairs
|
||||||
@ -297,6 +318,9 @@ func (c *clientConfig) parseURI() bool {
|
|||||||
if insecure, err := strconv.ParseBool(q.Get("insecure")); err == nil {
|
if insecure, err := strconv.ParseBool(q.Get("insecure")); err == nil {
|
||||||
c.TLS.Insecure = insecure
|
c.TLS.Insecure = insecure
|
||||||
}
|
}
|
||||||
|
if pinSHA256 := q.Get("pinSHA256"); pinSHA256 != "" {
|
||||||
|
c.TLS.PinSHA256 = pinSHA256
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ func TestClientConfig(t *testing.T) {
|
|||||||
TLS: clientConfigTLS{
|
TLS: clientConfigTLS{
|
||||||
SNI: "another.example.com",
|
SNI: "another.example.com",
|
||||||
Insecure: true,
|
Insecure: true,
|
||||||
|
PinSHA256: "114515DEADBEEF",
|
||||||
CA: "custom_ca.crt",
|
CA: "custom_ca.crt",
|
||||||
},
|
},
|
||||||
QUIC: clientConfigQUIC{
|
QUIC: clientConfigQUIC{
|
||||||
@ -105,7 +106,7 @@ func TestClientConfigURI(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
uri: "hysteria2://noauth.com/?insecure=1&obfs=salamander&obfs-password=66ccff&sni=crap.cc",
|
uri: "hysteria2://noauth.com/?insecure=1&obfs=salamander&obfs-password=66ccff&pinSHA256=deadbeef&sni=crap.cc",
|
||||||
uriOK: true,
|
uriOK: true,
|
||||||
config: &clientConfig{
|
config: &clientConfig{
|
||||||
Server: "noauth.com",
|
Server: "noauth.com",
|
||||||
@ -119,6 +120,7 @@ func TestClientConfigURI(t *testing.T) {
|
|||||||
TLS: clientConfigTLS{
|
TLS: clientConfigTLS{
|
||||||
SNI: "crap.cc",
|
SNI: "crap.cc",
|
||||||
Insecure: true,
|
Insecure: true,
|
||||||
|
PinSHA256: "deadbeef",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -10,6 +10,7 @@ obfs:
|
|||||||
tls:
|
tls:
|
||||||
sni: another.example.com
|
sni: another.example.com
|
||||||
insecure: true
|
insecure: true
|
||||||
|
pinSHA256: 114515DEADBEEF
|
||||||
ca: custom_ca.crt
|
ca: custom_ca.crt
|
||||||
|
|
||||||
quic:
|
quic:
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/apernet/hysteria/extras/utils"
|
"github.com/apernet/hysteria/extras/utils"
|
||||||
"github.com/mdp/qrterminal/v3"
|
"github.com/mdp/qrterminal/v3"
|
||||||
@ -108,3 +109,12 @@ func (l *geoipLoader) Load() *geoip2.Reader {
|
|||||||
}
|
}
|
||||||
return l.db
|
return l.db
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// normalizeCertHash normalizes a certificate hash string.
|
||||||
|
// It converts all characters to lowercase and removes possible separators such as ":" and "-".
|
||||||
|
func normalizeCertHash(hash string) string {
|
||||||
|
r := strings.ToLower(hash)
|
||||||
|
r = strings.ReplaceAll(r, ":", "")
|
||||||
|
r = strings.ReplaceAll(r, "-", "")
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
@ -65,6 +65,7 @@ func (c *clientImpl) connect() error {
|
|||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
ServerName: c.config.TLSConfig.ServerName,
|
ServerName: c.config.TLSConfig.ServerName,
|
||||||
InsecureSkipVerify: c.config.TLSConfig.InsecureSkipVerify,
|
InsecureSkipVerify: c.config.TLSConfig.InsecureSkipVerify,
|
||||||
|
VerifyPeerCertificate: c.config.TLSConfig.VerifyPeerCertificate,
|
||||||
RootCAs: c.config.TLSConfig.RootCAs,
|
RootCAs: c.config.TLSConfig.RootCAs,
|
||||||
}
|
}
|
||||||
quicConfig := &quic.Config{
|
quicConfig := &quic.Config{
|
||||||
|
@ -90,6 +90,7 @@ func (f *udpConnFactory) New(addr net.Addr) (net.PacketConn, error) {
|
|||||||
type TLSConfig struct {
|
type TLSConfig struct {
|
||||||
ServerName string
|
ServerName string
|
||||||
InsecureSkipVerify bool
|
InsecureSkipVerify bool
|
||||||
|
VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
|
||||||
RootCAs *x509.CertPool
|
RootCAs *x509.CertPool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user