UNPKG

dsig

Version:

Digital Signature with OpenPGP

313 lines (289 loc) 12.2 kB
#!/usr/bin/env node /*! ** DSIG -- Digital Signature with OpenPGP ** Copyright (c) 2015-2022 Dr. Ralf S. Engelschall <rse@engelschall.com> ** Licensed under LGPL 3.0 <https://spdx.org/licenses/LGPL-3.0-only> */ /* external requirements */ const yargs = require("yargs") const CLIio = require("cli-io") /* internal requirements */ const my = require("../package.json") const DSIG = require("./dsig-api.js") /* establish asynchronous environment */ ;(async () => { /* helper function for parsing command-line options */ /* eslint indent: off */ const parseArgs = (argv, config, args, handler) => { let obj = yargs() .parserConfiguration(Object.assign({}, { "duplicate-arguments-array": true, "set-placeholder-key": true, "flatten-duplicate-arrays": true, "camel-case-expansion": true, "strip-aliased": false, "dot-notation": false }, config)) .version(false) .help(true) .showHelpOnFail(true) .strict(true) obj = handler(obj) const options = obj.parse(argv) delete options.$0 if (typeof args.min === "number" && options._.length < args.min) throw new Error(`too less arguments (at least ${args.min} expected)`) if (typeof args.max === "number" && options._.length > args.max) throw new Error(`too many arguments (at most ${args.max} expected)`) return options } /* parse global command-line options */ let argv = process.argv.slice(2) const optsGlobal = parseArgs(argv, { "halt-at-non-option": true }, { min: 1 }, (yargs) => yargs.usage( "USAGE: dsig [--help|-h] <command> [<options>]\n" + "\n" + "Commands:\n" + " version, keygen, fingerprint, sign, verify" ) .option("help", { alias: "h", type: "boolean", describe: "display usage help", nargs: 0, default: false }) ) /* establish CLI input/output */ const io = new CLIio({ encoding: "utf8" }) /* define commands */ const commands = { /* command: "version" */ async version (optsGlobal, argv) { /* parse command line options */ parseArgs(argv, {}, { min: 0, max: 0 }, (yargs) => yargs.usage("USAGE: dsig version") ) process.stdout.write(`${my.name} ${my.version}\n`) process.stdout.write(`${my.description} <${my.homepage}>\n`) process.stdout.write(`${my.author.name} <${my.author.email}> <${my.author.url}>\n`) return 0 }, /* command: "keygen" */ async keygen (optsGlobal, argv) { /* parse command line options */ const opts = parseArgs(argv, {}, { min: 0, max: 0 }, (yargs) => yargs.usage([ "USAGE: dsig keygen", "--user-name|-n <user-name>", "--user-email|-m <user-email>", "--pass-phrase|-w <pass-phase>", "--private-key|-k <private-key-file>", "--public-key|-p <public-key-file>" ].join(" ")) .option("user-name", { alias: "n", type: "string", describe: "full name of user", nargs: 1, demandOption: true }) .option("user-email", { alias: "m", type: "string", describe: "email address of user", nargs: 1, demandOption: true }) .option("pass-phrase", { alias: "w", type: "string", describe: "pass-phrase for private key", nargs: 1, demandOption: true }) .option("private-key", { alias: "k", type: "string", describe: "file to store private key", nargs: 1, demandOption: true }) .option("public-key", { alias: "p", type: "string", describe: "file to store public key", nargs: 1, demandOption: true }) ) /* perform underlying API operation */ const keypair = await DSIG.keygen(opts.userName, opts.userEmail, opts.passPhrase) /* write output */ await io.output(opts.privateKey, keypair.privateKey, { mode: 0o600 }) await io.output(opts.publicKey, keypair.publicKey, { mode: 0o644 }) return 0 }, /* command: "fingerprint" */ async fingerprint (optsGlobal, argv) { /* parse command line options */ const opts = parseArgs(argv, {}, { min: 0, max: 0 }, (yargs) => yargs.usage([ "USAGE: dsig fingerprint", "--public-key|-p <public-key-file>", "--fingerprint|-f <fingerprint-file>" ].join(" ")) .option("public-key", { alias: "p", type: "string", describe: "file to read public key", nargs: 1, demandOption: true }) .option("fingerprint", { alias: "f", type: "string", describe: "file to write fingerprint", nargs: 1, demandOption: true }) ) /* read input */ const key = await io.input(opts.publicKey) /* perform underlying API operation */ const fingerprint = await DSIG.fingerprint(key) /* write output */ await io.output(opts.fingerprint, `${fingerprint}\n`) return 0 }, /* command: "sign" */ async sign (optsGlobal, argv) { /* parse command line options */ const opts = parseArgs(argv, {}, { min: 0, max: 0 }, (yargs) => yargs.usage([ "USAGE: dsig sign", "[--payload|-p <payload-file>]", "--private-key|-k <private-key-file>", "--pass-phrase|-w <pass-phase>", "[--meta-info|-m <meta-info-file>]", "--signature|-s <signature-file>" ].join(" ")) .option("payload", { alias: "p", type: "string", describe: "file to read payload", nargs: 1, default: "" }) .option("private-key", { alias: "k", type: "string", describe: "file to store private key", nargs: 1, demandOption: true }) .option("pass-phrase", { alias: "w", type: "string", describe: "pass-phrase for private key", nargs: 1, demandOption: true }) .option("meta-info", { alias: "i", type: "string", describe: "file to read meta information", nargs: 1, default: "" }) .option("signature", { alias: "s", type: "string", describe: "file to store digital signature", nargs: 1, demandOption: true }) ) /* read input */ const key = await io.input(opts.privateKey) const payload = opts.payload !== "" ? await io.input(opts.payload, { encoding: null }) : null const metaInfo = opts.metaInfo !== "" ? await io.input(opts.metaInfo) : null /* perform underlying API operation */ const sig = await DSIG.sign(payload, key, opts.passPhrase, metaInfo) /* write output */ await io.output(opts.signature, sig) return 0 }, /* command: "verify" */ async verify (optsGlobal, argv) { /* parse command line options */ const opts = parseArgs(argv, {}, { min: 0, max: 0 }, (yargs) => yargs.usage([ "USAGE: dsig verify", "[--payload|-p <payload-file>]", "--signature|-s <signature-file>", "--public-key|-k <public-key-file>", "[--fingerprint|-f <fingerprint-file>]", "[--meta-info|-m <meta-info-file>]" ].join(" ")) .option("payload", { alias: "p", type: "string", describe: "file to read payload", nargs: 1, default: "" }) .option("signature", { alias: "s", type: "string", describe: "file to read digital signature", nargs: 1, demandOption: true }) .option("public-key", { alias: "k", type: "string", describe: "file to read public key", nargs: 1, demandOption: true }) .option("fingerprint", { alias: "f", type: "string", describe: "file to read fingerprint of public key", nargs: 1, default: "" }) .option("meta-info", { alias: "i", type: "string", describe: "file to write meta information", nargs: 1, default: "" }) ) /* read input */ const payload = opts.payload !== "" ? await io.input(opts.payload, { encoding: null }) : null const signature = await io.input(opts.signature) const publicKey = await io.input(opts.publicKey) const fingerprint = opts.fingerprint !== "" ? await io.input(opts.fingerprint) : null /* perform underlying API operation */ const metaInfo = await DSIG.verify(payload, signature, publicKey, fingerprint) /* write output */ if (metaInfo !== null && opts.metaInfo !== "") await io.output(opts.metaInfo, metaInfo) return 0 } } /* dispatch command */ argv = optsGlobal._ delete optsGlobal._ const cmd = argv.shift() if (typeof commands[cmd] !== "function") throw new Error(`unknown command: "${cmd}" (expected "version", "keygen", "fingerprint", "sign" or "verify")`) const rc = await commands[cmd](optsGlobal, argv) process.exit(rc) })().catch((err) => { process.stderr.write(`dsig: ERROR: ${err.message}\n`) process.exit(1) })