Compare commits

..

21 Commits

Author SHA1 Message Date
风扇滑翔翼
591ed82441 Use channel for pool 2025-11-26 17:39:40 +00:00
RPRX
e8aecbc9af Avoid panic
https://github.com/XTLS/Xray-core/pull/5270#issuecomment-3483155727
2025-11-04 02:06:58 +00:00
RPRX
2dc9729864 VLESS outbound: Add pre-connect (early test, for Vision Seed)
https://t.me/projectXtls/1034
2025-11-02 10:16:20 +00:00
dependabot[bot]
cb4f943f50 Bump actions/upload-artifact from 4 to 5 (#5259)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-01 11:07:19 +00:00
RPRX
b69a376aa1 v25.10.15
Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

VLESS Post-Quantum Encryption: https://github.com/XTLS/Xray-core/pull/5067
VLESS NFT: https://opensea.io/collection/vless

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2025-10-15 08:07:23 +00:00
RPRX
12f4a014e0 VLESS Reverse Proxy: Transfer real Source & Local (IP & port), enabled by default
https://t.me/projectXtls/1039

https://github.com/XTLS/Xray-core/pull/5101#issuecomment-3404979909
2025-10-15 07:41:49 +00:00
RPRX
9cc7907234 XHTTP client: Change default maxConcurrency to 1 for speed testing
https://t.me/projectXray/4386271
2025-10-14 23:33:06 +00:00
patterniha
21a9658519 Router: Use built-in-dns only once for all rules (in "IPOnDemand"/"IPIfNonMatch" mode) (#5210) 2025-10-14 20:59:04 +00:00
Random Guy
7f436f5318 README.md: Add PasarGuard to Web Panels (#5224) 2025-10-14 20:34:14 +00:00
RPRX
dcfde8dc92 Update github.com/xtls/reality to 20251014195629
e4eec45205
2025-10-14 20:16:20 +00:00
dependabot[bot]
898db92d51 Bump golang.org/x/net from 0.44.0 to 0.46.0 (#5215)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.44.0 to 0.46.0.
- [Commits](https://github.com/golang/net/compare/v0.44.0...v0.46.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-14 19:57:40 +00:00
dependabot[bot]
8dd0e388a2 Bump google.golang.org/grpc from 1.75.1 to 1.76.0 (#5212)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.75.1 to 1.76.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.75.1...v1.76.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.76.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-14 19:18:46 +00:00
RPRX
40f0a541bf transport/internet/reality/reality.go: Safely get negotiated CurveID in VerifyPeerCertificate()
Requires github.com/refraction-networking/utls v1.8.1+
2025-10-14 19:12:14 +00:00
dependabot[bot]
1762d6c8cc Bump github.com/refraction-networking/utls from 1.8.0 to 1.8.1 (#5229)
Bumps [github.com/refraction-networking/utls](https://github.com/refraction-networking/utls) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/refraction-networking/utls/releases)
- [Commits](https://github.com/refraction-networking/utls/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/refraction-networking/utls
  dependency-version: 1.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-15 00:08:11 +08:00
风扇滑翔翼
195248801d Fix shadowsocks2022 memory leak (#5166)
* Fix ss2022 gouroutine leak

* ErrReadTimeout
2025-10-05 20:15:53 -04:00
wwqgtxx
4a825c0260 fix: darwin arm64 always has AESGCMHardwareSupport (#5176)
https://github.com/refraction-networking/utls/pull/371
2025-10-05 20:14:45 -04:00
dependabot[bot]
514c9e5a22 Bump github.com/quic-go/quic-go from 0.54.1 to 0.55.0 (#5208)
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.54.1 to 0.55.0.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.54.1...v0.55.0)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-version: 0.55.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-05 20:14:03 -04:00
Yury Kastov
2f366aed2e feat(config): add unix socket HTTP config loader support (#5200)
Adds support for loading configuration from HTTP endpoints served over Unix domain sockets using the http+unix:// protocol scheme.
2025-10-04 23:13:47 -04:00
风扇滑翔翼
c0c88f3d73 Fix vless reverse panic in vision (#5189)
* Fix vless reverse panic in vision

* Add panic
2025-10-04 23:04:18 -04:00
dependabot[bot]
d0344bcff8 Bump github.com/quic-go/quic-go from 0.54.0 to 0.54.1 (#5180)
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.54.0 to 0.54.1.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.54.0...v0.54.1)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-version: 0.54.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-04 23:01:23 -04:00
dependabot[bot]
a6ebb3061c Bump google.golang.org/protobuf from 1.36.9 to 1.36.10 (#5203)
Bumps google.golang.org/protobuf from 1.36.9 to 1.36.10.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-version: 1.36.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-04 23:01:07 -04:00
36 changed files with 754 additions and 406 deletions

View File

@@ -132,7 +132,7 @@ jobs:
mv build_assets Xray-${{ env.ASSET_NAME }}
- name: Upload files to Artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: Xray-${{ env.ASSET_NAME }}
path: |

View File

@@ -238,7 +238,7 @@ jobs:
mv build_assets Xray-${{ env.ASSET_NAME }}
- name: Upload files to Artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: Xray-${{ env.ASSET_NAME }}
path: |

View File

@@ -45,6 +45,7 @@
- [wulabing/xray_docker](https://github.com/wulabing/xray_docker)
- Web Panel - **WARNING: Please DO NOT USE plain HTTP panels like 3X-UI**, as they are believed to be bribed by Iran GFW for supporting plain HTTP by default and refused to change (https://github.com/XTLS/Xray-core/pull/3884#issuecomment-2439595331), which has already put many users' data security in danger in the past few years. **If you are already using 3X-UI, please switch to the following panels, which are verified to support HTTPS and SSH port forwarding only:**
- [X-Panel](https://github.com/xeefei/X-Panel)
- [PasarGuard](https://github.com/PasarGuard/panel)
- [Remnawave](https://github.com/remnawave/panel)
- [Marzban](https://github.com/Gozargah/Marzban)
- [Xray-UI](https://github.com/qist/xray-ui)

View File

@@ -108,7 +108,7 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
}
h.proxyConfig = proxyConfig
ctx = session.ContextWithHandler(ctx, h)
ctx = session.ContextWithFullHandler(ctx, h)
rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
if err != nil {
@@ -317,8 +317,12 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti
conn, err := internet.Dial(ctx, dest, h.streamSettings)
conn = h.getStatCouterConnection(conn)
outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1]
ob.Conn = conn
if outbounds != nil {
ob := outbounds[len(outbounds)-1]
ob.Conn = conn
} else {
// for Vision's pre-connect
}
return conn, err
}

View File

@@ -198,9 +198,11 @@ func (w *BridgeWorker) handleInternalConn(link *transport.Link) {
func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) {
if !isInternalDomain(dest) {
ctx = session.ContextWithInbound(ctx, &session.Inbound{
Tag: w.Tag,
})
if session.InboundFromContext(ctx) == nil {
ctx = session.ContextWithInbound(ctx, &session.Inbound{
Tag: w.Tag,
})
}
return w.Dispatcher.Dispatch(ctx, dest)
}
@@ -221,9 +223,11 @@ func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*tra
func (w *BridgeWorker) DispatchLink(ctx context.Context, dest net.Destination, link *transport.Link) error {
if !isInternalDomain(dest) {
ctx = session.ContextWithInbound(ctx, &session.Inbound{
Tag: w.Tag,
})
if session.InboundFromContext(ctx) == nil {
ctx = session.ContextWithInbound(ctx, &session.Inbound{
Tag: w.Tag,
})
}
return w.Dispatcher.DispatchLink(ctx, dest, link)
}

View File

@@ -264,7 +264,11 @@ func fetchInput(ctx context.Context, s *Session, output buf.Writer) {
transferType = protocol.TransferTypePacket
}
s.transferType = transferType
writer := NewWriter(s.ID, ob.Target, output, transferType, xudp.GetGlobalID(ctx))
var inbound *session.Inbound
if session.IsReverseMuxFromContext(ctx) {
inbound = session.InboundFromContext(ctx)
}
writer := NewWriter(s.ID, ob.Target, output, transferType, xudp.GetGlobalID(ctx), inbound)
defer s.Close(false)
defer writer.Close()
@@ -384,7 +388,7 @@ func (m *ClientWorker) fetchOutput() {
var meta FrameMetadata
for {
err := meta.Unmarshal(reader)
err := meta.Unmarshal(reader, false)
if err != nil {
if errors.Cause(err) != io.EOF {
errors.LogInfoInner(context.Background(), err, "failed to read metadata")

View File

@@ -11,6 +11,7 @@ import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/session"
)
type SessionStatus byte
@@ -60,6 +61,7 @@ type FrameMetadata struct {
Option bitmask.Byte
SessionStatus SessionStatus
GlobalID [8]byte
Inbound *session.Inbound
}
func (f FrameMetadata) WriteTo(b *buf.Buffer) error {
@@ -79,11 +81,23 @@ func (f FrameMetadata) WriteTo(b *buf.Buffer) error {
case net.Network_UDP:
common.Must(b.WriteByte(byte(TargetNetworkUDP)))
}
if err := addrParser.WriteAddressPort(b, f.Target.Address, f.Target.Port); err != nil {
return err
}
if b.UDP != nil { // make sure it's user's proxy request
if f.Inbound != nil {
if f.Inbound.Source.Network == net.Network_TCP || f.Inbound.Source.Network == net.Network_UDP {
common.Must(b.WriteByte(byte(f.Inbound.Source.Network - 1)))
if err := addrParser.WriteAddressPort(b, f.Inbound.Source.Address, f.Inbound.Source.Port); err != nil {
return err
}
if f.Inbound.Local.Network == net.Network_TCP || f.Inbound.Local.Network == net.Network_UDP {
common.Must(b.WriteByte(byte(f.Inbound.Local.Network - 1)))
if err := addrParser.WriteAddressPort(b, f.Inbound.Local.Address, f.Inbound.Local.Port); err != nil {
return err
}
}
}
} else if b.UDP != nil { // make sure it's user's proxy request
b.Write(f.GlobalID[:]) // no need to check whether it's empty
}
} else if b.UDP != nil {
@@ -97,7 +111,7 @@ func (f FrameMetadata) WriteTo(b *buf.Buffer) error {
}
// Unmarshal reads FrameMetadata from the given reader.
func (f *FrameMetadata) Unmarshal(reader io.Reader) error {
func (f *FrameMetadata) Unmarshal(reader io.Reader, readSourceAndLocal bool) error {
metaLen, err := serial.ReadUint16(reader)
if err != nil {
return err
@@ -112,12 +126,12 @@ func (f *FrameMetadata) Unmarshal(reader io.Reader) error {
if _, err := b.ReadFullFrom(reader, int32(metaLen)); err != nil {
return err
}
return f.UnmarshalFromBuffer(b)
return f.UnmarshalFromBuffer(b, readSourceAndLocal)
}
// UnmarshalFromBuffer reads a FrameMetadata from the given buffer.
// Visible for testing only.
func (f *FrameMetadata) UnmarshalFromBuffer(b *buf.Buffer) error {
func (f *FrameMetadata) UnmarshalFromBuffer(b *buf.Buffer, readSourceAndLocal bool) error {
if b.Len() < 4 {
return errors.New("insufficient buffer: ", b.Len())
}
@@ -150,6 +164,54 @@ func (f *FrameMetadata) UnmarshalFromBuffer(b *buf.Buffer) error {
}
}
if f.SessionStatus == SessionStatusNew && readSourceAndLocal {
f.Inbound = &session.Inbound{}
if b.Len() == 0 {
return nil // for heartbeat, etc.
}
network := TargetNetwork(b.Byte(0))
if network == 0 {
return nil // may be padding
}
b.Advance(1)
addr, port, err := addrParser.ReadAddressPort(nil, b)
if err != nil {
return errors.New("reading source: failed to parse address and port").Base(err)
}
switch network {
case TargetNetworkTCP:
f.Inbound.Source = net.TCPDestination(addr, port)
case TargetNetworkUDP:
f.Inbound.Source = net.UDPDestination(addr, port)
default:
return errors.New("reading source: unknown network type: ", network)
}
if b.Len() == 0 {
return nil
}
network = TargetNetwork(b.Byte(0))
if network == 0 {
return nil
}
b.Advance(1)
addr, port, err = addrParser.ReadAddressPort(nil, b)
if err != nil {
return errors.New("reading local: failed to parse address and port").Base(err)
}
switch network {
case TargetNetworkTCP:
f.Inbound.Local = net.TCPDestination(addr, port)
case TargetNetworkUDP:
f.Inbound.Local = net.UDPDestination(addr, port)
default:
return errors.New("reading local: unknown network type: ", network)
}
return nil
}
// Application data is essential, to test whether the pipe is closed.
if f.SessionStatus == SessionStatusNew && f.Option.Has(OptionData) &&
f.Target.Network == net.Network_UDP && b.Len() >= 8 {

View File

@@ -10,6 +10,7 @@ import (
. "github.com/xtls/xray-core/common/mux"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/transport/pipe"
)
@@ -32,13 +33,13 @@ func TestReaderWriter(t *testing.T) {
pReader, pWriter := pipe.New(pipe.WithSizeLimit(1024))
dest := net.TCPDestination(net.DomainAddress("example.com"), 80)
writer := NewWriter(1, dest, pWriter, protocol.TransferTypeStream, [8]byte{})
writer := NewWriter(1, dest, pWriter, protocol.TransferTypeStream, [8]byte{}, &session.Inbound{})
dest2 := net.TCPDestination(net.LocalHostIP, 443)
writer2 := NewWriter(2, dest2, pWriter, protocol.TransferTypeStream, [8]byte{})
writer2 := NewWriter(2, dest2, pWriter, protocol.TransferTypeStream, [8]byte{}, &session.Inbound{})
dest3 := net.TCPDestination(net.LocalHostIPv6, 18374)
writer3 := NewWriter(3, dest3, pWriter, protocol.TransferTypeStream, [8]byte{})
writer3 := NewWriter(3, dest3, pWriter, protocol.TransferTypeStream, [8]byte{}, &session.Inbound{})
writePayload := func(writer *Writer, payload ...byte) error {
b := buf.New()
@@ -62,7 +63,7 @@ func TestReaderWriter(t *testing.T) {
{
var meta FrameMetadata
common.Must(meta.Unmarshal(bytesReader))
common.Must(meta.Unmarshal(bytesReader, false))
if r := cmp.Diff(meta, FrameMetadata{
SessionID: 1,
SessionStatus: SessionStatusNew,
@@ -81,7 +82,7 @@ func TestReaderWriter(t *testing.T) {
{
var meta FrameMetadata
common.Must(meta.Unmarshal(bytesReader))
common.Must(meta.Unmarshal(bytesReader, false))
if r := cmp.Diff(meta, FrameMetadata{
SessionStatus: SessionStatusNew,
SessionID: 2,
@@ -94,7 +95,7 @@ func TestReaderWriter(t *testing.T) {
{
var meta FrameMetadata
common.Must(meta.Unmarshal(bytesReader))
common.Must(meta.Unmarshal(bytesReader, false))
if r := cmp.Diff(meta, FrameMetadata{
SessionID: 1,
SessionStatus: SessionStatusKeep,
@@ -112,7 +113,7 @@ func TestReaderWriter(t *testing.T) {
{
var meta FrameMetadata
common.Must(meta.Unmarshal(bytesReader))
common.Must(meta.Unmarshal(bytesReader, false))
if r := cmp.Diff(meta, FrameMetadata{
SessionID: 3,
SessionStatus: SessionStatusNew,
@@ -131,7 +132,7 @@ func TestReaderWriter(t *testing.T) {
{
var meta FrameMetadata
common.Must(meta.Unmarshal(bytesReader))
common.Must(meta.Unmarshal(bytesReader, false))
if r := cmp.Diff(meta, FrameMetadata{
SessionID: 1,
SessionStatus: SessionStatusEnd,
@@ -143,7 +144,7 @@ func TestReaderWriter(t *testing.T) {
{
var meta FrameMetadata
common.Must(meta.Unmarshal(bytesReader))
common.Must(meta.Unmarshal(bytesReader, false))
if r := cmp.Diff(meta, FrameMetadata{
SessionID: 3,
SessionStatus: SessionStatusEnd,
@@ -155,7 +156,7 @@ func TestReaderWriter(t *testing.T) {
{
var meta FrameMetadata
common.Must(meta.Unmarshal(bytesReader))
common.Must(meta.Unmarshal(bytesReader, false))
if r := cmp.Diff(meta, FrameMetadata{
SessionID: 2,
SessionStatus: SessionStatusKeep,
@@ -173,7 +174,7 @@ func TestReaderWriter(t *testing.T) {
{
var meta FrameMetadata
common.Must(meta.Unmarshal(bytesReader))
common.Must(meta.Unmarshal(bytesReader, false))
if r := cmp.Diff(meta, FrameMetadata{
SessionID: 2,
SessionStatus: SessionStatusEnd,
@@ -187,7 +188,7 @@ func TestReaderWriter(t *testing.T) {
{
var meta FrameMetadata
err := meta.Unmarshal(bytesReader)
err := meta.Unmarshal(bytesReader, false)
if err == nil {
t.Error("nil error")
}

View File

@@ -166,6 +166,14 @@ func (w *ServerWorker) handleStatusKeepAlive(meta *FrameMetadata, reader *buf.Bu
func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata, reader *buf.BufferedReader) error {
ctx = session.SubContextFromMuxInbound(ctx)
if meta.Inbound != nil && meta.Inbound.Source.IsValid() && meta.Inbound.Local.IsValid() {
if inbound := session.InboundFromContext(ctx); inbound != nil {
newInbound := *inbound
newInbound.Source = meta.Inbound.Source
newInbound.Local = meta.Inbound.Local
ctx = session.ContextWithInbound(ctx, &newInbound)
}
}
errors.LogInfo(ctx, "received request for ", meta.Target)
{
msg := &log.AccessMessage{
@@ -329,7 +337,7 @@ func (w *ServerWorker) handleStatusEnd(meta *FrameMetadata, reader *buf.Buffered
func (w *ServerWorker) handleFrame(ctx context.Context, reader *buf.BufferedReader) error {
var meta FrameMetadata
err := meta.Unmarshal(reader)
err := meta.Unmarshal(reader, session.IsReverseMuxFromContext(ctx))
if err != nil {
return errors.New("failed to read metadata").Base(err)
}
@@ -340,7 +348,7 @@ func (w *ServerWorker) handleFrame(ctx context.Context, reader *buf.BufferedRead
case SessionStatusEnd:
err = w.handleStatusEnd(&meta, reader)
case SessionStatusNew:
err = w.handleStatusNew(ctx, &meta, reader)
err = w.handleStatusNew(session.ContextWithIsReverseMux(ctx, false), &meta, reader)
case SessionStatusKeep:
err = w.handleStatusKeep(&meta, reader)
default:

View File

@@ -6,6 +6,7 @@ import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/common/session"
)
type Writer struct {
@@ -16,9 +17,10 @@ type Writer struct {
hasError bool
transferType protocol.TransferType
globalID [8]byte
inbound *session.Inbound
}
func NewWriter(id uint16, dest net.Destination, writer buf.Writer, transferType protocol.TransferType, globalID [8]byte) *Writer {
func NewWriter(id uint16, dest net.Destination, writer buf.Writer, transferType protocol.TransferType, globalID [8]byte, inbound *session.Inbound) *Writer {
return &Writer{
id: id,
dest: dest,
@@ -26,6 +28,7 @@ func NewWriter(id uint16, dest net.Destination, writer buf.Writer, transferType
followup: false,
transferType: transferType,
globalID: globalID,
inbound: inbound,
}
}
@@ -43,6 +46,7 @@ func (w *Writer) getNextFrameMeta() FrameMetadata {
SessionID: w.id,
Target: w.dest,
GlobalID: w.globalID,
Inbound: w.inbound,
}
if w.followup {

View File

@@ -73,7 +73,7 @@ type ResponseHeader struct {
var (
// Keep in sync with crypto/tls/cipher_suites.go.
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
hasGCMAsmARM64 = (cpu.ARM64.HasAES && cpu.ARM64.HasPMULL) || (runtime.GOOS == "darwin" && runtime.GOARCH == "arm64")
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH
hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le"

View File

@@ -17,13 +17,13 @@ const (
inboundSessionKey ctx.SessionKey = 1
outboundSessionKey ctx.SessionKey = 2
contentSessionKey ctx.SessionKey = 3
muxPreferredSessionKey ctx.SessionKey = 4 // unused
isReverseMuxKey ctx.SessionKey = 4 // is reverse mux
sockoptSessionKey ctx.SessionKey = 5 // used by dokodemo to only receive sockopt.Mark
trackedConnectionErrorKey ctx.SessionKey = 6 // used by observer to get outbound error
dispatcherKey ctx.SessionKey = 7 // used by ss2022 inbounds to get dispatcher
timeoutOnlyKey ctx.SessionKey = 8 // mux context's child contexts to only cancel when its own traffic times out
allowedNetworkKey ctx.SessionKey = 9 // muxcool server control incoming request tcp/udp
handlerSessionKey ctx.SessionKey = 10 // outbound gets full handler
fullHandlerKey ctx.SessionKey = 10 // outbound gets full handler
mitmAlpn11Key ctx.SessionKey = 11 // used by TLS dialer
mitmServerNameKey ctx.SessionKey = 12 // used by TLS dialer
)
@@ -75,25 +75,21 @@ func ContentFromContext(ctx context.Context) *Content {
return nil
}
// ContextWithMuxPreferred returns a new context with the given bool
func ContextWithMuxPreferred(ctx context.Context, forced bool) context.Context {
return context.WithValue(ctx, muxPreferredSessionKey, forced)
func ContextWithIsReverseMux(ctx context.Context, isReverseMux bool) context.Context {
return context.WithValue(ctx, isReverseMuxKey, isReverseMux)
}
// MuxPreferredFromContext returns value in this context, or false if not contained.
func MuxPreferredFromContext(ctx context.Context) bool {
if val, ok := ctx.Value(muxPreferredSessionKey).(bool); ok {
func IsReverseMuxFromContext(ctx context.Context) bool {
if val, ok := ctx.Value(isReverseMuxKey).(bool); ok {
return val
}
return false
}
// ContextWithSockopt returns a new context with Socket configs included
func ContextWithSockopt(ctx context.Context, s *Sockopt) context.Context {
return context.WithValue(ctx, sockoptSessionKey, s)
}
// SockoptFromContext returns Socket configs in this context, or nil if not contained.
func SockoptFromContext(ctx context.Context) *Sockopt {
if sockopt, ok := ctx.Value(sockoptSessionKey).(*Sockopt); ok {
return sockopt
@@ -164,12 +160,12 @@ func AllowedNetworkFromContext(ctx context.Context) net.Network {
return net.Network_Unknown
}
func ContextWithHandler(ctx context.Context, handler outbound.Handler) context.Context {
return context.WithValue(ctx, handlerSessionKey, handler)
func ContextWithFullHandler(ctx context.Context, handler outbound.Handler) context.Context {
return context.WithValue(ctx, fullHandlerKey, handler)
}
func HandlerFromContext(ctx context.Context) outbound.Handler {
if val, ok := ctx.Value(handlerSessionKey).(outbound.Handler); ok {
func FullHandlerFromContext(ctx context.Context) outbound.Handler {
if val, ok := ctx.Value(fullHandlerKey).(outbound.Handler); ok {
return val
}
return nil

View File

@@ -4,8 +4,10 @@ import (
"context"
"io"
"net"
"time"
"github.com/sagernet/sing/common/bufio"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/transport"
)
@@ -33,8 +35,26 @@ func (w *PipeConnWrapper) Close() error {
return nil
}
// This Read implemented a timeout to avoid goroutine leak.
// as a temporarily solution
func (w *PipeConnWrapper) Read(b []byte) (n int, err error) {
return w.R.Read(b)
type readResult struct {
n int
err error
}
c := make(chan readResult, 1)
go func() {
n, err := w.R.Read(b)
c <- readResult{n: n, err: err}
}()
select {
case result := <-c:
return result.n, result.err
case <-time.After(300 * time.Second):
common.Close(w.R)
common.Interrupt(w.R)
return 0, buf.ErrReadTimeout
}
}
func (w *PipeConnWrapper) Write(p []byte) (n int, err error) {

View File

@@ -18,8 +18,8 @@ import (
var (
Version_x byte = 25
Version_y byte = 9
Version_z byte = 11
Version_y byte = 10
Version_z byte = 15
)
var (

View File

@@ -12,14 +12,19 @@ import (
// ResolvableContext is an implementation of routing.Context, with domain resolving capability.
type ResolvableContext struct {
routing.Context
dnsClient dns.Client
resolvedIPs []net.IP
dnsClient dns.Client
cacheIPs []net.IP
hasError bool
}
// GetTargetIPs overrides original routing.Context's implementation.
func (ctx *ResolvableContext) GetTargetIPs() []net.IP {
if len(ctx.resolvedIPs) > 0 {
return ctx.resolvedIPs
if len(ctx.cacheIPs) > 0 {
return ctx.cacheIPs
}
if ctx.hasError {
return nil
}
if domain := ctx.GetTargetDomain(); len(domain) != 0 {
@@ -29,16 +34,18 @@ func (ctx *ResolvableContext) GetTargetIPs() []net.IP {
FakeEnable: false,
})
if err == nil {
ctx.resolvedIPs = ips
ctx.cacheIPs = ips
return ips
}
errors.LogInfoInner(context.Background(), err, "resolve ip for ", domain)
}
if ips := ctx.Context.GetTargetIPs(); len(ips) != 0 {
ctx.cacheIPs = ips
return ips
}
ctx.hasError = true
return nil
}

25
go.mod
View File

@@ -11,23 +11,23 @@ require (
github.com/miekg/dns v1.1.68
github.com/pelletier/go-toml v1.9.5
github.com/pires/go-proxyproto v0.8.1
github.com/quic-go/quic-go v0.54.0
github.com/refraction-networking/utls v1.8.0
github.com/quic-go/quic-go v0.55.0
github.com/refraction-networking/utls v1.8.1
github.com/sagernet/sing v0.5.1
github.com/sagernet/sing-shadowsocks v0.2.7
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771
github.com/stretchr/testify v1.11.1
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
github.com/vishvananda/netlink v1.3.1
github.com/xtls/reality v0.0.0-20250904214705-431b6ff8c67c
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.42.0
golang.org/x/net v0.44.0
golang.org/x/crypto v0.43.0
golang.org/x/net v0.46.0
golang.org/x/sync v0.17.0
golang.org/x/sys v0.36.0
golang.org/x/sys v0.37.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
google.golang.org/grpc v1.75.1
google.golang.org/protobuf v1.36.9
google.golang.org/grpc v1.76.0
google.golang.org/protobuf v1.36.10
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5
h12.io/socks v1.0.3
lukechampine.com/blake3 v1.4.1
@@ -46,13 +46,12 @@ require (
github.com/quic-go/qpack v0.5.1 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
go.uber.org/mock v0.5.0 // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.36.0 // indirect
golang.org/x/tools v0.37.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

52
go.sum
View File

@@ -51,10 +51,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/refraction-networking/utls v1.8.0 h1:L38krhiTAyj9EeiQQa2sg+hYb4qwLCqdMcpZrRfbONE=
github.com/refraction-networking/utls v1.8.0/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo=
github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
@@ -75,8 +75,8 @@ github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20250904214705-431b6ff8c67c h1:LHLhQY3mKXSpTcQAkjFR4/6ar3rXjQryNeM7khK3AHU=
github.com/xtls/reality v0.0.0-20250904214705-431b6ff8c67c/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 h1:nwobseOLLRtdbP6z7Z2aVI97u8ZptTgD1ofovhAKmeU=
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
@@ -90,22 +90,22 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
@@ -117,21 +117,21 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -141,12 +141,12 @@ golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uI
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@@ -289,8 +289,8 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
return nil, errors.New("maxConnections cannot be specified together with maxConcurrency")
}
if c.Xmux == (XmuxConfig{}) {
c.Xmux.MaxConcurrency.From = 16
c.Xmux.MaxConcurrency.To = 32
c.Xmux.MaxConcurrency.From = 1
c.Xmux.MaxConcurrency.To = 1
c.Xmux.HMaxRequestTimes.From = 600
c.Xmux.HMaxRequestTimes.To = 900
c.Xmux.HMaxReusableSecs.From = 1800
@@ -395,26 +395,27 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
}
type TLSConfig struct {
Insecure bool `json:"allowInsecure"`
Certs []*TLSCertConfig `json:"certificates"`
ServerName string `json:"serverName"`
ALPN *StringList `json:"alpn"`
EnableSessionResumption bool `json:"enableSessionResumption"`
DisableSystemRoot bool `json:"disableSystemRoot"`
MinVersion string `json:"minVersion"`
MaxVersion string `json:"maxVersion"`
CipherSuites string `json:"cipherSuites"`
Fingerprint string `json:"fingerprint"`
RejectUnknownSNI bool `json:"rejectUnknownSni"`
PinnedPeerCertSha256 string `json:"pinnedPeerCertSha256"`
CurvePreferences *StringList `json:"curvePreferences"`
MasterKeyLog string `json:"masterKeyLog"`
ServerNameToVerify string `json:"serverNameToVerify"`
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
ECHServerKeys string `json:"echServerKeys"`
ECHConfigList string `json:"echConfigList"`
ECHForceQuery string `json:"echForceQuery"`
ECHSocketSettings *SocketConfig `json:"echSockopt"`
Insecure bool `json:"allowInsecure"`
Certs []*TLSCertConfig `json:"certificates"`
ServerName string `json:"serverName"`
ALPN *StringList `json:"alpn"`
EnableSessionResumption bool `json:"enableSessionResumption"`
DisableSystemRoot bool `json:"disableSystemRoot"`
MinVersion string `json:"minVersion"`
MaxVersion string `json:"maxVersion"`
CipherSuites string `json:"cipherSuites"`
Fingerprint string `json:"fingerprint"`
RejectUnknownSNI bool `json:"rejectUnknownSni"`
PinnedPeerCertificateChainSha256 *[]string `json:"pinnedPeerCertificateChainSha256"`
PinnedPeerCertificatePublicKeySha256 *[]string `json:"pinnedPeerCertificatePublicKeySha256"`
CurvePreferences *StringList `json:"curvePreferences"`
MasterKeyLog string `json:"masterKeyLog"`
ServerNameToVerify string `json:"serverNameToVerify"`
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
ECHServerKeys string `json:"echServerKeys"`
ECHConfigList string `json:"echConfigList"`
ECHForceQuery string `json:"echForceQuery"`
ECHSocketSettings *SocketConfig `json:"echSockopt"`
}
// Build implements Buildable.
@@ -457,20 +458,25 @@ func (c *TLSConfig) Build() (proto.Message, error) {
}
config.RejectUnknownSni = c.RejectUnknownSNI
if c.PinnedPeerCertSha256 != "" {
config.PinnedPeerCertSha256 = [][]byte{}
// Split by tilde separator
hashes := strings.Split(c.PinnedPeerCertSha256, "~")
for _, v := range hashes {
v = strings.TrimSpace(v)
if v == "" {
continue
}
hashValue, err := hex.DecodeString(v)
if c.PinnedPeerCertificateChainSha256 != nil {
config.PinnedPeerCertificateChainSha256 = [][]byte{}
for _, v := range *c.PinnedPeerCertificateChainSha256 {
hashValue, err := base64.StdEncoding.DecodeString(v)
if err != nil {
return nil, err
}
config.PinnedPeerCertSha256 = append(config.PinnedPeerCertSha256, hashValue)
config.PinnedPeerCertificateChainSha256 = append(config.PinnedPeerCertificateChainSha256, hashValue)
}
}
if c.PinnedPeerCertificatePublicKeySha256 != nil {
config.PinnedPeerCertificatePublicKeySha256 = [][]byte{}
for _, v := range *c.PinnedPeerCertificatePublicKeySha256 {
hashValue, err := base64.StdEncoding.DecodeString(v)
if err != nil {
return nil, err
}
config.PinnedPeerCertificatePublicKeySha256 = append(config.PinnedPeerCertificatePublicKeySha256, hashValue)
}
}

View File

@@ -212,6 +212,7 @@ type VLessOutboundConfig struct {
Seed string `json:"seed"`
Encryption string `json:"encryption"`
Reverse *vless.Reverse `json:"reverse"`
Testpre uint32 `json:"testpre"`
Vnext []*VLessOutboundVnext `json:"vnext"`
}
@@ -258,6 +259,7 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
//account.Seed = c.Seed
account.Encryption = c.Encryption
account.Reverse = c.Reverse
account.Testpre = c.Testpre
} else {
if err := json.Unmarshal(rawUser, account); err != nil {
return nil, errors.New(`VLESS users: invalid user`).Base(err)

View File

@@ -0,0 +1,40 @@
package tls
import (
"flag"
"fmt"
"os"
"github.com/xtls/xray-core/main/commands/base"
"github.com/xtls/xray-core/transport/internet/tls"
)
var cmdCertChainHash = &base.Command{
UsageLine: "{{.Exec}} certChainHash",
Short: "Calculate TLS certificates hash.",
Long: `
xray tls certChainHash --cert <cert.pem>
Calculate TLS certificate chain hash.
`,
}
func init() {
cmdCertChainHash.Run = executeCertChainHash // break init loop
}
var input = cmdCertChainHash.Flag.String("cert", "fullchain.pem", "The file path of the certificates chain")
func executeCertChainHash(cmd *base.Command, args []string) {
fs := flag.NewFlagSet("certChainHash", flag.ContinueOnError)
if err := fs.Parse(args); err != nil {
fmt.Println(err)
return
}
certContent, err := os.ReadFile(*input)
if err != nil {
fmt.Println(err)
return
}
certChainHashB64 := tls.CalculatePEMCertChainSHA256Hash(certContent)
fmt.Println(certChainHashB64)
}

View File

@@ -1,44 +0,0 @@
package tls
import (
"flag"
"fmt"
"os"
"github.com/xtls/xray-core/main/commands/base"
"github.com/xtls/xray-core/transport/internet/tls"
)
var cmdLeafCertHash = &base.Command{
UsageLine: "{{.Exec}} tls leafCertHash",
Short: "Calculate TLS leaf certificate hash.",
Long: `
xray tls leafCertHash --cert <cert.pem>
Calculate TLS leaf certificate hash.
`,
}
func init() {
cmdLeafCertHash.Run = executeLeafCertHash // break init loop
}
var input = cmdLeafCertHash.Flag.String("cert", "fullchain.pem", "The file path of the leaf certificate")
func executeLeafCertHash(cmd *base.Command, args []string) {
fs := flag.NewFlagSet("leafCertHash", flag.ContinueOnError)
if err := fs.Parse(args); err != nil {
fmt.Println(err)
return
}
certContent, err := os.ReadFile(*input)
if err != nil {
fmt.Println(err)
return
}
certChainHashB64, err := tls.CalculatePEMLeafCertSHA256Hash(certContent)
if err != nil {
fmt.Println("failed to decode cert", err)
return
}
fmt.Println(certChainHashB64)
}

View File

@@ -3,7 +3,7 @@ package tls
import (
gotls "crypto/tls"
"crypto/x509"
"encoding/hex"
"encoding/base64"
"fmt"
"net"
"strconv"
@@ -156,14 +156,8 @@ func printTLSConnDetail(tlsConn *gotls.Conn) {
func showCert() func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
var hash []byte
for _, asn1Data := range rawCerts {
cert, _ := x509.ParseCertificate(asn1Data)
if cert.IsCA {
hash = GenerateCertHash(cert)
}
}
fmt.Println("Certificate Leaf Hash: ", hex.EncodeToString(hash))
hash := GenerateCertChainHash(rawCerts)
fmt.Println("Certificate Chain Hash: ", base64.StdEncoding.EncodeToString(hash))
return nil
}
}

View File

@@ -13,7 +13,7 @@ var CmdTLS = &base.Command{
Commands: []*base.Command{
cmdCert,
cmdPing,
cmdLeafCertHash,
cmdCertChainHash,
cmdECH,
},
}

View File

@@ -2,6 +2,8 @@ package external
import (
"bytes"
"context"
"net"
"io"
"net/http"
"net/url"
@@ -18,6 +20,9 @@ import (
func ConfigLoader(arg string) (out io.Reader, err error) {
var data []byte
switch {
case strings.HasPrefix(arg, "http+unix://"):
data, err = FetchUnixSocketHTTPContent(arg)
case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"):
data, err = FetchHTTPContent(arg)
@@ -70,6 +75,60 @@ func FetchHTTPContent(target string) ([]byte, error) {
return content, nil
}
// Format: http+unix:///path/to/socket.sock/api/endpoint
func FetchUnixSocketHTTPContent(target string) ([]byte, error) {
path := strings.TrimPrefix(target, "http+unix://")
if !strings.HasPrefix(path, "/") {
return nil, errors.New("unix socket path must be absolute")
}
var socketPath, httpPath string
sockIdx := strings.Index(path, ".sock")
if sockIdx != -1 {
socketPath = path[:sockIdx+5]
httpPath = path[sockIdx+5:]
if httpPath == "" {
httpPath = "/"
}
} else {
return nil, errors.New("cannot determine socket path, socket file should have .sock extension")
}
if _, err := os.Stat(socketPath); err != nil {
return nil, errors.New("socket file not found: ", socketPath).Base(err)
}
client := &http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, "unix", socketPath)
},
},
}
defer client.CloseIdleConnections()
resp, err := client.Get("http://localhost" + httpPath)
if err != nil {
return nil, errors.New("failed to fetch from unix socket: ", socketPath).Base(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, errors.New("unexpected HTTP status code: ", resp.StatusCode)
}
content, err := buf.ReadAllToBytes(resp.Body)
if err != nil {
return nil, errors.New("failed to read response").Base(err)
}
return content, nil
}
func ExtConfigLoader(files []string, reader io.Reader) (io.Reader, error) {
buf, err := ctlcmd.Run(append([]string{"convert"}, files...), reader)
if err != nil {

View File

@@ -22,6 +22,7 @@ func (a *Account) AsAccount() (protocol.Account, error) {
Seconds: a.Seconds,
Padding: a.Padding,
Reverse: a.Reverse,
Testpre: a.Testpre,
}, nil
}
@@ -38,6 +39,8 @@ type MemoryAccount struct {
Padding string
Reverse *Reverse
Testpre uint32
}
// Equals implements protocol.Account.Equals().
@@ -58,5 +61,6 @@ func (a *MemoryAccount) ToProto() proto.Message {
Seconds: a.Seconds,
Padding: a.Padding,
Reverse: a.Reverse,
Testpre: a.Testpre,
}
}

View File

@@ -79,6 +79,7 @@ type Account struct {
Seconds uint32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds,omitempty"`
Padding string `protobuf:"bytes,6,opt,name=padding,proto3" json:"padding,omitempty"`
Reverse *Reverse `protobuf:"bytes,7,opt,name=reverse,proto3" json:"reverse,omitempty"`
Testpre uint32 `protobuf:"varint,8,opt,name=testpre,proto3" json:"testpre,omitempty"`
}
func (x *Account) Reset() {
@@ -160,6 +161,13 @@ func (x *Account) GetReverse() *Reverse {
return nil
}
func (x *Account) GetTestpre() uint32 {
if x != nil {
return x.Testpre
}
return 0
}
var File_proxy_vless_account_proto protoreflect.FileDescriptor
var file_proxy_vless_account_proto_rawDesc = []byte{
@@ -167,7 +175,7 @@ var file_proxy_vless_account_proto_rawDesc = []byte{
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61,
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x1b, 0x0a,
0x07, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0xd0, 0x01, 0x0a, 0x07, 0x41,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0xea, 0x01, 0x0a, 0x07, 0x41,
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x6e,
@@ -180,13 +188,15 @@ var file_proxy_vless_account_proto_rawDesc = []byte{
0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x33, 0x0a, 0x07, 0x72, 0x65, 0x76, 0x65,
0x72, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79,
0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x52, 0x65, 0x76,
0x65, 0x72, 0x73, 0x65, 0x52, 0x07, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x42, 0x52, 0x0a,
0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e,
0x76, 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f,
0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02,
0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73,
0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x65, 0x72, 0x73, 0x65, 0x52, 0x07, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x18, 0x0a,
0x07, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07,
0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x65, 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78,
0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x50,
0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74,
0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f,
0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e,
0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
}
var (

View File

@@ -22,4 +22,6 @@ message Account {
string padding = 6;
Reverse reverse = 7;
uint32 testpre = 8;
}

View File

@@ -666,7 +666,7 @@ func (r *Reverse) Dispatch(ctx context.Context, link *transport.Link) {
link.Reader = &buf.EndpointOverrideReader{Reader: link.Reader, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address}
link.Writer = &buf.EndpointOverrideWriter{Writer: link.Writer, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address}
}
r.client.Dispatch(ctx, link)
r.client.Dispatch(session.ContextWithIsReverseMux(ctx, true), link)
}
}

View File

@@ -7,6 +7,7 @@ import (
"encoding/base64"
"reflect"
"strings"
"sync"
"time"
"unsafe"
@@ -52,6 +53,12 @@ type Handler struct {
cone bool
encryption *encryption.ClientInstance
reverse *Reverse
testpre uint32
initConns sync.Once
preConns chan stat.Connection
preConnWait chan struct{}
preConnStop chan struct{}
}
// New creates a new VLess outbound handler.
@@ -89,8 +96,11 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
handler.reverse = &Reverse{
tag: a.Reverse.Tag,
dispatcher: v.GetFeature(routing.DispatcherType()).(routing.Dispatcher),
ctx: ctx,
handler: handler,
ctx: session.ContextWithInbound(ctx, &session.Inbound{
Tag: a.Reverse.Tag,
User: handler.server.User, // TODO: email
}),
handler: handler,
}
handler.reverse.monitorTask = &task.Periodic{
Execute: handler.reverse.monitor,
@@ -102,11 +112,20 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
}()
}
handler.testpre = a.Testpre
return handler, nil
}
// Close implements common.Closable.Close().
func (h *Handler) Close() error {
if h.preConnStop != nil {
close(h.preConnStop)
for range h.testpre {
conn := <-h.preConns
common.CloseIfExists(conn)
}
}
if h.reverse != nil {
return h.reverse.Close()
}
@@ -125,18 +144,38 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
rec := h.server
var conn stat.Connection
if err := retry.ExponentialBackoff(5, 200).On(func() error {
var err error
conn, err = dialer.Dial(ctx, rec.Destination)
if err != nil {
return err
if h.testpre > 0 && h.reverse == nil {
h.initConns.Do(func() {
h.preConns = make(chan stat.Connection, h.testpre)
h.preConnStop = make(chan struct{})
go h.preConnWorker(dialer, rec.Destination)
})
select {
case h.preConnWait <- struct{}{}:
default:
}
select {
case conn = <-h.preConns:
default:
}
}
if conn == nil {
if err := retry.ExponentialBackoff(5, 200).On(func() error {
var err error
conn, err = dialer.Dial(ctx, rec.Destination)
if err != nil {
return err
}
return nil
}); err != nil {
return errors.New("failed to find an available destination").Base(err).AtWarning()
}
return nil
}); err != nil {
return errors.New("failed to find an available destination").Base(err).AtWarning()
}
defer conn.Close()
ob.Conn = conn // for Vision's pre-connect
iConn := conn
if statConn, ok := iConn.(*stat.CounterConnection); ok {
iConn = statConn.Connection
@@ -198,7 +237,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
}
case protocol.RequestCommandMux:
fallthrough // let server break Mux connections that contain TCP requests
case protocol.RequestCommandTCP:
case protocol.RequestCommandTCP, protocol.RequestCommandRvs:
var t reflect.Type
var p uintptr
if commonConn, ok := conn.(*encryption.CommonConn); ok {
@@ -223,6 +262,8 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
r, _ := t.FieldByName("rawInput")
input = (*bytes.Reader)(unsafe.Pointer(p + i.Offset))
rawInput = (*bytes.Buffer)(unsafe.Pointer(p + r.Offset))
default:
panic("unknown VLESS request command")
}
default:
ob.CanSpliceCopy = 3
@@ -395,7 +436,7 @@ func (r *Reverse) monitor() error {
Tag: r.tag,
Dispatcher: r.dispatcher,
}
worker, err := mux.NewServerWorker(r.ctx, w, link1)
worker, err := mux.NewServerWorker(session.ContextWithIsReverseMux(r.ctx, true), w, link1)
if err != nil {
errors.LogWarningInner(r.ctx, err, "failed to create mux server worker")
return nil
@@ -406,7 +447,7 @@ func (r *Reverse) monitor() error {
ctx := session.ContextWithOutbounds(r.ctx, []*session.Outbound{{
Target: net.Destination{Address: net.DomainAddress("v1.rvs.cool")},
}})
r.handler.Process(ctx, link2, session.HandlerFromContext(ctx).(*proxyman.Handler))
r.handler.Process(ctx, link2, session.FullHandlerFromContext(ctx).(*proxyman.Handler))
common.Interrupt(reader1)
common.Interrupt(reader2)
}()
@@ -421,3 +462,51 @@ func (r *Reverse) Start() error {
func (r *Reverse) Close() error {
return r.monitorTask.Close()
}
func (h *Handler) preConnWorker(dialer internet.Dialer, dest net.Destination) {
// conn in conns may be nil
conns := make(chan stat.Connection)
dial := func() {
conn, err := dialer.Dial(context.Background(), dest)
if err != nil {
errors.LogError(context.Background(), "failed to dial VLESS pre connection: ", err)
common.CloseIfExists(conn)
}
conns <- conn
}
go func() {
go dial() // get a conn immediately
for range h.testpre - 1 {
select {
case <-h.preConnWait:
go dial()
case <-h.preConnStop:
return
}
}
}()
for {
select {
case conn := <-conns:
if conn != nil {
select {
case h.preConns <- conn:
case <-h.preConnStop:
common.CloseIfExists(conn)
return
}
go dial()
} else {
// sleep until next client try if dial failed
select {
case <-h.preConnWait:
go dial()
case <-h.preConnStop:
return
}
}
case <-h.preConnStop:
return
}
}
}

View File

@@ -92,7 +92,7 @@ func TestSimpleTLSConnection(t *testing.T) {
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
}),
@@ -203,7 +203,7 @@ func TestAutoIssuingCertificate(t *testing.T) {
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
}),
@@ -304,7 +304,7 @@ func TestTLSOverKCP(t *testing.T) {
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
}),
@@ -400,7 +400,7 @@ func TestTLSOverWebSocket(t *testing.T) {
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
}),
@@ -512,7 +512,7 @@ func TestGRPC(t *testing.T) {
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
}),
@@ -624,7 +624,7 @@ func TestGRPCMultiMode(t *testing.T) {
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
}),
@@ -674,7 +674,7 @@ func TestSimpleTLSConnectionPinned(t *testing.T) {
defer tcpServer.Close()
certificateDer := cert.MustGenerate(nil)
certificate := tls.ParseCertificate(certificateDer)
certHash := tls.GenerateCertHash(certificateDer.Certificate)
certHash := tls.GenerateCertChainHash([][]byte{certificateDer.Certificate})
userID := protocol.NewID(uuid.New())
serverPort := tcp.PickPort()
serverConfig := &core.Config{
@@ -731,7 +731,7 @@ func TestSimpleTLSConnectionPinned(t *testing.T) {
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
}),
@@ -743,8 +743,8 @@ func TestSimpleTLSConnectionPinned(t *testing.T) {
SecurityType: serial.GetMessageType(&tls.Config{}),
SecuritySettings: []*serial.TypedMessage{
serial.ToTypedMessage(&tls.Config{
AllowInsecure: true,
PinnedPeerCertSha256: [][]byte{certHash},
AllowInsecure: true,
PinnedPeerCertificateChainSha256: [][]byte{certHash},
}),
},
},
@@ -771,7 +771,7 @@ func TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) {
defer tcpServer.Close()
certificateDer := cert.MustGenerate(nil)
certificate := tls.ParseCertificate(certificateDer)
certHash := tls.GenerateCertHash(certificateDer.Certificate)
certHash := tls.GenerateCertChainHash([][]byte{certificateDer.Certificate})
certHash[1] += 1
userID := protocol.NewID(uuid.New())
serverPort := tcp.PickPort()
@@ -829,7 +829,7 @@ func TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) {
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
}),
@@ -841,8 +841,8 @@ func TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) {
SecurityType: serial.GetMessageType(&tls.Config{}),
SecuritySettings: []*serial.TypedMessage{
serial.ToTypedMessage(&tls.Config{
AllowInsecure: true,
PinnedPeerCertSha256: [][]byte{certHash},
AllowInsecure: true,
PinnedPeerCertificateChainSha256: [][]byte{certHash},
}),
},
},
@@ -869,7 +869,7 @@ func TestUTLSConnectionPinned(t *testing.T) {
defer tcpServer.Close()
certificateDer := cert.MustGenerate(nil)
certificate := tls.ParseCertificate(certificateDer)
certHash := tls.GenerateCertHash(certificateDer.Certificate)
certHash := tls.GenerateCertChainHash([][]byte{certificateDer.Certificate})
userID := protocol.NewID(uuid.New())
serverPort := tcp.PickPort()
serverConfig := &core.Config{
@@ -926,7 +926,7 @@ func TestUTLSConnectionPinned(t *testing.T) {
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
}),
@@ -938,9 +938,9 @@ func TestUTLSConnectionPinned(t *testing.T) {
SecurityType: serial.GetMessageType(&tls.Config{}),
SecuritySettings: []*serial.TypedMessage{
serial.ToTypedMessage(&tls.Config{
Fingerprint: "random",
AllowInsecure: true,
PinnedPeerCertSha256: [][]byte{certHash},
Fingerprint: "random",
AllowInsecure: true,
PinnedPeerCertificateChainSha256: [][]byte{certHash},
}),
},
},
@@ -967,7 +967,7 @@ func TestUTLSConnectionPinnedWrongCert(t *testing.T) {
defer tcpServer.Close()
certificateDer := cert.MustGenerate(nil)
certificate := tls.ParseCertificate(certificateDer)
certHash := tls.GenerateCertHash(certificateDer.Certificate)
certHash := tls.GenerateCertChainHash([][]byte{certificateDer.Certificate})
certHash[1] += 1
userID := protocol.NewID(uuid.New())
serverPort := tcp.PickPort()
@@ -1025,7 +1025,7 @@ func TestUTLSConnectionPinnedWrongCert(t *testing.T) {
Receiver: &protocol.ServerEndpoint{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: &protocol.User{
User: &protocol.User{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
}),
@@ -1037,9 +1037,9 @@ func TestUTLSConnectionPinnedWrongCert(t *testing.T) {
SecurityType: serial.GetMessageType(&tls.Config{}),
SecuritySettings: []*serial.TypedMessage{
serial.ToTypedMessage(&tls.Config{
Fingerprint: "random",
AllowInsecure: true,
PinnedPeerCertSha256: [][]byte{certHash},
Fingerprint: "random",
AllowInsecure: true,
PinnedPeerCertificateChainSha256: [][]byte{certHash},
}),
},
},

View File

@@ -75,8 +75,7 @@ func (c *UConn) HandshakeAddress() net.Address {
func (c *UConn) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
if c.Config.Show {
localAddr := c.LocalAddr().String()
curveID := *(*utls.CurveID)(unsafe.Pointer(reflect.ValueOf(c).Elem().FieldByName("curveID").UnsafeAddr()))
fmt.Printf("REALITY localAddr: %v\tis using X25519MLKEM768 for TLS' communication: %v\n", localAddr, curveID == utls.X25519MLKEM768)
fmt.Printf("REALITY localAddr: %v\tis using X25519MLKEM768 for TLS' communication: %v\n", localAddr, c.HandshakeState.ServerHello.ServerShare.Group == utls.X25519MLKEM768)
fmt.Printf("REALITY localAddr: %v\tis using ML-DSA-65 for cert's extra verification: %v\n", localAddr, len(c.Config.Mldsa65Verify) > 0)
}
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")

View File

@@ -7,6 +7,7 @@ import (
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"os"
"slices"
"strings"
@@ -280,35 +281,15 @@ func (c *Config) parseServerName() string {
return c.ServerName
}
func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) (err error) {
// extract x509 certificates from rawCerts(verifiedChains will be nil if InsecureSkipVerify is true)
certs := make([]*x509.Certificate, len(rawCerts))
for i, asn1Data := range rawCerts {
certs[i], _ = x509.ParseCertificate(asn1Data)
}
// directly return success if pinned cert is leaf
// or add the CA to RootCAs if pinned cert is CA(and can be used in VerifyPeerCertInNames for Self signed CA)
RootCAs := r.RootCAs
if r.PinnedPeerCertSha256 != nil {
verifyResult, verifiedCert := verifyChain(certs, r.PinnedPeerCertSha256)
switch verifyResult {
case certNotFound:
return errors.New("peer cert is unrecognized")
case foundLeaf:
return nil
case foundCA:
RootCAs = x509.NewCertPool()
RootCAs.AddCert(verifiedCert)
default:
panic("impossible PinnedPeerCertificateSha256 verify result")
}
}
func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
if r.VerifyPeerCertInNames != nil {
if len(r.VerifyPeerCertInNames) > 0 {
certs := make([]*x509.Certificate, len(rawCerts))
for i, asn1Data := range rawCerts {
certs[i], _ = x509.ParseCertificate(asn1Data)
}
opts := x509.VerifyOptions{
Roots: RootCAs,
Roots: r.RootCAs,
CurrentTime: time.Now(),
Intermediates: x509.NewCertPool(),
}
@@ -321,14 +302,42 @@ func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509
}
}
}
if r.PinnedPeerCertificateChainSha256 == nil {
return errors.New("peer cert is invalid.")
}
}
if r.PinnedPeerCertificateChainSha256 != nil {
hashValue := GenerateCertChainHash(rawCerts)
for _, v := range r.PinnedPeerCertificateChainSha256 {
if hmac.Equal(hashValue, v) {
return nil
}
}
return errors.New("peer cert is unrecognized: ", base64.StdEncoding.EncodeToString(hashValue))
}
if r.PinnedPeerCertificatePublicKeySha256 != nil {
for _, v := range verifiedChains {
for _, cert := range v {
publicHash := GenerateCertPublicKeyHash(cert)
for _, c := range r.PinnedPeerCertificatePublicKeySha256 {
if hmac.Equal(publicHash, c) {
return nil
}
}
}
}
return errors.New("peer public key is unrecognized.")
}
return nil
}
type RandCarrier struct {
RootCAs *x509.CertPool
VerifyPeerCertInNames []string
PinnedPeerCertSha256 [][]byte
RootCAs *x509.CertPool
VerifyPeerCertInNames []string
PinnedPeerCertificateChainSha256 [][]byte
PinnedPeerCertificatePublicKeySha256 [][]byte
}
func (r *RandCarrier) Read(p []byte) (n int, err error) {
@@ -353,9 +362,10 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
}
randCarrier := &RandCarrier{
RootCAs: root,
VerifyPeerCertInNames: slices.Clone(c.VerifyPeerCertInNames),
PinnedPeerCertSha256: c.PinnedPeerCertSha256,
RootCAs: root,
VerifyPeerCertInNames: slices.Clone(c.VerifyPeerCertInNames),
PinnedPeerCertificateChainSha256: c.PinnedPeerCertificateChainSha256,
PinnedPeerCertificatePublicKeySha256: c.PinnedPeerCertificatePublicKeySha256,
}
config := &tls.Config{
Rand: randCarrier,
@@ -516,28 +526,3 @@ func ParseCurveName(curveNames []string) []tls.CurveID {
func IsFromMitm(str string) bool {
return strings.ToLower(str) == "frommitm"
}
type verifyResult int
const (
certNotFound verifyResult = iota
foundLeaf
foundCA
)
func verifyChain(certs []*x509.Certificate, PinnedPeerCertificateSha256 [][]byte) (verifyResult, *x509.Certificate) {
for _, cert := range certs {
certHash := GenerateCertHash(cert)
for _, c := range PinnedPeerCertificateSha256 {
if hmac.Equal(certHash, c) {
if cert.IsCA {
return foundCA, cert
} else {
return foundLeaf, cert
}
}
}
}
return certNotFound, nil
}

View File

@@ -222,7 +222,6 @@ type Config struct {
EchConfigList string `protobuf:"bytes,19,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"`
EchForceQuery string `protobuf:"bytes,20,opt,name=ech_force_query,json=echForceQuery,proto3" json:"ech_force_query,omitempty"`
EchSocketSettings *internet.SocketConfig `protobuf:"bytes,21,opt,name=ech_socket_settings,json=echSocketSettings,proto3" json:"ech_socket_settings,omitempty"`
PinnedPeerCertSha256 [][]byte `protobuf:"bytes,22,rep,name=pinned_peer_cert_sha256,json=pinnedPeerCertSha256,proto3" json:"pinned_peer_cert_sha256,omitempty"`
}
func (x *Config) Reset() {
@@ -395,13 +394,6 @@ func (x *Config) GetEchSocketSettings() *internet.SocketConfig {
return nil
}
func (x *Config) GetPinnedPeerCertSha256() [][]byte {
if x != nil {
return x.PinnedPeerCertSha256
}
return nil
}
var File_transport_internet_tls_config_proto protoreflect.FileDescriptor
var file_transport_internet_tls_config_proto_rawDesc = []byte{
@@ -435,7 +427,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{
0x45, 0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14,
0x0a, 0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49,
0x46, 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54,
0x59, 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xa0, 0x08, 0x0a, 0x06, 0x43, 0x6f,
0x59, 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xe9, 0x07, 0x0a, 0x06, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e,
0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c,
0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63,
@@ -498,18 +490,15 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{
0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74,
0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x52, 0x11, 0x65, 0x63, 0x68, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x65, 0x74,
0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x5f,
0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36,
0x18, 0x16, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x14, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x50, 0x65,
0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x42, 0x73, 0x0a, 0x1f,
0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f,
0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50,
0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74,
0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61,
0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f,
0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73,
0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c,
0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x74, 0x69, 0x6e, 0x67, 0x73, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61,
0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79,
0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58,
0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e,
0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (

View File

@@ -101,6 +101,4 @@ message Config {
string ech_force_query = 20;
SocketConfig ech_socket_settings = 21;
repeated bytes pinned_peer_cert_sha256 = 22;
}

View File

@@ -3,37 +3,40 @@ package tls
import (
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/base64"
"encoding/pem"
)
func CalculatePEMLeafCertSHA256Hash(certContent []byte) (string, error) {
var leafCert *x509.Certificate
func CalculatePEMCertChainSHA256Hash(certContent []byte) string {
var certChain [][]byte
for {
var err error
block, remain := pem.Decode(certContent)
if block == nil {
break
}
leafCert, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return "", err
}
certChain = append(certChain, block.Bytes)
certContent = remain
}
certHash := GenerateCertHash(leafCert)
certHashHex := hex.EncodeToString(certHash)
return certHashHex, nil
certChainHash := GenerateCertChainHash(certChain)
certChainHashB64 := base64.StdEncoding.EncodeToString(certChainHash)
return certChainHashB64
}
// []byte must be ASN.1 DER content
func GenerateCertHash[T *x509.Certificate | []byte](cert T) []byte {
var out [32]byte
switch v := any(cert).(type) {
case *x509.Certificate:
out = sha256.Sum256(v.Raw)
case []byte:
out = sha256.Sum256(v)
func GenerateCertChainHash(rawCerts [][]byte) []byte {
var hashValue []byte
for _, certValue := range rawCerts {
out := sha256.Sum256(certValue)
if hashValue == nil {
hashValue = out[:]
} else {
newHashValue := sha256.Sum256(append(hashValue, out[:]...))
hashValue = newHashValue[:]
}
}
return hashValue
}
func GenerateCertPublicKeyHash(cert *x509.Certificate) []byte {
out := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
return out[:]
}

View File

@@ -2,7 +2,7 @@ package tls
import (
"crypto/x509"
"encoding/hex"
"encoding/base64"
"encoding/pem"
"testing"
@@ -10,88 +10,190 @@ import (
)
func TestCalculateCertHash(t *testing.T) {
const Single = `-----BEGIN CERTIFICATE-----
MIINWzCCC0OgAwIBAgITMwK6ajqdrV0tahuIrQAAArpqOjANBgkqhkiG9w0BAQwF
ADBdMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
MS4wLAYDVQQDEyVNaWNyb3NvZnQgQXp1cmUgUlNBIFRMUyBJc3N1aW5nIENBIDA0
MB4XDTI1MDkwOTEwMzE1NloXDTI2MDMwODEwMzE1NlowYzELMAkGA1UEBhMCVVMx
CzAJBgNVBAgTAldBMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
ZnQgQ29ycG9yYXRpb24xFTATBgNVBAMTDHd3dy5iaW5nLmNvbTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAMBflymLifrVkjp8K4/XrHSt+/xDrrZIJyTI
JOhIGZJZ88sNjo4OChQWV8O3CTQwrbKJDd6KjZFFc6BPKpEJZ891w2zkymMbE7wh
vQVviSCIVCO+49pLrEvfh5ZvdbXhtNzm/ZRvkoI8h4ZKPBRNmX5sGpSQ9p0loJBj
Jk1HbzLv0vRk5bLb/J6x7YexaAu86C9TjqnC4irO+AZZNI/0S70ZHxX+ETZVV0EX
QU8UmqV68e4YhAQwiLYdAQw125n2hGWoLokQSZTyEiIIoubB00pE5zf0Qaq6Q4s8
Go5Ukw1A4HjWMisHVKq369pgI8VDZtMzOhS+O0DEQZLwOFETZxECAwEAAaOCCQww
ggkIMIIBgAYKKwYBBAHWeQIEAgSCAXAEggFsAWoAdgCWl2S/VViXrfdDh2g3CEJ3
6fA61fak8zZuRqQ/D8qpxgAAAZkuEXLdAAAEAwBHMEUCIBLzX4AJgVJdQshSMBLS
hBMQX8zgRm2U3IXjLk37JM3QAiEAkVrmCFx0+BM3NOoCAXBU1WzVuniPxJP3Ysbd
OO3dkEAAdwBkEcRspBLsp4kcogIuALyrTygH1B41J6vq/tUDyX3N8AAAAZkuEXKd
AAAEAwBIMEYCIQCCO1ys+tlI8Fhp4J/Dqk3VVtSi408Nuw8T6YciDL6LPgIhAPjp
fm/gMkASgNimNuMFH8oiJbqeQ/yo2zQfub894iMuAHcAVmzVo3a+g9/jQrZ1xJwj
JJinabrDgsurSaOHfZqzLQEAAAGZLhFy2QAABAMASDBGAiEA/93O6XiiYhfeANHh
0n2nJyVvFAc6sBNT2S7WOR28vR0CIQC7i+leDRRIeY2BYJwaRlAqHlSyU4DZu5IG
caxiWFeavzAnBgkrBgEEAYI3FQoEGjAYMAoGCCsGAQUFBwMCMAoGCCsGAQUFBwMB
MDwGCSsGAQQBgjcVBwQvMC0GJSsGAQQBgjcVCIe91xuB5+tGgoGdLo7QDIfw2h1d
gqvnMIft8R8CAWQCAS0wgbQGCCsGAQUFBwEBBIGnMIGkMHMGCCsGAQUFBzAChmdo
dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUy
MEF6dXJlJTIwUlNBJTIwVExTJTIwSXNzdWluZyUyMENBJTIwMDQlMjAtJTIweHNp
Z24uY3J0MC0GCCsGAQUFBzABhiFodHRwOi8vb25lb2NzcC5taWNyb3NvZnQuY29t
L29jc3AwHQYDVR0OBBYEFAsWImxddBew8yEv3yGDsmy90FzPMA4GA1UdDwEB/wQE
AwIFoDCCBREGA1UdEQSCBQgwggUEghMqLnBsYXRmb3JtLmJpbmcuY29tggoqLmJp
bmcuY29tgghiaW5nLmNvbYIWaWVvbmxpbmUubWljcm9zb2Z0LmNvbYITKi53aW5k
b3dzc2VhcmNoLmNvbYIZY24uaWVvbmxpbmUubWljcm9zb2Z0LmNvbYIRKi5vcmln
aW4uYmluZy5jb22CDSoubW0uYmluZy5uZXSCDiouYXBpLmJpbmcuY29tgg0qLmNu
LmJpbmcubmV0gg0qLmNuLmJpbmcuY29tghBzc2wtYXBpLmJpbmcuY29tghBzc2wt
YXBpLmJpbmcubmV0gg4qLmFwaS5iaW5nLm5ldIIOKi5iaW5nYXBpcy5jb22CD2Jp
bmdzYW5kYm94LmNvbYIWZmVlZGJhY2subWljcm9zb2Z0LmNvbYIbaW5zZXJ0bWVk
aWEuYmluZy5vZmZpY2UubmV0gg5yLmJhdC5iaW5nLmNvbYIQKi5yLmJhdC5iaW5n
LmNvbYIPKi5kaWN0LmJpbmcuY29tgg4qLnNzbC5iaW5nLmNvbYIQKi5hcHBleC5i
aW5nLmNvbYIWKi5wbGF0Zm9ybS5jbi5iaW5nLmNvbYINd3AubS5iaW5nLmNvbYIM
Ki5tLmJpbmcuY29tgg9nbG9iYWwuYmluZy5jb22CEXdpbmRvd3NzZWFyY2guY29t
gg5zZWFyY2gubXNuLmNvbYIRKi5iaW5nc2FuZGJveC5jb22CGSouYXBpLnRpbGVz
LmRpdHUubGl2ZS5jb22CGCoudDAudGlsZXMuZGl0dS5saXZlLmNvbYIYKi50MS50
aWxlcy5kaXR1LmxpdmUuY29tghgqLnQyLnRpbGVzLmRpdHUubGl2ZS5jb22CGCou
dDMudGlsZXMuZGl0dS5saXZlLmNvbYILM2QubGl2ZS5jb22CE2FwaS5zZWFyY2gu
bGl2ZS5jb22CFGJldGEuc2VhcmNoLmxpdmUuY29tghVjbndlYi5zZWFyY2gubGl2
ZS5jb22CDWRpdHUubGl2ZS5jb22CEWZhcmVjYXN0LmxpdmUuY29tgg5pbWFnZS5s
aXZlLmNvbYIPaW1hZ2VzLmxpdmUuY29tghFsb2NhbC5saXZlLmNvbS5hdYIUbG9j
YWxzZWFyY2gubGl2ZS5jb22CFGxzNGQuc2VhcmNoLmxpdmUuY29tgg1tYWlsLmxp
dmUuY29tghFtYXBpbmRpYS5saXZlLmNvbYIObG9jYWwubGl2ZS5jb22CDW1hcHMu
bGl2ZS5jb22CEG1hcHMubGl2ZS5jb20uYXWCD21pbmRpYS5saXZlLmNvbYINbmV3
cy5saXZlLmNvbYIcb3JpZ2luLmNud2ViLnNlYXJjaC5saXZlLmNvbYIWcHJldmll
dy5sb2NhbC5saXZlLmNvbYIPc2VhcmNoLmxpdmUuY29tghJ0ZXN0Lm1hcHMubGl2
ZS5jb22CDnZpZGVvLmxpdmUuY29tgg92aWRlb3MubGl2ZS5jb22CFXZpcnR1YWxl
YXJ0aC5saXZlLmNvbYIMd2FwLmxpdmUuY29tghJ3ZWJtYXN0ZXIubGl2ZS5jb22C
FXd3dy5sb2NhbC5saXZlLmNvbS5hdYIUd3d3Lm1hcHMubGl2ZS5jb20uYXWCE3dl
Ym1hc3RlcnMubGl2ZS5jb22CGGVjbi5kZXYudmlydHVhbGVhcnRoLm5ldIIMd3d3
LmJpbmcuY29tMAwGA1UdEwEB/wQCMAAwagYDVR0fBGMwYTBfoF2gW4ZZaHR0cDov
L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwQXp1cmUl
MjBSU0ElMjBUTFMlMjBJc3N1aW5nJTIwQ0ElMjAwNC5jcmwwZgYDVR0gBF8wXTBR
BgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3Nv
ZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMAgGBmeBDAECAjAfBgNV
HSMEGDAWgBQ7cNFT6XYlnWCoymYPxpuub1QWajAdBgNVHSUEFjAUBggrBgEFBQcD
AgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEMBQADggIBAEQCoppNllgoHtfLJt2m7cVL
AILYFxJdi9qc4LUBfaQEdUwAfsC1pSk5YFB0aGcmVFKMvMMOeENOrWgNJVTLYI05
8mu6XmbiqUeIu1Rlye/yNirYm33Js2f3VXYp6HSzisF5cWq4QwYqA6XIMfDl61/y
IXVb5l5eTfproM2grn3RcVVbk5DuEUfyDPzYYNm8elxzac4RrbkDif/b+tVFxmrJ
CUx1o3VLiVVzbIFCDc5r6pPArm1EdgseJ7pRdXzg6flwA0INRpeLCpjtvkHeZCh7
GS2JUBhFv7M+lneJljNU/trTkYiho+ZRW9AgLcN73c4+1wHttPHk+w19m5Ge182V
HzCQdO27IGovKN8jkprGafGxYhyCn4KdSYbRrG7fjkckzpJrjCpF2/bJJ+o4Zi9P
rJIKHzY5lIMXcD7wwwT2WwlKXoTDrgm4QKN18V+kZaoOILdKyMlEww4jPFUqk6j1
0Qeod55F5h4tCq2lmwDIa/jyWTGgqTr4UESqj46NB5+JkGYl0O1PPbS1nUm9sN1l
hkY45iskXVXqLl6AVVcXyxMTefD43M81tFVuJJgpdD/BaMaXAuBdNDfTQcJwhP99
uI6HqHFD3iEct8fBkYfQiwH2e1eu9OwgujiWHsutyK8VvzVB3/YnhQ/TzciRjPqz
7ykUutQNUALq8dQwoTnK
/* This is used to make sure that the hash signature generated is consistent
Do NOT change this test to suit your modification.
*/
const CertBundle = `
-----BEGIN CERTIFICATE-----
MIIFJjCCBA6gAwIBAgISBL8FgUdEcVYEjdMkTZPgC3ocMA0GCSqGSIb3DQEBCwUA
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
EwJSMzAeFw0yMTAzMjkwMTM2MzlaFw0yMTA2MjcwMTM2MzlaMBsxGTAXBgNVBAMT
EHNlY3VyZS5ra2Rldi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDOF/j+s7rHaDMXdhYjffoOFjNZb7n3sCuvubI3qOcgJmr1WPlCEry50KoY8FaB
IF2HstMIZceN4NoUK7mr3WAvsQTA47uBfuhp+XQmAQW0T/fYD+XbvxtCENEin+xm
JsvTKZLTKbE08E964J4H+1sBmueP6rvy2Wt95z0XkqoQiikpmLE87WdltQcATvVX
qqrL64hV0nN4Hdi2Bv1cQ92aR7lZGj8jiQRtTj8y5Ah3Gk3fPoao+yI7gnzembqo
fddePzz/u8iEuvYAsIYZKn9bbS7rkYoJazL2/xiDZR7usn0SomzmM6lGXDD3FF4b
lyTkLYwgFVgbGWoz1+eOHD5BAgMBAAGjggJLMIICRzAOBgNVHQ8BAf8EBAMCBaAw
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYD
VR0OBBYEFOPRdApL8XENLXDuU3oPisykGyp+MB8GA1UdIwQYMBaAFBQusxe3WFbL
rlAJQOYfr52LFMLGMFUGCCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDov
L3IzLm8ubGVuY3Iub3JnMCIGCCsGAQUFBzAChhZodHRwOi8vcjMuaS5sZW5jci5v
cmcvMBsGA1UdEQQUMBKCEHNlY3VyZS5ra2Rldi5vcmcwTAYDVR0gBEUwQzAIBgZn
gQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5s
ZXRzZW5jcnlwdC5vcmcwggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAAdQD2XJQv0Xcw
IhRUGAgwlFaO400TGTO/3wwvIAvMTvFk4wAAAXh71yBGAAAEAwBGMEQCIDmziDOn
ehPY2KoAFX8fPWiCm4EBTbGJXBWF1LCotPJBAiBLSCg+krXvbyoABnTm8knv0hbG
/ZOk8LV6qpw9VoQwGwB3AG9Tdqwx8DEZ2JkApFEV/3cVHBHZAsEAKQaNsgiaN9kT
AAABeHvXIIAAAAQDAEgwRgIhAOkeKc52wR3n5QWZfa3zbbicMMSQrTGbQ+1fHNs7
SsRvAiEAqbflDx1nZRsTA22FfNYfgF6v5Z3/svjiTleWSQad4WswDQYJKoZIhvcN
AQELBQADggEBAEj8tg+Agf5sNBM9CvjeXbA0fkpGDaQzXEkwefAea5vPgKoGiWSN
pHDkyr0i7+mqa7cMXhmmo20V0/+QDv0nrsCw8pgJuviC3GvK6agT6WfkXM2djJuo
osPeXOL9KEF/ATv0EyM5tr9TIoRSSYQoRhuqEdg3Dz9Ii8GXR5HhbYr6Cd7gwNHS
kYeivKDmgv31GHb4twPSE9LZ/U+56lNVvSbJ4csupIF3GnxnxrFSmijYNOPoM6mj
tzY45d4mjPs0fKCFKSsVM6YT0tX4NwIKsOaeQg30WLtRyDwYm6ma/a/UUUS0FloZ
2/p85glOgzownfoRjzTbqHu8ewtMd6Apc0E=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEZTCCA02gAwIBAgIQQAF1BIMUpMghjISpDBbN3zANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTIwMTAwNzE5MjE0MFoXDTIxMDkyOTE5MjE0MFow
MjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxCzAJBgNVBAMT
AlIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuwIVKMz2oJTTDxLs
jVWSw/iC8ZmmekKIp10mqrUrucVMsa+Oa/l1yKPXD0eUFFU1V4yeqKI5GfWCPEKp
Tm71O8Mu243AsFzzWTjn7c9p8FoLG77AlCQlh/o3cbMT5xys4Zvv2+Q7RVJFlqnB
U840yFLuta7tj95gcOKlVKu2bQ6XpUA0ayvTvGbrZjR8+muLj1cpmfgwF126cm/7
gcWt0oZYPRfH5wm78Sv3htzB2nFd1EbjzK0lwYi8YGd1ZrPxGPeiXOZT/zqItkel
/xMY6pgJdz+dU/nPAeX1pnAXFK9jpP+Zs5Od3FOnBv5IhR2haa4ldbsTzFID9e1R
oYvbFQIDAQABo4IBaDCCAWQwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8E
BAMCAYYwSwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5p
ZGVudHJ1c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTE
p7Gkeyxx+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEE
AYLfEwEBATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2Vu
Y3J5cHQub3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0
LmNvbS9EU1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYf
r52LFMLGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0B
AQsFAAOCAQEA2UzgyfWEiDcx27sT4rP8i2tiEmxYt0l+PAK3qB8oYevO4C5z70kH
ejWEHx2taPDY/laBL21/WKZuNTYQHHPD5b1tXgHXbnL7KqC401dk5VvCadTQsvd8
S8MXjohyc9z9/G2948kLjmE6Flh9dDYrVYA9x2O+hEPGOaEOa1eePynBgPayvUfL
qjBstzLhWVQLGAkXXmNs+5ZnPBxzDJOLxhF2JIbeQAcH5H0tZrUlo5ZYyOqA7s9p
O5b85o3AM/OJ+CktFBQtfvBhcJVd9wvlwPsk+uyOy2HI7mNxKKgsBTt375teA2Tw
UdHkhVNcsAKX1H7GNNLOEADksd86wuoXvg==
-----END CERTIFICATE-----
`
t.Run("bundle", func(t *testing.T) {
hash := CalculatePEMCertChainSHA256Hash([]byte(CertBundle))
assert.Equal(t, "WF65fBkgltadMnXryOMZ6TEYeV4d5Q0uu4SGXGZ0RjI=", hash)
})
const Single = `-----BEGIN CERTIFICATE-----
MIIFJjCCBA6gAwIBAgISBL8FgUdEcVYEjdMkTZPgC3ocMA0GCSqGSIb3DQEBCwUA
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
EwJSMzAeFw0yMTAzMjkwMTM2MzlaFw0yMTA2MjcwMTM2MzlaMBsxGTAXBgNVBAMT
EHNlY3VyZS5ra2Rldi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDOF/j+s7rHaDMXdhYjffoOFjNZb7n3sCuvubI3qOcgJmr1WPlCEry50KoY8FaB
IF2HstMIZceN4NoUK7mr3WAvsQTA47uBfuhp+XQmAQW0T/fYD+XbvxtCENEin+xm
JsvTKZLTKbE08E964J4H+1sBmueP6rvy2Wt95z0XkqoQiikpmLE87WdltQcATvVX
qqrL64hV0nN4Hdi2Bv1cQ92aR7lZGj8jiQRtTj8y5Ah3Gk3fPoao+yI7gnzembqo
fddePzz/u8iEuvYAsIYZKn9bbS7rkYoJazL2/xiDZR7usn0SomzmM6lGXDD3FF4b
lyTkLYwgFVgbGWoz1+eOHD5BAgMBAAGjggJLMIICRzAOBgNVHQ8BAf8EBAMCBaAw
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYD
VR0OBBYEFOPRdApL8XENLXDuU3oPisykGyp+MB8GA1UdIwQYMBaAFBQusxe3WFbL
rlAJQOYfr52LFMLGMFUGCCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDov
L3IzLm8ubGVuY3Iub3JnMCIGCCsGAQUFBzAChhZodHRwOi8vcjMuaS5sZW5jci5v
cmcvMBsGA1UdEQQUMBKCEHNlY3VyZS5ra2Rldi5vcmcwTAYDVR0gBEUwQzAIBgZn
gQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5s
ZXRzZW5jcnlwdC5vcmcwggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAAdQD2XJQv0Xcw
IhRUGAgwlFaO400TGTO/3wwvIAvMTvFk4wAAAXh71yBGAAAEAwBGMEQCIDmziDOn
ehPY2KoAFX8fPWiCm4EBTbGJXBWF1LCotPJBAiBLSCg+krXvbyoABnTm8knv0hbG
/ZOk8LV6qpw9VoQwGwB3AG9Tdqwx8DEZ2JkApFEV/3cVHBHZAsEAKQaNsgiaN9kT
AAABeHvXIIAAAAQDAEgwRgIhAOkeKc52wR3n5QWZfa3zbbicMMSQrTGbQ+1fHNs7
SsRvAiEAqbflDx1nZRsTA22FfNYfgF6v5Z3/svjiTleWSQad4WswDQYJKoZIhvcN
AQELBQADggEBAEj8tg+Agf5sNBM9CvjeXbA0fkpGDaQzXEkwefAea5vPgKoGiWSN
pHDkyr0i7+mqa7cMXhmmo20V0/+QDv0nrsCw8pgJuviC3GvK6agT6WfkXM2djJuo
osPeXOL9KEF/ATv0EyM5tr9TIoRSSYQoRhuqEdg3Dz9Ii8GXR5HhbYr6Cd7gwNHS
kYeivKDmgv31GHb4twPSE9LZ/U+56lNVvSbJ4csupIF3GnxnxrFSmijYNOPoM6mj
tzY45d4mjPs0fKCFKSsVM6YT0tX4NwIKsOaeQg30WLtRyDwYm6ma/a/UUUS0FloZ
2/p85glOgzownfoRjzTbqHu8ewtMd6Apc0E=
-----END CERTIFICATE-----
`
t.Run("single", func(t *testing.T) {
hash := CalculatePEMCertChainSHA256Hash([]byte(Single))
assert.Equal(t, "FW3SVMCL6um2wVltOdgJ3DpI82aredw83YoCblkMkVM=", hash)
})
}
func TestCalculateCertPublicKeyHash(t *testing.T) {
const Single = `-----BEGIN CERTIFICATE-----
MIINWTCCC0GgAwIBAgITLQAxbA/A+lw/1sLDAAAAADFsDzANBgkqhkiG9w0BAQsF
ADBPMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
MSAwHgYDVQQDExdNaWNyb3NvZnQgUlNBIFRMUyBDQSAwMjAeFw0yMjExMjUwMDU2
NTZaFw0yMzA1MjUwMDU2NTZaMBcxFTATBgNVBAMTDHd3dy5iaW5nLmNvbTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOH89lKmtkDnClFiQwfZofZO4h8C
Ye/+ChI67pEw5Q6/MxJzHiMKe8f1WaNuc+wkdHdct+BmQ+AftozIJt+eSN6IF7eY
dsutBvR87GNLFe40MBvfyvTQVM9Ulv04JxOpKTYnsf2wmktEI3y7FCgfm9RT71n+
Zef8Z8fa4By7aGfbbCQ0DsHl5P9o3ug/eLQODzK9NuQlwcVBHD2Zvgo+K7WOsjgE
k8JnOr+2zc0WWT4OrWSDJE/3l+jvhxmZkrwgmks4m9zUZvAnYAz/xxVCJRqbI3Ou
S5fkJJ3f6IxPbS2i8OWz6tma1aIkgQaFNJQuYOJa1esfQcEzs6kb/Xx5DXUCAwEA
AaOCCWQwgglgMIIBfQYKKwYBBAHWeQIEAgSCAW0EggFpAWcAdgCt9776fP8QyIud
PZwePhhqtGcpXc+xDCTKhYY069yCigAAAYSsUtxtAAAEAwBHMEUCIQCP/Jpp337p
cKITqS/kNlA4bNY6TK1Ad0VlsdkzQU+oZgIgFZb2AcsyT1UKCmM3ziGsLdvS9MAT
D1g/kztyDXhkA70AdgBVgdTCFpA2AUrqC5tXPFPwwOQ4eHAlCBcvo6odBxPTDAAA
AYSsUtsZAAAEAwBHMEUCIQDvlqXrdA440PW6b+JLj4F0ZVQNKHcv1lub0FhQqHgR
wAIgAtC7eXvXXhVBuO+Bd3fkDI0aGQM+pcvIesBoygzStjQAdQB6MoxU2LcttiDq
OOBSHumEFnAyE4VNO9IrwTpXo1LrUgAAAYSsUtmfAAAEAwBGMEQCIDgjSYt6e/h8
dv2KGEL3AJZUBH2gp1AA5saH8o3OyMJhAiBOCzo3oWlVFeF/8c0fxIIs9Fj4w8BY
INo0jNP/k7apgTAnBgkrBgEEAYI3FQoEGjAYMAoGCCsGAQUFBwMBMAoGCCsGAQUF
BwMCMD4GCSsGAQQBgjcVBwQxMC8GJysGAQQBgjcVCIfahnWD7tkBgsmFG4G1nmGF
9OtggV2Fho5Bh8KYUAIBZAIBJzCBhwYIKwYBBQUHAQEEezB5MFMGCCsGAQUFBzAC
hkdodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL21zY29ycC9NaWNyb3NvZnQl
MjBSU0ElMjBUTFMlMjBDQSUyMDAyLmNydDAiBggrBgEFBQcwAYYWaHR0cDovL29j
c3AubXNvY3NwLmNvbTAdBgNVHQ4EFgQUpuSPPchFlPGu8FTbzPhJTFxQ7RowDgYD
VR0PAQH/BAQDAgSwMIIFbQYDVR0RBIIFZDCCBWCCDHd3dy5iaW5nLmNvbYIQZGlj
dC5iaW5nLmNvbS5jboITKi5wbGF0Zm9ybS5iaW5nLmNvbYIKKi5iaW5nLmNvbYII
YmluZy5jb22CFmllb25saW5lLm1pY3Jvc29mdC5jb22CEyoud2luZG93c3NlYXJj
aC5jb22CGWNuLmllb25saW5lLm1pY3Jvc29mdC5jb22CESoub3JpZ2luLmJpbmcu
Y29tgg0qLm1tLmJpbmcubmV0gg4qLmFwaS5iaW5nLmNvbYIYZWNuLmRldi52aXJ0
dWFsZWFydGgubmV0gg0qLmNuLmJpbmcubmV0gg0qLmNuLmJpbmcuY29tghBzc2wt
YXBpLmJpbmcuY29tghBzc2wtYXBpLmJpbmcubmV0gg4qLmFwaS5iaW5nLm5ldIIO
Ki5iaW5nYXBpcy5jb22CD2JpbmdzYW5kYm94LmNvbYIWZmVlZGJhY2subWljcm9z
b2Z0LmNvbYIbaW5zZXJ0bWVkaWEuYmluZy5vZmZpY2UubmV0gg5yLmJhdC5iaW5n
LmNvbYIQKi5yLmJhdC5iaW5nLmNvbYISKi5kaWN0LmJpbmcuY29tLmNugg8qLmRp
Y3QuYmluZy5jb22CDiouc3NsLmJpbmcuY29tghAqLmFwcGV4LmJpbmcuY29tghYq
LnBsYXRmb3JtLmNuLmJpbmcuY29tgg13cC5tLmJpbmcuY29tggwqLm0uYmluZy5j
b22CD2dsb2JhbC5iaW5nLmNvbYIRd2luZG93c3NlYXJjaC5jb22CDnNlYXJjaC5t
c24uY29tghEqLmJpbmdzYW5kYm94LmNvbYIZKi5hcGkudGlsZXMuZGl0dS5saXZl
LmNvbYIPKi5kaXR1LmxpdmUuY29tghgqLnQwLnRpbGVzLmRpdHUubGl2ZS5jb22C
GCoudDEudGlsZXMuZGl0dS5saXZlLmNvbYIYKi50Mi50aWxlcy5kaXR1LmxpdmUu
Y29tghgqLnQzLnRpbGVzLmRpdHUubGl2ZS5jb22CFSoudGlsZXMuZGl0dS5saXZl
LmNvbYILM2QubGl2ZS5jb22CE2FwaS5zZWFyY2gubGl2ZS5jb22CFGJldGEuc2Vh
cmNoLmxpdmUuY29tghVjbndlYi5zZWFyY2gubGl2ZS5jb22CDGRldi5saXZlLmNv
bYINZGl0dS5saXZlLmNvbYIRZmFyZWNhc3QubGl2ZS5jb22CDmltYWdlLmxpdmUu
Y29tgg9pbWFnZXMubGl2ZS5jb22CEWxvY2FsLmxpdmUuY29tLmF1ghRsb2NhbHNl
YXJjaC5saXZlLmNvbYIUbHM0ZC5zZWFyY2gubGl2ZS5jb22CDW1haWwubGl2ZS5j
b22CEW1hcGluZGlhLmxpdmUuY29tgg5sb2NhbC5saXZlLmNvbYINbWFwcy5saXZl
LmNvbYIQbWFwcy5saXZlLmNvbS5hdYIPbWluZGlhLmxpdmUuY29tgg1uZXdzLmxp
dmUuY29tghxvcmlnaW4uY253ZWIuc2VhcmNoLmxpdmUuY29tghZwcmV2aWV3Lmxv
Y2FsLmxpdmUuY29tgg9zZWFyY2gubGl2ZS5jb22CEnRlc3QubWFwcy5saXZlLmNv
bYIOdmlkZW8ubGl2ZS5jb22CD3ZpZGVvcy5saXZlLmNvbYIVdmlydHVhbGVhcnRo
LmxpdmUuY29tggx3YXAubGl2ZS5jb22CEndlYm1hc3Rlci5saXZlLmNvbYITd2Vi
bWFzdGVycy5saXZlLmNvbYIVd3d3LmxvY2FsLmxpdmUuY29tLmF1ghR3d3cubWFw
cy5saXZlLmNvbS5hdTCBsAYDVR0fBIGoMIGlMIGioIGfoIGchk1odHRwOi8vbXNj
cmwubWljcm9zb2Z0LmNvbS9wa2kvbXNjb3JwL2NybC9NaWNyb3NvZnQlMjBSU0El
MjBUTFMlMjBDQSUyMDAyLmNybIZLaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3Br
aS9tc2NvcnAvY3JsL01pY3Jvc29mdCUyMFJTQSUyMFRMUyUyMENBJTIwMDIuY3Js
MFcGA1UdIARQME4wQgYJKwYBBAGCNyoBMDUwMwYIKwYBBQUHAgEWJ2h0dHA6Ly93
d3cubWljcm9zb2Z0LmNvbS9wa2kvbXNjb3JwL2NwczAIBgZngQwBAgEwHwYDVR0j
BBgwFoAU/y9/4Qb0OPMt7SWNmML+DvZs/PowHQYDVR0lBBYwFAYIKwYBBQUHAwEG
CCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4ICAQB4OIB/EHxpF64iFZME7XkJjZYn
ZiYIfOfHs6EGDNn7fxvpZS9HVy1jOWv/RvzEbMuSV3b/fItaJN/zATBg5/6hb5Jq
HGIcnKmb+tYrKlYhSOngHSu/8/OYP1dFFIqcVe0769kwXaKUzLh6UVRaS+mB7GFc
sXmPMbv5NM7mCUEdMkOaoSmubfw/WzmmRGrcSmtCxtIwMcp8Jf13Esunq//4+9w3
M/JXa8ubmXyrY63zt/Oz/NkVJvja89ueovscy6s5sw2r+Su4bRsJjmxwCbakp56K
rbh7z417LzW88MMuATvOyk/O8Rbw2KYVSEiQgO54kHI0YkHkJ/6IoeAT1pmCfHUE
Rd+Ec8T+/lE2BPLVqp8SjogDYiybb0IR5Gn2vYyUdzsS2h/C5qGNd2t5ehxfjQoL
G6Y3GJZQRxkSX6TLPYU0U63wWb4yeSxabpBlARaZMaAoqDa3cX53WCnrAXDz8vuH
yAtX2/Jq7IpybFK5kFzbxfI02Ik0aCWJUnXPL8L6esTskwvkzX8rSI/bjPrzcJL5
B9pONLy6wc8/Arfu2eNlMbs8s/g8c5zkEc3fBZ9tJ1dqlnMAVgB2+fwI3aK4F34N
uyfZW7Xu65KkPhbMnO0GVGM7X4Lkyjm4ysQ9PIRV3MwMfXH+RBSXlIayLTcYG4gl
XF1a/qnao6nMjyTIyQ==
-----END CERTIFICATE-----
`
t.Run("singlepublickey", func(t *testing.T) {
block, _ := pem.Decode([]byte(Single))
cert, err := x509.ParseCertificate(block.Bytes)
assert.Equal(t, err, nil)
hash := GenerateCertHash(cert)
fingerprint, _ := hex.DecodeString("ae243d668ec9c7f74a0dcd1ad21c6676b4efe30c39728934b362093af886bf77")
assert.Equal(t, fingerprint, hash)
hash := GenerateCertPublicKeyHash(cert)
hashstr := base64.StdEncoding.EncodeToString(hash)
assert.Equal(t, "xI/4mNm8xF9uDT4vA9G1+aKAaybwNlkRECnN8vGAHTM=", hashstr)
})
}