-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmasque.go
150 lines (134 loc) · 4.91 KB
/
masque.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package api
import (
"context"
"crypto/ecdsa"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net"
"net/http"
connectip "github.com/Diniboy1123/connect-ip-go"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
"github.com/yosida95/uritemplate/v3"
)
// PrepareTlsConfig creates a TLS configuration using the provided certificate and SNI (Server Name Indication).
// It also verifies the peer's public key against the provided public key.
//
// Parameters:
// - privKey: *ecdsa.PrivateKey - The private key to use for TLS authentication.
// - peerPubKey: *ecdsa.PublicKey - The endpoint's public key to pin to.
// - cert: [][]byte - The certificate chain to use for TLS authentication.
// - sni: string - The Server Name Indication (SNI) to use.
//
// Returns:
// - *tls.Config: A TLS configuration for secure communication.
// - error: An error if TLS setup fails.
func PrepareTlsConfig(privKey *ecdsa.PrivateKey, peerPubKey *ecdsa.PublicKey, cert [][]byte, sni string) (*tls.Config, error) {
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{
{
Certificate: cert,
PrivateKey: privKey,
},
},
ServerName: sni,
NextProtos: []string{http3.NextProtoH3},
// WARN: SNI is usually not for the endpoint, so we must skip verification
InsecureSkipVerify: true,
// we pin to the endpoint public key
VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
if len(rawCerts) == 0 {
return nil
}
cert, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return err
}
if _, ok := cert.PublicKey.(*ecdsa.PublicKey); !ok {
// we only support ECDSA
// TODO: don't hardcode cert type in the future
// as backend can start using different cert types
return x509.ErrUnsupportedAlgorithm
}
if !cert.PublicKey.(*ecdsa.PublicKey).Equal(peerPubKey) {
// reason is incorrect, but the best I could figure
// detail explains the actual reason
//10 is NoValidChains, but we support go1.22 where it's not defined
return x509.CertificateInvalidError{Cert: cert, Reason: 10, Detail: "remote endpoint has a different public key than what we trust in config.json"}
}
return nil
},
}
return tlsConfig, nil
}
// ConnectTunnel establishes a QUIC connection and sets up a Connect-IP tunnel with the provided endpoint.
// Endpoint address is used to check whether the authentication/connection is successful or not.
// Requires modified connect-ip-go for now to support Cloudflare's non RFC compliant implementation.
//
// Parameters:
// - ctx: context.Context - The QUIC TLS context.
// - tlsConfig: *tls.Config - The TLS configuration for secure communication.
// - quicConfig: *quic.Config - The QUIC configuration settings.
// - connectUri: string - The URI template for the Connect-IP request.
// - endpoint: *net.UDPAddr - The UDP address of the QUIC server.
//
// Returns:
// - *net.UDPConn: The UDP connection used for the QUIC session.
// - *http3.Transport: The HTTP/3 transport used for initial request.
// - *connectip.Conn: The Connect-IP connection instance.
// - *http.Response: The response from the Connect-IP handshake.
// - error: An error if the connection setup fails.
func ConnectTunnel(ctx context.Context, tlsConfig *tls.Config, quicConfig *quic.Config, connectUri string, endpoint *net.UDPAddr) (*net.UDPConn, *http3.Transport, *connectip.Conn, *http.Response, error) {
var udpConn *net.UDPConn
var err error
if endpoint.IP.To4() == nil {
udpConn, err = net.ListenUDP("udp", &net.UDPAddr{
IP: net.IPv6zero,
Port: 0,
})
} else {
udpConn, err = net.ListenUDP("udp", &net.UDPAddr{
IP: net.IPv4zero,
Port: 0,
})
}
if err != nil {
return udpConn, nil, nil, nil, err
}
conn, err := quic.Dial(
ctx,
udpConn,
endpoint,
tlsConfig,
quicConfig,
)
if err != nil {
return udpConn, nil, nil, nil, err
}
tr := &http3.Transport{
EnableDatagrams: true,
AdditionalSettings: map[uint64]uint64{
// official client still sends this out as well, even though
// it's deprecated, see https://datatracker.ietf.org/doc/draft-ietf-masque-h3-datagram/00/
// SETTINGS_H3_DATAGRAM_00 = 0x0000000000000276
// https://github.com/cloudflare/quiche/blob/7c66757dbc55b8d0c3653d4b345c6785a181f0b7/quiche/src/h3/frame.rs#L46
0x276: 1,
},
DisableCompression: true,
}
hconn := tr.NewClientConn(conn)
additionalHeaders := http.Header{
"User-Agent": []string{""},
}
template := uritemplate.MustNew(connectUri)
ipConn, rsp, err := connectip.Dial(ctx, hconn, template, "cf-connect-ip", additionalHeaders, true)
if err != nil {
if err.Error() == "CRYPTO_ERROR 0x131 (remote): tls: access denied" {
return udpConn, nil, nil, nil, errors.New("login failed! Please double-check if your tls key and cert is enrolled in the Cloudflare Access service")
}
return udpConn, nil, nil, nil, fmt.Errorf("failed to dial connect-ip: %v", err)
}
return udpConn, tr, ipConn, rsp, nil
}