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