mirror of
https://github.com/chaos-zhu/easynode.git
synced 2026-10-07 15:08:33 +08:00
98 lines
2.7 KiB
JavaScript
98 lines
2.7 KiB
JavaScript
import selfsigned from 'selfsigned'
|
||
import fs from 'node:fs'
|
||
import path from 'node:path'
|
||
import tls from 'node:tls'
|
||
|
||
const SELF_SIGNED_CERT_FILE = 'https-selfsigned-cert.pem'
|
||
const SELF_SIGNED_KEY_FILE = 'https-selfsigned-key.pem'
|
||
|
||
const writeFileAtomically = (targetPath, contents, mode) => {
|
||
const temporaryPath = `${ targetPath }.${ process.pid }.tmp`
|
||
fs.writeFileSync(temporaryPath, contents, { mode })
|
||
fs.renameSync(temporaryPath, targetPath)
|
||
}
|
||
|
||
const loadPersistedCertificate = (certPath, keyPath) => {
|
||
if (!fs.existsSync(certPath) || !fs.existsSync(keyPath)) return null
|
||
|
||
try {
|
||
const cert = fs.readFileSync(certPath, 'utf8')
|
||
const key = fs.readFileSync(keyPath, 'utf8')
|
||
// Besides checking PEM syntax, this verifies that the certificate and
|
||
// private key belong to the same keypair. A partially written or manually
|
||
// damaged pair is regenerated instead of breaking HTTPS startup forever.
|
||
tls.createSecureContext({ cert, key })
|
||
fs.chmodSync(keyPath, 0o600)
|
||
return { cert, key }
|
||
} catch (error) {
|
||
logger.warn(`持久化自签名证书无效,将重新生成: ${ error.message }`)
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载持久化的自签名证书;首次运行时生成并保存。
|
||
* 默认保存在 app/db,Docker Compose 已持久化该目录。
|
||
* @returns {Object} 包含 cert 和 key 的对象
|
||
*/
|
||
function generateSelfSignedCert(storageDir = path.join(process.cwd(), 'app/db')) {
|
||
const certPath = path.join(storageDir, SELF_SIGNED_CERT_FILE)
|
||
const keyPath = path.join(storageDir, SELF_SIGNED_KEY_FILE)
|
||
|
||
const persisted = loadPersistedCertificate(certPath, keyPath)
|
||
if (persisted) {
|
||
logger.info(`已加载持久化自签名证书: ${ certPath }`)
|
||
return persisted
|
||
}
|
||
|
||
const attrs = [{ name: 'commonName', value: 'localhost' }]
|
||
const pems = selfsigned.generate(attrs, {
|
||
keySize: 2048,
|
||
days: 3650, // 10年有效期
|
||
algorithm: 'sha256',
|
||
extensions: [
|
||
{
|
||
name: 'basicConstraints',
|
||
cA: false
|
||
},
|
||
{
|
||
name: 'keyUsage',
|
||
digitalSignature: true,
|
||
keyEncipherment: true
|
||
},
|
||
{
|
||
name: 'extKeyUsage',
|
||
serverAuth: true
|
||
},
|
||
{
|
||
name: 'subjectAltName',
|
||
altNames: [
|
||
{
|
||
type: 2, // DNS
|
||
value: 'localhost'
|
||
},
|
||
{
|
||
type: 7, // IP
|
||
ip: '127.0.0.1'
|
||
}
|
||
]
|
||
}
|
||
]
|
||
})
|
||
|
||
fs.mkdirSync(storageDir, { recursive: true })
|
||
writeFileAtomically(certPath, pems.cert, 0o644)
|
||
writeFileAtomically(keyPath, pems.private, 0o600)
|
||
logger.info(`已生成并持久化自签名证书: ${ certPath }`)
|
||
return {
|
||
cert: pems.cert,
|
||
key: pems.private
|
||
}
|
||
}
|
||
|
||
export {
|
||
generateSelfSignedCert,
|
||
SELF_SIGNED_CERT_FILE,
|
||
SELF_SIGNED_KEY_FILE
|
||
}
|