Skip to content

Commit c2301f6

Browse files
committed
chore: rebuild fingerprint and keypair handle
1 parent 468cfc3 commit c2301f6

File tree

15 files changed

+153
-126
lines changed

15 files changed

+153
-126
lines changed

common/net/tls.go

-65
This file was deleted.

component/ca/config.go

-42
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
package ca
22

33
import (
4-
"bytes"
5-
"crypto/sha256"
64
"crypto/tls"
75
"crypto/x509"
86
_ "embed"
9-
"encoding/hex"
107
"errors"
118
"fmt"
129
"os"
1310
"strconv"
14-
"strings"
1511
"sync"
1612

1713
C "github.com/metacubex/mihomo/constant"
@@ -81,36 +77,6 @@ func getCertPool() *x509.CertPool {
8177
return globalCertPool
8278
}
8379

84-
func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
85-
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
86-
// ssl pining
87-
for i := range rawCerts {
88-
rawCert := rawCerts[i]
89-
cert, err := x509.ParseCertificate(rawCert)
90-
if err == nil {
91-
hash := sha256.Sum256(cert.Raw)
92-
if bytes.Equal(fingerprint[:], hash[:]) {
93-
return nil
94-
}
95-
}
96-
}
97-
return errNotMatch
98-
}
99-
}
100-
101-
func convertFingerprint(fingerprint string) (*[32]byte, error) {
102-
fingerprint = strings.TrimSpace(strings.Replace(fingerprint, ":", "", -1))
103-
fpByte, err := hex.DecodeString(fingerprint)
104-
if err != nil {
105-
return nil, err
106-
}
107-
108-
if len(fpByte) != 32 {
109-
return nil, fmt.Errorf("fingerprint string length error,need sha256 fingerprint")
110-
}
111-
return (*[32]byte)(fpByte), nil
112-
}
113-
11480
func GetCertPool(customCA string, customCAString string) (*x509.CertPool, error) {
11581
var certificate []byte
11682
var err error
@@ -133,14 +99,6 @@ func GetCertPool(customCA string, customCAString string) (*x509.CertPool, error)
13399
}
134100
}
135101

