Skip to content

Commit cfdcc03

Browse files
committed
feat: Surge 支持 server-cert-verify-name
1 parent eef5532 commit cfdcc03

5 files changed

Lines changed: 98 additions & 13 deletions

File tree

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sub-store",
3-
"version": "2.36.7",
3+
"version": "2.36.8",
44
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
55
"main": "src/main.js",
66
"packageManager": "pnpm@11.0.9",

backend/src/core/proxy-utils/parsers/peggy/surge.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/
384384
}
385385
handleShadowTLS();
386386
}
387-
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/vmess_method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/alpn/tls_fingerprint/tls_verification/client_cert/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
387+
vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/vmess_method/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls/sni/cert_verify_name/alpn/tls_fingerprint/tls_verification/client_cert/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
388388
proxy.type = "vmess";
389389
proxy.cipher = proxy.cipher || "auto";
390390
// Surfboard 与 Surge 默认不一致, 不管 Surfboard https://getsurfboard.com/docs/profile-format/proxy/external-proxy/vmess
@@ -396,17 +396,17 @@ vmess = tag equals "vmess" address (vmess_uuid/vmess_aead/ws/ws_path/ws_headers/
396396
handleWebsocket();
397397
handleShadowTLS();
398398
}
399-
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/alpn/tls_fingerprint/tls_verification/client_cert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
399+
trojan = tag equals "trojan" address (passwordk/ws/ws_path/ws_headers/tls/sni/cert_verify_name/alpn/tls_fingerprint/tls_verification/client_cert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
400400
proxy.type = "trojan";
401401
handleWebsocket();
402402
handleShadowTLS();
403403
}
404-
https = tag equals "https" address (username password)? (usernamek passwordk)? (headers/sni/alpn/tls_fingerprint/tls_verification/client_cert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
404+
https = tag equals "https" address (username password)? (usernamek passwordk)? (headers/sni/cert_verify_name/alpn/tls_fingerprint/tls_verification/client_cert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
405405
proxy.type = "http";
406406
proxy.tls = true;
407407
handleShadowTLS();
408408
}
409-
h2_connect = tag equals "h2-connect" address (username password)? (usernamek passwordk)? (headers/max_streams/sni/alpn/tls_fingerprint/tls_verification/client_cert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
409+
h2_connect = tag equals "h2-connect" address (username password)? (usernamek passwordk)? (headers/max_streams/sni/cert_verify_name/alpn/tls_fingerprint/tls_verification/client_cert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
410410
proxy.type = "h2-connect";
411411
proxy.tls = true;
412412
handleShadowTLS();
@@ -429,11 +429,11 @@ snell = tag equals "snell" address (snell_version/snell_mode/snell_psk/obfs/obfs
429429
}
430430
handleShadowTLS();
431431
}
432-
tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/client_cert/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* {
432+
tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/client_cert/sni/cert_verify_name/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* {
433433
proxy.type = "tuic";
434434
handleShadowTLS();
435435
}
436-
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/client_cert/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* {
436+
tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/client_cert/sni/cert_verify_name/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* {
437437
proxy.type = "tuic";
438438
proxy.version = 5;
439439
handleShadowTLS();
@@ -442,24 +442,24 @@ wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/under
442442
proxy.type = "wireguard-surge";
443443
handleShadowTLS();
444444
}
445-
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/alpn/tls_verification/client_cert/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/salamander_password/gecko_password/others)* {
445+
hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/cert_verify_name/alpn/tls_verification/client_cert/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/salamander_password/gecko_password/others)* {
446446
proxy.type = "hysteria2";
447447
handleShadowTLS();
448448
}
449449
socks5 = tag equals "socks5" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
450450
proxy.type = "socks5";
451451
handleShadowTLS();
452452
}
453-
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/alpn/tls_fingerprint/tls_verification/client_cert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
453+
socks5_tls = tag equals "socks5-tls" address (username password)? (usernamek passwordk)? (udp_relay/no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/cert_verify_name/alpn/tls_fingerprint/tls_verification/client_cert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
454454
proxy.type = "socks5";
455455
proxy.tls = true;
456456
handleShadowTLS();
457457
}
458-
anytls = tag equals "anytls" address (passwordk/reuse/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/client_cert/sni/alpn/fast_open/tfo/block_quic/others)* {
458+
anytls = tag equals "anytls" address (passwordk/reuse/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/client_cert/sni/cert_verify_name/alpn/fast_open/tfo/block_quic/others)* {
459459
proxy.type = "anytls";
460460
proxy.tls = true;
461461
}
462-
trust_tunnel = tag equals "trust-tunnel" address (usernamek/passwordk/headers/max_streams/reuse/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/client_cert/sni/alpn/fast_open/tfo/block_quic/others)* {
462+
trust_tunnel = tag equals "trust-tunnel" address (usernamek/passwordk/headers/max_streams/reuse/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/client_cert/sni/cert_verify_name/alpn/fast_open/tfo/block_quic/others)* {
463463
proxy.type = "trusttunnel";
464464
proxy.tls = true;
465465
}
@@ -536,6 +536,7 @@ sni = comma "sni" equals match:[^,]+ {
536536
proxy.sni = sni;
537537
}
538538
}
539+
cert_verify_name = comma "server-cert-verify-name" equals match:[^,]+ { proxy["name-cert-verify"] = stripQuotes(match.join("")); }
539540
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
540541
tls_fingerprint = comma "server-cert-fingerprint-sha256" equals tls_fingerprint:$[^,]+ { proxy["tls-fingerprint"] = tls_fingerprint.trim(); }
541542
client_cert = comma "client-cert" equals match:[^,]+ { proxy["keystore-client-cert"] = stripQuotes(match.join("")); }

backend/src/core/proxy-utils/producers/surge.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ function appendTlsProxyParams(result, proxy, enabled = true) {
102102
'tls-fingerprint',
103103
);
104104
result.appendIfPresent(`,sni="${proxy.sni}"`, 'sni');
105+
result.appendIfPresent(
106+
`,server-cert-verify-name=${quoteSurgeValue(
107+
proxy['name-cert-verify'],
108+
)}`,
109+
'name-cert-verify',
110+
);
105111
if (proxy.plugin !== 'shadow-tls') {
106112
appendAlpn(result, proxy);
107113
}

backend/src/test/proxy-parsers/v2ray-and-platforms.spec.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3837,6 +3837,58 @@ describe('Platform raw-format parser coverage', function () {
38373837
}
38383838
});
38393839

3840+
it('parses Surge server-cert-verify-name for TLS protocol lines', function () {
3841+
const cases = [
3842+
{
3843+
input: `Surge VMess Verify Name = vmess,vmess-verify.example.com,443,username=${UUID},tls=true,vmess-aead=true,server-cert-verify-name=verify.example.com`,
3844+
expected: { 'name-cert-verify': 'verify.example.com' },
3845+
},
3846+
{
3847+
input: `Surge Trojan Verify Name = trojan,trojan-verify.example.com,443,password=secret,server-cert-verify-name='verify.example.com'`,
3848+
expected: { 'name-cert-verify': 'verify.example.com' },
3849+
},
3850+
{
3851+
input: `Surge HTTPS Verify Name = https,https-verify.example.com,443,sni=sni.example.com,server-cert-verify-name="verify.example.com"`,
3852+
expected: {
3853+
sni: 'sni.example.com',
3854+
'name-cert-verify': 'verify.example.com',
3855+
},
3856+
},
3857+
{
3858+
input: `Surge H2 Verify Name = h2-connect,h2-verify.example.com,443,server-cert-verify-name='verify.example.com'`,
3859+
expected: { 'name-cert-verify': 'verify.example.com' },
3860+
},
3861+
{
3862+
input: `Surge SOCKS5 Verify Name = socks5-tls,socks-verify.example.com,1080,server-cert-verify-name="verify.example.com"`,
3863+
expected: { 'name-cert-verify': 'verify.example.com' },
3864+
},
3865+
{
3866+
input: `Surge AnyTLS Verify Name = anytls,anytls-verify.example.com,443,password=secret,server-cert-verify-name=verify.example.com`,
3867+
expected: { 'name-cert-verify': 'verify.example.com' },
3868+
},
3869+
{
3870+
input: `Surge TrustTunnel Verify Name = trust-tunnel,trust-verify.example.com,443,server-cert-verify-name='verify.example.com'`,
3871+
expected: { 'name-cert-verify': 'verify.example.com' },
3872+
},
3873+
{
3874+
input: `Surge TUIC Verify Name = tuic,tuic-verify.example.com,443,token=secret,server-cert-verify-name="verify.example.com"`,
3875+
expected: { 'name-cert-verify': 'verify.example.com' },
3876+
},
3877+
{
3878+
input: `Surge TUIC v5 Verify Name = tuic-v5,tuic-v5-verify.example.com,443,uuid=${UUID},password=secret,server-cert-verify-name=verify.example.com`,
3879+
expected: { 'name-cert-verify': 'verify.example.com' },
3880+
},
3881+
{
3882+
input: `Surge Hysteria2 Verify Name = hysteria2,hy2-verify.example.com,443,password=secret,server-cert-verify-name='verify.example.com'`,
3883+
expected: { 'name-cert-verify': 'verify.example.com' },
3884+
},
3885+
];
3886+
3887+
for (const { input, expected } of cases) {
3888+
expectSubset(parseOne(input), expected);
3889+
}
3890+
});
3891+
38403892
it('rejects Surge shadow-tls version 1 lines', function () {
38413893
expect(
38423894
parseAll(

backend/src/test/proxy-producers/text.spec.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -879,8 +879,9 @@ describe('Proxy text producers', function () {
879879
);
880880
});
881881

882-
it('emits quoted Surge alpn for TLS protocol outputs', function () {
882+
it('emits Surge alpn and server-cert-verify-name for TLS protocol outputs', function () {
883883
const alpn = ['http/1.1', 'h2', 'h3'];
884+
const nameCertVerify = 'verify.example.com';
884885
const output = produceExternal('Surge', [
885886
{
886887
type: 'vmess',
@@ -892,6 +893,7 @@ describe('Proxy text producers', function () {
892893
alterId: 0,
893894
tls: true,
894895
alpn,
896+
'name-cert-verify': nameCertVerify,
895897
},
896898
{
897899
type: 'trojan',
@@ -900,21 +902,25 @@ describe('Proxy text producers', function () {
900902
port: 443,
901903
password: 'secret',
902904
alpn,
905+
'name-cert-verify': nameCertVerify,
903906
},
904907
{
905908
type: 'http',
906909
name: 'Surge HTTPS ALPN',
907910
server: 'https-alpn.example.com',
908911
port: 443,
909912
tls: true,
913+
sni: 'sni.example.com',
910914
alpn,
915+
'name-cert-verify': nameCertVerify,
911916
},
912917
{
913918
type: 'h2-connect',
914919
name: 'Surge H2 ALPN',
915920
server: 'h2-alpn.example.com',
916921
port: 443,
917922
alpn,
923+
'name-cert-verify': nameCertVerify,
918924
},
919925
{
920926
type: 'socks5',
@@ -923,6 +929,7 @@ describe('Proxy text producers', function () {
923929
port: 1080,
924930
tls: true,
925931
alpn,
932+
'name-cert-verify': nameCertVerify,
926933
},
927934
{
928935
type: 'anytls',
@@ -931,13 +938,15 @@ describe('Proxy text producers', function () {
931938
port: 443,
932939
password: 'secret',
933940
alpn,
941+
'name-cert-verify': nameCertVerify,
934942
},
935943
{
936944
type: 'trusttunnel',
937945
name: 'Surge TrustTunnel ALPN',
938946
server: 'trust-alpn.example.com',
939947
port: 443,
940948
alpn,
949+
'name-cert-verify': nameCertVerify,
941950
},
942951
{
943952
type: 'tuic',
@@ -947,6 +956,7 @@ describe('Proxy text producers', function () {
947956
uuid: UUID,
948957
password: 'secret',
949958
alpn,
959+
'name-cert-verify': nameCertVerify,
950960
},
951961
{
952962
type: 'hysteria2',
@@ -955,13 +965,25 @@ describe('Proxy text producers', function () {
955965
port: 443,
956966
password: 'secret',
957967
alpn,
968+
'name-cert-verify': nameCertVerify,
958969
},
959970
]);
960971

961972
expect(output.match(/alpn="http\/1\.1,h2,h3"/g)).to.have.length(9);
973+
expect(
974+
output.match(/server-cert-verify-name="verify\.example\.com"/g),
975+
).to.have.length(9);
976+
977+
const roundTripped = ProxyUtils.parse(output).find(
978+
({ name }) => name === 'Surge HTTPS ALPN',
979+
);
980+
expect(roundTripped).to.include({
981+
sni: 'sni.example.com',
982+
'name-cert-verify': nameCertVerify,
983+
});
962984
});
963985

964-
it('omits Surge alpn for non-TLS outputs', function () {
986+
it('omits Surge alpn and server-cert-verify-name for non-TLS outputs', function () {
965987
const output = produceExternal('Surge', [
966988
{
967989
type: 'vmess',
@@ -971,24 +993,28 @@ describe('Proxy text producers', function () {
971993
uuid: UUID,
972994
alterId: 0,
973995
alpn: ['http/1.1', 'h2'],
996+
'name-cert-verify': 'verify.example.com',
974997
},
975998
{
976999
type: 'http',
9771000
name: 'Surge HTTP Plain ALPN',
9781001
server: 'http-plain-alpn.example.com',
9791002
port: 80,
9801003
alpn: ['http/1.1', 'h2'],
1004+
'name-cert-verify': 'verify.example.com',
9811005
},
9821006
{
9831007
type: 'socks5',
9841008
name: 'Surge SOCKS5 Plain ALPN',
9851009
server: 'socks-plain-alpn.example.com',
9861010
port: 1080,
9871011
alpn: ['http/1.1', 'h2'],
1012+
'name-cert-verify': 'verify.example.com',
9881013
},
9891014
]);
9901015

9911016
expect(output).to.not.include('alpn=');
1017+
expect(output).to.not.include('server-cert-verify-name=');
9921018
});
9931019

9941020
it('produces Surge TUIC v5 lines with port hopping', function () {

0 commit comments

Comments
 (0)