mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-10-11 17:58:13 +08:00
chore: authenticate JLS ServerHello using its wire encoding
JLS authenticates the serialized ServerHello with only the random field zeroed. The previous marshalForJLS implementation reordered extensions to match rustls-jls's internal struct order, even when that order differed from the bytes sent on the wire. That canonical order is not defined by TLS or the JLS specification and couples this package to a rustls-jls implementation detail. Use the normal marshal output when sending and the original wire bytes when receiving, preserving extension order and leaving the TLS transcript untouched. This intentionally removes compatibility with rustls-jls clients that re-encode ServerHello extensions before JLS verification.
This commit is contained in:
1 parent
e3c1ea06a4
commit
5c1b8bfc31
3 files changed
+12
-88
No files matched your search
@@ -24,7 +24,7 @@ require (
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||
github.com/metacubex/http v0.1.6
|
||||
github.com/metacubex/jls-quic-go v0.0.0-20260712113821-d34e6f9b4c7f
|
||||
github.com/metacubex/jls-tls v0.0.0-20260712171131-15870a03ac51
|
||||
github.com/metacubex/jls-tls v0.0.0-20260716010121-e13c4e54a728
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604
|
||||
github.com/metacubex/mhurl v0.1.0
|
||||
github.com/metacubex/mlkem v0.1.0
|
||||
|
||||
@@ -133,8 +133,8 @@ github.com/metacubex/http v0.1.6 h1:xvXuvXMCMxCWMF5nEJF4yiKvXL+p2atWMzs37e80m1I=
|
||||
github.com/metacubex/http v0.1.6/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
|
||||
github.com/metacubex/jls-quic-go v0.0.0-20260712113821-d34e6f9b4c7f h1:ywMFrpfRTU6oiRYhn2PGefm/RWdtdwsD13sDFmrJ6+s=
|
||||
github.com/metacubex/jls-quic-go v0.0.0-20260712113821-d34e6f9b4c7f/go.mod h1:fXVJbX1dv67OpH+jGaecl7DYRj6KDLwOya8OSUPBIxo=
|
||||
github.com/metacubex/jls-tls v0.0.0-20260712171131-15870a03ac51 h1:xTFfQ+fybBXCl8mkp6ojGp+tCrfIa2ZEqSrx+4unTVM=
|
||||
github.com/metacubex/jls-tls v0.0.0-20260712171131-15870a03ac51/go.mod h1:mmqs889W/TqPlfNRDa2UyJvRiLyiTJIEnWHkcj3SKB8=
|
||||
github.com/metacubex/jls-tls v0.0.0-20260716010121-e13c4e54a728 h1:4IbvO5xjKMJLs5GmLl0fYI+lDcJxAoZ3hJtaqq2/YC0=
|
||||
github.com/metacubex/jls-tls v0.0.0-20260716010121-e13c4e54a728/go.mod h1:mmqs889W/TqPlfNRDa2UyJvRiLyiTJIEnWHkcj3SKB8=
|
||||
github.com/metacubex/jsonv2 v0.0.0-20260518173308-f4597c22f1df h1:S0vBzqjXok24VopstOgPd1JdgglW9tXehrqvwpQWbQ8=
|
||||
github.com/metacubex/jsonv2 v0.0.0-20260518173308-f4597c22f1df/go.mod h1:F4sVXat6QjPXkNsKRDyyG3BhSkxPFFnRPEIwmmyCgbg=
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
|
||||
|
||||
+9
-85
@@ -18,20 +18,16 @@ import (
|
||||
"github.com/metacubex/http"
|
||||
"github.com/metacubex/randv2"
|
||||
utls "github.com/metacubex/utls"
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
)
|
||||
|
||||
const (
|
||||
jlsClientHelloType = 1
|
||||
jlsServerHelloType = 2
|
||||
jlsHandshakeHeaderLen = 4
|
||||
jlsHelloLegacyVersionLen = 2
|
||||
jlsHelloRandomLen = 32
|
||||
jlsHelloRandomOffset = jlsHandshakeHeaderLen + jlsHelloLegacyVersionLen
|
||||
jlsRandomSeedLen = jlsHelloRandomLen / 2
|
||||
jlsExtensionPreSharedKey = 41
|
||||
jlsExtensionSupportedVers = 43
|
||||
jlsExtensionKeyShare = 51
|
||||
jlsClientHelloType = 1
|
||||
jlsServerHelloType = 2
|
||||
jlsHandshakeHeaderLen = 4
|
||||
jlsHelloLegacyVersionLen = 2
|
||||
jlsHelloRandomLen = 32
|
||||
jlsHelloRandomOffset = jlsHandshakeHeaderLen + jlsHelloLegacyVersionLen
|
||||
jlsRandomSeedLen = jlsHelloRandomLen / 2
|
||||
)
|
||||
|
||||
func newUTLSClient(ctx context.Context, conn net.Conn, config *ClientConfig) (net.Conn, bool, error) {
|
||||
@@ -254,80 +250,8 @@ func jlsServerHelloAuthData(raw []byte) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := cryptobyte.String(msg)
|
||||
var messageType, compressionMethod uint8
|
||||
var legacyVersion, cipherSuite uint16
|
||||
var body, sessionID, extensions cryptobyte.String
|
||||
var random []byte
|
||||
if !s.ReadUint8(&messageType) ||
|
||||
!s.ReadUint24LengthPrefixed(&body) ||
|
||||
!s.Empty() ||
|
||||
!body.ReadUint16(&legacyVersion) ||
|
||||
!body.ReadBytes(&random, jlsHelloRandomLen) ||
|
||||
!body.ReadUint8LengthPrefixed(&sessionID) ||
|
||||
!body.ReadUint16(&cipherSuite) ||
|
||||
!body.ReadUint8(&compressionMethod) ||
|
||||
!body.ReadUint16LengthPrefixed(&extensions) ||
|
||||
!body.Empty() {
|
||||
return nil, errors.New("jls: invalid uTLS server hello")
|
||||
}
|
||||
|
||||
type extension struct {
|
||||
typeID uint16
|
||||
wire []byte
|
||||
}
|
||||
extensionBytes := extensions
|
||||
parsed := make([]extension, 0, 3)
|
||||
for len(extensions) > 0 {
|
||||
remaining := extensions
|
||||
var typeID uint16
|
||||
var data cryptobyte.String
|
||||
if !extensions.ReadUint16(&typeID) || !extensions.ReadUint16LengthPrefixed(&data) {
|
||||
return nil, errors.New("jls: invalid uTLS server hello extensions")
|
||||
}
|
||||
wireLen := len(remaining) - len(extensions)
|
||||
parsed = append(parsed, extension{typeID: typeID, wire: remaining[:wireLen]})
|
||||
}
|
||||
|
||||
// rustls decodes ServerHello extensions into fields and serializes them in
|
||||
// this order when it calculates the JLS random. The wire order is irrelevant
|
||||
// to TLS but not to JLS, whose authentication input must match byte for byte.
|
||||
canonicalTypes := [...]uint16{
|
||||
jlsExtensionKeyShare,
|
||||
jlsExtensionPreSharedKey,
|
||||
jlsExtensionSupportedVers,
|
||||
}
|
||||
canonical := make(map[uint16][]byte, len(canonicalTypes))
|
||||
for _, ext := range parsed {
|
||||
for _, typeID := range canonicalTypes {
|
||||
if ext.typeID == typeID {
|
||||
canonical[typeID] = ext.wire
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reinsert the canonical extensions at their first original position. Their
|
||||
// encoded bytes and all unrelated extensions remain untouched, and the total
|
||||
// length does not change.
|
||||
extensionOffset := len(msg) - len(extensionBytes)
|
||||
result := append([]byte(nil), msg[:extensionOffset]...)
|
||||
canonicalWritten := false
|
||||
for _, ext := range parsed {
|
||||
if _, ok := canonical[ext.typeID]; ok {
|
||||
if !canonicalWritten {
|
||||
for _, typeID := range canonicalTypes {
|
||||
result = append(result, canonical[typeID]...)
|
||||
}
|
||||
canonicalWritten = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
result = append(result, ext.wire...)
|
||||
}
|
||||
zeroJLSBytes(result[jlsHelloRandomOffset : jlsHelloRandomOffset+jlsHelloRandomLen])
|
||||
return result, nil
|
||||
zeroJLSBytes(msg[jlsHelloRandomOffset : jlsHelloRandomOffset+jlsHelloRandomLen])
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func cloneJLSHello(raw []byte, messageType byte) ([]byte, error) {
|
||||
|
||||
Reference in new issue
Block a user