136-
func NewFingerprintVerifier(fingerprint string) (func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error, error) {
137-
fingerprintBytes, err := convertFingerprint(fingerprint)
138-
if err != nil {
139-
return nil, err
140-
}
141-
return verifyFingerprint(fingerprintBytes), nil
142-
}
143-
144102
// GetTLSConfig specified fingerprint, customCA and customCAString
145103
func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, customCAString string) (_ *tls.Config, err error) {
146104
if tlsConfig == nil {

component/ca/fingerprint.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package ca
2+
3+
import (
4+
"bytes"
5+
"crypto/sha256"
6+
"crypto/x509"
7+
"encoding/hex"
8+
"fmt"
9+
"strings"
10+
)
11+
12+
// NewFingerprintVerifier returns a function that verifies whether a certificate's SHA-256 fingerprint matches the given one.
13+
func NewFingerprintVerifier(fingerprint string) (func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error, error) {
14+
fingerprint = strings.TrimSpace(strings.Replace(fingerprint, ":", "", -1))
15+
fpByte, err := hex.DecodeString(fingerprint)
16+
if err != nil {
17+
return nil, err
18+
}
19+
20+
if len(fpByte) != 32 {
21+
return nil, fmt.Errorf("fingerprint string length error,need sha256 fingerprint")
22+
}
23+
24+
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
25+
// ssl pining
26+
for _, rawCert := range rawCerts {
27+
hash := sha256.Sum256(rawCert)
28+
if bytes.Equal(fpByte, hash[:]) {
29+
return nil
30+
}
31+
}
32+
return errNotMatch
33+
}, nil
34+
}
35+
36+
// CalculateFingerprint computes the SHA-256 fingerprint of the given DER-encoded certificate and returns it as a hex string.
37+
func CalculateFingerprint(certDER []byte) string {
38+
hash := sha256.Sum256(certDER)
39+
return hex.EncodeToString(hash[:])
40+
}

component/ca/keypair.go

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package ca
2+
3+
import (
4+
"crypto"
5+
"crypto/ecdsa"
6+
"crypto/ed25519"
7+
"crypto/elliptic"
8+
"crypto/rand"
9+
"crypto/rsa"
10+
"crypto/tls"
11+
"crypto/x509"
12+
"encoding/pem"
13+
"fmt"
14+
"math/big"
15+
)
16+
17+
type Path interface {
18+
Resolve(path string) string
19+
}
20+
21+
// LoadTLSKeyPair loads a TLS key pair from the provided certificate and private key data or file paths, supporting fallback resolution.
22+
// Returns a tls.Certificate and an error, where the error indicates issues during parsing or file loading.
23+
// If both certificate and privateKey are empty, generates a random TLS RSA key pair.
24+
// Accepts a Path interface for resolving file paths when necessary.
25+
func LoadTLSKeyPair(certificate, privateKey string, path Path) (tls.Certificate, error) {
26+
if certificate == "" && privateKey == "" {
27+
var err error
28+
certificate, privateKey, _, err = NewRandomTLSKeyPair(KeyPairTypeRSA)
29+
if err != nil {
30+
return tls.Certificate{}, err
31+
}
32+
}
33+
cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey))
34+
if painTextErr == nil {
35+
return cert, nil
36+
}
37+
if path == nil {
38+
return tls.Certificate{}, painTextErr
39+
}
40+
41+
certificate = path.Resolve(certificate)
42+
privateKey = path.Resolve(privateKey)
43+
cert, loadErr := tls.LoadX509KeyPair(certificate, privateKey)
44+
if loadErr != nil {
45+
return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())
46+
}
47+
return cert, nil
48+
}
49+
50+
type KeyPairType string
51+
52+
const (
53+
KeyPairTypeRSA KeyPairType = "rsa"
54+
KeyPairTypeP256 KeyPairType = "p256"
55+
KeyPairTypeP384 KeyPairType = "p384"
56+
KeyPairTypeEd25519 KeyPairType = "ed25519"
57+
)
58+
59+
// NewRandomTLSKeyPair generates a random TLS key pair based on the specified KeyPairType and returns it with a SHA256 fingerprint.
60+
// Note: Most browsers do not support KeyPairTypeEd25519 type of certificate, and utls.UConn will also reject this type of certificate.
61+
func NewRandomTLSKeyPair(keyPairType KeyPairType) (certificate string, privateKey string, fingerprint string, err error) {
62+
var key crypto.Signer
63+
switch keyPairType {
64+
case KeyPairTypeRSA:
65+
key, err = rsa.GenerateKey(rand.Reader, 2048)
66+
case KeyPairTypeP256:
67+
key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
68+
case KeyPairTypeP384:
69+
key, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
70+
case KeyPairTypeEd25519:
71+
_, key, err = ed25519.GenerateKey(rand.Reader)
72+
default: // fallback to KeyPairTypeRSA
73+
key, err = rsa.GenerateKey(rand.Reader, 2048)
74+
}
75+
if err != nil {
76+
return
77+
}
78+
79+
template := x509.Certificate{SerialNumber: big.NewInt(1)}
80+
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, key.Public(), key)
81+
if err != nil {
82+
return
83+
}
84+
privBytes, err := x509.MarshalPKCS8PrivateKey(key)
85+
if err != nil {
86+
return
87+
}
88+
fingerprint = CalculateFingerprint(certDER)
89+
privateKey = string(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}))
90+
certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}))
91+
return
92+
}

