mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-06 14:12:09 +03:00
fix(qr): hide the QR only for links carrying post-quantum keys
A share link's QR is suppressed only when it carries a post-quantum key
payload too large to scan: an ML-DSA-65 verify key (pqv) or an ML-KEM-768
VLESS-encryption auth key. isPostQuantumLink substring-matched "mlkem768"
anywhere in the URL, so it misfired on:
- every VLESS/Trojan REALITY link, since ce221c33 added the
support-x25519mlkem768=true hint (235 chars, QR version 10);
- every VLESS-encryption link authenticated by an X25519 key, whose
value always starts with mlkem768x25519plus (321 chars, version 11);
- any remark or host containing mlkem768 / mldsa65 / ML-KEM-768.
All four QR surfaces (inbound QR, client QR, client info, public sub
page) lost their QR button for those links. The detector now reads the
query: a non-empty pqv, or an encryption whose auth key
vlessEncryptionAuthKind classifies as ML-KEM-768.
Closes #6730
This commit is contained in:
@@ -17,6 +17,7 @@ import { parseGeckoPacketSize } from '@/lib/xray/forms/transport/FinalMaskForm';
|
||||
import { getHeaderValue } from './headers';
|
||||
import { canEnableTlsFlow } from './protocol-capabilities';
|
||||
import { deriveSpiderX } from './spider-x';
|
||||
import { vlessEncryptionAuthKind } from './vless-encryption';
|
||||
import { normalizeTuicCongestionController, resolveTuicServerSettings } from '@/lib/tuic';
|
||||
|
||||
// Share-link generators. Each per-protocol fn takes a typed inbound plus
|
||||
@@ -1723,9 +1724,13 @@ function wgPeerCommentSuffix(peer: unknown): string {
|
||||
return typeof comment === 'string' && comment.trim() !== '' ? ` (${comment.trim()})` : '';
|
||||
}
|
||||
|
||||
// Only the post-quantum key payloads outgrow a QR; the REALITY ML-KEM hint and the
|
||||
// mlkem768x25519plus prefix of an X25519-authenticated encryption do not (#6730).
|
||||
export function isPostQuantumLink(link: string): boolean {
|
||||
if (/[?&]pqv=/.test(link)) return true;
|
||||
if (link.includes('mlkem768') || link.includes('mldsa65')) return true;
|
||||
if (link.includes('ML-KEM-768')) return true;
|
||||
return false;
|
||||
const withoutRemark = link.split('#', 1)[0];
|
||||
const queryStart = withoutRemark.indexOf('?');
|
||||
if (queryStart < 0) return false;
|
||||
const params = new URLSearchParams(withoutRemark.slice(queryStart + 1));
|
||||
if (params.get('pqv')) return true;
|
||||
return vlessEncryptionAuthKind(params.get('encryption') ?? '')?.startsWith('mlkem768') ?? false;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
genVmessLink,
|
||||
genWireguardConfig,
|
||||
genWireguardLink,
|
||||
isPostQuantumLink,
|
||||
preferPublicHost,
|
||||
resolveAddr,
|
||||
} from '@/lib/xray/inbound-link';
|
||||
@@ -1415,3 +1416,46 @@ describe('genTuicLink', () => {
|
||||
expect(link).not.toContain('#TUIC-Node-US-US');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isPostQuantumLink', () => {
|
||||
type RealityFixture = {
|
||||
settings: { clients: Array<{ id: string }>; encryption?: string };
|
||||
streamSettings: { realitySettings: { settings: { mldsa65Verify?: string } } };
|
||||
};
|
||||
const [, raw] = fixturesForProtocol('vless').find(([name]) => name === 'vless-tcp-reality')!;
|
||||
const clientId = (raw as RealityFixture).settings.clients[0].id;
|
||||
const x25519Key = 'G3cdPSd1-NnlpTbWNSM5vHsT5VNzWfFzYSKwbUMnV1Y';
|
||||
const mlkem768Key = 'A'.repeat(1579);
|
||||
|
||||
function realityLink(edit: (inbound: RealityFixture) => void = () => {}): string {
|
||||
const copy = structuredClone(raw) as RealityFixture;
|
||||
edit(copy);
|
||||
return genVlessLink({ inbound: InboundSchema.parse(copy), address: 'example.test', clientId });
|
||||
}
|
||||
|
||||
// #6730: the REALITY ML-KEM support hint is a short flag, not a large PQ payload.
|
||||
it('keeps the QR for a plain REALITY link', () => {
|
||||
expect(isPostQuantumLink(realityLink())).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps the QR for VLESS encryption authenticated by an X25519 key', () => {
|
||||
const link = realityLink((ib) => {
|
||||
ib.settings.encryption = `mlkem768x25519plus.native.0rtt.${x25519Key}`;
|
||||
});
|
||||
expect(isPostQuantumLink(link)).toBe(false);
|
||||
});
|
||||
|
||||
it('hides the QR for VLESS encryption authenticated by an ML-KEM-768 key', () => {
|
||||
const link = realityLink((ib) => {
|
||||
ib.settings.encryption = `mlkem768x25519plus.native.0rtt.${mlkem768Key}`;
|
||||
});
|
||||
expect(isPostQuantumLink(link)).toBe(true);
|
||||
});
|
||||
|
||||
it('hides the QR for a REALITY link carrying an ML-DSA-65 verify key', () => {
|
||||
const link = realityLink((ib) => {
|
||||
ib.streamSettings.realitySettings.settings.mldsa65Verify = 'B'.repeat(2603);
|
||||
});
|
||||
expect(isPostQuantumLink(link)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user