+++ /dev/null
-/*
- * 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;
-}