hub/route/server.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import (
1515
"time"
1616

1717
"github.com/metacubex/mihomo/adapter/inbound"
18-
CN "github.com/metacubex/mihomo/common/net"
1918
"github.com/metacubex/mihomo/common/utils"
19+
"github.com/metacubex/mihomo/component/ca"
2020
C "github.com/metacubex/mihomo/constant"
2121
"github.com/metacubex/mihomo/log"
2222
"github.com/metacubex/mihomo/tunnel/statistic"
@@ -186,7 +186,7 @@ func startTLS(cfg *Config) {
186186

187187
// handle tlsAddr
188188
if len(cfg.TLSAddr) > 0 {
189-
c, err := CN.ParseCert(cfg.Certificate, cfg.PrivateKey, C.Path)
189+
c, err := ca.LoadTLSKeyPair(cfg.Certificate, cfg.PrivateKey, C.Path)
190190
if err != nil {
191191
log.Errorln("External controller tls listen error: %s", err)
192192
return

listener/anytls/server.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"github.com/metacubex/mihomo/adapter/inbound"
1313
"github.com/metacubex/mihomo/common/atomic"
1414
"github.com/metacubex/mihomo/common/buf"
15-
N "github.com/metacubex/mihomo/common/net"
15+
"github.com/metacubex/mihomo/component/ca"
1616
C "github.com/metacubex/mihomo/constant"
1717
LC "github.com/metacubex/mihomo/listener/config"
1818
"github.com/metacubex/mihomo/listener/sing"
@@ -43,7 +43,7 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition)
4343

4444
tlsConfig := &tls.Config{}
4545
if config.Certificate != "" && config.PrivateKey != "" {
46-
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
46+
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path)
4747
if err != nil {
4848
return nil, err
4949
}

listener/http/server.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"net"
77

88
"github.com/metacubex/mihomo/adapter/inbound"
9-
N "github.com/metacubex/mihomo/common/net"
9+
"github.com/metacubex/mihomo/component/ca"
1010
C "github.com/metacubex/mihomo/constant"
1111
authStore "github.com/metacubex/mihomo/listener/auth"
1212
LC "github.com/metacubex/mihomo/listener/config"
@@ -68,7 +68,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
6868
var realityBuilder *reality.Builder
6969

7070
if config.Certificate != "" && config.PrivateKey != "" {
71-
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
71+
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path)
7272
if err != nil {
7373
return nil, err
7474
}

listener/inbound/common_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ var httpPath = "/inbound_test"
3030
var httpData = make([]byte, 10240)
3131
var remoteAddr = netip.MustParseAddr("1.2.3.4")
3232
var userUUID = utils.NewUUIDV4().String()
33-
var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = N.NewRandomTLSKeyPair()
33+
var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256)
3434
var tlsConfigCert, _ = tls.X509KeyPair([]byte(tlsCertificate), []byte(tlsPrivateKey))
3535
var tlsConfig = &tls.Config{Certificates: []tls.Certificate{tlsConfigCert}, NextProtos: []string{"h2", "http/1.1"}}
3636
var tlsClientConfig, _ = ca.GetTLSConfig(nil, tlsFingerprint, "", "")

listener/mixed/mixed.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/metacubex/mihomo/adapter/inbound"
99
N "github.com/metacubex/mihomo/common/net"
1010
"github.com/metacubex/mihomo/component/auth"
11+
"github.com/metacubex/mihomo/component/ca"
1112
C "github.com/metacubex/mihomo/constant"
1213
authStore "github.com/metacubex/mihomo/listener/auth"
1314
LC "github.com/metacubex/mihomo/listener/config"
@@ -63,7 +64,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
6364
var realityBuilder *reality.Builder
6465

6566
if config.Certificate != "" && config.PrivateKey != "" {
66-
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
67+
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path)
6768
if err != nil {
6869
return nil, err
6970
}

listener/sing_hysteria2/server.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import (
1313

1414
"github.com/metacubex/mihomo/adapter/inbound"
1515
"github.com/metacubex/mihomo/adapter/outbound"
16-
CN "github.com/metacubex/mihomo/common/net"
1716
"github.com/metacubex/mihomo/common/sockopt"
17+
"github.com/metacubex/mihomo/component/ca"
1818
tlsC "github.com/metacubex/mihomo/component/tls"
1919
C "github.com/metacubex/mihomo/constant"
2020
LC "github.com/metacubex/mihomo/listener/config"
@@ -56,7 +56,7 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
5656

5757
sl = &Listener{false, config, nil, nil}
5858

59-
cert, err := CN.ParseCert(config.Certificate, config.PrivateKey, C.Path)
59+
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path)
6060
if err != nil {
6161
return nil, err
6262
}

listener/sing_vless/server.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"unsafe"
1212

1313
"github.com/metacubex/mihomo/adapter/inbound"
14-
N "github.com/metacubex/mihomo/common/net"
14+
"github.com/metacubex/mihomo/component/ca"
1515
tlsC "github.com/metacubex/mihomo/component/tls"
1616
C "github.com/metacubex/mihomo/constant"
1717
LC "github.com/metacubex/mihomo/listener/config"
@@ -87,7 +87,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
8787
var httpHandler http.Handler
8888

8989
if config.Certificate != "" && config.PrivateKey != "" {
90-
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
90+
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path)
9191
if err != nil {
9292
return nil, err
9393
}

0 commit comments

Comments
 (0)