@cto.af/ca
Version:
Testing-only Certificate Authority (CA) for your local development environment ONLY. This is in no way suitable for production of any kind.
173 lines (158 loc) • 4.7 kB
JavaScript
// If debug needed:
// #!/usr/bin/env node -S node --enable-source-maps
import {
CertificateAuthority,
DEFAULT_CA_OPTIONS,
DEFAULT_COMMON_CERT_OPTIONS,
KeyCert,
} from '../lib/index.js';
import {Command} from 'commander';
import filenamify from 'filenamify';
import fs from 'node:fs/promises';
import path from 'node:path';
import pkg from '../package.json' with {type: 'json'};
let logLevel = 0;
const prettyIgnore = 'pid,hostname,ns';
function collect(value, previous) {
if (typeof previous === 'string') {
return [value];
}
return previous.concat([value]);
}
const program = new Command();
program
.version(pkg.version)
.option('-d, --dir <DIRECTORY>', 'Directory for CA certs', DEFAULT_CA_OPTIONS.dir)
.option('-q, --quiet', 'Less verbose', () => --logLevel)
.option('-v, --verbose', 'More verbose', () => ++logLevel)
.configureHelp({
showGlobalOptions: true,
sortOptions: true,
});
program
.command('cert')
.description('Create a cert signed by the CA')
.option('-f, --force', 'Always create a new certificate')
.option('-H, --host <HOSTNAME>', 'Hostname for the certificate', collect, DEFAULT_COMMON_CERT_OPTIONS.host)
.option('-s, --subject <SUBJECT>', 'Subject for the CA cert', collect, DEFAULT_CA_OPTIONS.host)
.option('-t, --temp', 'Do not output files')
.action(async (_, cmd) => {
const args = cmd.optsWithGlobals();
args.forceCert = Boolean(args.force);
const ca = new CertificateAuthority({
host: args.subject,
temp: args.temp,
dir: args.dir,
logLevel,
prettyIgnore,
});
const kc = await ca.issue({
force: args.force,
host: args.host,
temp: args.temp,
noKey: true,
});
if (args.temp) {
console.log(kc.cert);
console.log(kc.key);
} else {
console.log(kc.notAfter, kc.subject);
}
});
program
.command('create')
.description('Create a CA certificate')
.option('-f, --force', 'Always create a new CA certificate')
.option('-s, --subject <SUBJECT>', 'Subject for the CA cert', collect, DEFAULT_CA_OPTIONS.host)
.option('-t, --temp', 'Do not output files')
.action(async (_, cmd) => {
const args = cmd.optsWithGlobals();
const ca = new CertificateAuthority({
dir: args.dir,
force: args.force,
host: args.subject,
temp: args.temp,
logLevel,
prettyIgnore,
});
const kc = await ca.init();
if (args.temp) {
console.log(kc.cert);
console.log(kc.key);
} else {
console.log(kc.notAfter, kc.subject);
}
});
program
.command('dir')
.description('Show directory for CA certs')
.action((_, cmd) => {
const args = cmd.optsWithGlobals();
console.log(args.dir);
});
program
.command('list')
.description('List exising CA certs by subject')
.action(async (_, cmd) => {
const {dir} = cmd.optsWithGlobals();
for await (const kc of CertificateAuthority.list({dir, noKey: true})) {
console.log(kc.notAfter, kc.subject);
}
});
program
.command('certs')
.description('List exising certs by subject')
.action(async (_, cmd) => {
const {dir} = cmd.optsWithGlobals();
const ca = new CertificateAuthority({
dir,
temp: true, // Don't save a CA for this if it doesn't exist.
noKey: true,
logLevel,
prettyIgnore,
});
for await (const kc of ca.list()) {
console.log(kc.notAfter, kc.subject);
}
});
program
.command('rm <SUBJECT>')
.description('Remove a CA cert by subject')
.action(async (subject, _, cmd) => {
const {dir} = cmd.optsWithGlobals();
const fn = path.join(dir, filenamify(subject));
await fs.rm(`${fn}.cert.pem`).catch(_ignored => undefined);
await fs.rm(`${fn}.key.pem`).catch(_ignored => undefined);
});
program
.command('clear')
.description('Remove all CA certs and keys')
.action(async (_, cmd) => {
const {dir} = cmd.optsWithGlobals();
const opts = {dir, noKey: true};
const log = CertificateAuthority.logger({
logLevel,
prettyIgnore,
});
for await (const kc of CertificateAuthority.list(opts)) {
await kc.delete(opts, log);
}
});
program
.command('clearkeys [pattern]')
.description('Remove all keys from CAs and certs')
.action(async pattern => {
const log = CertificateAuthority.logger({
logLevel,
prettyIgnore,
});
const re = pattern ? new RegExp(pattern) : null;
for await (const e of KeyCert.listKeys()) {
if (!re || re.test(e.account)) {
log.info('Deleting key: "%s"', e.account);
await e.entry.deletePassword();
}
}
});
await program.parseAsync();