Skip to content

Commit c6a31f4

Browse files
authored
MITM: Allow using local received SNI in the outgoing serverName & verifyPeerCertInNames
#4348 (comment) Local received SNI was sent by browser/app. In freedom RAW's `tlsSettings`, set `"serverName": "fromMitm"` to forward it to the real website. In freedom RAW's `tlsSettings`, set `"verifyPeerCertInNames": ["fromMitm"]` to use all possible names to verify the certificate.
1 parent 9b78411 commit c6a31f4

File tree

8 files changed

+150
-85
lines changed

8 files changed

+150
-85
lines changed

common/session/context.go

+12
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const (
2424
allowedNetworkKey ctx.SessionKey = 9
2525
handlerSessionKey ctx.SessionKey = 10
2626
mitmAlpn11Key ctx.SessionKey = 11
27+
mitmServerNameKey ctx.SessionKey = 12
2728
)
2829

2930
func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context {
@@ -174,3 +175,14 @@ func MitmAlpn11FromContext(ctx context.Context) bool {
174175
}
175176
return false
176177
}
178+
179+
func ContextWithMitmServerName(ctx context.Context, serverName string) context.Context {
180+
return context.WithValue(ctx, mitmServerNameKey, serverName)
181+
}
182+
183+
func MitmServerNameFromContext(ctx context.Context) string {
184+
if val, ok := ctx.Value(mitmServerNameKey).(string); ok {
185+
return val
186+
}
187+
return ""
188+
}

infra/conf/transport_internet.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ type TLSConfig struct {
411411
CurvePreferences *StringList `json:"curvePreferences"`
412412
MasterKeyLog string `json:"masterKeyLog"`
413413
ServerNameToVerify string `json:"serverNameToVerify"`
414+
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
414415
}
415416

416417
// Build implements Buildable.
@@ -469,10 +470,11 @@ func (c *TLSConfig) Build() (proto.Message, error) {
469470
}
470471

471472
config.MasterKeyLog = c.MasterKeyLog
472-
config.ServerNameToVerify = c.ServerNameToVerify
473-
if config.ServerNameToVerify != "" && config.Fingerprint == "unsafe" {
474-
return nil, errors.New(`serverNameToVerify only works with uTLS for now`)
473+
474+
if c.ServerNameToVerify != "" {
475+
return nil, errors.PrintRemovedFeatureError("serverNameToVerify", "verifyPeerCertInNames")
475476
}
477+
config.VerifyPeerCertInNames = c.VerifyPeerCertInNames
476478

477479
return config, nil
478480
}

proxy/dokodemo/dokodemo.go

+7-11
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,6 @@ func (d *DokodemoDoor) policy() policy.Session {
6464
return p
6565
}
6666

67-
type hasHandshakeAddressContext interface {
68-
HandshakeAddressContext(ctx context.Context) net.Address
69-
}
70-
7167
// Process implements proxy.Inbound.
7268
func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
7369
errors.LogDebug(ctx, "processing connection from: ", conn.RemoteAddr())
@@ -87,14 +83,14 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
8783
destinationOverridden = true
8884
}
8985
}
90-
if handshake, ok := conn.(hasHandshakeAddressContext); ok && !destinationOverridden {
91-
addr := handshake.HandshakeAddressContext(ctx)
92-
if addr != nil {
93-
dest.Address = addr
94-
if conn.(*tls.Conn).ConnectionState().NegotiatedProtocol == "http/1.1" {
95-
ctx = session.ContextWithMitmAlpn11(ctx, true)
96-
}
86+
if tlsConn, ok := conn.(tls.Interface); ok && !destinationOverridden {
87+
if serverName := tlsConn.HandshakeContextServerName(ctx); serverName != "" {
88+
dest.Address = net.DomainAddress(serverName)
9789
destinationOverridden = true
90+
ctx = session.ContextWithMitmServerName(ctx, serverName)
91+
}
92+
if tlsConn.NegotiatedProtocol() == "http/1.1" {
93+
ctx = session.ContextWithMitmAlpn11(ctx, true)
9894
}
9995
}
10096
}

transport/internet/tcp/dialer.go

+29-5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import (
1414
"github.com/xtls/xray-core/transport/internet/tls"
1515
)
1616

17+
func IsFromMitm(str string) bool {
18+
return strings.ToLower(str) == "frommitm"
19+
}
20+
1721
// Dial dials a new TCP connection to the given destination.
1822
func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
1923
errors.LogInfo(ctx, "dialing TCP to ", dest)
@@ -23,11 +27,28 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
2327
}
2428

