Files
easynode/server/app/utils/ssl-cert.js
T

98 lines
2.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}