diff --git a/frontend/src/lib/xray/inbound-link.ts b/frontend/src/lib/xray/inbound-link.ts index 7615b56a..7a0fae8a 100644 --- a/frontend/src/lib/xray/inbound-link.ts +++ b/frontend/src/lib/xray/inbound-link.ts @@ -225,6 +225,9 @@ export function genVmessLink(input: GenVmessLinkInput): string { if (tlsSettings.serverName.length > 0) obj.sni = tlsSettings.serverName; if (tlsSettings.settings.fingerprint.length > 0) obj.fp = tlsSettings.settings.fingerprint; if (tlsSettings.alpn.length > 0) obj.alpn = tlsSettings.alpn.join(','); + if (tlsSettings.settings.pinnedPeerCertSha256.length > 0) { + obj.pcs = tlsSettings.settings.pinnedPeerCertSha256.join(','); + } } applyExternalProxyTLSObj(externalProxy, obj, tls); @@ -349,6 +352,9 @@ export function genVlessLink(input: GenVlessLinkInput): string { params.set('alpn', tls.alpn.join(',')); if (tls.serverName.length > 0) params.set('sni', tls.serverName); if (tls.settings.echConfigList.length > 0) params.set('ech', tls.settings.echConfigList); + if (tls.settings.pinnedPeerCertSha256.length > 0) { + params.set('pcs', tls.settings.pinnedPeerCertSha256.join(',')); + } if (stream.network === 'tcp' && flow.length > 0) params.set('flow', flow); } applyExternalProxyTLSParams(externalProxy, params, security); @@ -428,6 +434,9 @@ function writeTlsParams(stream: NonNullable, params: params.set('alpn', tls.alpn.join(',')); if (tls.settings.echConfigList.length > 0) params.set('ech', tls.settings.echConfigList); if (tls.serverName.length > 0) params.set('sni', tls.serverName); + if (tls.settings.pinnedPeerCertSha256.length > 0) { + params.set('pcs', tls.settings.pinnedPeerCertSha256.join(',')); + } } // Reality query-string writer shared by VLESS and Trojan. Preserves the diff --git a/frontend/src/pages/inbounds/InboundFormModal.tsx b/frontend/src/pages/inbounds/InboundFormModal.tsx index 9713fbbd..c64a3b3a 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.tsx +++ b/frontend/src/pages/inbounds/InboundFormModal.tsx @@ -572,6 +572,21 @@ export default function InboundFormModal({ form.setFieldValue(['streamSettings', 'tlsSettings', 'settings', 'echConfigList'], ''); }; + const generateRandomPinHash = () => { + const bytes = new Uint8Array(32); + crypto.getRandomValues(bytes); + let binary = ''; + for (const b of bytes) binary += String.fromCharCode(b); + const hash = btoa(binary); + const current = (form.getFieldValue( + ['streamSettings', 'tlsSettings', 'settings', 'pinnedPeerCertSha256'], + ) as string[] | undefined) ?? []; + form.setFieldValue( + ['streamSettings', 'tlsSettings', 'settings', 'pinnedPeerCertSha256'], + [...current, hash], + ); + }; + const setCertFromPanel = async (certName: number) => { setSaving(true); try { @@ -2826,6 +2841,29 @@ export default function InboundFormModal({ > + + + +