mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-05 20:55:29 +03:00
feat(sub): export TLS cipher suites as the cs query param in share links (#6825)
Hosts and TLS inbounds can set cipher suites, but they reached only the JSON and Clash subscriptions; raw share links never carried them, so clients that read cs from links (v2rayNG forks used with fp=unsafe) had to enter them by hand. Share links now emit cs from the host or inbound TLS settings, the panel's own link generator and previews do the same, and both link parsers (the Go one and the panel's outbound import) read cs back into tlsSettings.cipherSuites.
This commit is contained in:
@@ -18,6 +18,7 @@ export type HostLinkInput = Pick<
|
|||||||
| 'pinnedPeerCertSha256'
|
| 'pinnedPeerCertSha256'
|
||||||
| 'verifyPeerCertByName'
|
| 'verifyPeerCertByName'
|
||||||
| 'echConfigList'
|
| 'echConfigList'
|
||||||
|
| 'cipherSuites'
|
||||||
| 'overrideSniFromAddress'
|
| 'overrideSniFromAddress'
|
||||||
| 'keepSniBlank'
|
| 'keepSniBlank'
|
||||||
| 'vlessRoute'
|
| 'vlessRoute'
|
||||||
@@ -55,6 +56,7 @@ export function hostToExternalProxyEntry(host: HostLinkInput): ExternalProxyEntr
|
|||||||
: undefined,
|
: undefined,
|
||||||
verifyPeerCertByName: host.verifyPeerCertByName || undefined,
|
verifyPeerCertByName: host.verifyPeerCertByName || undefined,
|
||||||
echConfigList: host.echConfigList || undefined,
|
echConfigList: host.echConfigList || undefined,
|
||||||
|
cipherSuites: host.cipherSuites || undefined,
|
||||||
vlessRoute: host.vlessRoute || undefined,
|
vlessRoute: host.vlessRoute || undefined,
|
||||||
allowInsecure: host.allowInsecure || undefined,
|
allowInsecure: host.allowInsecure || undefined,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -235,6 +235,8 @@ function applyExternalProxyTLSObj(
|
|||||||
}
|
}
|
||||||
if (externalProxy.echConfigList && externalProxy.echConfigList.length > 0)
|
if (externalProxy.echConfigList && externalProxy.echConfigList.length > 0)
|
||||||
obj.ech = externalProxy.echConfigList;
|
obj.ech = externalProxy.echConfigList;
|
||||||
|
if (externalProxy.cipherSuites && externalProxy.cipherSuites.length > 0)
|
||||||
|
obj.cs = externalProxy.cipherSuites;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenVmessLinkInput {
|
export interface GenVmessLinkInput {
|
||||||
@@ -337,6 +339,9 @@ export function genVmessLink(input: GenVmessLinkInput): string {
|
|||||||
if (tlsSettings.settings.pinnedPeerCertSha256.length > 0) {
|
if (tlsSettings.settings.pinnedPeerCertSha256.length > 0) {
|
||||||
obj.pcs = tlsSettings.settings.pinnedPeerCertSha256.join(',');
|
obj.pcs = tlsSettings.settings.pinnedPeerCertSha256.join(',');
|
||||||
}
|
}
|
||||||
|
if (tlsSettings.cipherSuites && tlsSettings.cipherSuites.length > 0) {
|
||||||
|
obj.cs = tlsSettings.cipherSuites;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyExternalProxyTLSObj(externalProxy, obj, tls);
|
applyExternalProxyTLSObj(externalProxy, obj, tls);
|
||||||
@@ -394,6 +399,8 @@ function applyExternalProxyTLSParams(
|
|||||||
}
|
}
|
||||||
if (externalProxy.echConfigList && externalProxy.echConfigList.length > 0)
|
if (externalProxy.echConfigList && externalProxy.echConfigList.length > 0)
|
||||||
params.set('ech', externalProxy.echConfigList);
|
params.set('ech', externalProxy.echConfigList);
|
||||||
|
if (externalProxy.cipherSuites && externalProxy.cipherSuites.length > 0)
|
||||||
|
params.set('cs', externalProxy.cipherSuites);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GenVlessLinkInput {
|
export interface GenVlessLinkInput {
|
||||||
@@ -497,6 +504,9 @@ export function genVlessLink(input: GenVlessLinkInput): string {
|
|||||||
if (tls.settings.pinnedPeerCertSha256.length > 0) {
|
if (tls.settings.pinnedPeerCertSha256.length > 0) {
|
||||||
params.set('pcs', tls.settings.pinnedPeerCertSha256.join(','));
|
params.set('pcs', tls.settings.pinnedPeerCertSha256.join(','));
|
||||||
}
|
}
|
||||||
|
if (tls.cipherSuites && tls.cipherSuites.length > 0) {
|
||||||
|
params.set('cs', tls.cipherSuites);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
applyExternalProxyTLSParams(externalProxy, params, security);
|
applyExternalProxyTLSParams(externalProxy, params, security);
|
||||||
} else if (security === 'reality') {
|
} else if (security === 'reality') {
|
||||||
@@ -602,6 +612,9 @@ function writeTlsParams(
|
|||||||
if (tls.settings.pinnedPeerCertSha256.length > 0) {
|
if (tls.settings.pinnedPeerCertSha256.length > 0) {
|
||||||
params.set('pcs', tls.settings.pinnedPeerCertSha256.join(','));
|
params.set('pcs', tls.settings.pinnedPeerCertSha256.join(','));
|
||||||
}
|
}
|
||||||
|
if (tls.cipherSuites && tls.cipherSuites.length > 0) {
|
||||||
|
params.set('cs', tls.cipherSuites);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reality query-string writer shared by VLESS and Trojan. Preserves the
|
// Reality query-string writer shared by VLESS and Trojan. Preserves the
|
||||||
|
|||||||
@@ -489,6 +489,8 @@ function applySecurityParams(stream: Raw, params: URLSearchParams): void {
|
|||||||
tls.echConfigList = params.get('ech') ?? '';
|
tls.echConfigList = params.get('ech') ?? '';
|
||||||
tls.verifyPeerCertByName = params.get('vcn') ?? '';
|
tls.verifyPeerCertByName = params.get('vcn') ?? '';
|
||||||
tls.pinnedPeerCertSha256 = params.get('pcs') ?? '';
|
tls.pinnedPeerCertSha256 = params.get('pcs') ?? '';
|
||||||
|
const cs = params.get('cs');
|
||||||
|
if (cs) tls.cipherSuites = cs;
|
||||||
} else if (stream.security === 'reality') {
|
} else if (stream.security === 'reality') {
|
||||||
const reality = stream.realitySettings as Raw;
|
const reality = stream.realitySettings as Raw;
|
||||||
reality.serverName = params.get('sni') ?? '';
|
reality.serverName = params.get('sni') ?? '';
|
||||||
@@ -567,6 +569,7 @@ export function parseVmessLink(link: string): Raw | null {
|
|||||||
if (typeof json.ech === 'string') tls.echConfigList = json.ech;
|
if (typeof json.ech === 'string') tls.echConfigList = json.ech;
|
||||||
if (typeof json.vcn === 'string') tls.verifyPeerCertByName = json.vcn;
|
if (typeof json.vcn === 'string') tls.verifyPeerCertByName = json.vcn;
|
||||||
if (typeof json.pcs === 'string') tls.pinnedPeerCertSha256 = json.pcs;
|
if (typeof json.pcs === 'string') tls.pinnedPeerCertSha256 = json.pcs;
|
||||||
|
if (typeof json.cs === 'string') tls.cipherSuites = json.cs;
|
||||||
}
|
}
|
||||||
|
|
||||||
const port = Number(json.port) || 443;
|
const port = Number(json.port) || 443;
|
||||||
|
|||||||
@@ -106,10 +106,12 @@ export default function HostFormModal({
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const security = (useWatch({ control: methods.control, name: 'security' }) ?? 'same') as string;
|
const security = (useWatch({ control: methods.control, name: 'security' }) ?? 'same') as string;
|
||||||
|
const fingerprint = (useWatch({ control: methods.control, name: 'fingerprint' }) ?? '') as string;
|
||||||
const hostEchConfig = useWatch({ control: methods.control, name: 'echConfigList' }) ?? '';
|
const hostEchConfig = useWatch({ control: methods.control, name: 'echConfigList' }) ?? '';
|
||||||
const hostSni = useWatch({ control: methods.control, name: 'sni' }) ?? '';
|
const hostSni = useWatch({ control: methods.control, name: 'sni' }) ?? '';
|
||||||
const showTls = security === 'tls' || security === 'reality' || security === 'same';
|
const showTls = security === 'tls' || security === 'reality' || security === 'same';
|
||||||
const showTlsExtras = security === 'tls' || security === 'same';
|
const showTlsExtras = security === 'tls' || security === 'same';
|
||||||
|
const isCipherSuitesEnabled = showTlsExtras && (!fingerprint || fingerprint === 'unsafe');
|
||||||
const watchedHosts = useWatch({ control: methods.control, name: 'hosts' });
|
const watchedHosts = useWatch({ control: methods.control, name: 'hosts' });
|
||||||
const hasCidr = Boolean(watchedHosts?.some((h) => typeof h === 'string' && h.includes('/')));
|
const hasCidr = Boolean(watchedHosts?.some((h) => typeof h === 'string' && h.includes('/')));
|
||||||
|
|
||||||
@@ -364,7 +366,10 @@ export default function HostFormModal({
|
|||||||
name="cipherSuites"
|
name="cipherSuites"
|
||||||
label={t('pages.inbounds.form.cipherSuites')}
|
label={t('pages.inbounds.form.cipherSuites')}
|
||||||
>
|
>
|
||||||
<CipherSuitesSelect />
|
<CipherSuitesSelect
|
||||||
|
disabled={!isCipherSuitesEnabled}
|
||||||
|
placeholder={t('pages.inbounds.form.autoOption')}
|
||||||
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField name="pinnedPeerCertSha256" label={t('pages.hosts.fields.pins')}>
|
<FormField name="pinnedPeerCertSha256" label={t('pages.hosts.fields.pins')}>
|
||||||
<Select mode="tags" allowClear tokenSeparators={[',']} />
|
<Select mode="tags" allowClear tokenSeparators={[',']} />
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export const ExternalProxyEntrySchema = z.object({
|
|||||||
pinnedPeerCertSha256: z.array(z.string()).optional(),
|
pinnedPeerCertSha256: z.array(z.string()).optional(),
|
||||||
verifyPeerCertByName: z.string().optional(),
|
verifyPeerCertByName: z.string().optional(),
|
||||||
echConfigList: z.string().optional(),
|
echConfigList: z.string().optional(),
|
||||||
|
cipherSuites: z.string().optional(),
|
||||||
vlessRoute: z.string().optional(),
|
vlessRoute: z.string().optional(),
|
||||||
allowInsecure: z.boolean().optional(),
|
allowInsecure: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ describe('hostToExternalProxyEntry', () => {
|
|||||||
pinnedPeerCertSha256: ['AAAA'],
|
pinnedPeerCertSha256: ['AAAA'],
|
||||||
verifyPeerCertByName: 'verify.example.com',
|
verifyPeerCertByName: 'verify.example.com',
|
||||||
echConfigList: 'ECH',
|
echConfigList: 'ECH',
|
||||||
|
cipherSuites: 'TLS_AES_128_GCM_SHA256',
|
||||||
overrideSniFromAddress: false,
|
overrideSniFromAddress: false,
|
||||||
keepSniBlank: false,
|
keepSniBlank: false,
|
||||||
vlessRoute: '',
|
vlessRoute: '',
|
||||||
@@ -34,6 +35,7 @@ describe('hostToExternalProxyEntry', () => {
|
|||||||
expect(ep.pinnedPeerCertSha256).toEqual(['AAAA']);
|
expect(ep.pinnedPeerCertSha256).toEqual(['AAAA']);
|
||||||
expect(ep.verifyPeerCertByName).toBe('verify.example.com');
|
expect(ep.verifyPeerCertByName).toBe('verify.example.com');
|
||||||
expect(ep.echConfigList).toBe('ECH');
|
expect(ep.echConfigList).toBe('ECH');
|
||||||
|
expect(ep.cipherSuites).toBe('TLS_AES_128_GCM_SHA256');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('maps reality/same security to forceTls "same"', () => {
|
it('maps reality/same security to forceTls "same"', () => {
|
||||||
|
|||||||
@@ -1496,4 +1496,31 @@ describe('isPostQuantumLink', () => {
|
|||||||
});
|
});
|
||||||
expect(isPostQuantumLink(link)).toBe(true);
|
expect(isPostQuantumLink(link)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('exports cipherSuites as cs query parameter on VLESS TLS links', () => {
|
||||||
|
const [, rawFixture] = fixturesForProtocol('vless')[0];
|
||||||
|
const stream = rawFixture.streamSettings as Record<string, unknown> | undefined;
|
||||||
|
const tls = stream?.tlsSettings as Record<string, unknown> | undefined;
|
||||||
|
const typed = InboundSchema.parse({
|
||||||
|
...rawFixture,
|
||||||
|
streamSettings: {
|
||||||
|
...stream,
|
||||||
|
security: 'tls',
|
||||||
|
tlsSettings: {
|
||||||
|
...tls,
|
||||||
|
cipherSuites: 'TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const link = genVlessLink({
|
||||||
|
inbound: typed,
|
||||||
|
address: 'example.test',
|
||||||
|
port: 443,
|
||||||
|
clientId: '11111111-2222-3333-4444-555555555555',
|
||||||
|
});
|
||||||
|
expect(new URL(link).searchParams.get('cs')).toBe(
|
||||||
|
'TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ describe('parseVmessLink', () => {
|
|||||||
ech: 'AEX+DQBB',
|
ech: 'AEX+DQBB',
|
||||||
vcn: 'vcn.example.com',
|
vcn: 'vcn.example.com',
|
||||||
pcs: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
|
pcs: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
|
||||||
|
cs: 'TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256',
|
||||||
};
|
};
|
||||||
const link = `vmess://${Base64.encode(JSON.stringify(json))}`;
|
const link = `vmess://${Base64.encode(JSON.stringify(json))}`;
|
||||||
const out = parseVmessLink(link);
|
const out = parseVmessLink(link);
|
||||||
@@ -82,6 +83,7 @@ describe('parseVmessLink', () => {
|
|||||||
const stream = out?.streamSettings as Record<string, unknown>;
|
const stream = out?.streamSettings as Record<string, unknown>;
|
||||||
const tls = stream.tlsSettings as Record<string, unknown>;
|
const tls = stream.tlsSettings as Record<string, unknown>;
|
||||||
expect(tls.echConfigList).toBe('AEX+DQBB');
|
expect(tls.echConfigList).toBe('AEX+DQBB');
|
||||||
|
expect(tls.cipherSuites).toBe('TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256');
|
||||||
expect(tls.verifyPeerCertByName).toBe('vcn.example.com');
|
expect(tls.verifyPeerCertByName).toBe('vcn.example.com');
|
||||||
expect(tls.pinnedPeerCertSha256).toBe('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=');
|
expect(tls.pinnedPeerCertSha256).toBe('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=');
|
||||||
});
|
});
|
||||||
@@ -428,6 +430,15 @@ describe('parseTrojanLink', () => {
|
|||||||
expect(stream.network).toBe('ws');
|
expect(stream.network).toBe('ws');
|
||||||
expect((stream.wsSettings as Record<string, unknown>).path).toBe('/tj');
|
expect((stream.wsSettings as Record<string, unknown>).path).toBe('/tj');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('reads the cs cipher suites into tlsSettings, as the Go link parser does', () => {
|
||||||
|
const out = parseTrojanLink(
|
||||||
|
'trojan://pw@srv.example:443?type=tcp&security=tls&sni=srv.example&fp=unsafe&cs=TLS_AES_256_GCM_SHA384#cs',
|
||||||
|
);
|
||||||
|
const stream = out!.streamSettings as Record<string, unknown>;
|
||||||
|
const tls = stream.tlsSettings as Record<string, unknown>;
|
||||||
|
expect(tls.cipherSuites).toBe('TLS_AES_256_GCM_SHA384');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('parseShadowsocksLink', () => {
|
describe('parseShadowsocksLink', () => {
|
||||||
|
|||||||
+25
-2
@@ -1867,6 +1867,11 @@ func applyShareTLSParams(stream map[string]any, params map[string]string) {
|
|||||||
if pins, ok := pinnedSha256List(tlsSettings); ok {
|
if pins, ok := pinnedSha256List(tlsSettings); ok {
|
||||||
params["pcs"] = strings.Join(pins, ",")
|
params["pcs"] = strings.Join(pins, ",")
|
||||||
}
|
}
|
||||||
|
if csValue, ok := searchKey(tlsSetting, "cipherSuites"); ok {
|
||||||
|
if cs, _ := csValue.(string); cs != "" {
|
||||||
|
params["cs"] = cs
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1902,6 +1907,11 @@ func applyVmessTLSParams(stream map[string]any, obj map[string]any) {
|
|||||||
if pins, ok := pinnedSha256List(tlsSettings); ok {
|
if pins, ok := pinnedSha256List(tlsSettings); ok {
|
||||||
obj["pcs"] = strings.Join(pins, ",")
|
obj["pcs"] = strings.Join(pins, ",")
|
||||||
}
|
}
|
||||||
|
if csValue, ok := searchKey(tlsSetting, "cipherSuites"); ok {
|
||||||
|
if cs, _ := csValue.(string); cs != "" {
|
||||||
|
obj["cs"] = cs
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2048,7 +2058,7 @@ func buildVmessLink(obj map[string]any) string {
|
|||||||
func cloneVmessShareObj(baseObj map[string]any, newSecurity string) map[string]any {
|
func cloneVmessShareObj(baseObj map[string]any, newSecurity string) map[string]any {
|
||||||
newObj := map[string]any{}
|
newObj := map[string]any{}
|
||||||
for key, value := range baseObj {
|
for key, value := range baseObj {
|
||||||
if newSecurity != "none" || (key != "alpn" && key != "sni" && key != "fp" && key != "pcs") {
|
if newSecurity != "none" || (key != "alpn" && key != "sni" && key != "fp" && key != "pcs" && key != "cs") {
|
||||||
newObj[key] = value
|
newObj[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2077,6 +2087,9 @@ func applyExternalProxyTLSObj(ep map[string]any, obj map[string]any, security st
|
|||||||
if ech, ok := ep["echConfigList"].(string); ok && ech != "" {
|
if ech, ok := ep["echConfigList"].(string); ok && ech != "" {
|
||||||
obj["ech"] = ech
|
obj["ech"] = ech
|
||||||
}
|
}
|
||||||
|
if cs, ok := externalProxyCipherSuites(ep); ok {
|
||||||
|
obj["cs"] = cs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyExternalProxyTLSParams(ep map[string]any, params map[string]string, security string) {
|
func applyExternalProxyTLSParams(ep map[string]any, params map[string]string, security string) {
|
||||||
@@ -2101,6 +2114,9 @@ func applyExternalProxyTLSParams(ep map[string]any, params map[string]string, se
|
|||||||
if ech, ok := ep["echConfigList"].(string); ok && ech != "" {
|
if ech, ok := ep["echConfigList"].(string); ok && ech != "" {
|
||||||
params["ech"] = ech
|
params["ech"] = ech
|
||||||
}
|
}
|
||||||
|
if cs, ok := externalProxyCipherSuites(ep); ok {
|
||||||
|
params["cs"] = cs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyExternalProxyHysteriaParams overrides the cert pin for a single
|
// applyExternalProxyHysteriaParams overrides the cert pin for a single
|
||||||
@@ -2211,6 +2227,13 @@ func applyExternalProxyTLSToStream(ep map[string]any, stream map[string]any, sec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func externalProxyCipherSuites(ep map[string]any) (string, bool) {
|
||||||
|
if cs, ok := ep["cipherSuites"].(string); ok && cs != "" {
|
||||||
|
return cs, true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
func externalProxySNI(ep map[string]any) (string, bool) {
|
func externalProxySNI(ep map[string]any) (string, bool) {
|
||||||
if sni, ok := ep["sni"].(string); ok && sni != "" {
|
if sni, ok := ep["sni"].(string); ok && sni != "" {
|
||||||
return sni, true
|
return sni, true
|
||||||
@@ -2357,7 +2380,7 @@ func appendQueryAndFragment(link string, params map[string]string, fragment, sec
|
|||||||
if securityOverride != "" && k == "security" {
|
if securityOverride != "" && k == "security" {
|
||||||
v = securityOverride
|
v = securityOverride
|
||||||
}
|
}
|
||||||
if omitTLSFields && (k == "alpn" || k == "sni" || k == "fp" || k == "pcs") {
|
if omitTLSFields && (k == "alpn" || k == "sni" || k == "fp" || k == "pcs" || k == "cs") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
q.Set(k, v)
|
q.Set(k, v)
|
||||||
|
|||||||
@@ -830,6 +830,35 @@ func TestApplyExternalProxyTLSParams_SetsPinnedPeerCert(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplyExternalProxyTLSParams_SetsCipherSuites(t *testing.T) {
|
||||||
|
params := map[string]string{"security": "tls"}
|
||||||
|
ep := map[string]any{
|
||||||
|
"dest": "proxy.example.com",
|
||||||
|
"cipherSuites": "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256",
|
||||||
|
}
|
||||||
|
|
||||||
|
applyExternalProxyTLSParams(ep, params, "tls")
|
||||||
|
|
||||||
|
if params["cs"] != "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" {
|
||||||
|
t.Fatalf("cs = %q, want TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256", params["cs"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyShareTLSParams_SetsCipherSuites(t *testing.T) {
|
||||||
|
stream := map[string]any{
|
||||||
|
"security": "tls",
|
||||||
|
"tlsSettings": map[string]any{
|
||||||
|
"cipherSuites": "TLS_AES_128_GCM_SHA256",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
params := map[string]string{}
|
||||||
|
applyShareTLSParams(stream, params)
|
||||||
|
|
||||||
|
if params["cs"] != "TLS_AES_128_GCM_SHA256" {
|
||||||
|
t.Fatalf("cs = %q, want TLS_AES_128_GCM_SHA256", params["cs"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestApplyExternalProxyTLSObj_SetsPinnedPeerCert(t *testing.T) {
|
func TestApplyExternalProxyTLSObj_SetsPinnedPeerCert(t *testing.T) {
|
||||||
obj := map[string]any{"tls": "tls"}
|
obj := map[string]any{"tls": "tls"}
|
||||||
ep := map[string]any{
|
ep := map[string]any{
|
||||||
|
|||||||
@@ -235,6 +235,7 @@ func parseVmess(link string) (*ParseResult, error) {
|
|||||||
tls["echConfigList"] = getString(j, "ech", "")
|
tls["echConfigList"] = getString(j, "ech", "")
|
||||||
tls["verifyPeerCertByName"] = getString(j, "vcn", "")
|
tls["verifyPeerCertByName"] = getString(j, "vcn", "")
|
||||||
tls["pinnedPeerCertSha256"] = getString(j, "pcs", "")
|
tls["pinnedPeerCertSha256"] = getString(j, "pcs", "")
|
||||||
|
tls["cipherSuites"] = getString(j, "cs", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
port := num(j["port"])
|
port := num(j["port"])
|
||||||
@@ -687,6 +688,7 @@ func buildStream(network, security string) map[string]any {
|
|||||||
stream["tlsSettings"] = map[string]any{
|
stream["tlsSettings"] = map[string]any{
|
||||||
"serverName": "", "alpn": []any{}, "fingerprint": "",
|
"serverName": "", "alpn": []any{}, "fingerprint": "",
|
||||||
"echConfigList": "", "verifyPeerCertByName": "", "pinnedPeerCertSha256": "",
|
"echConfigList": "", "verifyPeerCertByName": "", "pinnedPeerCertSha256": "",
|
||||||
|
"cipherSuites": "",
|
||||||
}
|
}
|
||||||
case "reality":
|
case "reality":
|
||||||
stream["realitySettings"] = map[string]any{
|
stream["realitySettings"] = map[string]any{
|
||||||
@@ -783,6 +785,9 @@ func applySecurity(stream map[string]any, p url.Values) {
|
|||||||
tls["echConfigList"] = p.Get("ech")
|
tls["echConfigList"] = p.Get("ech")
|
||||||
tls["verifyPeerCertByName"] = p.Get("vcn")
|
tls["verifyPeerCertByName"] = p.Get("vcn")
|
||||||
tls["pinnedPeerCertSha256"] = p.Get("pcs")
|
tls["pinnedPeerCertSha256"] = p.Get("pcs")
|
||||||
|
if cs := p.Get("cs"); cs != "" {
|
||||||
|
tls["cipherSuites"] = cs
|
||||||
|
}
|
||||||
case "reality":
|
case "reality":
|
||||||
re := stream["realitySettings"].(map[string]any)
|
re := stream["realitySettings"].(map[string]any)
|
||||||
re["serverName"] = p.Get("sni")
|
re["serverName"] = p.Get("sni")
|
||||||
|
|||||||
@@ -146,12 +146,12 @@ func TestParse_RealitySettingsAreXrayFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParse_TLSSecurityMapped(t *testing.T) {
|
func TestParse_TLSSecurityMapped(t *testing.T) {
|
||||||
res, err := ParseLink("trojan://pw@h.com:443?type=tcp&security=tls&sni=SNI&fp=chrome&alpn=h2,http/1.1&ech=ECH&vcn=VCN&pcs=PCS")
|
res, err := ParseLink("trojan://pw@h.com:443?type=tcp&security=tls&sni=SNI&fp=chrome&alpn=h2,http/1.1&ech=ECH&vcn=VCN&pcs=PCS&cs=TLS_AES_128_GCM_SHA256")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("parse: %v", err)
|
t.Fatalf("parse: %v", err)
|
||||||
}
|
}
|
||||||
tls := streamSub(t, res, "tlsSettings")
|
tls := streamSub(t, res, "tlsSettings")
|
||||||
if tls["serverName"] != "SNI" || tls["fingerprint"] != "chrome" || tls["echConfigList"] != "ECH" || tls["verifyPeerCertByName"] != "VCN" || tls["pinnedPeerCertSha256"] != "PCS" {
|
if tls["serverName"] != "SNI" || tls["fingerprint"] != "chrome" || tls["echConfigList"] != "ECH" || tls["verifyPeerCertByName"] != "VCN" || tls["pinnedPeerCertSha256"] != "PCS" || tls["cipherSuites"] != "TLS_AES_128_GCM_SHA256" {
|
||||||
t.Errorf("tlsSettings fields = %#v", tls)
|
t.Errorf("tlsSettings fields = %#v", tls)
|
||||||
}
|
}
|
||||||
if alpn, _ := tls["alpn"].([]string); !reflect.DeepEqual(alpn, []string{"h2", "http/1.1"}) {
|
if alpn, _ := tls["alpn"].([]string); !reflect.DeepEqual(alpn, []string{"h2", "http/1.1"}) {
|
||||||
@@ -259,6 +259,19 @@ func TestParse_VmessWSPathWithoutHostKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParse_VmessCipherSuites(t *testing.T) {
|
||||||
|
inner := `{"v":"2","add":"h","port":443,"id":"11111111-2222-4333-8444-555555555555","net":"ws","tls":"tls","cs":"TLS_AES_128_GCM_SHA256"}`
|
||||||
|
link := "vmess://" + base64.StdEncoding.EncodeToString([]byte(inner))
|
||||||
|
res, err := ParseLink(link)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse: %v", err)
|
||||||
|
}
|
||||||
|
tls := streamSub(t, res, "tlsSettings")
|
||||||
|
if tls["cipherSuites"] != "TLS_AES_128_GCM_SHA256" {
|
||||||
|
t.Errorf("tlsSettings cipherSuites = %v, want TLS_AES_128_GCM_SHA256", tls["cipherSuites"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParse_Hysteria2VerifyPeerCertByName(t *testing.T) {
|
func TestParse_Hysteria2VerifyPeerCertByName(t *testing.T) {
|
||||||
res, err := ParseLink("hysteria2://auth@h.com:443?security=tls&sni=decoy.com&vcn=real-cert.com#r")
|
res, err := ParseLink("hysteria2://auth@h.com:443?security=tls&sni=decoy.com&vcn=real-cert.com#r")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user