]> git.saurik.com Git - apple/security.git/blobdiff - SecurityTests/cspxutils/sshKey/sshKey.cpp
Security-57031.1.35.tar.gz
[apple/security.git] / SecurityTests / cspxutils / sshKey / sshKey.cpp
diff --git a/SecurityTests/cspxutils/sshKey/sshKey.cpp b/SecurityTests/cspxutils/sshKey/sshKey.cpp
new file mode 100644 (file)
index 0000000..6b18122
--- /dev/null
@@ -0,0 +1,1461 @@
+/*
+ * sshKey.cpp - Standalone SSH key parser and converter. Uses libcrypto for 
+ *              representing and storing RSA and DSA keys and for 
+ *              writing and reading BIGNUMS to/from memory.
+ */
+#include <stdlib.h>
+#include <strings.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <CommonCrypto/CommonDigest.h>
+#include <CommonCrypto/CommonCryptor.h>
+#include <security_cdsa_utils/cuFileIo.h>
+#include <security_cdsa_utils/cuEnc64.h>
+#include <openssl/rsa.h>
+#include <openssl/dsa.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <security_utilities/devrandom.h>
+#include <ctype.h>
+
+#define dprintf(s...)          printf(s)
+
+static void usage(char **argv)
+{
+       printf("usage: %s [options]\n", argv[0]);
+       printf("Options:\n");
+       printf("  -i inFile\n");
+       printf("  -o outFile\n");
+       printf("  -v             -- private key input; default is public\n");
+       printf("  -V             -- private key output; default is public\n");
+       printf("  -d             -- DSA; default is RSA\n");
+       printf("  -r             -- parse & print inFile\n");
+       printf("  -f ssh1|ssh2   -- input format; default = ssh2\n");
+       printf("  -F ssh1|ssh2   -- output format; default = ssh2\n");
+       printf("  -p password\n");
+       printf("  -P             -- no password; private keys in the clear\n");
+       printf("  -c comment\n");
+       exit(1);
+}
+
+static const char *authfile_id_string = "SSH PRIVATE KEY FILE FORMAT 1.1\n";
+
+/* from openssh cipher.h */
+#define SSH_CIPHER_NONE                0       /* no encryption */
+#define SSH_CIPHER_IDEA                1       /* IDEA CFB */
+#define SSH_CIPHER_DES         2       /* DES CBC */
+#define SSH_CIPHER_3DES                3       /* 3DES CBC */
+#define SSH_CIPHER_BROKEN_TSS  4       /* TRI's Simple Stream encryption CBC */
+#define SSH_CIPHER_BROKEN_RC4  5       /* Alleged RC4 */
+#define SSH_CIPHER_BLOWFISH    6
+#define SSH_CIPHER_RESERVED    7
+
+#define SSH2_RSA_HEADER                "ssh-rsa"
+#define SSH2_DSA_HEADER                "ssh-dss"
+
+#pragma mark --- commmon code --- 
+
+static uint32_t readUint32(
+       const unsigned char *&cp,               // IN/OUT
+       unsigned &len)                                  // IN/OUT 
+{
+       uint32_t r = 0;
+       
+       for(unsigned dex=0; dex<sizeof(uint32_t); dex++) {
+               r <<= 8;
+               r |= *cp++;
+       }
+       len -= 4;
+       return r;
+}
+
+static uint16_t readUint16(
+       const unsigned char *&cp,               // IN/OUT
+       unsigned &len)                                  // IN/OUT 
+{
+       uint16_t r = *cp++;
+       r <<= 8;
+       r |= *cp++;
+       len -= 2;
+       return r;
+}
+
+static void appendUint32(
+       CFMutableDataRef cfOut,
+       uint32_t ui)
+{
+       UInt8 buf[sizeof(uint32_t)];
+       
+       for(int dex=(sizeof(uint32_t) - 1); dex>=0; dex--) {
+               buf[dex] = ui & 0xff;
+               ui >>= 8;
+       }
+       CFDataAppendBytes(cfOut, buf, sizeof(uint32_t));
+}
+
+static void appendUint16(
+       CFMutableDataRef cfOut,
+       uint16_t ui)
+{
+       UInt8 buf[sizeof(uint16_t)];
+       
+       buf[1] = ui & 0xff;
+       ui >>= 8;
+       buf[0] = ui;
+       CFDataAppendBytes(cfOut, buf, sizeof(uint16_t));
+}
+
+/* parse text as decimal, return BIGNUM */
+static BIGNUM *parseDecimalBn(
+       const unsigned char *cp,
+       unsigned len)
+{
+       for(unsigned dex=0; dex<len; dex++) {
+               char c = *cp;
+               if((c < '0') || (c > '9')) {
+                       return NULL;
+               }
+       }
+       char *str = (char *)malloc(len + 1);
+       memmove(str, cp, len);
+       str[len] = '\0';
+       BIGNUM *bn = NULL;
+       BN_dec2bn(&bn, str);
+       free(str);
+       return bn;
+}
+
+/* Read BIGNUM, OpenSSH-1 version */
+static BIGNUM *readBigNum(
+       const unsigned char *&cp,       // IN/OUT
+       unsigned &remLen)                       // IN/OUT
+{
+       if(remLen < sizeof(uint16_t)) {
+               dprintf("readBigNum: short record(1)\n");
+               return NULL;
+       }
+       uint16_t numBits = readUint16(cp, remLen);
+       unsigned bytes = (numBits + 7) / 8;
+       if(remLen < bytes) {
+               dprintf("readBigNum: short record(2)\n");
+               return NULL;
+       }
+       BIGNUM *bn = BN_bin2bn(cp, bytes, NULL);
+       if(bn == NULL) {
+               dprintf("readBigNum: BN_bin2bn error\n");
+               return NULL;
+       }
+       cp += bytes;
+       remLen -= bytes;
+       return bn;
+}
+       
+/* Write BIGNUM, OpenSSH-1 version */
+static int appendBigNum(
+       CFMutableDataRef cfOut, 
+       const BIGNUM *bn)
+{
+       /* 16 bits of numbits */
+       unsigned numBits = BN_num_bits(bn);
+       appendUint16(cfOut, numBits);
+       
+       /* serialize the bytes */
+       int numBytes = (numBits + 7) / 8;
+       unsigned char outBytes[numBytes];       // gcc is so cool...
+       int moved = BN_bn2bin(bn, outBytes);
+       if(moved != numBytes) {
+               dprintf("appendBigNum: BN_bn2bin() screwup\n");
+               return -1;
+       }
+       CFDataAppendBytes(cfOut, (UInt8 *)outBytes, numBytes);
+       return 0;
+}
+
+/* read BIGNUM, OpenSSH-2 mpint version */
+static BIGNUM *readBigNum2(
+       const unsigned char *&cp,       // IN/OUT
+       unsigned &remLen)                       // IN/OUT
+{
+       if(remLen < 4) {
+               dprintf("readBigNum2: short record(1)\n");
+               return NULL;
+       }
+       uint32_t bytes = readUint32(cp, remLen);
+       if(remLen < bytes) {
+               dprintf("readBigNum2: short record(2)\n");
+               return NULL;
+       }
+       BIGNUM *bn = BN_bin2bn(cp, bytes, NULL);
+       if(bn == NULL) {
+               dprintf("readBigNum2: BN_bin2bn error\n");
+               return NULL;
+       }
+       cp += bytes;
+       remLen -= bytes;
+       return bn;
+}
+
+/* write BIGNUM, OpenSSH v2 format (with a 4-byte byte count) */
+static int appendBigNum2(
+       CFMutableDataRef cfOut,
+       const BIGNUM *bn)
+{
+       if(bn == NULL) {
+               dprintf("appendBigNum2: NULL bn");
+               return -1;
+       }
+       if (BN_is_zero(bn)) {
+               appendUint32(cfOut, 0);
+               return 0;
+       }
+       if(bn->neg) {
+               dprintf("appendBigNum2: negative numbers not supported\n");
+               return -1;
+       }
+       int numBytes = BN_num_bytes(bn);
+       unsigned char buf[numBytes];
+       int moved = BN_bn2bin(bn, buf);
+       if(moved != numBytes) {
+               dprintf("appendBigNum: BN_bn2bin() screwup\n");
+               return -1;
+       }
+       bool appendZero = false;
+       if(buf[0] & 0x80) {
+               /* prepend leading zero to make it positive */
+               appendZero = true;
+               numBytes++;             // to encode the correct 4-byte length 
+       }
+       appendUint32(cfOut, (uint32_t)numBytes);
+       if(appendZero) {
+               UInt8 z = 0;
+               CFDataAppendBytes(cfOut, &z, 1);
+               numBytes--;             // to append the correct number of bytes
+       }
+       CFDataAppendBytes(cfOut, buf, numBytes);
+       memset(buf, 0, numBytes);
+       return 0;
+}
+
+/* Write BIGNUM, OpenSSH-1 decimal (public key) version */
+static int appendBigNumDec(
+       CFMutableDataRef cfOut, 
+       const BIGNUM *bn)
+{
+       char *buf = BN_bn2dec(bn);
+       if(buf == NULL) {
+               dprintf("appendBigNumDec: BN_bn2dec() error");
+               return -1;
+       }
+       CFDataAppendBytes(cfOut, (const UInt8 *)buf, strlen(buf));
+       OPENSSL_free(buf);
+       return 0;
+}
+
+/* write string, OpenSSH v2 format (with a 4-byte byte count) */
+static void appendString(
+       CFMutableDataRef cfOut,
+       const char *str,
+       unsigned strLen)
+{
+       appendUint32(cfOut, (uint32_t)strLen);
+       CFDataAppendBytes(cfOut, (UInt8 *)str, strLen);
+}
+
+/* skip whitespace */
+static void skipWhite(
+       const unsigned char *&cp,
+       unsigned &bytesLeft)
+{
+       while(bytesLeft != 0) {
+               if(isspace((int)(*cp))) {
+                       cp++;
+                       bytesLeft--;
+               }
+               else {
+                       return;
+               }
+       }
+}
+
+/* find next whitespace or EOF - if EOF, rtn pointer points to one past EOF */
+static const unsigned char *findNextWhite(
+       const unsigned char *cp,
+       unsigned &bytesLeft)
+{
+       while(bytesLeft != 0) {
+               if(isspace((int)(*cp))) {
+                       return cp;
+               }
+               cp++;
+               bytesLeft--;
+       }
+       return cp;
+}
+
+
+/* 
+ * Calculate d mod{p-1,q-1}
+ * Used when decoding OpenSSH-1 private RSA key.
+ */
+static int
+rsa_generate_additional_parameters(RSA *rsa)
+{
+       BIGNUM *aux;
+       BN_CTX *ctx;
+       
+       if((rsa->dmq1 = BN_new()) == NULL) {
+               dprintf("rsa_generate_additional_parameters: BN_new failed");
+               return -1;
+       }
+       if((rsa->dmp1 = BN_new()) == NULL) {
+               dprintf("rsa_generate_additional_parameters: BN_new failed");
+               return -1;
+       }
+       if ((aux = BN_new()) == NULL) {
+               dprintf("rsa_generate_additional_parameters: BN_new failed");
+               return -1;
+       }
+       if ((ctx = BN_CTX_new()) == NULL) {
+               dprintf("rsa_generate_additional_parameters: BN_CTX_new failed");
+               BN_clear_free(aux);
+               return -1;
+       } 
+
+       BN_sub(aux, rsa->q, BN_value_one());
+       BN_mod(rsa->dmq1, rsa->d, aux, ctx);
+
+       BN_sub(aux, rsa->p, BN_value_one());
+       BN_mod(rsa->dmp1, rsa->d, aux, ctx);
+
+       BN_clear_free(aux);
+       BN_CTX_free(ctx);
+       return 0;
+}
+
+#pragma mark --- OpenSSH-1 crypto --- 
+
+static int ssh1DES3Crypt(
+       unsigned char cipher,
+       bool doEncrypt,
+       const unsigned char *inText,
+       unsigned inTextLen,
+       const char *password,           // C string
+       unsigned char *outText,         // data RETURNED here, caller mallocs
+       unsigned *outTextLen)           // RETURNED
+{
+       switch(cipher) {
+               case SSH_CIPHER_3DES:
+                       break;
+               case SSH_CIPHER_NONE:
+                       /* cleartext RSA private key, e.g. host key. */
+                       memmove(outText, inText, inTextLen);
+                       *outTextLen = inTextLen;
+                       return 0;
+               default:
+                       /* who knows how we're going to figure these out */
+                       printf("***Unsupported cipher (%u)\n", cipher);
+                       return -1;
+       }
+       
+       /* key starts with MD5(password) */
+       unsigned char pwdDigest[CC_MD5_DIGEST_LENGTH];
+       CC_MD5(password, strlen(password), pwdDigest);
+       
+       /* three keys from that, like so: */
+       unsigned char k1[kCCKeySizeDES];
+       unsigned char k2[kCCKeySizeDES];
+       unsigned char k3[kCCKeySizeDES];
+       memmove(k1, pwdDigest, kCCKeySizeDES);
+       memmove(k2, pwdDigest + kCCKeySizeDES, kCCKeySizeDES);
+       memmove(k3, pwdDigest, kCCKeySizeDES);
+       
+       CCOperation op1_3;
+       CCOperation op2;
+       if(doEncrypt) {
+               op1_3 = kCCEncrypt;
+               op2   = kCCDecrypt;
+       }
+       else {
+               op1_3 = kCCDecrypt;
+               op2   = kCCEncrypt;
+       }
+       
+       /* the openssh v1 pseudo triple DES. Each DES pass has its own CBC. */
+       size_t moved = 0;
+
+       CCCryptorStatus cstat = CCCrypt(op1_3, kCCAlgorithmDES, 
+               0,                                              // no padding
+               k1, kCCKeySizeDES, 
+               NULL,           // IV
+               inText, inTextLen,
+               outText, inTextLen, &moved);
+       if(cstat) {
+               dprintf("***ssh1DES3Crypt: CCCrypt()(1) returned %u\n", (unsigned)cstat);
+               return -1;
+       }
+       cstat = CCCrypt(op2, kCCAlgorithmDES, 
+               0,                                              // no padding - SSH does that itself 
+               k2, kCCKeySizeDES, 
+               NULL,           // IV
+               outText, moved,
+               outText, inTextLen, &moved);
+       if(cstat) {
+               dprintf("***ssh1DES3Crypt: CCCrypt()(2) returned %u\n", (unsigned)cstat);
+               return -1;
+       }
+       cstat = CCCrypt(op1_3, kCCAlgorithmDES, 
+               0,                                              // no padding - SSH does that itself 
+               k3, kCCKeySizeDES, 
+               NULL,           // IV
+               outText, moved,
+               outText, inTextLen, &moved);
+       if(cstat) {
+               dprintf("***ssh1DES3Crypt: CCCrypt()(3) returned %u\n", (unsigned)cstat);
+               return -1;
+       }
+
+       *outTextLen = moved;    
+       return 0;
+}
+       
+#pragma mark --- OpenSSH-1 decode --- 
+
+/* Decode OpenSSH-1 RSA private key */
+static int decodeSSH1RSAPrivKey(
+       const unsigned char *key,
+       unsigned keyLen,
+       char *password,
+       RSA *rsa,                                               // returned
+       char **comment)                                 // returned
+{
+       const unsigned char *cp = key;          // running pointer
+       unsigned remLen = keyLen;
+       unsigned len = strlen(authfile_id_string);
+       
+       /* length: ID string, NULL, Cipher, 4-byte spare */
+       if(remLen < (len + 6)) {
+               dprintf("decodeSSH1RSAPrivKey: short record(1)\n");
+               return -1;
+       }
+       
+       /* ID string plus a NULL */
+       if(memcmp(authfile_id_string, cp, len)) {
+               dprintf("decodeSSH1RSAPrivKey: bad header\n");
+               return -1;
+       }
+       cp += (len + 1);
+       remLen -= (len + 1);
+       
+       /* cipher */
+       unsigned char cipherSpec = *cp;
+       switch(cipherSpec) {
+               case SSH_CIPHER_NONE:
+                       if(password != NULL) {
+                               dprintf("decodeSSH1RSAPrivKey: Attempt to decrypt plaintext key\n");
+                               return -1;
+                       }
+                       break;
+               case SSH_CIPHER_3DES:
+                       if(password == NULL) {
+                               dprintf("decodeSSH1RSAPrivKey: Encrypted key with no decryptKey\n");
+                               return -1;
+                       }
+                       break;
+               default:
+                       /* I hope we don't see any other values here */
+                       dprintf("decodeOpenSSHv1PrivKey: unknown cipherSpec (%u)\n", cipherSpec);
+                               return -1;
+       }
+       
+       /* skip cipher, spares */
+       cp += 5;
+       remLen -= 5;
+       
+       /*
+        * Clear text public key:
+        * uint32 bits
+        * bignum n
+        * bignum e
+        */
+       if(remLen < sizeof(uint32_t)) {
+               dprintf("decodeSSH1RSAPrivKey: bad len(1)\n");
+               return -1;
+       }
+       /* skip over keybits */
+       readUint32(cp, remLen);
+       rsa->n = readBigNum(cp, remLen);
+       if(rsa->n == NULL) {
+               dprintf("decodeSSH1RSAPrivKey: error decoding n\n");
+               return -1;
+       }
+       rsa->e = readBigNum(cp, remLen);
+       if(rsa->e == NULL) {
+               dprintf("decodeSSH1RSAPrivKey: error decoding e\n");
+               return -1;
+       }
+       
+       /* comment string: 4-byte length and the string w/o NULL */
+       if(remLen < sizeof(uint32_t)) {
+               dprintf("decodeSSH1RSAPrivKey: bad len(2)\n");
+               return -1;
+       }
+       uint32_t commentLen = readUint32(cp, remLen);
+       if(commentLen > remLen) {
+               dprintf("decodeSSH1RSAPrivKey: bad len(3)\n");
+               return -1;
+       }
+       *comment = (char *)malloc(commentLen + 1);
+       memmove(*comment, cp, commentLen);
+       (*comment)[commentLen] = '\0';
+       cp += commentLen;
+       remLen -= commentLen;
+       
+       /* everything that remains is ciphertext */
+       unsigned char ptext[remLen];
+       unsigned ptextLen = 0;
+       if(ssh1DES3Crypt(cipherSpec, false, cp, remLen, password, ptext, &ptextLen)) {
+               dprintf("decodeSSH1RSAPrivKey: decrypt error\n");
+               return -1;
+       }
+       /* subsequent errors to errOut: */
+       
+       int ourRtn = 0;
+       
+       /* plaintext contents:
+       
+       [0-1]           -- random bytes
+       [2-3]           -- copy of [01] for passphrase validity checking
+       buffer_put_bignum(d)
+       buffer_put_bignum(iqmp)
+       buffer_put_bignum(q)
+       buffer_put_bignum(p)
+       pad to block size
+       */
+       cp = ptext;
+       remLen = ptextLen;
+       if(remLen < 4) {
+               dprintf("decodeSSH1RSAPrivKey: bad len(4)\n");
+               ourRtn = -1;
+               goto errOut;
+       }
+       if((cp[0] != cp[2]) || (cp[1] != cp[3])) {
+               /* decrypt fail */
+               dprintf("decodeSSH1RSAPrivKey: check byte error\n");
+               ourRtn = -1;
+               goto errOut;
+       }
+       cp += 4;
+       remLen -= 4;
+       
+       /* remainder comprises private portion of RSA key */
+       rsa->d = readBigNum(cp, remLen);
+       if(rsa->d == NULL) {
+               dprintf("decodeSSH1RSAPrivKey: error decoding d\n");
+               return -1;
+       }
+       rsa->iqmp = readBigNum(cp, remLen);
+       if(rsa->iqmp == NULL) {
+               dprintf("decodeSSH1RSAPrivKey: error decoding iqmp\n");
+               return -1;
+       }
+       rsa->q = readBigNum(cp, remLen);
+       if(rsa->q == NULL) {
+               dprintf("decodeSSH1RSAPrivKey: error decoding q\n");
+               return -1;
+       }
+       rsa->p = readBigNum(cp, remLen);
+       if(rsa->p == NULL) {
+               dprintf("decodeSSH1RSAPrivKey: error decoding p\n");
+               return -1;
+       }
+
+       /* calculate d mod{p-1,q-1} */
+       ourRtn = rsa_generate_additional_parameters(rsa);
+
+errOut:
+       memset(ptext, 0, ptextLen);
+       return ourRtn; 
+}
+
+/* Decode OpenSSH-1 RSA public key */
+static int decodeSSH1RSAPubKey(
+       const unsigned char *key,
+       unsigned keyLen,
+       RSA *rsa,                                               // returned
+       char **comment)                                 // returned
+{
+       const unsigned char *cp = key;          // running pointer
+       unsigned remLen = keyLen;
+       
+       *comment = NULL;
+       skipWhite(cp, remLen);
+       
+       /* 
+        * cp points to start of size_in_bits in ASCII decimal' we really don't care about 
+        * this field. Find next space.
+        */
+       cp = findNextWhite(cp, remLen);
+       if(remLen == 0) {
+               dprintf("decodeSSH1RSAPubKey: short key (1)\n");
+               return -1;
+       }
+       skipWhite(cp, remLen);
+       if(remLen == 0) {
+               dprintf("decodeSSH1RSAPubKey: short key (2)\n");
+               return -1;
+       }
+       
+       /*
+        * cp points to start of e
+        */
+       const unsigned char *ep = findNextWhite(cp, remLen);
+       if(remLen == 0) {
+               dprintf("decodeSSH1RSAPubKey: short key (3)\n");
+               return -1;
+       }
+       unsigned len = ep - cp;
+       rsa->e = parseDecimalBn(cp, len);
+       if(rsa->e == NULL) {
+               return -1;
+       }
+       cp += len;
+       remLen -= len;
+       
+       skipWhite(cp, remLen);
+       if(remLen == 0) {
+               dprintf("decodeSSH1RSAPubKey: short key (4)\n");
+               return -1;
+       }
+       
+       /* cp points to start of n */
+       ep = findNextWhite(cp, remLen);
+       len = ep - cp;
+       rsa->n = parseDecimalBn(cp, len);
+       if(rsa->n == NULL) {
+               return -1;
+       }
+       cp += len;
+       remLen -= len;
+       skipWhite(cp, remLen);
+       if(remLen == 0) {
+               /* no comment; we're done */
+               return 0;
+       }
+
+       ep = findNextWhite(cp, remLen);
+       len = ep - cp;
+       if(len == 0) {
+               return 0;
+       }
+       *comment = (char *)malloc(len + 1);
+       memmove(*comment, cp, len);
+       if((*comment)[len - 1] == '\n') {
+               /* normal case closes with a newline, not part of the comment */
+               len--;
+       }
+       (*comment)[len] = '\0';
+       return 0;
+
+}
+
+#pragma mark --- OpenSSH-1 encode --- 
+
+/* Encode OpenSSH-1 RSA private key */
+static int encodeSSH1RSAPrivKey(
+       RSA *rsa, 
+       const char *password, 
+       const char *comment, 
+       unsigned char **outKey,         // mallocd and RETURNED 
+       unsigned *outKeyLen)            // RETURNED
+{
+       CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
+       
+       /* ID string including NULL */
+       CFDataAppendBytes(cfOut, (const UInt8 *)authfile_id_string, strlen(authfile_id_string) + 1);
+       
+       /* one byte cipher */
+       UInt8 cipherSpec = SSH_CIPHER_3DES;
+       CFDataAppendBytes(cfOut, &cipherSpec, 1);
+       
+       /* spares */
+       UInt8 spares[4] = {0};
+       CFDataAppendBytes(cfOut, spares, 4);
+       
+       /*
+        * Clear text public key:
+        * uint32 bits
+        * bignum n
+        * bignum e
+        */
+       uint32_t keybits = RSA_size(rsa) * 8;
+       appendUint32(cfOut, keybits);
+       appendBigNum(cfOut, rsa->n);
+       appendBigNum(cfOut, rsa->e);
+
+       /* comment string: 4-byte length and the string w/o NULL */
+       if(comment) {
+               uint32_t len = strlen(comment);
+               appendUint32(cfOut, len);
+               CFDataAppendBytes(cfOut, (const UInt8 *)comment, len);
+       }
+       
+       /* 
+        * Remainder is encrypted, consisting of
+        *
+        * [0-1]                -- random bytes
+        * [2-3]                -- copy of [01] for passphrase validity checking
+        * buffer_put_bignum(d)
+        * buffer_put_bignum(iqmp)
+        * buffer_put_bignum(q)
+        * buffer_put_bignum(p)
+        * pad to block size
+        */
+       CFMutableDataRef ptext = CFDataCreateMutable(NULL, 0);
+       
+       /* [0..3] check bytes */
+       UInt8 checkBytes[4];
+       DevRandomGenerator rng = DevRandomGenerator();
+       rng.random(checkBytes, 2);
+       checkBytes[2] = checkBytes[0];
+       checkBytes[3] = checkBytes[1];
+       CFDataAppendBytes(ptext, checkBytes, 4);
+       
+       /* d, iqmp, q, p */
+       appendBigNum(ptext, rsa->d);
+       appendBigNum(ptext, rsa->iqmp);
+       appendBigNum(ptext, rsa->q);
+       appendBigNum(ptext, rsa->p);
+       
+       /* encrypt it */
+       unsigned ptextLen = CFDataGetLength(ptext);
+       unsigned padding = 0;
+       unsigned rem = ptextLen & 0x7;
+       if(rem) {
+               padding = 8 - rem;
+       }
+       UInt8 padByte = 0;
+       for(unsigned dex=0; dex<padding; dex++) {
+               CFDataAppendBytes(ptext, &padByte, 1);
+       }
+       ptextLen = CFDataGetLength(ptext);
+       unsigned char ctext[ptextLen];
+       unsigned ctextLen;
+       int ourRtn = ssh1DES3Crypt(SSH_CIPHER_3DES, true, 
+               (unsigned char *)CFDataGetBytePtr(ptext), ptextLen, 
+               password,
+               ctext, &ctextLen);
+       if(ourRtn != 0) {
+               goto errOut;
+       }
+       
+       /* appended encrypted portion */
+       CFDataAppendBytes(cfOut, ctext, ctextLen);
+       *outKeyLen = (unsigned)CFDataGetLength(cfOut);
+       *outKey = (unsigned char *)malloc(*outKeyLen);
+       memmove(*outKey, CFDataGetBytePtr(cfOut), *outKeyLen);
+errOut:
+       CFRelease(cfOut);
+       /* it would be proper to zero out ptext here, but we can't do that to a CFData */
+       CFRelease(ptext);
+       return ourRtn;
+}
+
+/* Encode OpenSSH-1 RSA public key */
+static int encodeSSH1RSAPubKey(
+       RSA *rsa, 
+       const char *comment, 
+       unsigned char **outKey,         // mallocd and RETURNED 
+       unsigned *outKeyLen)            // RETURNED
+{
+       CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
+       
+       /*
+        * Format is
+        * num_bits in decimal
+        * <space>
+        * e, bignum in decimal
+        * <space>
+        * n, bignum in decimal
+        * <space>
+        * optional comment
+        * newline
+        */
+       int ourRtn = 0;
+       unsigned numBits = BN_num_bits(rsa->n);
+       char bitString[20];
+       UInt8 c = ' ';
+
+       snprintf(bitString, sizeof(bitString), "%u ", numBits);
+       CFDataAppendBytes(cfOut, (const UInt8 *)bitString, strlen(bitString));
+       if(appendBigNumDec(cfOut, rsa->e)) {
+               ourRtn = -1;
+               goto errOut;
+       }
+       CFDataAppendBytes(cfOut, &c, 1);
+       if(appendBigNumDec(cfOut, rsa->n)) {
+               ourRtn = -1;
+               goto errOut;
+       }
+       if(comment != NULL) {
+               CFDataAppendBytes(cfOut, &c, 1);
+               CFDataAppendBytes(cfOut, (UInt8 *)comment, strlen(comment));
+       }
+       c = '\n';
+       CFDataAppendBytes(cfOut, &c, 1);
+       *outKeyLen = CFDataGetLength(cfOut);
+       *outKey = (unsigned char *)malloc(*outKeyLen);
+       memmove(*outKey, CFDataGetBytePtr(cfOut), *outKeyLen);
+errOut:
+       CFRelease(cfOut);
+       return ourRtn;
+}
+
+#pragma mark --- OpenSSH-2 public key decode --- 
+
+/* 
+ * Decode components from an SSHv2 public key.
+ * Also verifies the leading header, e.g. "ssh-rsa".
+ * The returned decodedBlob is algorithm-specific.
+ */
+static int parseSSH2PubKey(
+       const unsigned char *key,
+       unsigned keyLen,
+       const char *header,                             // SSH2_RSA_HEADER, SSH2_DSA_HEADER
+       unsigned char **decodedBlob,    // mallocd and RETURNED
+       unsigned *decodedBlobLen,               // RETURNED
+       char **comment)                                 // optionally mallocd and RETURNED, NULL terminated
+{
+       unsigned len = strlen(header);
+       const unsigned char *endOfKey = key + keyLen;
+       *decodedBlob = NULL;
+       *comment = NULL;
+       
+       /* ID string plus at least one space */
+       if(keyLen < (len + 1)) {
+               dprintf("parseSSH2PubKey: short record(1)\n");
+               return -1;
+       }
+       
+       if(memcmp(header, key, len)) {
+               dprintf("parseSSH2PubKey: bad header (1)\n");
+               return -1;
+       }
+       key += len;
+       if(*key++ != ' ') {
+               dprintf("parseSSH2PubKey: bad header (2)\n");
+               return -1;
+       }
+       keyLen -= (len + 1);
+
+       /* key points to first whitespace after header */
+       skipWhite(key, keyLen);
+       if(keyLen == 0) {
+               dprintf("parseSSH2PubKey: short key\n");
+               return -1;
+       }
+       
+       /* key is start of base64 blob */
+       const unsigned char *encodedBlob = key;
+       const unsigned char *endBlob = findNextWhite(key, keyLen);
+       unsigned encodedBlobLen = endBlob - encodedBlob;
+       
+       /* decode base 64 */
+       *decodedBlob = cuDec64(encodedBlob, encodedBlobLen, decodedBlobLen);
+       if(*decodedBlob == NULL) {
+               dprintf("parseSSH2PubKey: base64 decode error\n");
+               return -1;
+       }
+       
+       /* skip over the encoded blob and possible whitespace after it */
+       key = endBlob;
+       keyLen = endOfKey - endBlob;
+       skipWhite(key, keyLen);
+       if(keyLen == 0) {
+               /* nothing remains, no comment, no error */
+               return 0;
+       }
+       
+       /* optional comment */
+       *comment = (char *)malloc(keyLen + 1);
+       memmove(*comment, key, keyLen);
+       if((*comment)[keyLen - 1] == '\n') {
+               /* normal case closes with a newline, not part of the comment */
+               keyLen--;
+       }
+       (*comment)[keyLen] = '\0';
+       return 0;
+}
+       
+static int decodeSSH2RSAPubKey(
+       const unsigned char *key,
+       unsigned keyLen,
+       RSA *rsa,                                               // returned
+       char **comment)                                 // returned
+{
+       /* 
+        * Verify header
+        * get base64-decoded blob plus optional comment 
+        */
+       unsigned char *decodedBlob = NULL;
+       unsigned decodedBlobLen = 0;
+       if(parseSSH2PubKey(key, keyLen, SSH2_RSA_HEADER, &decodedBlob, &decodedBlobLen, comment)) {
+               return -1;
+       }
+       /* subsequent errors to errOut: */
+       
+       /*
+        * The inner base64-decoded blob, consisting of
+        * ssh-rsa
+        * e
+        * n
+        */
+       uint32_t decLen;
+       unsigned len;
+       int ourRtn = 0;
+       
+       key = decodedBlob;
+       keyLen = decodedBlobLen;
+       if(keyLen < 12) {
+               /* three length fields at least */
+               dprintf("decodeSSH2RSAPubKey: short record(2)\n");
+               ourRtn = -1;
+               goto errOut;
+       }
+       decLen = readUint32(key, keyLen);
+       len = strlen(SSH2_RSA_HEADER);
+       if(decLen != len) {
+               dprintf("decodeSSH2RSAPubKey: bad header (2)\n");
+               ourRtn = -1;
+               goto errOut;
+       }
+       if(memcmp(SSH2_RSA_HEADER, key, len)) {
+               dprintf("decodeSSH2RSAPubKey: bad header (1)\n");
+               return -1;
+       }
+       key += len;
+       keyLen -= len;
+       
+       rsa->e = readBigNum2(key, keyLen);
+       if(rsa->e == NULL) {
+               ourRtn = -1;
+               goto errOut;
+       }
+       rsa->n = readBigNum2(key, keyLen);
+       if(rsa->n == NULL) {
+               ourRtn = -1;
+               goto errOut;
+       }
+
+errOut:
+       free(decodedBlob);
+       return ourRtn;
+}
+
+static int decodeSSH2DSAPubKey(
+       const unsigned char *key,
+       unsigned keyLen,
+       DSA *dsa,                                               // returned
+       char **comment)                                 // returned
+{
+       /* 
+        * Verify header
+        * get base64-decoded blob plus optional comment 
+        */
+       unsigned char *decodedBlob = NULL;
+       unsigned decodedBlobLen = 0;
+       if(parseSSH2PubKey(key, keyLen, SSH2_DSA_HEADER, &decodedBlob, &decodedBlobLen, comment)) {
+               return -1;
+       }
+       /* subsequent errors to errOut: */
+
+       /*
+        * The inner base64-decoded blob, consisting of
+        * ssh-dss
+        * p
+        * q
+        * g
+        * pub_key
+        */
+       uint32_t decLen;
+       int ourRtn = 0;
+       unsigned len;
+       
+       key = decodedBlob;
+       keyLen = decodedBlobLen;
+       if(keyLen < 20) {
+               /* five length fields at least */
+               dprintf("decodeSSH2DSAPubKey: short record(2)\n");
+               ourRtn = -1;
+               goto errOut;
+       }
+       decLen = readUint32(key, keyLen);
+       len = strlen(SSH2_DSA_HEADER);
+       if(decLen != len) {
+               dprintf("decodeSSH2DSAPubKey: bad header (2)\n");
+               ourRtn = -1;
+               goto errOut;
+       }
+       if(memcmp(SSH2_DSA_HEADER, key, len)) {
+               dprintf("decodeSSH2DSAPubKey: bad header (1)\n");
+               return -1;
+       }
+       key += len;
+       keyLen -= len;
+       
+       dsa->p = readBigNum2(key, keyLen);
+       if(dsa->p == NULL) {
+               ourRtn = -1;
+               goto errOut;
+       }
+       dsa->q = readBigNum2(key, keyLen);
+       if(dsa->q == NULL) {
+               ourRtn = -1;
+               goto errOut;
+       }
+       dsa->g = readBigNum2(key, keyLen);
+       if(dsa->g == NULL) {
+               ourRtn = -1;
+               goto errOut;
+       }
+       dsa->pub_key = readBigNum2(key, keyLen);
+       if(dsa->pub_key == NULL) {
+               ourRtn = -1;
+               goto errOut;
+       }
+
+errOut:
+       free(decodedBlob);
+       return ourRtn;
+}
+
+#pragma mark --- OpenSSH-2 public key encode --- 
+
+static int encodeSSH2RSAPubKey(
+       RSA *rsa, 
+       const char *comment, 
+       unsigned char **outKey,         // mallocd and RETURNED 
+       unsigned *outKeyLen)            // RETURNED
+{
+       unsigned char *b64 = NULL;
+       unsigned b64Len;
+       UInt8 c;
+       
+       /* 
+        * First, the inner base64-encoded blob, consisting of
+        * ssh-rsa
+        * e
+        * n
+        */
+       CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
+       int ourRtn = 0;
+       appendString(cfOut, SSH2_RSA_HEADER, strlen(SSH2_RSA_HEADER));
+       ourRtn = appendBigNum2(cfOut, rsa->e);
+       if(ourRtn) {
+               goto errOut;
+       }
+       ourRtn = appendBigNum2(cfOut, rsa->n);
+       if(ourRtn) {
+               goto errOut;
+       }
+       
+       /* base64 encode that */
+       b64 = cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut), &b64Len);
+       
+       /* cuEnc64 added newline and NULL, which we really don't want */
+       b64Len -= 2;
+       
+       /* Now start over, dropping that base64 into a public blob. */
+       CFDataSetLength(cfOut, 0);
+       CFDataAppendBytes(cfOut, (UInt8 *)SSH2_RSA_HEADER, strlen(SSH2_RSA_HEADER));
+       c = ' ';
+       CFDataAppendBytes(cfOut, &c, 1);
+       CFDataAppendBytes(cfOut, b64, b64Len);
+       
+       /* optional comment */
+       if(comment) {
+               CFDataAppendBytes(cfOut, &c, 1);
+               CFDataAppendBytes(cfOut, (UInt8 *)comment, strlen(comment));
+       }
+       
+       /* finish it with a newline */
+       c = '\n';
+       CFDataAppendBytes(cfOut, &c, 1);
+       
+       *outKeyLen = (unsigned)CFDataGetLength(cfOut);
+       *outKey = (unsigned char *)malloc(*outKeyLen);
+       memmove(*outKey, CFDataGetBytePtr(cfOut), *outKeyLen);
+       
+errOut:
+       CFRelease(cfOut);
+       if(b64) {
+               free(b64);
+       }
+       return ourRtn;
+}
+
+static int encodeSSH2DSAPubKey(
+       DSA *dsa, 
+       const char *comment, 
+       unsigned char **outKey,         // mallocd and RETURNED 
+       unsigned *outKeyLen)            // RETURNED
+{
+       unsigned char *b64 = NULL;
+       unsigned b64Len;
+       UInt8 c;
+       
+       /* 
+        * First, the inner base64-encoded blob, consisting of
+        * ssh-dss
+        * p
+        * q
+        * g
+        * pub_key
+        */
+       CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
+       int ourRtn = 0;
+       appendString(cfOut, SSH2_DSA_HEADER, strlen(SSH2_DSA_HEADER));
+       ourRtn = appendBigNum2(cfOut, dsa->p);
+       if(ourRtn) {
+               goto errOut;
+       }
+       ourRtn = appendBigNum2(cfOut, dsa->q);
+       if(ourRtn) {
+               goto errOut;
+       }
+       ourRtn = appendBigNum2(cfOut, dsa->g);
+       if(ourRtn) {
+               goto errOut;
+       }
+       ourRtn = appendBigNum2(cfOut, dsa->pub_key);
+       if(ourRtn) {
+               goto errOut;
+       }
+       
+       /* base64 encode that */
+       b64 = cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut), &b64Len);
+       
+       /* cuEnc64 added newline and NULL, which we really don't want */
+       b64Len -= 2;
+       
+       /* Now start over, dropping that base64 into a public blob. */
+       CFDataSetLength(cfOut, 0);
+       CFDataAppendBytes(cfOut, (UInt8 *)SSH2_DSA_HEADER, strlen(SSH2_DSA_HEADER));
+       c = ' ';
+       CFDataAppendBytes(cfOut, &c, 1);
+       CFDataAppendBytes(cfOut, b64, b64Len);
+       
+       /* optional comment */
+       if(comment) {
+               CFDataAppendBytes(cfOut, &c, 1);
+               CFDataAppendBytes(cfOut, (UInt8 *)comment, strlen(comment));
+       }
+       
+       /* finish it with a newline */
+       c = '\n';
+       CFDataAppendBytes(cfOut, &c, 1);
+       
+       *outKeyLen = (unsigned)CFDataGetLength(cfOut);
+       *outKey = (unsigned char *)malloc(*outKeyLen);
+       memmove(*outKey, CFDataGetBytePtr(cfOut), *outKeyLen);
+       
+errOut:
+       CFRelease(cfOut);
+       if(b64) {
+               free(b64);
+       }
+       return ourRtn;
+}
+
+
+#pragma mark --- print RSA/DSA keys --- 
+
+static void printBNLong(
+       BN_ULONG bnl)
+{
+       /* for now assume it's 32 bits */
+       unsigned i = bnl >> 24;
+       printf("%02X ", i);
+       i = (bnl >> 16) & 0xff;
+       printf("%02X ", i);
+       i = (bnl >> 8) & 0xff;
+       printf("%02X ", i);
+       i = bnl & 0xff;
+       printf("%02X ", i);
+}
+
+static void printBN(
+       const char *label,
+       BIGNUM *bn)
+{
+       printf("%s: %d bits: bn->top %d: ", label, BN_num_bits(bn), bn->top);
+       for(int dex=bn->top-1; dex>=0; dex--) {
+               printBNLong(bn->d[dex]);
+       }
+       printf("\n");
+}
+static void printRSA(
+       RSA *rsa)
+{
+       if(rsa->n) {
+               printBN("   n", rsa->n);
+       }
+       if(rsa->e) {
+               printBN("   e", rsa->e);
+       }
+       if(rsa->d) {
+               printBN("   d", rsa->d);
+       }
+       if(rsa->p) {
+               printBN("   p", rsa->p);
+       }
+       if(rsa->q) {
+               printBN("   q", rsa->q);
+       }
+       if(rsa->dmp1) {
+               printBN("dmp1", rsa->dmp1);
+       }
+       if(rsa->dmq1) {
+               printBN("dmq1", rsa->dmq1);
+       }
+       if(rsa->iqmp) {
+               printBN("iqmp", rsa->iqmp);
+       }
+}
+
+/* only public keys here */
+static void printDSA(
+       DSA *dsa)
+{
+       if(dsa->p) {
+               printBN("   p", dsa->p);
+       }
+       if(dsa->q) {
+               printBN("   q", dsa->q);
+       }
+       if(dsa->g) {
+               printBN("   g", dsa->g);
+       }
+       if(dsa->pub_key) {
+               printBN(" pub", dsa->pub_key);
+       }
+}
+
+/* parse format string, returns nonzero on error */
+static int parseFormat(
+       const char *formatStr,
+       bool *isSSH1)
+{
+       if(!strcmp(formatStr, "ssh1")) {
+               *isSSH1 = true;
+               return 0;
+       }
+       else if(!strcmp(formatStr, "ssh2")) {
+               *isSSH1 = false;
+               return 0;
+       }
+       else {
+               return -1;
+       }
+}
+
+#pragma mark --- main --- 
+
+/* parse format string */
+int main(int argc, char **argv)
+{
+       char *inFile = NULL;
+       char *outFile = NULL;
+       bool privKeyIn = false;
+       bool privKeyOut = false;
+       char *password = NULL;
+       char *comment = NULL;
+       bool doPrint = false;
+       bool isDSA = false;
+       bool inputSSH1 = false;
+       bool outputSSH1 = false;
+       bool clearPrivKeys = false;
+       
+       int ourRtn = 0;
+       
+       extern char *optarg;
+       int arg;
+       while ((arg = getopt(argc, argv, "i:o:vVdrf:F:p:Pc:h")) != -1) {
+               switch (arg) {
+                       case 'i':
+                               inFile = optarg;
+                               break;
+                       case 'o':
+                               outFile = optarg;
+                               break;
+                       case 'v':
+                               privKeyIn = true;
+                               break;
+                       case 'V':
+                               privKeyOut = true;
+                               break;
+                       case 'd':
+                               isDSA = true;
+                               break;
+                       case 'r':
+                               doPrint = true;
+                               break;
+                       case 'f':
+                               if(parseFormat(optarg, &inputSSH1)) {
+                                       usage(argv);
+                               }
+                               break;
+                       case 'F':
+                               if(parseFormat(optarg, &outputSSH1)) {
+                                       usage(argv);
+                               }
+                               break;
+                       case 'p':
+                               password = optarg;
+                               break;
+                       case 'P':
+                               clearPrivKeys = true;
+                               break;
+                       case 'c':
+                               comment = optarg;
+                               break;
+                       case 'h':
+                       default:
+                               usage(argv);
+               }
+       }
+       
+       if(inFile == NULL) {
+               printf("***You must specify an input file.\n");
+               usage(argv);
+       }
+       if((privKeyIn && !inputSSH1) || (privKeyOut && !outputSSH1)) {
+               printf("***Private keys in SSH2 format are handled elsewhere - Wrapped OpenSSL.\n");
+               exit(1);
+       }
+       if((privKeyIn || privKeyOut) && (password == NULL) & !clearPrivKeys) {
+               printf("***Private key handling requires a password or the -P option.\n");
+               usage(argv);
+       }
+       unsigned char *inKey = NULL;
+       unsigned inKeyLen = 0;
+       if(readFile(inFile, &inKey, &inKeyLen)) {
+               printf("Error reading %s. Aborting.\n", inFile);
+               exit(1);
+       }
+       
+       RSA *rsa = NULL;
+       DSA *dsa = NULL;
+       
+       /* parse incoming key */
+       if(isDSA) {
+               if(inputSSH1) {
+                       printf("***SSHv1 did not support DSA keys.\n");
+                       exit(1);
+               }
+               /* already verified that this is not SSH2 & priv (Wrapped OpenSSL) */
+               dsa = DSA_new();
+               if(decodeSSH2DSAPubKey(inKey, inKeyLen, dsa, &comment)) {
+                       printf("***Error decoding SSH2 DSA public key.\n");
+                       exit(1);
+               }
+       }
+       else {
+               rsa = RSA_new();
+               if(privKeyIn) {
+                       /* already verified that this is SSH1 (SSH2 is Wrapped OpenSSL) */
+                       if(decodeSSH1RSAPrivKey(inKey, inKeyLen, password, rsa, &comment)) {
+                               printf("***Error decoding SSH1 RSA Private key.\n");
+                               exit(1);
+                       }
+               }
+               else {
+                       if(inputSSH1) {
+                               if(decodeSSH1RSAPubKey(inKey, inKeyLen, rsa, &comment)) {
+                                       printf("***Error decoding SSH1 RSA Public key.\n");
+                                       exit(1);
+                               }
+                       }
+                       else {
+                               if(decodeSSH2RSAPubKey(inKey, inKeyLen, rsa, &comment)) {
+                                       printf("***Error decoding SSH2 RSA Public key.\n");
+                                       exit(1);
+                               }
+                       }
+               }
+       }
+
+       /* optionally display the key */
+       if(doPrint) {
+               if(isDSA) {
+                       printf("DSA key:\n");
+                       printDSA(dsa);
+                       printf("Comment: %s\n", comment);
+               }
+               else {
+                       printf("RSA key:\n");
+                       printRSA(rsa);
+                       printf("Comment: %s\n", comment);
+               }
+       }
+       
+       /* optionally convert to (optionally different) output format */
+       
+       if(outFile) {
+               unsigned char *outKey = NULL;
+               unsigned outKeyLen = 0;
+               
+               if(isDSA) {
+                       if(outputSSH1 || privKeyOut) {
+                               printf("***DSA: Only public SSHv2 keys allowed.\n");
+                               exit(1);
+                       }
+                       if(encodeSSH2DSAPubKey(dsa, comment, &outKey, &outKeyLen)) {
+                               printf("***Error encoding DSA public key.\n");
+                               exit(1);
+                       }
+               }
+               else {
+                       if(privKeyOut) {
+                               /* already verified that this is SSH1 (SSH2 is Wrapped OpenSSL) */
+                               if(encodeSSH1RSAPrivKey(rsa, password, comment, &outKey, &outKeyLen)) {
+                                       printf("***Error encoding RSA private key.\n");
+                                       exit(1);
+                               }
+                       }
+                       else {
+                               if(outputSSH1) {
+                                       if(encodeSSH1RSAPubKey(rsa, comment, &outKey, &outKeyLen)) {
+                                               printf("***Error encoding RSA public key.\n");
+                                               exit(1);
+                                       }
+                               }
+                               else {
+                                       if(encodeSSH2RSAPubKey(rsa, comment, &outKey, &outKeyLen)) {
+                                               printf("***Error encoding RSA public key.\n");
+                                               exit(1);
+                                       }
+                               }
+                       }       /* RSA public */
+               }       /* RSA */
+               
+               if(writeFile(outFile, outKey, outKeyLen)) {
+                       printf("***Error writing to %s.\n", outFile);
+                       ourRtn = -1;
+               }
+               else {
+                       printf("...wrote %u bytes to %s.\n", outKeyLen, outFile);
+               }
+               free(outKey);
+       }
+       else if(!doPrint) {
+               printf("...parsed a key but you didn't ask me to do anything with it.\n");
+       }
+       if(rsa) {
+               RSA_free(rsa);
+       }
+       if(dsa) {
+               DSA_free(dsa);
+       }
+       
+       return 0;
+}