1#!/usr/bin/env node
2// -*- mode: js -*-
3// vim: set filetype=javascript :
4// Copyright 2018 Joyent, Inc.	All rights reserved.
5
6var dashdash = require('dashdash');
7var sshpk = require('../lib/index');
8var fs = require('fs');
9var path = require('path');
10var tty = require('tty');
11var readline = require('readline');
12var getPassword = require('getpass').getPass;
13
14var options = [
15	{
16		names: ['outformat', 't'],
17		type: 'string',
18		help: 'Output format'
19	},
20	{
21		names: ['informat', 'T'],
22		type: 'string',
23		help: 'Input format'
24	},
25	{
26		names: ['file', 'f'],
27		type: 'string',
28		help: 'Input file name (default stdin)'
29	},
30	{
31		names: ['out', 'o'],
32		type: 'string',
33		help: 'Output file name (default stdout)'
34	},
35	{
36		names: ['private', 'p'],
37		type: 'bool',
38		help: 'Produce a private key as output'
39	},
40	{
41		names: ['derive', 'd'],
42		type: 'string',
43		help: 'Output a new key derived from this one, with given algo'
44	},
45	{
46		names: ['identify', 'i'],
47		type: 'bool',
48		help: 'Print key metadata instead of converting'
49	},
50	{
51		names: ['fingerprint', 'F'],
52		type: 'bool',
53		help: 'Output key fingerprint'
54	},
55	{
56		names: ['hash', 'H'],
57		type: 'string',
58		help: 'Hash function to use for key fingeprint with -F'
59	},
60	{
61		names: ['spki', 's'],
62		type: 'bool',
63		help: 'With -F, generates an SPKI fingerprint instead of SSH'
64	},
65	{
66		names: ['comment', 'c'],
67		type: 'string',
68		help: 'Set key comment, if output format supports'
69	},
70	{
71		names: ['help', 'h'],
72		type: 'bool',
73		help: 'Shows this help text'
74	}
75];
76
77if (require.main === module) {
78	var parser = dashdash.createParser({
79		options: options
80	});
81
82	try {
83		var opts = parser.parse(process.argv);
84	} catch (e) {
85		console.error('sshpk-conv: error: %s', e.message);
86		process.exit(1);
87	}
88
89	if (opts.help || opts._args.length > 1) {
90		var help = parser.help({}).trimRight();
91		console.error('sshpk-conv: converts between SSH key formats\n');
92		console.error(help);
93		console.error('\navailable key formats:');
94		console.error('	 - pem, pkcs1	  eg id_rsa');
95		console.error('	 - ssh		  eg id_rsa.pub');
96		console.error('	 - pkcs8	  format you want for openssl');
97		console.error('	 - openssh	  like output of ssh-keygen -o');
98		console.error('	 - rfc4253	  raw OpenSSH wire format');
99		console.error('	 - dnssec	  dnssec-keygen format');
100		console.error('  - putty          PuTTY ppk format');
101		console.error('\navailable fingerprint formats:');
102		console.error('  - hex            colon-separated hex for SSH');
103		console.error('                   straight hex for SPKI');
104		console.error('  - base64         SHA256:* format from OpenSSH');
105		process.exit(1);
106	}
107
108	/*
109	 * Key derivation can only be done on private keys, so use of the -d
110	 * option necessarily implies -p.
111	 */
112	if (opts.derive)
113		opts.private = true;
114
115	var inFile = process.stdin;
116	var inFileName = 'stdin';
117
118	var inFilePath;
119	if (opts.file) {
120		inFilePath = opts.file;
121	} else if (opts._args.length === 1) {
122		inFilePath = opts._args[0];
123	}
124
125	if (inFilePath)
126		inFileName = path.basename(inFilePath);
127
128	try {
129		if (inFilePath) {
130			fs.accessSync(inFilePath, fs.R_OK);
131			inFile = fs.createReadStream(inFilePath);
132		}
133	} catch (e) {
134		ifError(e, 'error opening input file');
135	}
136
137	var outFile = process.stdout;
138
139	try {
140		if (opts.out && !opts.identify) {
141			fs.accessSync(path.dirname(opts.out), fs.W_OK);
142			outFile = fs.createWriteStream(opts.out);
143		}
144	} catch (e) {
145		ifError(e, 'error opening output file');
146	}
147
148	var bufs = [];
149	inFile.on('readable', function () {
150		var data;
151		while ((data = inFile.read()))
152			bufs.push(data);
153	});
154	var parseOpts = {};
155	parseOpts.filename = inFileName;
156	inFile.on('end', function processKey() {
157		var buf = Buffer.concat(bufs);
158		var fmt = 'auto';
159		if (opts.informat)
160			fmt = opts.informat;
161		var f = sshpk.parseKey;
162		if (opts.private)
163			f = sshpk.parsePrivateKey;
164		try {
165			var key = f(buf, fmt, parseOpts);
166		} catch (e) {
167			if (e.name === 'KeyEncryptedError') {
168				getPassword(function (err, pw) {
169					if (err)
170						ifError(err);
171					parseOpts.passphrase = pw;
172					processKey();
173				});
174				return;
175			}
176			ifError(e);
177		}
178
179		if (opts.derive)
180			key = key.derive(opts.derive);
181
182		if (opts.comment)
183			key.comment = opts.comment;
184
185		if (opts.identify) {
186			var kind = 'public';
187			if (sshpk.PrivateKey.isPrivateKey(key))
188				kind = 'private';
189			console.log('%s: a %d bit %s %s key', inFileName,
190			    key.size, key.type.toUpperCase(), kind);
191			if (key.type === 'ecdsa')
192				console.log('ECDSA curve: %s', key.curve);
193			if (key.comment)
194				console.log('Comment: %s', key.comment);
195			console.log('SHA256 fingerprint: ' +
196			    key.fingerprint('sha256').toString());
197			console.log('MD5 fingerprint: ' +
198			    key.fingerprint('md5').toString());
199			console.log('SPKI-SHA256 fingerprint: ' +
200			    key.fingerprint('sha256', 'spki').toString());
201			process.exit(0);
202			return;
203		}
204
205		if (opts.fingerprint) {
206			var hash = opts.hash;
207			var type = opts.spki ? 'spki' : 'ssh';
208			var format = opts.outformat;
209			var fp = key.fingerprint(hash, type).toString(format);
210			outFile.write(fp);
211			outFile.write('\n');
212			outFile.once('drain', function () {
213				process.exit(0);
214			});
215			return;
216		}
217
218		fmt = undefined;
219		if (opts.outformat)
220			fmt = opts.outformat;
221		outFile.write(key.toBuffer(fmt));
222		if (fmt === 'ssh' ||
223		    (!opts.private && fmt === undefined))
224			outFile.write('\n');
225		outFile.once('drain', function () {
226			process.exit(0);
227		});
228	});
229}
230
231function ifError(e, txt) {
232	if (txt)
233		txt = txt + ': ';
234	else
235		txt = '';
236	console.error('sshpk-conv: ' + txt + e.name + ': ' + e.message);
237	if (process.env['DEBUG'] || process.env['V']) {
238		console.error(e.stack);
239		if (e.innerErr)
240			console.error(e.innerErr.stack);
241	}
242	process.exit(1);
243}
244