moved self-signed certificate creation to server-side
Massimo Melina committed
Nov 24, 2024 at 23:42 UTC
3711589063ea50611922a0146d4537932b3e5e67
5 files changed
+51
-46
admin/index.html
-1
@@ -3,7 +3,6 @@
3
<head>
4
<meta charset="UTF-8" />
5
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <script async src='https://unpkg.com/node-forge@1.3.1/dist/forge.min.js'></script>
6
<title>HFS Admin-panel</title>
7
<script type="module" src="/src/index.ts"></script>
8
<link rel="icon" type="image/svg+xml" href="/hfs-logo-icon.svg" />
admin/src/OptionsPage.ts
+3
-26
@@ -8,7 +8,7 @@ import { Link as RouterLink } from 'react-router-dom'
8
import { CardMembership, EditNote, Refresh, Warning } from '@mui/icons-material'
9
import { adminApis } from '../../src/adminApis'
10
import {
11
- MAX_TILE_SIZE, REPO_URL, SORT_BY_OPTIONS, THEME_OPTIONS, PORT_DISABLED, CFG, IMAGE_FILEMASK,
11
+ MAX_TILE_SIZE, REPO_URL, SORT_BY_OPTIONS, THEME_OPTIONS, CFG, IMAGE_FILEMASK,
12
Dict, md, wait, with_, try_, ipForUrl,
13
} from './misc'
14
import { iconTooltip, InLink, LinkBtn, propsForModifiedValues, wikiLink, useBreakpoint, NetmaskField, WildcardsSupported } from './mui'
@@ -401,9 +401,8 @@ export async function suggestMakingCert() {
401
const stop = waitDialog()
402
try {
403
await wait(50) // give time to start animation before cpu intensive task
404
- const saved = await apiCall('save_pem', await makeCert({}))
404
+ const saved = await apiCall('make_self_signed_cert', { fileName: 'self' })
405
stop()
406
- await apiCall('set_config', { values: saved })
406
if (loaded) // when undefined we are not in this page
407
Object.assign(loaded, saved)
408
setTimeout(exposedReloadStatus!, 1000) // give some time for backend to apply
@@ -414,26 +413,4 @@ export async function suggestMakingCert() {
413
finally { stop() }
414
}
415
})
417
-}
418
-
419
-async function makeCert(attributes: Record<string, string>) {
420
- // this relies on having loaded node-forge/dist/forge.min.js
421
- const { pki } = (window as any).forge
422
- const keys = pki.rsa.generateKeyPair(2048);
423
- const cert = pki.createCertificate();
424
- cert.publicKey = keys.publicKey
425
- cert.serialNumber = '01'
426
- cert.validity.notBefore = new Date()
427
- cert.validity.notAfter = new Date()
428
- cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1)
429
-
430
- const attrs = Object.entries(attributes).map(x => ({ name: x[0], value: x[1] }))
431
- cert.setSubject(attrs)
432
- cert.setIssuer(attrs)
433
- cert.sign(keys.privateKey)
434
-
435
- return {
436
- cert: pki.certificateToPem(cert),
437
- private_key: pki.privateKeyToPem(keys.privateKey),
438
- }
439
-}
416
+}
\ No newline at end of file
package.json
+4
-2
@@ -91,9 +91,10 @@
91
"lodash": "^4.17.21",
92
"minimist": "^1.2.6",
93
"nat-upnp-rejetto": "^2.1.0",
94
+ "node-forge": "^1.3.1",
95
"open": "^8.4.0",
95
- "qr-creator": "^1.0.0",
96
"picomatch": "^3.0.1",
97
+ "qr-creator": "^1.0.0",
98
"tssrp6a": "^3.0.0",
99
"unzip-stream": "^0.3.4",
100
"valtio": "^1.10.3",
@@ -113,15 +114,16 @@
114
"@types/minimist": "^1.2.2",
115
"@types/mocha": "^9.0.0",
116
"@types/node": "^18.17.14",
117
+ "@types/node-forge": "^1.3.11",
118
"@types/picomatch": "^2.3.3",
119
"@types/tough-cookie": "^4.0.2",
120
"@types/unzipper": "^0.10.5",
121
+ "@yao-pkg/pkg": "^5.15.0",
122
"cross-env": "^7.0.3",
123
"koa-better-http-proxy": "^0.2.9",
124
"mocha": "^9.1.3",
125
"nm-prune": "^5.0.0",
126
"nodemon": "^2.0.15",
124
- "@yao-pkg/pkg": "^5.15.0",
127
"tough-cookie": "^4.0.0",
128
"ts-node": "^10.4.0"
129
}
src/adminApis.ts
+4
-17
@@ -4,13 +4,8 @@ import { ApiError, ApiHandler, ApiHandlers } from './apiMiddleware'
4
import { configFile, defineConfig, getWholeConfig, setConfig } from './config'
5
import { getBaseUrlOrDefault, getIps, getServerStatus, getUrls } from './listen'
6
import {
7
- API_VERSION,
8
- BUILD_TIMESTAMP,
9
- COMPATIBLE_API_VERSION,
10
- HFS_STARTED,
11
- IS_WINDOWS,
12
- VERSION,
13
- HTTP_UNAUTHORIZED, HTTP_BAD_REQUEST, HTTP_SERVER_ERROR, HTTP_FORBIDDEN
7
+ API_VERSION, BUILD_TIMESTAMP, COMPATIBLE_API_VERSION, HFS_STARTED, IS_WINDOWS, VERSION,
8
+ HTTP_UNAUTHORIZED, HTTP_SERVER_ERROR, HTTP_FORBIDDEN
9
} from './const'
10
import vfsApis from './api.vfs'
11
import accountsApis from './api.accounts'
@@ -19,12 +14,12 @@ import monitorApis from './api.monitor'
14
import langApis from './api.lang'
15
import netApis from './api.net'
16
import logApis from './api.log'
17
+import certApis from './api.cert'
18
import { getConnections } from './connections'
19
import { apiAssertTypes, debounceAsync, isLocalHost, makeNetMatcher, typedEntries, waitFor } from './misc'
20
import { accountCanLoginAdmin, accountsConfig } from './perm'
21
import Koa from 'koa'
22
import { cloudflareDetected, getProxyDetected } from './middlewares'
27
-import { writeFile } from 'fs/promises'
23
import { execFile } from 'child_process'
24
import { promisify } from 'util'
25
import { customHtmlSections, customHtml, saveCustomHtml } from './customHtml'
@@ -49,6 +44,7 @@ export const adminApis = {
44
...langApis,
45
...netApis,
46
...logApis,
47
+ ...certApis,
48
get_dynamic_dns_error,
49
50
async set_config({ values }) {
@@ -140,15 +136,6 @@ export const adminApis = {
136
}
137
},
138
143
- async save_pem({ cert, private_key, name='self' }) {
144
- if (!cert || !private_key)
145
- return new ApiError(HTTP_BAD_REQUEST)
146
- const files = { cert: name + '.cer', private_key: name + '.key' }
147
- await writeFile(files.private_key, private_key)
148
- await writeFile(files.cert, cert)
149
- return files
150
- },
151
-
139
async add_block({ merge, ip, expire, comment }: BlockingRule & { merge?: Partial<BlockingRule> }) {
140
apiAssertTypes({
141
string: { ip },
src/api.cert.ts
new
+40
@@ -0,0 +1,40 @@
1
+import { apiAssertTypes } from './misc'
2
+import { ApiHandlers } from './apiMiddleware'
3
+import { writeFile } from 'fs/promises'
4
+import { pki } from 'node-forge'
5
+import { setConfig } from './config'
6
+
7
+export default {
8
+
9
+ async make_self_signed_cert({ attributes, fileName }: { fileName?: string, attributes?: Record<string, string> }) {
10
+ apiAssertTypes({
11
+ object_undefined: { attributes },
12
+ string_undefined: { fileName },
13
+ })
14
+
15
+ const keys = pki.rsa.generateKeyPair(2048)
16
+ const cert = pki.createCertificate()
17
+ cert.publicKey = keys.publicKey
18
+ cert.serialNumber = '01'
19
+ cert.validity.notBefore = new Date()
20
+ cert.validity.notAfter = new Date()
21
+ cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1)
22
+
23
+ const attrs = Object.entries(attributes || {}).map(x => ({ name: x[0], value: x[1] }))
24
+ cert.setSubject(attrs)
25
+ cert.setIssuer(attrs)
26
+ cert.sign(keys.privateKey)
27
+ const ret = {
28
+ cert: pki.certificateToPem(cert),
29
+ private_key: pki.privateKeyToPem(keys.privateKey),
30
+ }
31
+ if (!fileName)
32
+ return ret
33
+ const configs = { cert: fileName + '.cer', private_key: fileName + '.key' }
34
+ await writeFile(configs.private_key, ret.private_key)
35
+ await writeFile(configs.cert, ret.cert)
36
+ setConfig(configs)
37
+ return configs
38
+ }
39
+
40
+} satisfies ApiHandlers
\ No newline at end of file