#include "opensshCoding.h"
#include <CoreFoundation/CFData.h>
#include <openssl/bn.h>
#include <openssl/crypto.h>
#include <security_cdsa_utils/cuEnc64.h>
#define SSH2_RSA_HEADER "ssh-rsa"
#define SSH2_DSA_HEADER "ssh-dss"
#ifndef NDEBUG
#include <stdio.h>
#define dprintf(s...) printf(s)
#else
#define dprintf(...)
#endif
#pragma mark --- commmon code ---
uint32_t readUint32(
const unsigned char *&cp, unsigned &len) {
uint32_t r = 0;
for(unsigned dex=0; dex<sizeof(uint32_t); dex++) {
r <<= 8;
r |= *cp++;
}
len -= 4;
return r;
}
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 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;
}
static CSSM_RETURN appendBigNum2(
CFMutableDataRef cfOut,
const BIGNUM *bn)
{
if(bn == NULL) {
dprintf("appendBigNum2: NULL bn");
return CSSMERR_CSP_INTERNAL_ERROR;
}
if (BN_is_zero(bn)) {
appendUint32(cfOut, 0);
return 0;
}
if(bn->neg) {
dprintf("appendBigNum2: negative numbers not supported\n");
return CSSMERR_CSP_INTERNAL_ERROR;
}
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 CSSMERR_CSP_INTERNAL_ERROR;
}
bool appendZero = false;
if(buf[0] & 0x80) {
appendZero = true;
numBytes++; }
appendUint32(cfOut, (uint32_t)numBytes);
if(appendZero) {
UInt8 z = 0;
CFDataAppendBytes(cfOut, &z, 1);
numBytes--; }
CFDataAppendBytes(cfOut, buf, numBytes);
memset(buf, 0, numBytes);
return CSSM_OK;
}
static BIGNUM *readBigNum2(
const unsigned char *&cp, unsigned &remLen) {
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;
}
static CSSM_RETURN appendBigNumDec(
CFMutableDataRef cfOut,
const BIGNUM *bn)
{
char *buf = BN_bn2dec(bn);
if(buf == NULL) {
dprintf("appendBigNumDec: BN_bn2dec() error");
return CSSMERR_CSP_INTERNAL_ERROR;
}
CFDataAppendBytes(cfOut, (const UInt8 *)buf, strlen(buf));
Free(buf);
return CSSM_OK;
}
static void appendString(
CFMutableDataRef cfOut,
const char *str,
unsigned strLen)
{
appendUint32(cfOut, (uint32_t)strLen);
CFDataAppendBytes(cfOut, (UInt8 *)str, strLen);
}
static void skipWhite(
const unsigned char *&cp,
unsigned &bytesLeft)
{
while(bytesLeft != 0) {
if(isspace((int)(*cp))) {
cp++;
bytesLeft--;
}
else {
return;
}
}
}
static const unsigned char *findNextWhite(
const unsigned char *cp,
unsigned &bytesLeft)
{
while(bytesLeft != 0) {
if(isspace((int)(*cp))) {
return cp;
}
cp++;
bytesLeft--;
}
return cp;
}
static CSSM_RETURN parseSSH2PubKey(
const unsigned char *key,
unsigned keyLen,
const char *header, unsigned char **decodedBlob, unsigned *decodedBlobLen) {
size_t len = strlen(header);
*decodedBlob = NULL;
if(keyLen < (len + 1)) {
dprintf("parseSSH2PubKey: short record(1)\n");
return CSSMERR_CSP_INVALID_KEY;
}
if(memcmp(header, key, len)) {
dprintf("parseSSH2PubKey: bad header (1)\n");
return CSSMERR_CSP_INVALID_KEY;
}
key += len;
if(*key++ != ' ') {
dprintf("parseSSH2PubKey: bad header (2)\n");
return CSSMERR_CSP_INVALID_KEY;
}
keyLen -= (len + 1);
skipWhite(key, keyLen);
if(keyLen == 0) {
dprintf("parseSSH2PubKey: short key\n");
return CSSMERR_CSP_INVALID_KEY;
}
const unsigned char *encodedBlob = key;
const unsigned char *endBlob = findNextWhite(key, keyLen);
unsigned encodedBlobLen = (unsigned)(endBlob - encodedBlob);
*decodedBlob = cuDec64(encodedBlob, encodedBlobLen, decodedBlobLen);
if(*decodedBlob == NULL) {
dprintf("parseSSH2PubKey: base64 decode error\n");
return CSSMERR_CSP_INVALID_KEY;
}
return CSSM_OK;
}
#pragma mark -- RSA OpenSSHv1 ---
CSSM_RETURN RSAPublicKeyEncodeOpenSSH1(
RSA *rsa,
const CssmData &descData,
CssmOwnedData &encodedKey)
{
CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
CSSM_RETURN ourRtn = CSSM_OK;
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((ourRtn = appendBigNumDec(cfOut, rsa->e))) {
goto errOut;
}
CFDataAppendBytes(cfOut, &c, 1);
if((ourRtn = appendBigNumDec(cfOut, rsa->n))) {
goto errOut;
}
if(descData.Length) {
CFDataAppendBytes(cfOut, &c, 1);
CFDataAppendBytes(cfOut, (UInt8 *)descData.Data, descData.Length);
}
c = '\n';
CFDataAppendBytes(cfOut, &c, 1);
encodedKey.copy(CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut));
errOut:
CFRelease(cfOut);
return ourRtn;
}
CSSM_RETURN RSAPublicKeyDecodeOpenSSH1(
RSA *rsa,
void *p,
size_t length)
{
const unsigned char *cp = (const unsigned char *)p;
unsigned remLen = (unsigned)length;
skipWhite(cp, remLen);
cp = findNextWhite(cp, remLen);
if(remLen == 0) {
dprintf("RSAPublicKeyDecodeOpenSSH1: short key (1)\n");
return CSSMERR_CSP_INVALID_KEY;
}
skipWhite(cp, remLen);
if(remLen == 0) {
dprintf("RSAPublicKeyDecodeOpenSSH1: short key (2)\n");
return CSSMERR_CSP_INVALID_KEY;
}
const unsigned char *ep = findNextWhite(cp, remLen);
if(remLen == 0) {
dprintf("RSAPublicKeyDecodeOpenSSH1: short key (3)\n");
return CSSMERR_CSP_INVALID_KEY;
}
unsigned len = (unsigned)(ep - cp);
rsa->e = parseDecimalBn(cp, len);
if(rsa->e == NULL) {
return CSSMERR_CSP_INVALID_KEY;
}
cp += len;
skipWhite(cp, remLen);
if(remLen == 0) {
dprintf("RSAPublicKeyDecodeOpenSSH1: short key (4)\n");
return -1;
}
ep = findNextWhite(cp, remLen);
len = (unsigned)(ep - cp);
rsa->n = parseDecimalBn(cp, len);
if(rsa->n == NULL) {
return CSSMERR_CSP_INVALID_KEY;
}
return CSSM_OK;
}
CSSM_RETURN RSAPrivateKeyEncodeOpenSSH1(
RSA *rsa,
const CssmData &descData,
CssmOwnedData &encodedKey)
{
CFDataRef cfOut;
CSSM_RETURN ourRtn;
ourRtn = encodeOpenSSHv1PrivKey(rsa, descData.Data, (unsigned)descData.Length, NULL, &cfOut);
if(ourRtn) {
return ourRtn;
}
encodedKey.copy(CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut));
CFRelease(cfOut);
return CSSM_OK;
}
extern CSSM_RETURN RSAPrivateKeyDecodeOpenSSH1(
RSA *openKey,
void *p,
size_t length)
{
return decodeOpenSSHv1PrivKey((const unsigned char *)p, (unsigned)length,
openKey, NULL, NULL, NULL);
}
#pragma mark -- RSA OpenSSHv2 ---
CSSM_RETURN RSAPublicKeyEncodeOpenSSH2(
RSA *rsa,
const CssmData &descData,
CssmOwnedData &encodedKey)
{
unsigned char *b64 = NULL;
unsigned b64Len;
UInt8 c;
CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
CSSM_RETURN ourRtn = CSSM_OK;
appendString(cfOut, SSH2_RSA_HEADER, strlen(SSH2_RSA_HEADER));
if((ourRtn = appendBigNum2(cfOut, rsa->e))) {
goto errOut;
}
if((ourRtn = appendBigNum2(cfOut, rsa->n))) {
goto errOut;
}
b64 = cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut), (unsigned)CFDataGetLength(cfOut), &b64Len);
b64Len -= 2;
CFDataSetLength(cfOut, 0);
CFDataAppendBytes(cfOut, (UInt8 *)SSH2_RSA_HEADER, strlen(SSH2_RSA_HEADER));
c = ' ';
CFDataAppendBytes(cfOut, &c, 1);
CFDataAppendBytes(cfOut, b64, b64Len);
if(descData.Length) {
CFDataAppendBytes(cfOut, &c, 1);
CFDataAppendBytes(cfOut, (UInt8 *)descData.Data, descData.Length);
}
c = '\n';
CFDataAppendBytes(cfOut, &c, 1);
encodedKey.copy(CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut));
errOut:
CFRelease(cfOut);
if(b64) {
free(b64);
}
return ourRtn;
}
CSSM_RETURN RSAPublicKeyDecodeOpenSSH2(
RSA *rsa,
void *p,
size_t length)
{
const unsigned char *key = (const unsigned char *)p;
unsigned keyLen = (unsigned)length;
CSSM_RETURN ourRtn;
unsigned char *decodedBlob = NULL;
unsigned decodedBlobLen = 0;
if((ourRtn = parseSSH2PubKey(key, keyLen, SSH2_RSA_HEADER, &decodedBlob, &decodedBlobLen))) {
return ourRtn;
}
uint32_t decLen;
unsigned len;
key = decodedBlob;
keyLen = decodedBlobLen;
if(keyLen < 12) {
dprintf("RSAPublicKeyDecodeOpenSSH2: short record(2)\n");
ourRtn = -1;
goto errOut;
}
decLen = readUint32(key, keyLen);
len = strlen(SSH2_RSA_HEADER);
if(decLen != len) {
dprintf("RSAPublicKeyDecodeOpenSSH2: bad header (2)\n");
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
if(memcmp(SSH2_RSA_HEADER, key, len)) {
dprintf("RSAPublicKeyDecodeOpenSSH2: bad header (1)\n");
return CSSMERR_CSP_INVALID_KEY;
}
key += len;
keyLen -= len;
rsa->e = readBigNum2(key, keyLen);
if(rsa->e == NULL) {
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
rsa->n = readBigNum2(key, keyLen);
if(rsa->n == NULL) {
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
errOut:
free(decodedBlob);
return ourRtn;
}
#pragma mark -- DSA OpenSSHv2 ---
CSSM_RETURN DSAPublicKeyEncodeOpenSSH2(
DSA *dsa,
const CssmData &descData,
CssmOwnedData &encodedKey)
{
unsigned char *b64 = NULL;
unsigned b64Len;
UInt8 c;
CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
int ourRtn = 0;
appendString(cfOut, SSH2_DSA_HEADER, strlen(SSH2_DSA_HEADER));
if((ourRtn = appendBigNum2(cfOut, dsa->p))) {
goto errOut;
}
if((ourRtn = appendBigNum2(cfOut, dsa->q))) {
goto errOut;
}
if((ourRtn = appendBigNum2(cfOut, dsa->g))) {
goto errOut;
}
if((ourRtn = appendBigNum2(cfOut, dsa->pub_key))) {
goto errOut;
}
b64 = cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut), (unsigned)CFDataGetLength(cfOut), &b64Len);
b64Len -= 2;
CFDataSetLength(cfOut, 0);
CFDataAppendBytes(cfOut, (UInt8 *)SSH2_DSA_HEADER, strlen(SSH2_DSA_HEADER));
c = ' ';
CFDataAppendBytes(cfOut, &c, 1);
CFDataAppendBytes(cfOut, b64, b64Len);
if(descData.Length) {
CFDataAppendBytes(cfOut, &c, 1);
CFDataAppendBytes(cfOut, (UInt8 *)descData.Data, descData.Length);
}
c = '\n';
CFDataAppendBytes(cfOut, &c, 1);
encodedKey.copy(CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut));
errOut:
CFRelease(cfOut);
if(b64) {
free(b64);
}
return ourRtn;
}
CSSM_RETURN DSAPublicKeyDecodeOpenSSH2(
DSA *dsa,
void *p,
size_t length)
{
const unsigned char *key = (const unsigned char *)p;
unsigned keyLen = (unsigned)length;
CSSM_RETURN ourRtn;
unsigned char *decodedBlob = NULL;
unsigned decodedBlobLen = 0;
if((ourRtn = parseSSH2PubKey(key, keyLen, SSH2_DSA_HEADER, &decodedBlob, &decodedBlobLen))) {
return ourRtn;
}
uint32_t decLen;
unsigned len;
key = decodedBlob;
keyLen = decodedBlobLen;
if(keyLen < 20) {
dprintf("DSAPublicKeyDecodeOpenSSH2: short record(2)\n");
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
decLen = readUint32(key, keyLen);
len = strlen(SSH2_DSA_HEADER);
if(decLen != len) {
dprintf("DSAPublicKeyDecodeOpenSSH2: bad header (2)\n");
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
if(memcmp(SSH2_DSA_HEADER, key, len)) {
dprintf("DSAPublicKeyDecodeOpenSSH2: bad header (1)\n");
return CSSMERR_CSP_INVALID_KEY;
}
key += len;
keyLen -= len;
dsa->p = readBigNum2(key, keyLen);
if(dsa->p == NULL) {
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
dsa->q = readBigNum2(key, keyLen);
if(dsa->q == NULL) {
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
dsa->g = readBigNum2(key, keyLen);
if(dsa->g == NULL) {
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
dsa->pub_key = readBigNum2(key, keyLen);
if(dsa->pub_key == NULL) {
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
errOut:
free(decodedBlob);
return ourRtn;
}