mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-29 16:39:35 +00:00
feat(tls): surface pinnedPeerCertSha256 in panel, share links, and subs
Adds a panel-only `pinnedPeerCertSha256` field on TLS settings with a tags input and a random-hash generator. The hashes ride share links as `pcs` (v2rayN-compatible), Clash sub as `pin-sha256`, and JSON sub as `pinnedPeerCertSha256`, while remaining stripped from the run-config sent to xray-core.
This commit is contained in:
@@ -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<Inbound['streamSettings']>, 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
|
||||
|
||||
@@ -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({
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.inbounds.form.pinnedPeerCertSha256')}
|
||||
tooltip={t('pages.inbounds.form.pinnedPeerCertSha256Tip')}
|
||||
>
|
||||
<Space.Compact block>
|
||||
<Form.Item
|
||||
name={['streamSettings', 'tlsSettings', 'settings', 'pinnedPeerCertSha256']}
|
||||
noStyle
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
tokenSeparators={[',', ' ']}
|
||||
placeholder={t('pages.inbounds.form.pinnedPeerCertSha256Placeholder')}
|
||||
style={{ width: 'calc(100% - 32px)' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={generateRandomPinHash}
|
||||
title={t('pages.inbounds.form.generateRandomPin')}
|
||||
/>
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
<Form.Item label=" ">
|
||||
<Space>
|
||||
<Button type="primary" loading={saving} onClick={getNewEchCert}>
|
||||
|
||||
@@ -51,6 +51,7 @@ export type TlsCert = z.infer<typeof TlsCertSchema>;
|
||||
export const TlsClientSettingsSchema = z.object({
|
||||
fingerprint: UtlsFingerprintSchema.default('chrome'),
|
||||
echConfigList: z.string().default(''),
|
||||
pinnedPeerCertSha256: z.array(z.string()).default([]),
|
||||
});
|
||||
export type TlsClientSettings = z.infer<typeof TlsClientSettingsSchema>;
|
||||
|
||||
@@ -67,6 +68,6 @@ export const TlsStreamSettingsSchema = z.object({
|
||||
certificates: z.array(TlsCertSchema).default([]),
|
||||
alpn: z.array(AlpnSchema).default(['h2', 'http/1.1']),
|
||||
echServerKeys: z.string().default(''),
|
||||
settings: TlsClientSettingsSchema.default({ fingerprint: 'chrome', echConfigList: '' }),
|
||||
settings: TlsClientSettingsSchema.default({ fingerprint: 'chrome', echConfigList: '', pinnedPeerCertSha256: [] }),
|
||||
});
|
||||
export type TlsStreamSettings = z.infer<typeof TlsStreamSettingsSchema>;
|
||||
|
||||
@@ -70,6 +70,7 @@ exports[`InboundSchema (full) fixtures > parses hysteria-v1-tls byte-stably 1`]
|
||||
"settings": {
|
||||
"echConfigList": "",
|
||||
"fingerprint": "chrome",
|
||||
"pinnedPeerCertSha256": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -207,6 +208,7 @@ exports[`InboundSchema (full) fixtures > parses trojan-ws-tls byte-stably 1`] =
|
||||
"settings": {
|
||||
"echConfigList": "",
|
||||
"fingerprint": "chrome",
|
||||
"pinnedPeerCertSha256": [],
|
||||
},
|
||||
},
|
||||
"wsSettings": {
|
||||
@@ -378,6 +380,7 @@ exports[`InboundSchema (full) fixtures > parses vless-ws-tls byte-stably 1`] = `
|
||||
"settings": {
|
||||
"echConfigList": "",
|
||||
"fingerprint": "chrome",
|
||||
"pinnedPeerCertSha256": [],
|
||||
},
|
||||
},
|
||||
"wsSettings": {
|
||||
@@ -394,6 +397,97 @@ exports[`InboundSchema (full) fixtures > parses vless-ws-tls byte-stably 1`] = `
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`InboundSchema (full) fixtures > parses vless-ws-tls-pinned byte-stably 1`] = `
|
||||
{
|
||||
"down": 0,
|
||||
"enable": true,
|
||||
"expiryTime": 0,
|
||||
"id": 43,
|
||||
"listen": "",
|
||||
"port": 443,
|
||||
"protocol": "vless",
|
||||
"remark": "alice-vless-ws-tls-pinned",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"comment": "",
|
||||
"email": "alice@example.test",
|
||||
"enable": true,
|
||||
"expiryTime": 0,
|
||||
"flow": "",
|
||||
"id": "8c14d6f7-2e3b-4a91-9d24-3f7a6b8c1e02",
|
||||
"limitIp": 0,
|
||||
"reset": 0,
|
||||
"subId": "abc123def",
|
||||
"tgId": 0,
|
||||
"totalGB": 0,
|
||||
},
|
||||
],
|
||||
"decryption": "none",
|
||||
"encryption": "none",
|
||||
"fallbacks": [],
|
||||
},
|
||||
"sniffing": {
|
||||
"destOverride": [
|
||||
"http",
|
||||
"tls",
|
||||
"quic",
|
||||
"fakedns",
|
||||
],
|
||||
"domainsExcluded": [],
|
||||
"enabled": true,
|
||||
"ipsExcluded": [],
|
||||
"metadataOnly": false,
|
||||
"routeOnly": false,
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "ws",
|
||||
"security": "tls",
|
||||
"tlsSettings": {
|
||||
"alpn": [
|
||||
"h2",
|
||||
"http/1.1",
|
||||
],
|
||||
"certificates": [
|
||||
{
|
||||
"buildChain": false,
|
||||
"certificateFile": "/etc/ssl/certs/cdn.example.test.crt",
|
||||
"keyFile": "/etc/ssl/private/cdn.example.test.key",
|
||||
"oneTimeLoading": false,
|
||||
"usage": "encipherment",
|
||||
},
|
||||
],
|
||||
"cipherSuites": "",
|
||||
"disableSystemRoot": false,
|
||||
"echServerKeys": "",
|
||||
"enableSessionResumption": false,
|
||||
"maxVersion": "1.3",
|
||||
"minVersion": "1.2",
|
||||
"rejectUnknownSni": false,
|
||||
"serverName": "cdn.example.test",
|
||||
"settings": {
|
||||
"echConfigList": "",
|
||||
"fingerprint": "chrome",
|
||||
"pinnedPeerCertSha256": [
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=",
|
||||
],
|
||||
},
|
||||
},
|
||||
"wsSettings": {
|
||||
"acceptProxyProtocol": false,
|
||||
"headers": {},
|
||||
"heartbeatPeriod": 0,
|
||||
"host": "cdn.example.test",
|
||||
"path": "/ws",
|
||||
},
|
||||
},
|
||||
"tag": "inbound-vless-pinned-1",
|
||||
"total": 0,
|
||||
"up": 0,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`InboundSchema (full) fixtures > parses vmess-tcp-tls byte-stably 1`] = `
|
||||
{
|
||||
"down": 0,
|
||||
@@ -468,6 +562,7 @@ exports[`InboundSchema (full) fixtures > parses vmess-tcp-tls byte-stably 1`] =
|
||||
"settings": {
|
||||
"echConfigList": "",
|
||||
"fingerprint": "chrome",
|
||||
"pinnedPeerCertSha256": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -12,6 +12,8 @@ exports[`genInboundLinks orchestrator > vless-tcp-reality: byte-stable 1`] = `"v
|
||||
|
||||
exports[`genInboundLinks orchestrator > vless-ws-tls: byte-stable 1`] = `"vless://8c14d6f7-2e3b-4a91-9d24-3f7a6b8c1e02@override.test:443?type=ws&encryption=none&path=%2Fws&host=cdn.example.test&security=tls&fp=chrome&alpn=h2%2Chttp%2F1.1&sni=cdn.example.test#parity-test"`;
|
||||
|
||||
exports[`genInboundLinks orchestrator > vless-ws-tls-pinned: byte-stable 1`] = `"vless://8c14d6f7-2e3b-4a91-9d24-3f7a6b8c1e02@override.test:443?type=ws&encryption=none&path=%2Fws&host=cdn.example.test&security=tls&fp=chrome&alpn=h2%2Chttp%2F1.1&sni=cdn.example.test&pcs=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%3D%2CBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB%3D#parity-test"`;
|
||||
|
||||
exports[`genInboundLinks orchestrator > vmess-tcp-tls: byte-stable 1`] = `"vmess://ewogICJ2IjogIjIiLAogICJwcyI6ICJwYXJpdHktdGVzdCIsCiAgImFkZCI6ICJvdmVycmlkZS50ZXN0IiwKICAicG9ydCI6IDg0NDMsCiAgImlkIjogIjExMTExMTExLTIyMjItNDMzMy04NDQ0LTU1NTU1NTU1NTU1NSIsCiAgInNjeSI6ICJhdXRvIiwKICAibmV0IjogInRjcCIsCiAgInRscyI6ICJ0bHMiLAogICJ0eXBlIjogIm5vbmUiLAogICJzbmkiOiAidm1lc3MuZXhhbXBsZS50ZXN0IiwKICAiZnAiOiAiY2hyb21lIiwKICAiYWxwbiI6ICJoMixodHRwLzEuMSIKfQ=="`;
|
||||
|
||||
exports[`genInboundLinks orchestrator > wireguard-server: byte-stable 1`] = `
|
||||
@@ -38,6 +40,8 @@ exports[`genVlessLink > vless-tcp-reality: byte-stable 1`] = `"vless://22222222-
|
||||
|
||||
exports[`genVlessLink > vless-ws-tls: byte-stable 1`] = `"vless://8c14d6f7-2e3b-4a91-9d24-3f7a6b8c1e02@example.test:443?type=ws&encryption=none&path=%2Fws&host=cdn.example.test&security=tls&fp=chrome&alpn=h2%2Chttp%2F1.1&sni=cdn.example.test#parity-test"`;
|
||||
|
||||
exports[`genVlessLink > vless-ws-tls-pinned: byte-stable 1`] = `"vless://8c14d6f7-2e3b-4a91-9d24-3f7a6b8c1e02@example.test:443?type=ws&encryption=none&path=%2Fws&host=cdn.example.test&security=tls&fp=chrome&alpn=h2%2Chttp%2F1.1&sni=cdn.example.test&pcs=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%3D%2CBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB%3D#parity-test"`;
|
||||
|
||||
exports[`genVmessLink > vmess-tcp-tls: byte-stable 1`] = `"vmess://ewogICJ2IjogIjIiLAogICJwcyI6ICJwYXJpdHktdGVzdCIsCiAgImFkZCI6ICJleGFtcGxlLnRlc3QiLAogICJwb3J0IjogODQ0MywKICAiaWQiOiAiMTExMTExMTEtMjIyMi00MzMzLTg0NDQtNTU1NTU1NTU1NTU1IiwKICAic2N5IjogImF1dG8iLAogICJuZXQiOiAidGNwIiwKICAidGxzIjogInRscyIsCiAgInR5cGUiOiAibm9uZSIsCiAgInNuaSI6ICJ2bWVzcy5leGFtcGxlLnRlc3QiLAogICJmcCI6ICJjaHJvbWUiLAogICJhbHBuIjogImgyLGh0dHAvMS4xIgp9"`;
|
||||
|
||||
exports[`genWireguardLink + genWireguardConfig > wireguard-server: byte-stable 1`] = `
|
||||
|
||||
@@ -66,6 +66,7 @@ exports[`SecuritySettingsSchema fixtures > parses tls-cert-file byte-stably 1`]
|
||||
"settings": {
|
||||
"echConfigList": "",
|
||||
"fingerprint": "chrome",
|
||||
"pinnedPeerCertSha256": [],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"id": 43,
|
||||
"up": 0,
|
||||
"down": 0,
|
||||
"total": 0,
|
||||
"remark": "alice-vless-ws-tls-pinned",
|
||||
"enable": true,
|
||||
"expiryTime": 0,
|
||||
"listen": "",
|
||||
"port": 443,
|
||||
"tag": "inbound-vless-pinned-1",
|
||||
"sniffing": {
|
||||
"enabled": true,
|
||||
"destOverride": ["http", "tls", "quic", "fakedns"],
|
||||
"metadataOnly": false,
|
||||
"routeOnly": false,
|
||||
"ipsExcluded": [],
|
||||
"domainsExcluded": []
|
||||
},
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "8c14d6f7-2e3b-4a91-9d24-3f7a6b8c1e02",
|
||||
"email": "alice@example.test",
|
||||
"flow": "",
|
||||
"limitIp": 0,
|
||||
"totalGB": 0,
|
||||
"expiryTime": 0,
|
||||
"enable": true,
|
||||
"tgId": 0,
|
||||
"subId": "abc123def",
|
||||
"comment": "",
|
||||
"reset": 0
|
||||
}
|
||||
],
|
||||
"decryption": "none",
|
||||
"encryption": "none",
|
||||
"fallbacks": []
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "ws",
|
||||
"wsSettings": {
|
||||
"acceptProxyProtocol": false,
|
||||
"path": "/ws",
|
||||
"host": "cdn.example.test",
|
||||
"headers": {},
|
||||
"heartbeatPeriod": 0
|
||||
},
|
||||
"security": "tls",
|
||||
"tlsSettings": {
|
||||
"serverName": "cdn.example.test",
|
||||
"minVersion": "1.2",
|
||||
"maxVersion": "1.3",
|
||||
"cipherSuites": "",
|
||||
"rejectUnknownSni": false,
|
||||
"disableSystemRoot": false,
|
||||
"enableSessionResumption": false,
|
||||
"certificates": [
|
||||
{
|
||||
"certificateFile": "/etc/ssl/certs/cdn.example.test.crt",
|
||||
"keyFile": "/etc/ssl/private/cdn.example.test.key",
|
||||
"oneTimeLoading": false,
|
||||
"usage": "encipherment",
|
||||
"buildChain": false
|
||||
}
|
||||
],
|
||||
"alpn": ["h2", "http/1.1"],
|
||||
"echServerKeys": "",
|
||||
"settings": {
|
||||
"fingerprint": "chrome",
|
||||
"echConfigList": "",
|
||||
"pinnedPeerCertSha256": [
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -482,6 +482,9 @@ func (s *SubClashService) tlsData(tData map[string]any) map[string]any {
|
||||
if fingerprint, ok := tlsClientSettings["fingerprint"].(string); ok {
|
||||
tlsData["fingerprint"] = fingerprint
|
||||
}
|
||||
if pins, ok := tlsClientSettings["pinnedPeerCertSha256"].([]any); ok && len(pins) > 0 {
|
||||
tlsData["pin-sha256"] = pins
|
||||
}
|
||||
return tlsData
|
||||
}
|
||||
|
||||
|
||||
@@ -272,6 +272,9 @@ func (s *SubJsonService) tlsData(tData map[string]any) map[string]any {
|
||||
if fingerprint, ok := tlsClientSettings["fingerprint"].(string); ok {
|
||||
tlsData["fingerprint"] = fingerprint
|
||||
}
|
||||
if pins, ok := tlsClientSettings["pinnedPeerCertSha256"].([]any); ok && len(pins) > 0 {
|
||||
tlsData["pinnedPeerCertSha256"] = pins
|
||||
}
|
||||
return tlsData
|
||||
}
|
||||
|
||||
|
||||
@@ -809,6 +809,9 @@ func applyShareTLSParams(stream map[string]any, params map[string]string) {
|
||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||
params["fp"], _ = fpValue.(string)
|
||||
}
|
||||
if pins, ok := pinnedSha256List(tlsSettings); ok {
|
||||
params["pcs"] = strings.Join(pins, ",")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -831,9 +834,39 @@ func applyVmessTLSParams(stream map[string]any, obj map[string]any) {
|
||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||
obj["fp"], _ = fpValue.(string)
|
||||
}
|
||||
if pins, ok := pinnedSha256List(tlsSettings); ok {
|
||||
obj["pcs"] = strings.Join(pins, ",")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pinnedSha256List extracts tlsSettings.settings.pinnedPeerCertSha256 as a
|
||||
// []string. The field is panel-only (stripped before the run-config reaches
|
||||
// xray-core via web/service/xray.go) but flows into share links so clients
|
||||
// can pin the server's certificate hash.
|
||||
func pinnedSha256List(tlsClientSettings any) ([]string, bool) {
|
||||
raw, ok := searchKey(tlsClientSettings, "pinnedPeerCertSha256")
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
arr, ok := raw.([]any)
|
||||
if !ok || len(arr) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
out := make([]string, 0, len(arr))
|
||||
for _, v := range arr {
|
||||
s, ok := v.(string)
|
||||
if !ok || s == "" {
|
||||
continue
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
func applyShareRealityParams(stream map[string]any, params map[string]string) {
|
||||
params["security"] = "reality"
|
||||
realitySetting, _ := stream["realitySettings"].(map[string]any)
|
||||
|
||||
@@ -536,6 +536,10 @@
|
||||
"buildChain": "بناء السلسلة",
|
||||
"echKey": "ECH key",
|
||||
"echConfig": "تكوين ECH",
|
||||
"pinnedPeerCertSha256": "SHA-256 لشهادة النظير المثبَّتة",
|
||||
"pinnedPeerCertSha256Tip": "تجزئات SHA-256 المُرمَّزة بـ Base64 لشهادة النظير. للوحة فقط — لا تُكتب في إعدادات xray على الخادم، لكنها تُضمَّن في روابط المشاركة ليتمكَّن العملاء من تثبيت الشهادة.",
|
||||
"pinnedPeerCertSha256Placeholder": "تجزئة (تجزئات) base64، مفصولة بفواصل",
|
||||
"generateRandomPin": "إنشاء تجزئة عشوائية",
|
||||
"getNewEchCert": "احصل على شهادة ECH جديدة",
|
||||
"show": "عرض",
|
||||
"xver": "Xver",
|
||||
|
||||
@@ -536,6 +536,10 @@
|
||||
"buildChain": "Build Chain",
|
||||
"echKey": "ECH key",
|
||||
"echConfig": "ECH config",
|
||||
"pinnedPeerCertSha256": "Pinned Peer Cert SHA-256",
|
||||
"pinnedPeerCertSha256Tip": "Base64-encoded SHA-256 hashes of the peer certificate. Panel-only — not written to the server's xray config, but included in share links so clients can pin the certificate.",
|
||||
"pinnedPeerCertSha256Placeholder": "base64 hash(es), comma-separated",
|
||||
"generateRandomPin": "Generate random hash",
|
||||
"getNewEchCert": "Get New ECH Cert",
|
||||
"show": "Show",
|
||||
"xver": "Xver",
|
||||
|
||||
@@ -536,6 +536,10 @@
|
||||
"buildChain": "Construir cadena",
|
||||
"echKey": "ECH key",
|
||||
"echConfig": "Config ECH",
|
||||
"pinnedPeerCertSha256": "SHA-256 del cert. del par fijado",
|
||||
"pinnedPeerCertSha256Tip": "Hashes SHA-256 codificados en Base64 del certificado del par. Solo en el panel — no se escribe en la config xray del servidor, pero se incluye en los enlaces para que los clientes puedan fijar el certificado.",
|
||||
"pinnedPeerCertSha256Placeholder": "hash(es) base64, separados por comas",
|
||||
"generateRandomPin": "Generar hash aleatorio",
|
||||
"getNewEchCert": "Obtener nuevo cert ECH",
|
||||
"show": "Mostrar",
|
||||
"xver": "Xver",
|
||||
|
||||
@@ -536,6 +536,10 @@
|
||||
"buildChain": "ساخت زنجیره",
|
||||
"echKey": "کلید ECH",
|
||||
"echConfig": "پیکربندی ECH",
|
||||
"pinnedPeerCertSha256": "SHA-256 پینشدهٔ گواهی همتا",
|
||||
"pinnedPeerCertSha256Tip": "هشهای SHA-256 با کدگذاری Base64 از گواهی همتا. فقط در پنل — در پیکربندی xray سرور نوشته نمیشود، اما در لینکهای اشتراکگذاری گنجانده میشود تا کلاینتها بتوانند گواهی را پین کنند.",
|
||||
"pinnedPeerCertSha256Placeholder": "هش(های) base64، با کاما جدا شوند",
|
||||
"generateRandomPin": "تولید هش تصادفی",
|
||||
"getNewEchCert": "دریافت گواهی ECH جدید",
|
||||
"show": "نمایش",
|
||||
"xver": "Xver",
|
||||
|
||||
@@ -536,6 +536,10 @@
|
||||
"buildChain": "Bangun rantai",
|
||||
"echKey": "ECH key",
|
||||
"echConfig": "Konfig ECH",
|
||||
"pinnedPeerCertSha256": "SHA-256 Sertifikat Peer Tersemat",
|
||||
"pinnedPeerCertSha256Tip": "Hash SHA-256 berenkode Base64 dari sertifikat peer. Hanya panel — tidak ditulis ke konfig xray server, tetapi disertakan dalam link berbagi agar klien dapat menyematkan sertifikat.",
|
||||
"pinnedPeerCertSha256Placeholder": "hash base64, dipisah koma",
|
||||
"generateRandomPin": "Hasilkan hash acak",
|
||||
"getNewEchCert": "Dapatkan sertifikat ECH baru",
|
||||
"show": "Tampilkan",
|
||||
"xver": "Xver",
|
||||
|
||||
@@ -536,6 +536,10 @@
|
||||
"buildChain": "Build Chain",
|
||||
"echKey": "ECH key",
|
||||
"echConfig": "ECH config",
|
||||
"pinnedPeerCertSha256": "ピン留めピア証明書 SHA-256",
|
||||
"pinnedPeerCertSha256Tip": "ピア証明書の Base64 エンコード SHA-256 ハッシュ。パネルのみ — サーバーの xray 設定には書き込まれませんが、共有リンクには含まれ、クライアントが証明書をピン留めできます。",
|
||||
"pinnedPeerCertSha256Placeholder": "Base64 ハッシュ、カンマ区切り",
|
||||
"generateRandomPin": "ランダムハッシュを生成",
|
||||
"getNewEchCert": "新しい ECH 証明書を取得",
|
||||
"show": "表示",
|
||||
"xver": "Xver",
|
||||
|
||||
@@ -536,6 +536,10 @@
|
||||
"buildChain": "Construir cadeia",
|
||||
"echKey": "ECH key",
|
||||
"echConfig": "Config ECH",
|
||||
"pinnedPeerCertSha256": "SHA-256 do cert. do par fixado",
|
||||
"pinnedPeerCertSha256Tip": "Hashes SHA-256 codificados em Base64 do certificado do par. Apenas no painel — não é gravado na config xray do servidor, mas é incluído nos links de compartilhamento para que clientes possam fixar o certificado.",
|
||||
"pinnedPeerCertSha256Placeholder": "hash(es) base64, separados por vírgula",
|
||||
"generateRandomPin": "Gerar hash aleatório",
|
||||
"getNewEchCert": "Obter novo certificado ECH",
|
||||
"show": "Mostrar",
|
||||
"xver": "Xver",
|
||||
|
||||
@@ -536,6 +536,10 @@
|
||||
"buildChain": "Build Chain",
|
||||
"echKey": "ECH key",
|
||||
"echConfig": "ECH config",
|
||||
"pinnedPeerCertSha256": "Закреплённый SHA-256 сертификата пира",
|
||||
"pinnedPeerCertSha256Tip": "SHA-256-хеши сертификата пира в кодировке Base64. Только для панели — не записывается в конфиг xray сервера, но включается в ссылки-приглашения, чтобы клиенты могли закрепить сертификат.",
|
||||
"pinnedPeerCertSha256Placeholder": "Base64-хеш(и), через запятую",
|
||||
"generateRandomPin": "Сгенерировать случайный хеш",
|
||||
"getNewEchCert": "Получить новый ECH-сертификат",
|
||||
"show": "Показать",
|
||||
"xver": "Xver",
|
||||
|
||||
@@ -536,6 +536,10 @@
|
||||
"buildChain": "Zincir oluştur",
|
||||
"echKey": "ECH key",
|
||||
"echConfig": "ECH yapılandırması",
|
||||
"pinnedPeerCertSha256": "Sabitlenmiş Peer Sertifikası SHA-256",
|
||||
"pinnedPeerCertSha256Tip": "Peer sertifikasının Base64 kodlu SHA-256 hash'leri. Sadece panel — sunucunun xray yapılandırmasına yazılmaz, ancak istemcilerin sertifikayı sabitleyebilmesi için paylaşım bağlantılarına eklenir.",
|
||||
"pinnedPeerCertSha256Placeholder": "base64 hash(ler), virgülle ayrılmış",
|
||||
"generateRandomPin": "Rastgele hash üret",
|
||||
"getNewEchCert": "Yeni ECH sertifikası al",
|
||||
"show": "Göster",
|
||||
"xver": "Xver",
|
||||
|
||||
@@ -536,6 +536,10 @@
|
||||
"buildChain": "Build Chain",
|
||||
"echKey": "ECH key",
|
||||
"echConfig": "ECH config",
|
||||
"pinnedPeerCertSha256": "Закріплений SHA-256 сертифіката пира",
|
||||
"pinnedPeerCertSha256Tip": "SHA-256-хеші сертифіката пира в кодуванні Base64. Лише панель — не записується в конфіг xray сервера, але додається до посилань спільного доступу, щоб клієнти могли закріпити сертифікат.",
|
||||
"pinnedPeerCertSha256Placeholder": "Base64-хеш(і), через кому",
|
||||
"generateRandomPin": "Згенерувати випадковий хеш",
|
||||
"getNewEchCert": "Отримати новий ECH-сертифікат",
|
||||
"show": "Показати",
|
||||
"xver": "Xver",
|
||||
|
||||
@@ -536,6 +536,10 @@
|
||||
"buildChain": "Tạo chuỗi",
|
||||
"echKey": "ECH key",
|
||||
"echConfig": "Cấu hình ECH",
|
||||
"pinnedPeerCertSha256": "SHA-256 chứng chỉ peer đã ghim",
|
||||
"pinnedPeerCertSha256Tip": "Hash SHA-256 mã hóa Base64 của chứng chỉ peer. Chỉ panel — không ghi vào cấu hình xray máy chủ, nhưng được đưa vào liên kết chia sẻ để client có thể ghim chứng chỉ.",
|
||||
"pinnedPeerCertSha256Placeholder": "hash base64, phân tách bằng dấu phẩy",
|
||||
"generateRandomPin": "Tạo hash ngẫu nhiên",
|
||||
"getNewEchCert": "Lấy chứng chỉ ECH mới",
|
||||
"show": "Hiện",
|
||||
"xver": "Xver",
|
||||
|
||||
@@ -536,6 +536,10 @@
|
||||
"buildChain": "构建证书链",
|
||||
"echKey": "ECH key",
|
||||
"echConfig": "ECH 配置",
|
||||
"pinnedPeerCertSha256": "固定对端证书 SHA-256",
|
||||
"pinnedPeerCertSha256Tip": "对端证书的 Base64 编码 SHA-256 哈希。仅面板使用 — 不写入服务器的 xray 配置,但会包含在分享链接中,以便客户端固定证书。",
|
||||
"pinnedPeerCertSha256Placeholder": "base64 哈希,逗号分隔",
|
||||
"generateRandomPin": "生成随机哈希",
|
||||
"getNewEchCert": "获取新 ECH 证书",
|
||||
"show": "显示",
|
||||
"xver": "Xver",
|
||||
|
||||
@@ -536,6 +536,10 @@
|
||||
"buildChain": "建立憑證鏈",
|
||||
"echKey": "ECH key",
|
||||
"echConfig": "ECH 設定",
|
||||
"pinnedPeerCertSha256": "釘選對端憑證 SHA-256",
|
||||
"pinnedPeerCertSha256Tip": "對端憑證的 Base64 編碼 SHA-256 雜湊。僅面板使用 — 不寫入伺服器的 xray 設定,但會包含在分享連結中,以便用戶端釘選憑證。",
|
||||
"pinnedPeerCertSha256Placeholder": "base64 雜湊,以逗號分隔",
|
||||
"generateRandomPin": "產生隨機雜湊",
|
||||
"getNewEchCert": "取得新 ECH 憑證",
|
||||
"show": "顯示",
|
||||
"xver": "Xver",
|
||||
|
||||
Reference in New Issue
Block a user