2529
if config := tls.ConfigFromStreamSettings(streamSettings); config != nil {
30+
mitmServerName := session.MitmServerNameFromContext(ctx)
31+
mitmAlpn11 := session.MitmAlpn11FromContext(ctx)
2632
tlsConfig := config.GetTLSConfig(tls.WithDestination(dest))
33+
if IsFromMitm(tlsConfig.ServerName) {
34+
tlsConfig.ServerName = mitmServerName
35+
}
36+
if r, ok := tlsConfig.Rand.(*tls.RandCarrier); ok && len(r.VerifyPeerCertInNames) > 0 && IsFromMitm(r.VerifyPeerCertInNames[0]) {
37+
r.VerifyPeerCertInNames = r.VerifyPeerCertInNames[1:]
38+
after := mitmServerName
39+
for {
40+
if len(after) > 0 {
41+
r.VerifyPeerCertInNames = append(r.VerifyPeerCertInNames, after)
42+
}
43+
_, after, _ = strings.Cut(after, ".")
44+
if !strings.Contains(after, ".") {
45+
break
46+
}
47+
}
48+
}
2749
if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil {
2850
conn = tls.UClient(conn, tlsConfig, fingerprint)
29-
if len(tlsConfig.NextProtos) == 1 && (tlsConfig.NextProtos[0] == "http/1.1" ||
30-
(strings.ToLower(tlsConfig.NextProtos[0]) == "frommitm" && session.MitmAlpn11FromContext(ctx))) {
51+
if len(tlsConfig.NextProtos) == 1 && (tlsConfig.NextProtos[0] == "http/1.1" || (IsFromMitm(tlsConfig.NextProtos[0]) && mitmAlpn11)) {
3152
if err := conn.(*tls.UConn).WebsocketHandshakeContext(ctx); err != nil {
3253
return nil, err
3354
}
@@ -37,14 +58,17 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
3758
}
3859
}
3960
} else {
40-
if len(tlsConfig.NextProtos) == 1 && strings.ToLower(tlsConfig.NextProtos[0]) == "frommitm" {
41-
if session.MitmAlpn11FromContext(ctx) {
42-
tlsConfig.NextProtos = []string{"http/1.1"} // new slice
61+
if len(tlsConfig.NextProtos) == 1 && IsFromMitm(tlsConfig.NextProtos[0]) {
62+
if mitmAlpn11 {
63+
tlsConfig.NextProtos[0] = "http/1.1"
4364
} else {
4465
tlsConfig.NextProtos = nil
4566
}
4667
}
4768
conn = tls.Client(conn, tlsConfig)
69+
if err := conn.(*tls.Conn).HandshakeContext(ctx); err != nil {
70+
return nil, err
71+
}
4872
}
4973
} else if config := reality.ConfigFromStreamSettings(streamSettings); config != nil {
5074
if conn, err = reality.UClient(conn, config, ctx, dest); err != nil {

transport/internet/tls/config.go

+49-11
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"crypto/x509"
1010
"encoding/base64"
1111
"os"
12+
"slices"
1213
"strings"
1314
"sync"
1415
"time"
@@ -277,22 +278,47 @@ func (c *Config) parseServerName() string {
277278
return c.ServerName
278279
}
279280

280-
func (c *Config) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
281-
if c.PinnedPeerCertificateChainSha256 != nil {
281+
func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
282+
if r.VerifyPeerCertInNames != nil {
283+
if len(r.VerifyPeerCertInNames) > 0 {
284+
certs := make([]*x509.Certificate, len(rawCerts))
285+
for i, asn1Data := range rawCerts {
286+
certs[i], _ = x509.ParseCertificate(asn1Data)
287+
}
288+
opts := x509.VerifyOptions{
289+
Roots: r.RootCAs,
290+
CurrentTime: time.Now(),
291+
Intermediates: x509.NewCertPool(),
292+
}
293+
for _, cert := range certs[1:] {
294+
opts.Intermediates.AddCert(cert)
295+
}
296+
for _, opts.DNSName = range r.VerifyPeerCertInNames {
297+
if _, err := certs[0].Verify(opts); err == nil {
298+
return nil
299+
}
300+
}
301+
}
302+
if r.PinnedPeerCertificateChainSha256 == nil {
303+
errors.New("peer cert is invalid.")
304+
}
305+
}
306+
307+
if r.PinnedPeerCertificateChainSha256 != nil {
282308
hashValue := GenerateCertChainHash(rawCerts)
283-
for _, v := range c.PinnedPeerCertificateChainSha256 {
309+
for _, v := range r.PinnedPeerCertificateChainSha256 {
284310
if hmac.Equal(hashValue, v) {
285311
return nil
286312
}
287313
}
288314
return errors.New("peer cert is unrecognized: ", base64.StdEncoding.EncodeToString(hashValue))
289315
}
290316

291-
if c.PinnedPeerCertificatePublicKeySha256 != nil {
317+
if r.PinnedPeerCertificatePublicKeySha256 != nil {
292318
for _, v := range verifiedChains {
293319
for _, cert := range v {
294320
publicHash := GenerateCertPublicKeyHash(cert)
295-
for _, c := range c.PinnedPeerCertificatePublicKeySha256 {
321+
for _, c := range r.PinnedPeerCertificatePublicKeySha256 {
296322
if hmac.Equal(publicHash, c) {
297323
return nil
298324
}
@@ -305,7 +331,10 @@ func (c *Config) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Cert
305331
}
306332

307333
type RandCarrier struct {
308-
ServerNameToVerify string
334+
RootCAs *x509.CertPool
335+
VerifyPeerCertInNames []string
336+
PinnedPeerCertificateChainSha256 [][]byte
337+
PinnedPeerCertificatePublicKeySha256 [][]byte
309338
}
310339

311340
func (r *RandCarrier) Read(p []byte) (n int, err error) {
@@ -329,16 +358,25 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
329358
}
330359
}
331360

361+
randCarrier := &RandCarrier{
362+
RootCAs: root,
363+
VerifyPeerCertInNames: slices.Clone(c.VerifyPeerCertInNames),
364+
PinnedPeerCertificateChainSha256: c.PinnedPeerCertificateChainSha256,
365+
PinnedPeerCertificatePublicKeySha256: c.PinnedPeerCertificatePublicKeySha256,
366+
}
332367
config := &tls.Config{
333-
Rand: &RandCarrier{
334-
ServerNameToVerify: c.ServerNameToVerify,
335-
},
368+
Rand: randCarrier,
336369
ClientSessionCache: globalSessionCache,
337370
RootCAs: root,
338371
InsecureSkipVerify: c.AllowInsecure,
339-
NextProtos: c.NextProtocol,
372+
NextProtos: slices.Clone(c.NextProtocol),
340373
SessionTicketsDisabled: !c.EnableSessionResumption,
341-
VerifyPeerCertificate: c.verifyPeerCert,
374+
VerifyPeerCertificate: randCarrier.verifyPeerCert,
375+
}
376+
if len(c.VerifyPeerCertInNames) > 0 {
377+
config.InsecureSkipVerify = true
378+
} else {
379+
randCarrier.VerifyPeerCertInNames = nil
342380
}
343381

344382
for _, opt := range opts {

transport/internet/tls/config.pb.go

+26-24
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

transport/internet/tls/config.proto

+9-7
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,14 @@ message Config {
6969

7070
bool reject_unknown_sni = 12;
7171

72-
/* @Document A pinned certificate chain sha256 hash.
73-
@Document If the server's hash does not match this value, the connection will be aborted.
74-
@Document This value replace allow_insecure.
72+
/* @Document Some certificate chain sha256 hashes.
73+
@Document After normal validation or allow_insecure, if the server's cert chain hash does not match any of these values, the connection will be aborted.
7574
@Critical
7675
*/
7776
repeated bytes pinned_peer_certificate_chain_sha256 = 13;
7877

79-
/* @Document A pinned certificate public key sha256 hash.
80-
@Document If the server's public key hash does not match this value, the connection will be aborted.
81-
@Document This value replace allow_insecure.
78+
/* @Document Some certificate public key sha256 hashes.
79+
@Document After normal validation (required), if the verified cert's public key hash does not match any of these values, the connection will be aborted.
8280
@Critical
8381
*/
8482
repeated bytes pinned_peer_certificate_public_key_sha256 = 14;
@@ -88,5 +86,9 @@ message Config {
8886
// Lists of string as CurvePreferences values.
8987
repeated string curve_preferences = 16;
9088

91-
string server_name_to_verify = 17;
89+
/* @Document Replaces server_name to verify the peer cert.
90+
@Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried.
91+
@Critical
92+
*/
93+
repeated string verify_peer_cert_in_names = 17;
9294
}

0 commit comments

Comments
 (0)