Files
AndroidLibXrayLite/libv2ray_certSha256.go
2026-06-10 09:31:09 +08:00

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)
}