tls.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. import { useTranslation } from 'react-i18next';
  2. import { Button, Form, Input, InputNumber, Radio, Select, Space, Switch } from 'antd';
  3. import { MinusOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
  4. import {
  5. ALPN_OPTION,
  6. TLS_CIPHER_OPTION,
  7. TLS_VERSION_OPTION,
  8. USAGE_OPTION,
  9. UTLS_FINGERPRINT,
  10. } from '@/schemas/primitives';
  11. const { TextArea } = Input;
  12. interface TlsFormProps {
  13. saving: boolean;
  14. setCertFromPanel: (certName: number) => void;
  15. clearCertFiles: (certName: number) => void;
  16. generateRandomPinHash: () => void;
  17. getNewEchCert: () => void;
  18. clearEchCert: () => void;
  19. }
  20. export default function TlsForm({
  21. saving,
  22. setCertFromPanel,
  23. clearCertFiles,
  24. generateRandomPinHash,
  25. getNewEchCert,
  26. clearEchCert,
  27. }: TlsFormProps) {
  28. const { t } = useTranslation();
  29. return (
  30. <>
  31. <Form.Item name={['streamSettings', 'tlsSettings', 'serverName']} label="SNI">
  32. <Input placeholder={t('pages.inbounds.form.serverNameIndication')} />
  33. </Form.Item>
  34. <Form.Item name={['streamSettings', 'tlsSettings', 'cipherSuites']} label={t('pages.inbounds.form.cipherSuites')}>
  35. <Select
  36. options={[
  37. { value: '', label: t('pages.inbounds.form.autoOption') },
  38. ...Object.entries(TLS_CIPHER_OPTION).map(([k, v]) => ({ value: v, label: k })),
  39. ]}
  40. />
  41. </Form.Item>
  42. <Form.Item label={t('pages.inbounds.form.minMaxVersion')}>
  43. <Space.Compact block>
  44. <Form.Item name={['streamSettings', 'tlsSettings', 'minVersion']} noStyle>
  45. <Select
  46. style={{ width: '50%' }}
  47. options={Object.values(TLS_VERSION_OPTION).map((v) => ({ value: v, label: v }))}
  48. />
  49. </Form.Item>
  50. <Form.Item name={['streamSettings', 'tlsSettings', 'maxVersion']} noStyle>
  51. <Select
  52. style={{ width: '50%' }}
  53. options={Object.values(TLS_VERSION_OPTION).map((v) => ({ value: v, label: v }))}
  54. />
  55. </Form.Item>
  56. </Space.Compact>
  57. </Form.Item>
  58. <Form.Item
  59. name={['streamSettings', 'tlsSettings', 'settings', 'fingerprint']}
  60. label="uTLS"
  61. >
  62. <Select
  63. options={[
  64. { value: '', label: 'None' },
  65. ...Object.values(UTLS_FINGERPRINT).map((fp) => ({ value: fp, label: fp })),
  66. ]}
  67. />
  68. </Form.Item>
  69. <Form.Item name={['streamSettings', 'tlsSettings', 'alpn']} label="ALPN">
  70. <Select
  71. mode="multiple"
  72. tokenSeparators={[',']}
  73. style={{ width: '100%' }}
  74. options={Object.values(ALPN_OPTION).map((a) => ({ value: a, label: a }))}
  75. />
  76. </Form.Item>
  77. <Form.Item
  78. name={['streamSettings', 'tlsSettings', 'rejectUnknownSni']}
  79. label={t('pages.inbounds.form.rejectUnknownSni')}
  80. valuePropName="checked"
  81. >
  82. <Switch />
  83. </Form.Item>
  84. <Form.Item
  85. name={['streamSettings', 'tlsSettings', 'disableSystemRoot']}
  86. label={t('pages.inbounds.form.disableSystemRoot')}
  87. valuePropName="checked"
  88. >
  89. <Switch />
  90. </Form.Item>
  91. <Form.Item
  92. name={['streamSettings', 'tlsSettings', 'enableSessionResumption']}
  93. label={t('pages.inbounds.form.sessionResumption')}
  94. valuePropName="checked"
  95. >
  96. <Switch />
  97. </Form.Item>
  98. <Form.List name={['streamSettings', 'tlsSettings', 'certificates']}>
  99. {(certFields, { add, remove }) => (
  100. <>
  101. <Form.Item label={t('certificate')}>
  102. <Button
  103. type="primary"
  104. size="small"
  105. onClick={() => add({
  106. useFile: true,
  107. certificateFile: '',
  108. keyFile: '',
  109. certificate: [],
  110. key: [],
  111. ocspStapling: 3600,
  112. oneTimeLoading: false,
  113. usage: 'encipherment',
  114. buildChain: false,
  115. })}
  116. >
  117. <PlusOutlined />
  118. </Button>
  119. </Form.Item>
  120. {certFields.map((certField, idx) => (
  121. <div key={certField.key}>
  122. <Form.Item
  123. name={[certField.name, 'useFile']}
  124. label={`${t('certificate')} ${idx + 1}`}
  125. >
  126. <Radio.Group buttonStyle="solid">
  127. <Radio.Button value={true}>
  128. {t('pages.inbounds.certificatePath')}
  129. </Radio.Button>
  130. <Radio.Button value={false}>
  131. {t('pages.inbounds.certificateContent')}
  132. </Radio.Button>
  133. </Radio.Group>
  134. </Form.Item>
  135. {certFields.length > 1 && (
  136. <Form.Item label=" ">
  137. <Button
  138. size="small"
  139. danger
  140. onClick={() => remove(certField.name)}
  141. >
  142. <MinusOutlined /> {t('remove')}
  143. </Button>
  144. </Form.Item>
  145. )}
  146. <Form.Item
  147. noStyle
  148. shouldUpdate={(prev, curr) =>
  149. prev.streamSettings?.tlsSettings?.certificates?.[certField.name]?.useFile
  150. !== curr.streamSettings?.tlsSettings?.certificates?.[certField.name]?.useFile
  151. }
  152. >
  153. {({ getFieldValue }) => {
  154. const useFile = getFieldValue([
  155. 'streamSettings', 'tlsSettings', 'certificates',
  156. certField.name, 'useFile',
  157. ]);
  158. return useFile ? (
  159. <>
  160. <Form.Item
  161. name={[certField.name, 'certificateFile']}
  162. label={t('pages.inbounds.publicKey')}
  163. >
  164. <Input />
  165. </Form.Item>
  166. <Form.Item
  167. name={[certField.name, 'keyFile']}
  168. label={t('pages.inbounds.privatekey')}
  169. >
  170. <Input />
  171. </Form.Item>
  172. <Form.Item label=" ">
  173. <Space>
  174. <Button
  175. type="primary"
  176. loading={saving}
  177. onClick={() => setCertFromPanel(certField.name)}
  178. >
  179. {t('pages.inbounds.setDefaultCert')}
  180. </Button>
  181. <Button danger onClick={() => clearCertFiles(certField.name)}>
  182. {t('clear')}
  183. </Button>
  184. </Space>
  185. </Form.Item>
  186. </>
  187. ) : (
  188. <>
  189. <Form.Item
  190. name={[certField.name, 'certificate']}
  191. label={t('pages.inbounds.publicKey')}
  192. normalize={(v) => typeof v === 'string'
  193. ? v.split('\n')
  194. : v}
  195. getValueProps={(v) => ({
  196. value: Array.isArray(v) ? v.join('\n') : v,
  197. })}
  198. >
  199. <TextArea autoSize={{ minRows: 3, maxRows: 8 }} />
  200. </Form.Item>
  201. <Form.Item
  202. name={[certField.name, 'key']}
  203. label={t('pages.inbounds.privatekey')}
  204. normalize={(v) => typeof v === 'string'
  205. ? v.split('\n')
  206. : v}
  207. getValueProps={(v) => ({
  208. value: Array.isArray(v) ? v.join('\n') : v,
  209. })}
  210. >
  211. <TextArea autoSize={{ minRows: 3, maxRows: 8 }} />
  212. </Form.Item>
  213. </>
  214. );
  215. }}
  216. </Form.Item>
  217. <Form.Item
  218. name={[certField.name, 'ocspStapling']}
  219. label="OCSP Stapling"
  220. >
  221. <InputNumber min={0} addonAfter="s" style={{ width: '50%' }} />
  222. </Form.Item>
  223. <Form.Item
  224. name={[certField.name, 'oneTimeLoading']}
  225. label={t('pages.inbounds.form.oneTimeLoading')}
  226. valuePropName="checked"
  227. >
  228. <Switch />
  229. </Form.Item>
  230. <Form.Item
  231. name={[certField.name, 'usage']}
  232. label={t('pages.inbounds.form.usageOption')}
  233. >
  234. <Select
  235. style={{ width: '50%' }}
  236. options={Object.values(USAGE_OPTION).map((u) => ({ value: u, label: u }))}
  237. />
  238. </Form.Item>
  239. <Form.Item
  240. noStyle
  241. shouldUpdate={(prev, curr) =>
  242. prev.streamSettings?.tlsSettings?.certificates?.[certField.name]?.usage
  243. !== curr.streamSettings?.tlsSettings?.certificates?.[certField.name]?.usage
  244. }
  245. >
  246. {({ getFieldValue }) => {
  247. const usage = getFieldValue([
  248. 'streamSettings', 'tlsSettings', 'certificates',
  249. certField.name, 'usage',
  250. ]);
  251. if (usage !== 'issue') return null;
  252. return (
  253. <Form.Item
  254. name={[certField.name, 'buildChain']}
  255. label={t('pages.inbounds.form.buildChain')}
  256. valuePropName="checked"
  257. >
  258. <Switch />
  259. </Form.Item>
  260. );
  261. }}
  262. </Form.Item>
  263. </div>
  264. ))}
  265. </>
  266. )}
  267. </Form.List>
  268. <Form.Item name={['streamSettings', 'tlsSettings', 'echServerKeys']} label={t('pages.inbounds.form.echKey')}>
  269. <Input />
  270. </Form.Item>
  271. <Form.Item
  272. name={['streamSettings', 'tlsSettings', 'settings', 'echConfigList']}
  273. label={t('pages.inbounds.form.echConfig')}
  274. >
  275. <Input />
  276. </Form.Item>
  277. <Form.Item
  278. label={t('pages.inbounds.form.pinnedPeerCertSha256')}
  279. tooltip={t('pages.inbounds.form.pinnedPeerCertSha256Tip')}
  280. >
  281. <Space.Compact block>
  282. <Form.Item
  283. name={['streamSettings', 'tlsSettings', 'settings', 'pinnedPeerCertSha256']}
  284. noStyle
  285. >
  286. <Select
  287. mode="tags"
  288. tokenSeparators={[',', ' ']}
  289. placeholder={t('pages.inbounds.form.pinnedPeerCertSha256Placeholder')}
  290. style={{ width: 'calc(100% - 32px)' }}
  291. />
  292. </Form.Item>
  293. <Button
  294. icon={<ReloadOutlined />}
  295. onClick={generateRandomPinHash}
  296. title={t('pages.inbounds.form.generateRandomPin')}
  297. />
  298. </Space.Compact>
  299. </Form.Item>
  300. <Form.Item label=" ">
  301. <Space>
  302. <Button type="primary" loading={saving} onClick={getNewEchCert}>
  303. {t('pages.inbounds.form.getNewEchCert')}
  304. </Button>
  305. <Button danger onClick={clearEchCert}>{t('clear')}</Button>
  306. </Space>
  307. </Form.Item>
  308. </>
  309. );
  310. }