1/*
2 * sshKey.cpp - Standalone SSH key parser and converter. Uses libcrypto for
3 *              representing and storing RSA and DSA keys and for
4 *              writing and reading BIGNUMS to/from memory.
5 */
6
7#include <stdlib.h>
8#include <strings.h>
9#include <stdio.h>
10#include <unistd.h>
11#include <CommonCrypto/CommonDigest.h>
12#include <CommonCrypto/CommonCryptor.h>
13#include <security_cdsa_utils/cuFileIo.h>
14#include <security_cdsa_utils/cuEnc64.h>
15#include <openssl/rsa.h>
16#include <openssl/dsa.h>
17#include <CoreFoundation/CoreFoundation.h>
18#include <security_utilities/devrandom.h>
19#include <ctype.h>
20
21#define dprintf(s...)		printf(s)
22
23static void usage(char **argv)
24{
25	printf("usage: %s [options]\n", argv[0]);
26	printf("Options:\n");
27	printf("  -i inFile\n");
28	printf("  -o outFile\n");
29	printf("  -v             -- private key input; default is public\n");
30	printf("  -V             -- private key output; default is public\n");
31	printf("  -d             -- DSA; default is RSA\n");
32	printf("  -r             -- parse & print inFile\n");
33	printf("  -f ssh1|ssh2   -- input format; default = ssh2\n");
34	printf("  -F ssh1|ssh2   -- output format; default = ssh2\n");
35	printf("  -p password\n");
36	printf("  -P             -- no password; private keys in the clear\n");
37	printf("  -c comment\n");
38	exit(1);
39}
40
41static const char *authfile_id_string = "SSH PRIVATE KEY FILE FORMAT 1.1\n";
42
43/* from openssh cipher.h */
44#define SSH_CIPHER_NONE		0	/* no encryption */
45#define SSH_CIPHER_IDEA		1	/* IDEA CFB */
46#define SSH_CIPHER_DES		2	/* DES CBC */
47#define SSH_CIPHER_3DES		3	/* 3DES CBC */
48#define SSH_CIPHER_BROKEN_TSS	4	/* TRI's Simple Stream encryption CBC */
49#define SSH_CIPHER_BROKEN_RC4	5	/* Alleged RC4 */
50#define SSH_CIPHER_BLOWFISH	6
51#define SSH_CIPHER_RESERVED	7
52
53#define SSH2_RSA_HEADER		"ssh-rsa"
54#define SSH2_DSA_HEADER		"ssh-dss"
55
56#pragma mark --- commmon code ---
57
58static uint32_t readUint32(
59	const unsigned char *&cp,		// IN/OUT
60	unsigned &len)					// IN/OUT
61{
62	uint32_t r = 0;
63
64	for(unsigned dex=0; dex<sizeof(uint32_t); dex++) {
65		r <<= 8;
66		r |= *cp++;
67	}
68	len -= 4;
69	return r;
70}
71
72static uint16_t readUint16(
73	const unsigned char *&cp,		// IN/OUT
74	unsigned &len)					// IN/OUT
75{
76	uint16_t r = *cp++;
77	r <<= 8;
78	r |= *cp++;
79	len -= 2;
80	return r;
81}
82
83static void appendUint32(
84	CFMutableDataRef cfOut,
85	uint32_t ui)
86{
87	UInt8 buf[sizeof(uint32_t)];
88
89	for(int dex=(sizeof(uint32_t) - 1); dex>=0; dex--) {
90		buf[dex] = ui & 0xff;
91		ui >>= 8;
92	}
93	CFDataAppendBytes(cfOut, buf, sizeof(uint32_t));
94}
95
96static void appendUint16(
97	CFMutableDataRef cfOut,
98	uint16_t ui)
99{
100	UInt8 buf[sizeof(uint16_t)];
101
102	buf[1] = ui & 0xff;
103	ui >>= 8;
104	buf[0] = ui;
105	CFDataAppendBytes(cfOut, buf, sizeof(uint16_t));
106}
107
108/* parse text as decimal, return BIGNUM */
109static BIGNUM *parseDecimalBn(
110	const unsigned char *cp,
111	unsigned len)
112{
113	for(unsigned dex=0; dex<len; dex++) {
114		char c = *cp;
115		if((c < '0') || (c > '9')) {
116			return NULL;
117		}
118	}
119	char *str = (char *)malloc(len + 1);
120	memmove(str, cp, len);
121	str[len] = '\0';
122	BIGNUM *bn = NULL;
123	BN_dec2bn(&bn, str);
124	free(str);
125	return bn;
126}
127
128/* Read BIGNUM, OpenSSH-1 version */
129static BIGNUM *readBigNum(
130	const unsigned char *&cp,	// IN/OUT
131	unsigned &remLen)			// IN/OUT
132{
133	if(remLen < sizeof(uint16_t)) {
134		dprintf("readBigNum: short record(1)\n");
135		return NULL;
136	}
137	uint16_t numBits = readUint16(cp, remLen);
138	unsigned bytes = (numBits + 7) / 8;
139	if(remLen < bytes) {
140		dprintf("readBigNum: short record(2)\n");
141		return NULL;
142	}
143	BIGNUM *bn = BN_bin2bn(cp, bytes, NULL);
144	if(bn == NULL) {
145		dprintf("readBigNum: BN_bin2bn error\n");
146		return NULL;
147	}
148	cp += bytes;
149	remLen -= bytes;
150	return bn;
151}
152
153/* Write BIGNUM, OpenSSH-1 version */
154static int appendBigNum(
155	CFMutableDataRef cfOut,
156	const BIGNUM *bn)
157{
158	/* 16 bits of numbits */
159	unsigned numBits = BN_num_bits(bn);
160	appendUint16(cfOut, numBits);
161
162	/* serialize the bytes */
163	int numBytes = (numBits + 7) / 8;
164	unsigned char outBytes[numBytes];	// gcc is so cool...
165	int moved = BN_bn2bin(bn, outBytes);
166	if(moved != numBytes) {
167		dprintf("appendBigNum: BN_bn2bin() screwup\n");
168		return -1;
169	}
170	CFDataAppendBytes(cfOut, (UInt8 *)outBytes, numBytes);
171	return 0;
172}
173
174/* read BIGNUM, OpenSSH-2 mpint version */
175static BIGNUM *readBigNum2(
176	const unsigned char *&cp,	// IN/OUT
177	unsigned &remLen)			// IN/OUT
178{
179	if(remLen < 4) {
180		dprintf("readBigNum2: short record(1)\n");
181		return NULL;
182	}
183	uint32_t bytes = readUint32(cp, remLen);
184	if(remLen < bytes) {
185		dprintf("readBigNum2: short record(2)\n");
186		return NULL;
187	}
188	BIGNUM *bn = BN_bin2bn(cp, bytes, NULL);
189	if(bn == NULL) {
190		dprintf("readBigNum2: BN_bin2bn error\n");
191		return NULL;
192	}
193	cp += bytes;
194	remLen -= bytes;
195	return bn;
196}
197
198/* write BIGNUM, OpenSSH v2 format (with a 4-byte byte count) */
199static int appendBigNum2(
200	CFMutableDataRef cfOut,
201	const BIGNUM *bn)
202{
203	if(bn == NULL) {
204		dprintf("appendBigNum2: NULL bn");
205		return -1;
206	}
207	if (BN_is_zero(bn)) {
208		appendUint32(cfOut, 0);
209		return 0;
210	}
211	if(bn->neg) {
212		dprintf("appendBigNum2: negative numbers not supported\n");
213		return -1;
214	}
215	int numBytes = BN_num_bytes(bn);
216	unsigned char buf[numBytes];
217	int moved = BN_bn2bin(bn, buf);
218	if(moved != numBytes) {
219		dprintf("appendBigNum: BN_bn2bin() screwup\n");
220		return -1;
221	}
222	bool appendZero = false;
223	if(buf[0] & 0x80) {
224		/* prepend leading zero to make it positive */
225		appendZero = true;
226		numBytes++;		// to encode the correct 4-byte length
227	}
228	appendUint32(cfOut, (uint32_t)numBytes);
229	if(appendZero) {
230		UInt8 z = 0;
231		CFDataAppendBytes(cfOut, &z, 1);
232		numBytes--;		// to append the correct number of bytes
233	}
234	CFDataAppendBytes(cfOut, buf, numBytes);
235	memset(buf, 0, numBytes);
236	return 0;
237}
238
239/* Write BIGNUM, OpenSSH-1 decimal (public key) version */
240static int appendBigNumDec(
241	CFMutableDataRef cfOut,
242	const BIGNUM *bn)
243{
244	char *buf = BN_bn2dec(bn);
245	if(buf == NULL) {
246		dprintf("appendBigNumDec: BN_bn2dec() error");
247		return -1;
248	}
249	CFDataAppendBytes(cfOut, (const UInt8 *)buf, strlen(buf));
250	OPENSSL_free(buf);
251	return 0;
252}
253
254/* write string, OpenSSH v2 format (with a 4-byte byte count) */
255static void appendString(
256	CFMutableDataRef cfOut,
257	const char *str,
258	unsigned strLen)
259{
260	appendUint32(cfOut, (uint32_t)strLen);
261	CFDataAppendBytes(cfOut, (UInt8 *)str, strLen);
262}
263
264/* skip whitespace */
265static void skipWhite(
266	const unsigned char *&cp,
267	unsigned &bytesLeft)
268{
269	while(bytesLeft != 0) {
270		if(isspace((int)(*cp))) {
271			cp++;
272			bytesLeft--;
273		}
274		else {
275			return;
276		}
277	}
278}
279
280/* find next whitespace or EOF - if EOF, rtn pointer points to one past EOF */
281static const unsigned char *findNextWhite(
282	const unsigned char *cp,
283	unsigned &bytesLeft)
284{
285	while(bytesLeft != 0) {
286		if(isspace((int)(*cp))) {
287			return cp;
288		}
289		cp++;
290		bytesLeft--;
291	}
292	return cp;
293}
294
295
296/*
297 * Calculate d mod{p-1,q-1}
298 * Used when decoding OpenSSH-1 private RSA key.
299 */
300static int
301rsa_generate_additional_parameters(RSA *rsa)
302{
303	BIGNUM *aux;
304	BN_CTX *ctx;
305
306	if((rsa->dmq1 = BN_new()) == NULL) {
307		dprintf("rsa_generate_additional_parameters: BN_new failed");
308		return -1;
309	}
310	if((rsa->dmp1 = BN_new()) == NULL) {
311		dprintf("rsa_generate_additional_parameters: BN_new failed");
312		return -1;
313	}
314	if ((aux = BN_new()) == NULL) {
315		dprintf("rsa_generate_additional_parameters: BN_new failed");
316		return -1;
317	}
318	if ((ctx = BN_CTX_new()) == NULL) {
319		dprintf("rsa_generate_additional_parameters: BN_CTX_new failed");
320		BN_clear_free(aux);
321		return -1;
322	}
323
324	BN_sub(aux, rsa->q, BN_value_one());
325	BN_mod(rsa->dmq1, rsa->d, aux, ctx);
326
327	BN_sub(aux, rsa->p, BN_value_one());
328	BN_mod(rsa->dmp1, rsa->d, aux, ctx);
329
330	BN_clear_free(aux);
331	BN_CTX_free(ctx);
332	return 0;
333}
334
335#pragma mark --- OpenSSH-1 crypto ---
336
337static int ssh1DES3Crypt(
338	unsigned char cipher,
339	bool doEncrypt,
340	const unsigned char *inText,
341	unsigned inTextLen,
342	const char *password,		// C string
343	unsigned char *outText,		// data RETURNED here, caller mallocs
344	unsigned *outTextLen)		// RETURNED
345{
346	switch(cipher) {
347		case SSH_CIPHER_3DES:
348			break;
349		case SSH_CIPHER_NONE:
350			/* cleartext RSA private key, e.g. host key. */
351			memmove(outText, inText, inTextLen);
352			*outTextLen = inTextLen;
353			return 0;
354		default:
355			/* who knows how we're going to figure these out */
356			printf("***Unsupported cipher (%u)\n", cipher);
357			return -1;
358	}
359
360	/* key starts with MD5(password) */
361	unsigned char pwdDigest[CC_MD5_DIGEST_LENGTH];
362	CC_MD5(password, strlen(password), pwdDigest);
363
364	/* three keys from that, like so: */
365	unsigned char k1[kCCKeySizeDES];
366	unsigned char k2[kCCKeySizeDES];
367	unsigned char k3[kCCKeySizeDES];
368	memmove(k1, pwdDigest, kCCKeySizeDES);
369	memmove(k2, pwdDigest + kCCKeySizeDES, kCCKeySizeDES);
370	memmove(k3, pwdDigest, kCCKeySizeDES);
371
372	CCOperation op1_3;
373	CCOperation op2;
374	if(doEncrypt) {
375		op1_3 = kCCEncrypt;
376		op2   = kCCDecrypt;
377	}
378	else {
379		op1_3 = kCCDecrypt;
380		op2   = kCCEncrypt;
381	}
382
383	/* the openssh v1 pseudo triple DES. Each DES pass has its own CBC. */
384	size_t moved = 0;
385
386	CCCryptorStatus cstat = CCCrypt(op1_3, kCCAlgorithmDES,
387		0,						// no padding
388		k1, kCCKeySizeDES,
389		NULL,		// IV
390		inText, inTextLen,
391		outText, inTextLen, &moved);
392	if(cstat) {
393		dprintf("***ssh1DES3Crypt: CCCrypt()(1) returned %u\n", (unsigned)cstat);
394		return -1;
395	}
396	cstat = CCCrypt(op2, kCCAlgorithmDES,
397		0,						// no padding - SSH does that itself
398		k2, kCCKeySizeDES,
399		NULL,		// IV
400		outText, moved,
401		outText, inTextLen, &moved);
402	if(cstat) {
403		dprintf("***ssh1DES3Crypt: CCCrypt()(2) returned %u\n", (unsigned)cstat);
404		return -1;
405	}
406	cstat = CCCrypt(op1_3, kCCAlgorithmDES,
407		0,						// no padding - SSH does that itself
408		k3, kCCKeySizeDES,
409		NULL,		// IV
410		outText, moved,
411		outText, inTextLen, &moved);
412	if(cstat) {
413		dprintf("***ssh1DES3Crypt: CCCrypt()(3) returned %u\n", (unsigned)cstat);
414		return -1;
415	}
416
417	*outTextLen = moved;
418	return 0;
419}
420
421#pragma mark --- OpenSSH-1 decode ---
422
423/* Decode OpenSSH-1 RSA private key */
424static int decodeSSH1RSAPrivKey(
425	const unsigned char *key,
426	unsigned keyLen,
427	char *password,
428	RSA *rsa,						// returned
429	char **comment)					// returned
430{
431	const unsigned char *cp = key;		// running pointer
432	unsigned remLen = keyLen;
433	unsigned len = strlen(authfile_id_string);
434
435	/* length: ID string, NULL, Cipher, 4-byte spare */
436	if(remLen < (len + 6)) {
437		dprintf("decodeSSH1RSAPrivKey: short record(1)\n");
438		return -1;
439	}
440
441	/* ID string plus a NULL */
442	if(memcmp(authfile_id_string, cp, len)) {
443		dprintf("decodeSSH1RSAPrivKey: bad header\n");
444		return -1;
445	}
446	cp += (len + 1);
447	remLen -= (len + 1);
448
449	/* cipher */
450	unsigned char cipherSpec = *cp;
451	switch(cipherSpec) {
452		case SSH_CIPHER_NONE:
453			if(password != NULL) {
454				dprintf("decodeSSH1RSAPrivKey: Attempt to decrypt plaintext key\n");
455				return -1;
456			}
457			break;
458		case SSH_CIPHER_3DES:
459			if(password == NULL) {
460				dprintf("decodeSSH1RSAPrivKey: Encrypted key with no decryptKey\n");
461				return -1;
462			}
463			break;
464		default:
465			/* I hope we don't see any other values here */
466			dprintf("decodeOpenSSHv1PrivKey: unknown cipherSpec (%u)\n", cipherSpec);
467				return -1;
468	}
469
470	/* skip cipher, spares */
471	cp += 5;
472	remLen -= 5;
473
474	/*
475	 * Clear text public key:
476	 * uint32 bits
477	 * bignum n
478	 * bignum e
479	 */
480	if(remLen < sizeof(uint32_t)) {
481		dprintf("decodeSSH1RSAPrivKey: bad len(1)\n");
482		return -1;
483	}
484	/* skip over keybits */
485	readUint32(cp, remLen);
486	rsa->n = readBigNum(cp, remLen);
487	if(rsa->n == NULL) {
488		dprintf("decodeSSH1RSAPrivKey: error decoding n\n");
489		return -1;
490	}
491	rsa->e = readBigNum(cp, remLen);
492	if(rsa->e == NULL) {
493		dprintf("decodeSSH1RSAPrivKey: error decoding e\n");
494		return -1;
495	}
496
497	/* comment string: 4-byte length and the string w/o NULL */
498	if(remLen < sizeof(uint32_t)) {
499		dprintf("decodeSSH1RSAPrivKey: bad len(2)\n");
500		return -1;
501	}
502	uint32_t commentLen = readUint32(cp, remLen);
503	if(commentLen > remLen) {
504		dprintf("decodeSSH1RSAPrivKey: bad len(3)\n");
505		return -1;
506	}
507	*comment = (char *)malloc(commentLen + 1);
508	memmove(*comment, cp, commentLen);
509	(*comment)[commentLen] = '\0';
510	cp += commentLen;
511	remLen -= commentLen;
512
513	/* everything that remains is ciphertext */
514	unsigned char ptext[remLen];
515	unsigned ptextLen = 0;
516	if(ssh1DES3Crypt(cipherSpec, false, cp, remLen, password, ptext, &ptextLen)) {
517		dprintf("decodeSSH1RSAPrivKey: decrypt error\n");
518		return -1;
519	}
520	/* subsequent errors to errOut: */
521
522	int ourRtn = 0;
523
524	/* plaintext contents:
525
526	[0-1]		-- random bytes
527	[2-3]		-- copy of [01] for passphrase validity checking
528	buffer_put_bignum(d)
529	buffer_put_bignum(iqmp)
530	buffer_put_bignum(q)
531	buffer_put_bignum(p)
532	pad to block size
533	*/
534	cp = ptext;
535	remLen = ptextLen;
536	if(remLen < 4) {
537		dprintf("decodeSSH1RSAPrivKey: bad len(4)\n");
538		ourRtn = -1;
539		goto errOut;
540	}
541	if((cp[0] != cp[2]) || (cp[1] != cp[3])) {
542		/* decrypt fail */
543		dprintf("decodeSSH1RSAPrivKey: check byte error\n");
544		ourRtn = -1;
545		goto errOut;
546	}
547	cp += 4;
548	remLen -= 4;
549
550	/* remainder comprises private portion of RSA key */
551	rsa->d = readBigNum(cp, remLen);
552	if(rsa->d == NULL) {
553		dprintf("decodeSSH1RSAPrivKey: error decoding d\n");
554		return -1;
555	}
556	rsa->iqmp = readBigNum(cp, remLen);
557	if(rsa->iqmp == NULL) {
558		dprintf("decodeSSH1RSAPrivKey: error decoding iqmp\n");
559		return -1;
560	}
561	rsa->q = readBigNum(cp, remLen);
562	if(rsa->q == NULL) {
563		dprintf("decodeSSH1RSAPrivKey: error decoding q\n");
564		return -1;
565	}
566	rsa->p = readBigNum(cp, remLen);
567	if(rsa->p == NULL) {
568		dprintf("decodeSSH1RSAPrivKey: error decoding p\n");
569		return -1;
570	}
571
572	/* calculate d mod{p-1,q-1} */
573	ourRtn = rsa_generate_additional_parameters(rsa);
574
575errOut:
576	memset(ptext, 0, ptextLen);
577	return ourRtn;
578}
579
580/* Decode OpenSSH-1 RSA public key */
581static int decodeSSH1RSAPubKey(
582	const unsigned char *key,
583	unsigned keyLen,
584	RSA *rsa,						// returned
585	char **comment)					// returned
586{
587	const unsigned char *cp = key;		// running pointer
588	unsigned remLen = keyLen;
589
590	*comment = NULL;
591	skipWhite(cp, remLen);
592
593	/*
594	 * cp points to start of size_in_bits in ASCII decimal' we really don't care about
595	 * this field. Find next space.
596	 */
597	cp = findNextWhite(cp, remLen);
598	if(remLen == 0) {
599		dprintf("decodeSSH1RSAPubKey: short key (1)\n");
600		return -1;
601	}
602	skipWhite(cp, remLen);
603	if(remLen == 0) {
604		dprintf("decodeSSH1RSAPubKey: short key (2)\n");
605		return -1;
606	}
607
608	/*
609	 * cp points to start of e
610	 */
611	const unsigned char *ep = findNextWhite(cp, remLen);
612	if(remLen == 0) {
613		dprintf("decodeSSH1RSAPubKey: short key (3)\n");
614		return -1;
615	}
616	unsigned len = ep - cp;
617	rsa->e = parseDecimalBn(cp, len);
618	if(rsa->e == NULL) {
619		return -1;
620	}
621	cp += len;
622	remLen -= len;
623
624	skipWhite(cp, remLen);
625	if(remLen == 0) {
626		dprintf("decodeSSH1RSAPubKey: short key (4)\n");
627		return -1;
628	}
629
630	/* cp points to start of n */
631	ep = findNextWhite(cp, remLen);
632	len = ep - cp;
633	rsa->n = parseDecimalBn(cp, len);
634	if(rsa->n == NULL) {
635		return -1;
636	}
637	cp += len;
638	remLen -= len;
639	skipWhite(cp, remLen);
640	if(remLen == 0) {
641		/* no comment; we're done */
642		return 0;
643	}
644
645	ep = findNextWhite(cp, remLen);
646	len = ep - cp;
647	if(len == 0) {
648		return 0;
649	}
650	*comment = (char *)malloc(len + 1);
651	memmove(*comment, cp, len);
652	if((*comment)[len - 1] == '\n') {
653		/* normal case closes with a newline, not part of the comment */
654		len--;
655	}
656	(*comment)[len] = '\0';
657	return 0;
658
659}
660
661#pragma mark --- OpenSSH-1 encode ---
662
663/* Encode OpenSSH-1 RSA private key */
664static int encodeSSH1RSAPrivKey(
665	RSA *rsa,
666	const char *password,
667	const char *comment,
668	unsigned char **outKey,		// mallocd and RETURNED
669	unsigned *outKeyLen) 		// RETURNED
670{
671	CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
672
673	/* ID string including NULL */
674	CFDataAppendBytes(cfOut, (const UInt8 *)authfile_id_string, strlen(authfile_id_string) + 1);
675
676	/* one byte cipher */
677	UInt8 cipherSpec = SSH_CIPHER_3DES;
678	CFDataAppendBytes(cfOut, &cipherSpec, 1);
679
680	/* spares */
681	UInt8 spares[4] = {0};
682	CFDataAppendBytes(cfOut, spares, 4);
683
684	/*
685	 * Clear text public key:
686	 * uint32 bits
687	 * bignum n
688	 * bignum e
689	 */
690	uint32_t keybits = RSA_size(rsa) * 8;
691	appendUint32(cfOut, keybits);
692	appendBigNum(cfOut, rsa->n);
693	appendBigNum(cfOut, rsa->e);
694
695	/* comment string: 4-byte length and the string w/o NULL */
696	if(comment) {
697		uint32_t len = strlen(comment);
698		appendUint32(cfOut, len);
699		CFDataAppendBytes(cfOut, (const UInt8 *)comment, len);
700	}
701
702	/*
703	 * Remainder is encrypted, consisting of
704	 *
705	 * [0-1]		-- random bytes
706	 * [2-3]		-- copy of [01] for passphrase validity checking
707	 * buffer_put_bignum(d)
708	 * buffer_put_bignum(iqmp)
709	 * buffer_put_bignum(q)
710	 * buffer_put_bignum(p)
711	 * pad to block size
712	 */
713	CFMutableDataRef ptext = CFDataCreateMutable(NULL, 0);
714
715	/* [0..3] check bytes */
716	UInt8 checkBytes[4];
717	DevRandomGenerator rng = DevRandomGenerator();
718	rng.random(checkBytes, 2);
719	checkBytes[2] = checkBytes[0];
720	checkBytes[3] = checkBytes[1];
721	CFDataAppendBytes(ptext, checkBytes, 4);
722
723	/* d, iqmp, q, p */
724	appendBigNum(ptext, rsa->d);
725	appendBigNum(ptext, rsa->iqmp);
726	appendBigNum(ptext, rsa->q);
727	appendBigNum(ptext, rsa->p);
728
729	/* encrypt it */
730	unsigned ptextLen = CFDataGetLength(ptext);
731	unsigned padding = 0;
732	unsigned rem = ptextLen & 0x7;
733	if(rem) {
734		padding = 8 - rem;
735	}
736	UInt8 padByte = 0;
737	for(unsigned dex=0; dex<padding; dex++) {
738		CFDataAppendBytes(ptext, &padByte, 1);
739	}
740	ptextLen = CFDataGetLength(ptext);
741	unsigned char ctext[ptextLen];
742	unsigned ctextLen;
743	int ourRtn = ssh1DES3Crypt(SSH_CIPHER_3DES, true,
744		(unsigned char *)CFDataGetBytePtr(ptext), ptextLen,
745		password,
746		ctext, &ctextLen);
747	if(ourRtn != 0) {
748		goto errOut;
749	}
750
751	/* appended encrypted portion */
752	CFDataAppendBytes(cfOut, ctext, ctextLen);
753	*outKeyLen = (unsigned)CFDataGetLength(cfOut);
754	*outKey = (unsigned char *)malloc(*outKeyLen);
755	memmove(*outKey, CFDataGetBytePtr(cfOut), *outKeyLen);
756errOut:
757	CFRelease(cfOut);
758	/* it would be proper to zero out ptext here, but we can't do that to a CFData */
759	CFRelease(ptext);
760	return ourRtn;
761}
762
763/* Encode OpenSSH-1 RSA public key */
764static int encodeSSH1RSAPubKey(
765	RSA *rsa,
766	const char *comment,
767	unsigned char **outKey,		// mallocd and RETURNED
768	unsigned *outKeyLen) 		// RETURNED
769{
770	CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
771
772	/*
773	 * Format is
774	 * num_bits in decimal
775	 * <space>
776	 * e, bignum in decimal
777	 * <space>
778	 * n, bignum in decimal
779	 * <space>
780	 * optional comment
781	 * newline
782	 */
783	int ourRtn = 0;
784	unsigned numBits = BN_num_bits(rsa->n);
785	char bitString[20];
786	UInt8 c = ' ';
787
788	snprintf(bitString, sizeof(bitString), "%u ", numBits);
789	CFDataAppendBytes(cfOut, (const UInt8 *)bitString, strlen(bitString));
790	if(appendBigNumDec(cfOut, rsa->e)) {
791		ourRtn = -1;
792		goto errOut;
793	}
794	CFDataAppendBytes(cfOut, &c, 1);
795	if(appendBigNumDec(cfOut, rsa->n)) {
796		ourRtn = -1;
797		goto errOut;
798	}
799	if(comment != NULL) {
800		CFDataAppendBytes(cfOut, &c, 1);
801		CFDataAppendBytes(cfOut, (UInt8 *)comment, strlen(comment));
802	}
803	c = '\n';
804	CFDataAppendBytes(cfOut, &c, 1);
805	*outKeyLen = CFDataGetLength(cfOut);
806	*outKey = (unsigned char *)malloc(*outKeyLen);
807	memmove(*outKey, CFDataGetBytePtr(cfOut), *outKeyLen);
808errOut:
809	CFRelease(cfOut);
810	return ourRtn;
811}
812
813#pragma mark --- OpenSSH-2 public key decode ---
814
815/*
816 * Decode components from an SSHv2 public key.
817 * Also verifies the leading header, e.g. "ssh-rsa".
818 * The returned decodedBlob is algorithm-specific.
819 */
820static int parseSSH2PubKey(
821	const unsigned char *key,
822	unsigned keyLen,
823	const char *header,				// SSH2_RSA_HEADER, SSH2_DSA_HEADER
824	unsigned char **decodedBlob,	// mallocd and RETURNED
825	unsigned *decodedBlobLen,		// RETURNED
826	char **comment)					// optionally mallocd and RETURNED, NULL terminated
827{
828	unsigned len = strlen(header);
829	const unsigned char *endOfKey = key + keyLen;
830	*decodedBlob = NULL;
831	*comment = NULL;
832
833	/* ID string plus at least one space */
834	if(keyLen < (len + 1)) {
835		dprintf("parseSSH2PubKey: short record(1)\n");
836		return -1;
837	}
838
839	if(memcmp(header, key, len)) {
840		dprintf("parseSSH2PubKey: bad header (1)\n");
841		return -1;
842	}
843	key += len;
844	if(*key++ != ' ') {
845		dprintf("parseSSH2PubKey: bad header (2)\n");
846		return -1;
847	}
848	keyLen -= (len + 1);
849
850	/* key points to first whitespace after header */
851	skipWhite(key, keyLen);
852	if(keyLen == 0) {
853		dprintf("parseSSH2PubKey: short key\n");
854		return -1;
855	}
856
857	/* key is start of base64 blob */
858	const unsigned char *encodedBlob = key;
859	const unsigned char *endBlob = findNextWhite(key, keyLen);
860	unsigned encodedBlobLen = endBlob - encodedBlob;
861
862	/* decode base 64 */
863	*decodedBlob = cuDec64(encodedBlob, encodedBlobLen, decodedBlobLen);
864	if(*decodedBlob == NULL) {
865		dprintf("parseSSH2PubKey: base64 decode error\n");
866		return -1;
867	}
868
869	/* skip over the encoded blob and possible whitespace after it */
870	key = endBlob;
871	keyLen = endOfKey - endBlob;
872	skipWhite(key, keyLen);
873	if(keyLen == 0) {
874		/* nothing remains, no comment, no error */
875		return 0;
876	}
877
878	/* optional comment */
879	*comment = (char *)malloc(keyLen + 1);
880	memmove(*comment, key, keyLen);
881	if((*comment)[keyLen - 1] == '\n') {
882		/* normal case closes with a newline, not part of the comment */
883		keyLen--;
884	}
885	(*comment)[keyLen] = '\0';
886	return 0;
887}
888
889static int decodeSSH2RSAPubKey(
890	const unsigned char *key,
891	unsigned keyLen,
892	RSA *rsa,						// returned
893	char **comment)					// returned
894{
895	/*
896	 * Verify header
897	 * get base64-decoded blob plus optional comment
898	 */
899	unsigned char *decodedBlob = NULL;
900	unsigned decodedBlobLen = 0;
901	if(parseSSH2PubKey(key, keyLen, SSH2_RSA_HEADER, &decodedBlob, &decodedBlobLen, comment)) {
902		return -1;
903	}
904	/* subsequent errors to errOut: */
905
906	/*
907	 * The inner base64-decoded blob, consisting of
908	 * ssh-rsa
909	 * e
910	 * n
911	 */
912	uint32_t decLen;
913	unsigned len;
914	int ourRtn = 0;
915
916	key = decodedBlob;
917	keyLen = decodedBlobLen;
918	if(keyLen < 12) {
919		/* three length fields at least */
920		dprintf("decodeSSH2RSAPubKey: short record(2)\n");
921		ourRtn = -1;
922		goto errOut;
923	}
924	decLen = readUint32(key, keyLen);
925	len = strlen(SSH2_RSA_HEADER);
926	if(decLen != len) {
927		dprintf("decodeSSH2RSAPubKey: bad header (2)\n");
928		ourRtn = -1;
929		goto errOut;
930	}
931	if(memcmp(SSH2_RSA_HEADER, key, len)) {
932		dprintf("decodeSSH2RSAPubKey: bad header (1)\n");
933		return -1;
934	}
935	key += len;
936	keyLen -= len;
937
938	rsa->e = readBigNum2(key, keyLen);
939	if(rsa->e == NULL) {
940		ourRtn = -1;
941		goto errOut;
942	}
943	rsa->n = readBigNum2(key, keyLen);
944	if(rsa->n == NULL) {
945		ourRtn = -1;
946		goto errOut;
947	}
948
949errOut:
950	free(decodedBlob);
951	return ourRtn;
952}
953
954static int decodeSSH2DSAPubKey(
955	const unsigned char *key,
956	unsigned keyLen,
957	DSA *dsa,						// returned
958	char **comment)					// returned
959{
960	/*
961	 * Verify header
962	 * get base64-decoded blob plus optional comment
963	 */
964	unsigned char *decodedBlob = NULL;
965	unsigned decodedBlobLen = 0;
966	if(parseSSH2PubKey(key, keyLen, SSH2_DSA_HEADER, &decodedBlob, &decodedBlobLen, comment)) {
967		return -1;
968	}
969	/* subsequent errors to errOut: */
970
971	/*
972	 * The inner base64-decoded blob, consisting of
973	 * ssh-dss
974	 * p
975	 * q
976	 * g
977	 * pub_key
978	 */
979	uint32_t decLen;
980	int ourRtn = 0;
981	unsigned len;
982
983	key = decodedBlob;
984	keyLen = decodedBlobLen;
985	if(keyLen < 20) {
986		/* five length fields at least */
987		dprintf("decodeSSH2DSAPubKey: short record(2)\n");
988		ourRtn = -1;
989		goto errOut;
990	}
991	decLen = readUint32(key, keyLen);
992	len = strlen(SSH2_DSA_HEADER);
993	if(decLen != len) {
994		dprintf("decodeSSH2DSAPubKey: bad header (2)\n");
995		ourRtn = -1;
996		goto errOut;
997	}
998	if(memcmp(SSH2_DSA_HEADER, key, len)) {
999		dprintf("decodeSSH2DSAPubKey: bad header (1)\n");
1000		return -1;
1001	}
1002	key += len;
1003	keyLen -= len;
1004
1005	dsa->p = readBigNum2(key, keyLen);
1006	if(dsa->p == NULL) {
1007		ourRtn = -1;
1008		goto errOut;
1009	}
1010	dsa->q = readBigNum2(key, keyLen);
1011	if(dsa->q == NULL) {
1012		ourRtn = -1;
1013		goto errOut;
1014	}
1015	dsa->g = readBigNum2(key, keyLen);
1016	if(dsa->g == NULL) {
1017		ourRtn = -1;
1018		goto errOut;
1019	}
1020	dsa->pub_key = readBigNum2(key, keyLen);
1021	if(dsa->pub_key == NULL) {
1022		ourRtn = -1;
1023		goto errOut;
1024	}
1025
1026errOut:
1027	free(decodedBlob);
1028	return ourRtn;
1029}
1030
1031#pragma mark --- OpenSSH-2 public key encode ---
1032
1033static int encodeSSH2RSAPubKey(
1034	RSA *rsa,
1035	const char *comment,
1036	unsigned char **outKey,		// mallocd and RETURNED
1037	unsigned *outKeyLen) 		// RETURNED
1038{
1039	unsigned char *b64 = NULL;
1040	unsigned b64Len;
1041	UInt8 c;
1042
1043	/*
1044	 * First, the inner base64-encoded blob, consisting of
1045	 * ssh-rsa
1046	 * e
1047	 * n
1048	 */
1049	CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
1050	int ourRtn = 0;
1051	appendString(cfOut, SSH2_RSA_HEADER, strlen(SSH2_RSA_HEADER));
1052	ourRtn = appendBigNum2(cfOut, rsa->e);
1053	if(ourRtn) {
1054		goto errOut;
1055	}
1056	ourRtn = appendBigNum2(cfOut, rsa->n);
1057	if(ourRtn) {
1058		goto errOut;
1059	}
1060
1061	/* base64 encode that */
1062	b64 = cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut), &b64Len);
1063
1064	/* cuEnc64 added newline and NULL, which we really don't want */
1065	b64Len -= 2;
1066
1067	/* Now start over, dropping that base64 into a public blob. */
1068	CFDataSetLength(cfOut, 0);
1069	CFDataAppendBytes(cfOut, (UInt8 *)SSH2_RSA_HEADER, strlen(SSH2_RSA_HEADER));
1070	c = ' ';
1071	CFDataAppendBytes(cfOut, &c, 1);
1072	CFDataAppendBytes(cfOut, b64, b64Len);
1073
1074	/* optional comment */
1075	if(comment) {
1076		CFDataAppendBytes(cfOut, &c, 1);
1077		CFDataAppendBytes(cfOut, (UInt8 *)comment, strlen(comment));
1078	}
1079
1080	/* finish it with a newline */
1081	c = '\n';
1082	CFDataAppendBytes(cfOut, &c, 1);
1083
1084	*outKeyLen = (unsigned)CFDataGetLength(cfOut);
1085	*outKey = (unsigned char *)malloc(*outKeyLen);
1086	memmove(*outKey, CFDataGetBytePtr(cfOut), *outKeyLen);
1087
1088errOut:
1089	CFRelease(cfOut);
1090	if(b64) {
1091		free(b64);
1092	}
1093	return ourRtn;
1094}
1095
1096static int encodeSSH2DSAPubKey(
1097	DSA *dsa,
1098	const char *comment,
1099	unsigned char **outKey,		// mallocd and RETURNED
1100	unsigned *outKeyLen) 		// RETURNED
1101{
1102	unsigned char *b64 = NULL;
1103	unsigned b64Len;
1104	UInt8 c;
1105
1106	/*
1107	 * First, the inner base64-encoded blob, consisting of
1108	 * ssh-dss
1109	 * p
1110	 * q
1111	 * g
1112	 * pub_key
1113	 */
1114	CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
1115	int ourRtn = 0;
1116	appendString(cfOut, SSH2_DSA_HEADER, strlen(SSH2_DSA_HEADER));
1117	ourRtn = appendBigNum2(cfOut, dsa->p);
1118	if(ourRtn) {
1119		goto errOut;
1120	}
1121	ourRtn = appendBigNum2(cfOut, dsa->q);
1122	if(ourRtn) {
1123		goto errOut;
1124	}
1125	ourRtn = appendBigNum2(cfOut, dsa->g);
1126	if(ourRtn) {
1127		goto errOut;
1128	}
1129	ourRtn = appendBigNum2(cfOut, dsa->pub_key);
1130	if(ourRtn) {
1131		goto errOut;
1132	}
1133
1134	/* base64 encode that */
1135	b64 = cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut), &b64Len);
1136
1137	/* cuEnc64 added newline and NULL, which we really don't want */
1138	b64Len -= 2;
1139
1140	/* Now start over, dropping that base64 into a public blob. */
1141	CFDataSetLength(cfOut, 0);
1142	CFDataAppendBytes(cfOut, (UInt8 *)SSH2_DSA_HEADER, strlen(SSH2_DSA_HEADER));
1143	c = ' ';
1144	CFDataAppendBytes(cfOut, &c, 1);
1145	CFDataAppendBytes(cfOut, b64, b64Len);
1146
1147	/* optional comment */
1148	if(comment) {
1149		CFDataAppendBytes(cfOut, &c, 1);
1150		CFDataAppendBytes(cfOut, (UInt8 *)comment, strlen(comment));
1151	}
1152
1153	/* finish it with a newline */
1154	c = '\n';
1155	CFDataAppendBytes(cfOut, &c, 1);
1156
1157	*outKeyLen = (unsigned)CFDataGetLength(cfOut);
1158	*outKey = (unsigned char *)malloc(*outKeyLen);
1159	memmove(*outKey, CFDataGetBytePtr(cfOut), *outKeyLen);
1160
1161errOut:
1162	CFRelease(cfOut);
1163	if(b64) {
1164		free(b64);
1165	}
1166	return ourRtn;
1167}
1168
1169
1170#pragma mark --- print RSA/DSA keys ---
1171
1172static void printBNLong(
1173	BN_ULONG bnl)
1174{
1175	/* for now assume it's 32 bits */
1176	unsigned i = bnl >> 24;
1177	printf("%02X ", i);
1178	i = (bnl >> 16) & 0xff;
1179	printf("%02X ", i);
1180	i = (bnl >> 8) & 0xff;
1181	printf("%02X ", i);
1182	i = bnl & 0xff;
1183	printf("%02X ", i);
1184}
1185
1186static void printBN(
1187	const char *label,
1188	BIGNUM *bn)
1189{
1190	printf("%s: %d bits: bn->top %d: ", label, BN_num_bits(bn), bn->top);
1191	for(int dex=bn->top-1; dex>=0; dex--) {
1192		printBNLong(bn->d[dex]);
1193	}
1194	printf("\n");
1195}
1196static void printRSA(
1197	RSA *rsa)
1198{
1199	if(rsa->n) {
1200		printBN("   n", rsa->n);
1201	}
1202	if(rsa->e) {
1203		printBN("   e", rsa->e);
1204	}
1205	if(rsa->d) {
1206		printBN("   d", rsa->d);
1207	}
1208	if(rsa->p) {
1209		printBN("   p", rsa->p);
1210	}
1211	if(rsa->q) {
1212		printBN("   q", rsa->q);
1213	}
1214	if(rsa->dmp1) {
1215		printBN("dmp1", rsa->dmp1);
1216	}
1217	if(rsa->dmq1) {
1218		printBN("dmq1", rsa->dmq1);
1219	}
1220	if(rsa->iqmp) {
1221		printBN("iqmp", rsa->iqmp);
1222	}
1223}
1224
1225/* only public keys here */
1226static void printDSA(
1227	DSA *dsa)
1228{
1229	if(dsa->p) {
1230		printBN("   p", dsa->p);
1231	}
1232	if(dsa->q) {
1233		printBN("   q", dsa->q);
1234	}
1235	if(dsa->g) {
1236		printBN("   g", dsa->g);
1237	}
1238	if(dsa->pub_key) {
1239		printBN(" pub", dsa->pub_key);
1240	}
1241}
1242
1243/* parse format string, returns nonzero on error */
1244static int parseFormat(
1245	const char *formatStr,
1246	bool *isSSH1)
1247{
1248	if(!strcmp(formatStr, "ssh1")) {
1249		*isSSH1 = true;
1250		return 0;
1251	}
1252	else if(!strcmp(formatStr, "ssh2")) {
1253		*isSSH1 = false;
1254		return 0;
1255	}
1256	else {
1257		return -1;
1258	}
1259}
1260
1261#pragma mark --- main ---
1262
1263/* parse format string */
1264int main(int argc, char **argv)
1265{
1266	char *inFile = NULL;
1267	char *outFile = NULL;
1268	bool privKeyIn = false;
1269	bool privKeyOut = false;
1270	char *password = NULL;
1271	char *comment = NULL;
1272	bool doPrint = false;
1273	bool isDSA = false;
1274	bool inputSSH1 = false;
1275	bool outputSSH1 = false;
1276	bool clearPrivKeys = false;
1277
1278	int ourRtn = 0;
1279
1280	extern char *optarg;
1281	int arg;
1282	while ((arg = getopt(argc, argv, "i:o:vVdrf:F:p:Pc:h")) != -1) {
1283		switch (arg) {
1284			case 'i':
1285				inFile = optarg;
1286				break;
1287			case 'o':
1288				outFile = optarg;
1289				break;
1290			case 'v':
1291				privKeyIn = true;
1292				break;
1293			case 'V':
1294				privKeyOut = true;
1295				break;
1296			case 'd':
1297				isDSA = true;
1298				break;
1299			case 'r':
1300				doPrint = true;
1301				break;
1302			case 'f':
1303				if(parseFormat(optarg, &inputSSH1)) {
1304					usage(argv);
1305				}
1306				break;
1307			case 'F':
1308				if(parseFormat(optarg, &outputSSH1)) {
1309					usage(argv);
1310				}
1311				break;
1312			case 'p':
1313				password = optarg;
1314				break;
1315			case 'P':
1316				clearPrivKeys = true;
1317				break;
1318			case 'c':
1319				comment = optarg;
1320				break;
1321			case 'h':
1322			default:
1323				usage(argv);
1324		}
1325	}
1326
1327	if(inFile == NULL) {
1328		printf("***You must specify an input file.\n");
1329		usage(argv);
1330	}
1331	if((privKeyIn && !inputSSH1) || (privKeyOut && !outputSSH1)) {
1332		printf("***Private keys in SSH2 format are handled elsewhere - Wrapped OpenSSL.\n");
1333		exit(1);
1334	}
1335	if((privKeyIn || privKeyOut) && (password == NULL) & !clearPrivKeys) {
1336		printf("***Private key handling requires a password or the -P option.\n");
1337		usage(argv);
1338	}
1339	unsigned char *inKey = NULL;
1340	unsigned inKeyLen = 0;
1341	if(readFile(inFile, &inKey, &inKeyLen)) {
1342		printf("Error reading %s. Aborting.\n", inFile);
1343		exit(1);
1344	}
1345
1346	RSA *rsa = NULL;
1347	DSA *dsa = NULL;
1348
1349	/* parse incoming key */
1350	if(isDSA) {
1351		if(inputSSH1) {
1352			printf("***SSHv1 did not support DSA keys.\n");
1353			exit(1);
1354		}
1355		/* already verified that this is not SSH2 & priv (Wrapped OpenSSL) */
1356		dsa = DSA_new();
1357		if(decodeSSH2DSAPubKey(inKey, inKeyLen, dsa, &comment)) {
1358			printf("***Error decoding SSH2 DSA public key.\n");
1359			exit(1);
1360		}
1361	}
1362	else {
1363		rsa = RSA_new();
1364		if(privKeyIn) {
1365			/* already verified that this is SSH1 (SSH2 is Wrapped OpenSSL) */
1366			if(decodeSSH1RSAPrivKey(inKey, inKeyLen, password, rsa, &comment)) {
1367				printf("***Error decoding SSH1 RSA Private key.\n");
1368				exit(1);
1369			}
1370		}
1371		else {
1372			if(inputSSH1) {
1373				if(decodeSSH1RSAPubKey(inKey, inKeyLen, rsa, &comment)) {
1374					printf("***Error decoding SSH1 RSA Public key.\n");
1375					exit(1);
1376				}
1377			}
1378			else {
1379				if(decodeSSH2RSAPubKey(inKey, inKeyLen, rsa, &comment)) {
1380					printf("***Error decoding SSH2 RSA Public key.\n");
1381					exit(1);
1382				}
1383			}
1384		}
1385	}
1386
1387	/* optionally display the key */
1388	if(doPrint) {
1389		if(isDSA) {
1390			printf("DSA key:\n");
1391			printDSA(dsa);
1392			printf("Comment: %s\n", comment);
1393		}
1394		else {
1395			printf("RSA key:\n");
1396			printRSA(rsa);
1397			printf("Comment: %s\n", comment);
1398		}
1399	}
1400
1401	/* optionally convert to (optionally different) output format */
1402
1403	if(outFile) {
1404		unsigned char *outKey = NULL;
1405		unsigned outKeyLen = 0;
1406
1407		if(isDSA) {
1408			if(outputSSH1 || privKeyOut) {
1409				printf("***DSA: Only public SSHv2 keys allowed.\n");
1410				exit(1);
1411			}
1412			if(encodeSSH2DSAPubKey(dsa, comment, &outKey, &outKeyLen)) {
1413				printf("***Error encoding DSA public key.\n");
1414				exit(1);
1415			}
1416		}
1417		else {
1418			if(privKeyOut) {
1419				/* already verified that this is SSH1 (SSH2 is Wrapped OpenSSL) */
1420				if(encodeSSH1RSAPrivKey(rsa, password, comment, &outKey, &outKeyLen)) {
1421					printf("***Error encoding RSA private key.\n");
1422					exit(1);
1423				}
1424			}
1425			else {
1426				if(outputSSH1) {
1427					if(encodeSSH1RSAPubKey(rsa, comment, &outKey, &outKeyLen)) {
1428						printf("***Error encoding RSA public key.\n");
1429						exit(1);
1430					}
1431				}
1432				else {
1433					if(encodeSSH2RSAPubKey(rsa, comment, &outKey, &outKeyLen)) {
1434						printf("***Error encoding RSA public key.\n");
1435						exit(1);
1436					}
1437				}
1438			}	/* RSA public */
1439		}	/* RSA */
1440
1441		if(writeFile(outFile, outKey, outKeyLen)) {
1442			printf("***Error writing to %s.\n", outFile);
1443			ourRtn = -1;
1444		}
1445		else {
1446			printf("...wrote %u bytes to %s.\n", outKeyLen, outFile);
1447		}
1448		free(outKey);
1449	}
1450	else if(!doPrint) {
1451		printf("...parsed a key but you didn't ask me to do anything with it.\n");
1452	}
1453	if(rsa) {
1454		RSA_free(rsa);
1455	}
1456	if(dsa) {
1457		DSA_free(dsa);
1458	}
1459
1460	return 0;
1461}
1462