/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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=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 '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 * e, bignum in decimal * * n, bignum in decimal * * 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; }