82 lines
2.5 KiB
JavaScript
82 lines
2.5 KiB
JavaScript
import fsp from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import {
|
|
DEFAULT_PRIVATE_KEY,
|
|
DEFAULT_PUBLIC_KEY,
|
|
assertOutsideRepo,
|
|
generateEd25519KeyPair,
|
|
parseOptions,
|
|
publicKeyFingerprint,
|
|
resolvePath,
|
|
runProcess,
|
|
usageError,
|
|
writeFileAtomic
|
|
} from './lib.mjs'
|
|
|
|
const usage = `Usage:
|
|
node scripts/updates/init-key.mjs [--private-key <path>] [--public-key <path>] [--force]
|
|
|
|
The private key must remain outside the repository. The default private key is:
|
|
${DEFAULT_PRIVATE_KEY}
|
|
`
|
|
|
|
function secureWindowsAcl(filename, isDirectory = false) {
|
|
if (process.platform !== 'win32') return
|
|
const identity = runProcess('whoami', []).trim()
|
|
if (!identity) throw new Error('Unable to determine the current Windows identity')
|
|
const permission = isDirectory ? `${identity}:(OI)(CI)F` : `${identity}:(F)`
|
|
runProcess('icacls', [filename, '/inheritance:r', '/grant:r', permission])
|
|
}
|
|
|
|
async function pathExists(filename) {
|
|
try {
|
|
await fsp.access(filename)
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
const options = parseOptions(process.argv.slice(2), {
|
|
values: ['private-key', 'public-key'],
|
|
booleans: ['force']
|
|
})
|
|
const privateKeyPath = await assertOutsideRepo(
|
|
resolvePath(options['private-key'] || DEFAULT_PRIVATE_KEY),
|
|
'Private key'
|
|
)
|
|
const publicKeyPath = resolvePath(options['public-key'] || DEFAULT_PUBLIC_KEY)
|
|
|
|
if (!options.force) {
|
|
if (await pathExists(privateKeyPath)) throw new Error(`Refusing to overwrite existing private key: ${privateKeyPath}`)
|
|
if (await pathExists(publicKeyPath)) throw new Error(`Refusing to overwrite existing public key: ${publicKeyPath}`)
|
|
}
|
|
|
|
const keyPair = generateEd25519KeyPair()
|
|
const directory = path.dirname(privateKeyPath)
|
|
await fsp.mkdir(directory, { recursive: true, mode: 0o700 })
|
|
try {
|
|
await fsp.chmod(directory, 0o700)
|
|
} catch {
|
|
// Windows ACLs are applied below.
|
|
}
|
|
secureWindowsAcl(directory, true)
|
|
|
|
await writeFileAtomic(privateKeyPath, keyPair.privatePem, 0o600)
|
|
try {
|
|
await fsp.chmod(privateKeyPath, 0o600)
|
|
} catch {
|
|
// Windows ACLs are applied below.
|
|
}
|
|
secureWindowsAcl(privateKeyPath)
|
|
await writeFileAtomic(publicKeyPath, keyPair.publicPem, 0o644)
|
|
|
|
console.log(`Private key: ${privateKeyPath}`)
|
|
console.log(`Public key: ${publicKeyPath}`)
|
|
console.log(`Public key SHA-256 fingerprint: ${publicKeyFingerprint(keyPair.publicPem)}`)
|
|
console.log('Private key material was not printed. Back up the private key separately before rotating it.')
|
|
}
|
|
|
|
main().catch((error) => usageError(usage, error))
|