vouchsafe
Version:
Self-verifying identity and offline trust verification for JWTs, including attestations, vouches, revocations, and multi-hop trust chains.
130 lines (111 loc) • 4.45 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { Command } from 'commander';
import { createVouchsafeIdentity, createVouchsafeIdentityFromKeypair } from '../index.mjs';
const program = new Command();
function toPem(label, base64Key) {
const lines = base64Key.match(/.{1,64}/g).join('\n');
return `-----BEGIN ${label}-----\n${lines}\n-----END ${label}-----\n`;
}
let status = function() {
console.error(...arguments);
};
program.name('create_vouchsafe_id')
.description('Create a new Vouchsafe identity with associated keypair.\n\n' +
'By default creates a new keypair. To use an existing\n' +
'keypair, use the --public and --private to provide them');
program
.option('-l, --label <label>', 'Identity label (required)')
.option('-s, --separate', 'Output in separate files instead of json')
.option('-q, --quiet', 'Do not output status messages')
.option('-e, --existing <filename>', 'Load an existing identity file rather than creating from scratch')
.option('-o, --output <filename>', 'output filename (or prefix in separate files mode)')
.option('--public <filename>', 'existing public key PEM file')
.option('--private <filename>', 'existing private key PEM file')
program.parse(process.argv);
const options = program.opts();
const label = options.label;
const keyPrefix = options.output || label;
if (typeof label == 'undefined') {
status('!!! Identity label (-l label) is required\n');
program.help();
process.exit();
}
if (label.length < 3) {
status('Label must be at least 3 characters in length');
process.exit();
}
if (options.quiet) {
// override the status function if we are told to be quiet
status = function() {
// do nothing
};
}
function readPemFile(filename) {
const contents = fs.readFileSync(filename, 'utf8');
return contents.replace(/^-----BEGIN .*?-----\n|\n-----END .*?-----\n?/g, '').replace(/\n/g, '');
}
function writeFile(path_or_handle, data, encoding) {
let out = path_or_handle;
let type = 'handle';
if (typeof path_or_handle == 'string') {
out = fs.createWriteStream(path_or_handle, {
encoding
});
type = 'file'
}
out.write(data);
if (type == 'file') {
out.end();
}
}
try {
let identity;
if (options.public && options.private) {
const pubKey = readPemFile(options.public);
const privKey = readPemFile(options.private);
identity = await createVouchsafeIdentityFromKeypair(label, { publicKey: pubKey, privateKey: privKey });
} else if (options.existing) {
const filedata = fs.readFileSync(options.existing, 'utf8');
let identity_from_file = JSON.parse(filedata);
identity = await createVouchsafeIdentityFromKeypair(label, identity_from_file.keypair);
} else {
identity = await createVouchsafeIdentity(label);
}
const pubPem = toPem('PUBLIC KEY', identity.keypair.publicKey);
const privPem = toPem('PRIVATE KEY', identity.keypair.privateKey);
status(`Created identity: ${identity.urn}`);
if (!options.separate) {
let json_output = JSON.stringify(identity, undefined, 4);
let output_filename = options.output || label + '.json';
if (output_filename == '-') {
output_filename = process.stdout;
}
if (options.existing == output_filename) {
throw new Error('Stubbornly refusing to overwrite original identity file');
}
await writeFile(output_filename, json_output, 'utf8');
if (options.output != '-') {
status(`Saved to: ${output_filename}`);
}
} else {
let urnFilename = `${keyPrefix}.urn`;
let pubKeyFilename = `${keyPrefix}.public.pem`;
let privateKeyFilename = `${keyPrefix}.private.pem`;
if (keyPrefix == '-') {
urnFilename = process.stdout;
pubKeyFilename = process.stdout;
privateKeyFilename = process.stdout;
}
await writeFile(urnFilename, identity.urn + '\n', 'utf8');
await writeFile(privateKeyFilename, privPem, 'utf8');
await writeFile(pubKeyFilename, pubPem, 'utf8');
if (keyPrefix != '-') {
status(`Saved to: ${keyPrefix}.urn, ${keyPrefix}.private.pem and ${keyPrefix}.public.pem `);
}
}
} catch (err) {
status('Error:', err);
process.exit(1);
}