146 lines
3.2 KiB
Go
146 lines
3.2 KiB
Go
package libv2ray
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"crypto/tls"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"net"
|
|
"strconv"
|
|
"time"
|
|
|
|
quic "github.com/apernet/quic-go"
|
|
)
|
|
|
|
type certSha256Request struct {
|
|
Address string `json:"address"`
|
|
Port int `json:"port"`
|
|
ServerName string `json:"serverName"`
|
|
TimeoutMs int64 `json:"timeoutMs"`
|
|
}
|
|
|
|
type certSha256Result struct {
|
|
Sha256 string `json:"sha256"`
|
|
Error string `json:"error"`
|
|
}
|
|
|
|
func FetchTlsCertSha256(requestJSON string) string {
|
|
return fetchCertSha256(requestJSON, fetchTLSCertSha256)
|
|
}
|
|
|
|
func FetchQuicCertSha256(requestJSON string) string {
|
|
return fetchCertSha256(requestJSON, fetchQUICCertSha256)
|
|
}
|
|
|
|
func fetchCertSha256(
|
|
requestJSON string,
|
|
fetcher func(certSha256Request) (string, error),
|
|
) string {
|
|
var request certSha256Request
|
|
if err := json.Unmarshal([]byte(requestJSON), &request); err != nil {
|
|
return marshalCertSha256Result(certSha256Result{Error: err.Error()})
|
|
}
|
|
|
|
sha256Value, err := fetcher(request)
|
|
if err != nil {
|
|
return marshalCertSha256Result(certSha256Result{Error: err.Error()})
|
|
}
|
|
|
|
return marshalCertSha256Result(certSha256Result{Sha256: sha256Value})
|
|
}
|
|
|
|
func fetchTLSCertSha256(request certSha256Request) (string, error) {
|
|
address, serverName, timeout, err := normalizeCertRequest(request)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
conn, err := tls.DialWithDialer(
|
|
&net.Dialer{Timeout: timeout},
|
|
"tcp",
|
|
address,
|
|
&tls.Config{
|
|
ServerName: serverName,
|
|
InsecureSkipVerify: true,
|
|
MinVersion: tls.VersionTLS12,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer conn.Close()
|
|
|
|
state := conn.ConnectionState()
|
|
if len(state.PeerCertificates) == 0 {
|
|
return "", errors.New("peer certificate is empty")
|
|
}
|
|
|
|
return rawCertSHA256Hex(state.PeerCertificates[0].Raw), nil
|
|
}
|
|
|
|
func fetchQUICCertSha256(request certSha256Request) (string, error) {
|
|
address, serverName, timeout, err := normalizeCertRequest(request)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
defer cancel()
|
|
|
|
conn, err := quic.DialAddr(
|
|
ctx,
|
|
address,
|
|
&tls.Config{
|
|
ServerName: serverName,
|
|
InsecureSkipVerify: true,
|
|
MinVersion: tls.VersionTLS12,
|
|
NextProtos: []string{"h3"},
|
|
},
|
|
&quic.Config{
|
|
HandshakeIdleTimeout: timeout,
|
|
MaxIdleTimeout: timeout,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer conn.CloseWithError(0, "")
|
|
|
|
state := conn.ConnectionState()
|
|
if len(state.TLS.PeerCertificates) == 0 {
|
|
return "", errors.New("peer certificate is empty")
|
|
}
|
|
|
|
return rawCertSHA256Hex(state.TLS.PeerCertificates[0].Raw), nil
|
|
}
|
|
|
|
func normalizeCertRequest(req certSha256Request) (string, string, time.Duration, error) {
|
|
if req.Address == "" {
|
|
return "", "", 0, errors.New("address is empty")
|
|
}
|
|
|
|
port := req.Port
|
|
if port <= 0 {
|
|
port = 443
|
|
}
|
|
|
|
timeout := time.Duration(req.TimeoutMs) * time.Millisecond
|
|
|
|
return net.JoinHostPort(req.Address, strconv.Itoa(port)), req.ServerName, timeout, nil
|
|
}
|
|
|
|
func rawCertSHA256Hex(raw []byte) string {
|
|
sum := sha256.Sum256(raw)
|
|
return hex.EncodeToString(sum[:])
|
|
}
|
|
|
|
func marshalCertSha256Result(result certSha256Result) string {
|
|
data, err := json.Marshal(result)
|
|
if err != nil {
|
|
return `{"error":"failed to marshal result"}`
|
|
}
|
|
return string(data)
|
|
}
|