SecImportExportOpenSSH.cpp [plain text]
#include "SecImportExportOpenSSH.h"
#include "SecImportExportUtils.h"
#include "SecImportExportCrypto.h"
#include <ctype.h>
#include <CommonCrypto/CommonDigest.h>
#include <security_utilities/debugging.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include <security_cdsa_utils/cuCdsaUtils.h>
#define SecSSHDbg(args...) secdebug("openssh", ## args)
#define SSHv2_PUB_KEY_NAME "OpenSSHv2 Public Key"
#define SSHv1_PUB_KEY_NAME "OpenSSHv1 Public Key"
#define SSHv1_PRIV_KEY_NAME "OpenSSHv1 Private Key"
#pragma mark --- Utility functions ---
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 char *commentAsNthField(
const unsigned char *key,
unsigned keyLen,
unsigned n)
{
unsigned dex;
skipWhite(key, keyLen);
if(keyLen == 0) {
return NULL;
}
for(dex=0; dex<(n-1); dex++) {
key = findNextWhite(key, keyLen);
if(keyLen == 0) {
return NULL;
}
skipWhite(key, keyLen);
if(keyLen == 0) {
return NULL;
}
}
char *rtnStr = (char *)malloc(keyLen + 1);
memmove(rtnStr, key, keyLen);
if(rtnStr[keyLen - 1] == '\n') {
rtnStr[keyLen - 1] = '\0';
}
else {
rtnStr[keyLen] = '\0';
}
return rtnStr;
}
static 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;
}
static uint16_t readUint16(
const unsigned char *&cp, unsigned &len) {
uint16_t r = *cp++;
r <<= 8;
r |= *cp++;
len -= 2;
return r;
}
static void skipBigNum(
const unsigned char *&cp, unsigned &len) {
if(len < 2) {
cp += len;
len = 0;
return;
}
uint16 numBits = readUint16(cp, len);
unsigned numBytes = (numBits + 7) / 8;
if(numBytes > len) {
cp += len;
len = 0;
return;
}
cp += numBytes;
len -= numBytes;
}
static char *genPrintName(
const char *header, const char *comment) {
unsigned totalLen = strlen(header) + 1;
if(comment) {
totalLen += strlen(comment);
totalLen += 2;
}
char *rtnStr = (char *)malloc(totalLen);
if(comment) {
snprintf(rtnStr, totalLen, "%s: %s", header, comment);
}
else {
strcpy(rtnStr, header);
}
return rtnStr;
}
#pragma mark --- Infer PrintName attribute from raw keys ---
static char *opensshV2PubComment(
const unsigned char *key,
unsigned keyLen)
{
char *comment = commentAsNthField(key, keyLen, 3);
char *rtnStr = genPrintName(SSHv2_PUB_KEY_NAME, comment);
if(comment) {
free(comment);
}
return rtnStr;
}
static char *opensshV1PubComment(
const unsigned char *key,
unsigned keyLen)
{
char *comment = commentAsNthField(key, keyLen, 4);
char *rtnStr = genPrintName(SSHv1_PUB_KEY_NAME, comment);
if(comment) {
free(comment);
}
return rtnStr;
}
static const char *authfile_id_string = "SSH PRIVATE KEY FILE FORMAT 1.1\n";
static char *opensshV1PrivComment(
const unsigned char *key,
unsigned keyLen)
{
unsigned len = strlen(authfile_id_string);
if(keyLen < (len + 6)) {
return NULL;
}
if(memcmp(authfile_id_string, key, len)) {
return NULL;
}
key += (len + 6);
keyLen -= (len + 6);
if(keyLen < 4) {
return NULL;
}
key += 4;
keyLen -= 4;
skipBigNum(key, keyLen);
if(keyLen == 0) {
return NULL;
}
skipBigNum(key, keyLen);
if(keyLen == 0) {
return NULL;
}
char *comment = NULL;
uint32 commentLen = readUint32(key, keyLen);
if((commentLen != 0) && (commentLen <= keyLen)) {
comment = (char *)malloc(commentLen + 1);
memmove(comment, key, commentLen);
comment[commentLen] = '\0';
}
char *rtnStr = genPrintName(SSHv1_PRIV_KEY_NAME, comment);
if(comment) {
free(comment);
}
return rtnStr;
}
char *impExpOpensshInferPrintName(
CFDataRef external,
SecExternalItemType externType,
SecExternalFormat externFormat)
{
const unsigned char *key = (const unsigned char *)CFDataGetBytePtr(external);
unsigned keyLen = CFDataGetLength(external);
switch(externType) {
case kSecItemTypePublicKey:
switch(externFormat) {
case kSecFormatSSH:
return opensshV1PubComment(key, keyLen);
case kSecFormatSSHv2:
return opensshV2PubComment(key, keyLen);
default:
break;
}
break;
case kSecItemTypePrivateKey:
switch(externFormat) {
case kSecFormatSSH:
case kSecFormatWrappedSSH:
return opensshV1PrivComment(key, keyLen);
default:
break;
}
break;
default:
break;
}
return NULL;
}
#pragma mark --- Infer DescriptiveData from PrintName ---
void impExpOpensshInferDescData(
SecKeyRef keyRef,
CssmOwnedData &descData)
{
OSStatus ortn;
SecKeychainAttributeInfo attrInfo;
SecKeychainAttrType attrType = kSecKeyPrintName;
attrInfo.count = 1;
attrInfo.tag = &attrType;
attrInfo.format = NULL;
SecKeychainAttributeList *attrList = NULL;
ortn = SecKeychainItemCopyAttributesAndData(
(SecKeychainItemRef)keyRef,
&attrInfo,
NULL, &attrList,
NULL, NULL);
if(ortn) {
SecSSHDbg("SecKeychainItemCopyAttributesAndData returned %ld", ortn);
return;
}
SecKeychainAttribute *attr = attrList->attr;
unsigned toStrip = 0;
unsigned len = strlen(SSHv2_PUB_KEY_NAME) + 1;
char *printNameStr = NULL;
if(len < attr->length) {
printNameStr = (char *)malloc(attr->length + 1);
memmove(printNameStr, attr->data, attr->length);
printNameStr[attr->length] = '\0';
if(strstr(printNameStr, SSHv2_PUB_KEY_NAME) == printNameStr) {
toStrip = strlen(SSHv2_PUB_KEY_NAME);
}
else if(strstr(printNameStr, SSHv1_PUB_KEY_NAME) == printNameStr) {
toStrip = strlen(SSHv1_PUB_KEY_NAME);
}
else if(strstr(printNameStr, SSHv1_PRIV_KEY_NAME) == printNameStr) {
toStrip = strlen(SSHv1_PRIV_KEY_NAME);
}
if(toStrip) {
if((printNameStr[toStrip] == ':') && (printNameStr[toStrip+1] == ' ')) {
toStrip += 2;
}
}
}
if(printNameStr) {
free(printNameStr);
}
len = attr->length;
unsigned char *attrVal;
if(len < toStrip) {
SecSSHDbg("impExpOpensshInferDescData: string parse screwup");
goto errOut;
}
if(len > toStrip) {
len -= toStrip;
}
else {
toStrip = 0;
}
attrVal = ((unsigned char *)attr->data) + toStrip;
descData.copy(attrVal, len);
errOut:
SecKeychainItemFreeAttributesAndData(attrList, NULL);
return;
}
#pragma mark --- Derive SSHv1 wrap/unwrap key ---
static CSSM_RETURN openSSHv1DeriveKey(
CSSM_CSP_HANDLE cspHand,
const SecKeyImportExportParameters *keyParams, impExpVerifyPhrase verifyPhrase, CSSM_KEY_PTR symKey) {
CSSM_KEY *passKey = NULL;
CFDataRef cfPhrase = NULL;
CSSM_RETURN crtn;
OSStatus ortn;
CSSM_DATA dummyLabel;
uint32 keyAttr;
CSSM_CC_HANDLE ccHand = 0;
CSSM_ACCESS_CREDENTIALS creds;
CSSM_CRYPTO_DATA seed;
CSSM_DATA nullParam = {0, NULL};
memset(symKey, 0, sizeof(CSSM_KEY));
ortn = impExpPassphraseCommon(keyParams, cspHand, SPF_Data, verifyPhrase,
(CFTypeRef *)&cfPhrase, &passKey);
if(ortn) {
return ortn;
}
memset(&seed, 0, sizeof(seed));
if(cfPhrase != NULL) {
unsigned len = CFDataGetLength(cfPhrase);
seed.Param.Data = (uint8 *)malloc(len);
seed.Param.Length = len;
memmove(seed.Param.Data, CFDataGetBytePtr(cfPhrase), len);
CFRelease(cfPhrase);
}
memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
crtn = CSSM_CSP_CreateDeriveKeyContext(cspHand,
CSSM_ALGID_OPENSSH1,
CSSM_ALGID_OPENSSH1,
CC_MD5_DIGEST_LENGTH * 8,
&creds,
passKey, 0, NULL, &seed,
&ccHand);
if(crtn) {
SecSSHDbg("openSSHv1DeriveKey CSSM_CSP_CreateDeriveKeyContext failure");
goto errOut;
}
keyAttr = CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_SENSITIVE;
dummyLabel.Data = (uint8 *)"temp unwrap key";
dummyLabel.Length = strlen((char *)dummyLabel.Data);
crtn = CSSM_DeriveKey(ccHand,
&nullParam,
CSSM_KEYUSE_ANY,
keyAttr,
&dummyLabel,
NULL, symKey);
if(crtn) {
SecSSHDbg("openSSHv1DeriveKey CSSM_DeriveKey failure");
}
errOut:
if(ccHand != 0) {
CSSM_DeleteContext(ccHand);
}
if(passKey != NULL) {
CSSM_FreeKey(cspHand, NULL, passKey, CSSM_FALSE);
free(passKey);
}
if(seed.Param.Data) {
memset(seed.Param.Data, 0, seed.Param.Length);
free(seed.Param.Data);
}
return crtn;
}
#pragma mark -- OpenSSHv1 Wrap/Unwrap ---
OSStatus impExpWrappedOpenSSHImport(
CFDataRef inData,
SecKeychainRef importKeychain, CSSM_CSP_HANDLE cspHand, SecItemImportExportFlags flags,
const SecKeyImportExportParameters *keyParams, const char *printName,
CFMutableArrayRef outArray) {
OSStatus ortn;
impExpKeyUnwrapParams unwrapParams;
assert(cspHand != 0);
if(keyParams == NULL) {
return paramErr;
}
memset(&unwrapParams, 0, sizeof(unwrapParams));
CSSM_KEY unwrappingKey;
ortn = openSSHv1DeriveKey(cspHand, keyParams, VP_Import, &unwrappingKey);
if(ortn) {
return ortn;
}
CSSM_KEY wrappedKey;
CSSM_KEYHEADER &hdr = wrappedKey.KeyHeader;
memset(&wrappedKey, 0, sizeof(CSSM_KEY));
hdr.HeaderVersion = CSSM_KEYHEADER_VERSION;
hdr.BlobType = CSSM_KEYBLOB_WRAPPED;
hdr.Format = CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSH1;
hdr.AlgorithmId = CSSM_ALGID_RSA;
hdr.KeyClass = CSSM_KEYCLASS_PRIVATE_KEY;
hdr.KeyAttr = CSSM_KEYATTR_EXTRACTABLE;
hdr.KeyUsage = CSSM_KEYUSE_ANY;
wrappedKey.KeyData.Data = (uint8 *)CFDataGetBytePtr(inData);
wrappedKey.KeyData.Length = CFDataGetLength(inData);
unwrapParams.unwrappingKey = &unwrappingKey;
unwrapParams.encrAlg = CSSM_ALGID_OPENSSH1;
ortn = impExpImportKeyCommon(&wrappedKey, importKeychain, cspHand,
flags, keyParams, &unwrapParams, printName, outArray);
if(unwrappingKey.KeyData.Data != NULL) {
CSSM_FreeKey(cspHand, NULL, &unwrappingKey, CSSM_FALSE);
}
return ortn;
}
OSStatus impExpWrappedOpenSSHExport(
SecKeyRef secKey,
SecItemImportExportFlags flags,
const SecKeyImportExportParameters *keyParams, const CssmData &descData,
CFMutableDataRef outData) {
CSSM_CSP_HANDLE cspdlHand = 0;
OSStatus ortn;
bool releaseCspHand = false;
CSSM_RETURN crtn;
if(keyParams == NULL) {
return paramErr;
}
ortn = SecKeyGetCSPHandle(secKey, &cspdlHand);
if(ortn) {
cspdlHand = cuCspStartup(CSSM_FALSE);
if(cspdlHand == 0) {
return CSSMERR_CSSM_ADDIN_LOAD_FAILED;
}
releaseCspHand = true;
}
CSSM_KEY wrappingKey;
crtn = openSSHv1DeriveKey(cspdlHand, keyParams, VP_Export, &wrappingKey);
if(crtn) {
goto errOut;
}
CSSM_KEY wrappedKey;
memset(&wrappedKey, 0, sizeof(CSSM_KEY));
crtn = impExpExportKeyCommon(cspdlHand, secKey, &wrappingKey, &wrappedKey,
CSSM_ALGID_OPENSSH1, CSSM_ALGMODE_NONE, CSSM_PADDING_NONE,
CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSH1, CSSM_ATTRIBUTE_NONE, CSSM_KEYBLOB_RAW_FORMAT_NONE,
&descData,
NULL); if(crtn) {
goto errOut;
}
CFDataAppendBytes(outData, wrappedKey.KeyData.Data, wrappedKey.KeyData.Length);
CSSM_FreeKey(cspdlHand, NULL, &wrappedKey, CSSM_FALSE);
errOut:
if(releaseCspHand) {
cuCspDetachUnload(cspdlHand, CSSM_FALSE);
}
return crtn;
}