]> git.saurik.com Git - apple/security.git/blame - SecurityTests/cspxutils/sshKey/sshKey.cpp
Security-57740.31.2.tar.gz
[apple/security.git] / SecurityTests / cspxutils / sshKey / sshKey.cpp
CommitLineData
d8f41ccd
A
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}