#include "AppleCSPSession.h"
#include "AppleCSPContext.h"
#include "AppleCSPUtils.h"
#include "AppleCSPKeys.h"
#include "RSA_DSA_Keys.h"
#include "opensshCoding.h"
#include "cspdebugging.h"
#include <CommonCrypto/CommonDigest.h>
#include <CommonCrypto/CommonCryptor.h>
#include <openssl/rsa.h>
#include <openssl/bn.h>
#include <security_utilities/devrandom.h>
static const char *authfile_id_string = "SSH PRIVATE KEY FILE FORMAT 1.1\n";
#define OPENSSH1_COMMENT "Encoded by Mac OS X Security.framework"
#define SSH_CIPHER_NONE 0
#define SSH_CIPHER_IDEA 1
#define SSH_CIPHER_DES 2
#define SSH_CIPHER_3DES 3
#define SSH_CIPHER_BROKEN_TSS 4
#define SSH_CIPHER_BROKEN_RC4 5
#define SSH_CIPHER_BLOWFISH 6
#define SSH_CIPHER_RESERVED 7
#pragma mark --- utilities ---
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));
}
static uint16_t readUint16(
const unsigned char *&cp, unsigned &len) {
uint16_t r = *cp++;
r <<= 8;
r |= *cp++;
len -= 2;
return r;
}
static CSSM_RETURN appendBigNum(
CFMutableDataRef cfOut,
const BIGNUM *bn)
{
unsigned numBits = BN_num_bits(bn);
appendUint16(cfOut, numBits);
int numBytes = (numBits + 7) / 8;
unsigned char outBytes[numBytes]; int moved = BN_bn2bin(bn, outBytes);
if(moved != numBytes) {
errorLog0("appendBigNum: BN_bn2bin() screwup\n");
return CSSMERR_CSP_INTERNAL_ERROR;
}
CFDataAppendBytes(cfOut, (UInt8 *)outBytes, numBytes);
return CSSM_OK;
}
static BIGNUM *readBigNum(
const unsigned char *&cp, unsigned &remLen) {
if(remLen < sizeof(uint16_t)) {
errorLog0("readBigNum: short record(1)\n");
return NULL;
}
uint16_t numBits = readUint16(cp, remLen);
unsigned bytes = (numBits + 7) / 8;
if(remLen < bytes) {
errorLog0("readBigNum: short record(2)\n");
return NULL;
}
BIGNUM *bn = BN_bin2bn(cp, bytes, NULL);
if(bn == NULL) {
errorLog0("readBigNum: BN_bin2bn error\n");
return NULL;
}
cp += bytes;
remLen -= bytes;
return bn;
}
static CSSM_RETURN rsa_generate_additional_parameters(RSA *rsa)
{
BIGNUM *aux;
BN_CTX *ctx;
if((rsa->dmq1 = BN_new()) == NULL) {
errorLog0("rsa_generate_additional_parameters: BN_new failed");
return CSSMERR_CSP_INTERNAL_ERROR;
}
if((rsa->dmp1 = BN_new()) == NULL) {
errorLog0("rsa_generate_additional_parameters: BN_new failed");
return CSSMERR_CSP_INTERNAL_ERROR;
}
if ((aux = BN_new()) == NULL) {
errorLog0("rsa_generate_additional_parameters: BN_new failed");
return CSSMERR_CSP_INTERNAL_ERROR;
}
if ((ctx = BN_CTX_new()) == NULL) {
errorLog0("rsa_generate_additional_parameters: BN_CTX_new failed");
BN_clear_free(aux);
return CSSMERR_CSP_INTERNAL_ERROR;
}
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 CSSM_OK;
}
#pragma mark --- encrypt/decrypt ---
static CSSM_RETURN ssh1DES3Crypt(
unsigned char cipher,
bool doEncrypt,
const unsigned char *inText,
unsigned inTextLen,
const uint8 *key, CSSM_SIZE keyLen,
unsigned char *outText, unsigned *outTextLen) {
switch(cipher) {
case SSH_CIPHER_3DES:
break;
case SSH_CIPHER_NONE:
memmove(outText, inText, inTextLen);
*outTextLen = inTextLen;
return CSSM_OK;
default:
errorLog1("***ssh1DES3Crypt: Unsupported cipher (%u)\n", cipher);
return CSSMERR_CSP_INVALID_KEY;
}
if(keyLen != CC_MD5_DIGEST_LENGTH) {
errorLog0("ssh1DES3Crypt: bad key length\n");
return CSSMERR_CSP_INVALID_KEY;
}
unsigned char k1[kCCKeySizeDES];
unsigned char k2[kCCKeySizeDES];
unsigned char k3[kCCKeySizeDES];
memmove(k1, key, kCCKeySizeDES);
memmove(k2, key + kCCKeySizeDES, kCCKeySizeDES);
memmove(k3, key, kCCKeySizeDES);
CCOperation op1_3;
CCOperation op2;
if(doEncrypt) {
op1_3 = kCCEncrypt;
op2 = kCCDecrypt;
}
else {
op1_3 = kCCDecrypt;
op2 = kCCEncrypt;
}
size_t moved = 0;
CCCryptorStatus cstat = CCCrypt(op1_3, kCCAlgorithmDES,
0, k1, kCCKeySizeDES,
NULL, inText, inTextLen,
outText, inTextLen, &moved);
if(cstat) {
errorLog1("***ssh1DES3Crypt: CCCrypt()(1) returned %u\n", (unsigned)cstat);
return CSSMERR_CSP_INTERNAL_ERROR;
}
cstat = CCCrypt(op2, kCCAlgorithmDES,
0, k2, kCCKeySizeDES,
NULL, outText, moved,
outText, inTextLen, &moved);
if(cstat) {
errorLog1("***ssh1DES3Crypt: CCCrypt()(2) returned %u\n", (unsigned)cstat);
return CSSMERR_CSP_INTERNAL_ERROR;
}
cstat = CCCrypt(op1_3, kCCAlgorithmDES,
0, k3, kCCKeySizeDES,
NULL, outText, moved,
outText, inTextLen, &moved);
if(cstat) {
errorLog1("***ssh1DES3Crypt: CCCrypt()(3) returned %u\n", (unsigned)cstat);
return CSSMERR_CSP_INTERNAL_ERROR;
}
*outTextLen = (unsigned)moved;
return CSSM_OK;
}
#pragma mark --- DeriveKey ---
void AppleCSPSession::DeriveKey_OpenSSH1(
const Context &context,
CSSM_ALGORITHMS algId,
const CssmData &Param, CSSM_DATA *keyData) {
CSSM_DATA pwd = {0, NULL};
if(keyData->Length != CC_MD5_DIGEST_LENGTH) {
errorLog0("DeriveKey_OpenSSH1: invalid key length\n");
CssmError::throwMe(CSSMERR_CSP_UNSUPPORTED_KEY_SIZE);
}
CssmCryptoData *cryptData =
context.get<CssmCryptoData>(CSSM_ATTRIBUTE_SEED);
if((cryptData != NULL) && (cryptData->Param.Length != 0)) {
pwd = cryptData->Param;
}
else {
CssmKey *passKey = context.get<CssmKey>(CSSM_ATTRIBUTE_KEY);
if (passKey != NULL) {
AppleCSPContext::symmetricKeyBits(context, *this,
CSSM_ALGID_SECURE_PASSPHRASE, CSSM_KEYUSE_DERIVE,
pwd.Data, pwd.Length);
}
}
if(pwd.Data == NULL) {
errorLog0("DeriveKey_PKCS5_V1_5: null Passphrase\n");
CssmError::throwMe(CSSMERR_CSP_INVALID_DATA);
}
if(pwd.Length == 0) {
errorLog0("DeriveKey_PKCS5_V1_5: zero length passphrase\n");
CssmError::throwMe(CSSMERR_CSP_INVALID_INPUT_POINTER);
}
CC_MD5(pwd.Data, (CC_LONG)pwd.Length, keyData->Data);
}
#pragma mark --- Encode/Wrap OpenSSHv1 private key ---
CSSM_RETURN encodeOpenSSHv1PrivKey(
RSA *rsa,
const uint8 *comment,
unsigned commentLen,
const uint8 *encryptKey,
CFDataRef *encodedKey)
{
CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
CSSM_RETURN ourRtn = CSSM_OK;
CFDataAppendBytes(cfOut, (const UInt8 *)authfile_id_string, strlen(authfile_id_string) + 1);
UInt8 cipherSpec = encryptKey ? SSH_CIPHER_3DES : SSH_CIPHER_NONE;
CFDataAppendBytes(cfOut, &cipherSpec, 1);
UInt8 spares[4] = {0};
CFDataAppendBytes(cfOut, spares, 4);
uint32_t keybits = RSA_size(rsa) * 8;
appendUint32(cfOut, keybits);
appendBigNum(cfOut, rsa->n);
appendBigNum(cfOut, rsa->e);
if((comment == NULL) || (commentLen == 0)) {
comment = (const UInt8 *)OPENSSH1_COMMENT;
commentLen = strlen(OPENSSH1_COMMENT);
}
appendUint32(cfOut, commentLen);
CFDataAppendBytes(cfOut, comment, commentLen);
CFMutableDataRef ptext = CFDataCreateMutable(NULL, 0);
UInt8 checkBytes[4];
DevRandomGenerator rng = DevRandomGenerator();
rng.random(checkBytes, 2);
checkBytes[2] = checkBytes[0];
checkBytes[3] = checkBytes[1];
CFDataAppendBytes(ptext, checkBytes, 4);
appendBigNum(ptext, rsa->d);
appendBigNum(ptext, rsa->iqmp);
appendBigNum(ptext, rsa->q);
appendBigNum(ptext, rsa->p);
CFIndex ptextLen = CFDataGetLength(ptext);
unsigned padding = 0;
unsigned rem = (unsigned)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;
ourRtn = ssh1DES3Crypt(cipherSpec, true,
(unsigned char *)CFDataGetBytePtr(ptext), (unsigned)ptextLen,
encryptKey, encryptKey ? CC_MD5_DIGEST_LENGTH : 0,
ctext, &ctextLen);
if(ourRtn != 0) {
goto errOut;
}
CFDataAppendBytes(cfOut, ctext, ctextLen);
*encodedKey = cfOut;
errOut:
CFRelease(ptext);
return ourRtn;
}
void AppleCSPSession::WrapKeyOpenSSH1(
CSSM_CC_HANDLE CCHandle,
const Context &context,
const AccessCredentials &AccessCred,
BinaryKey &unwrappedBinKey,
CssmData &rawBlob,
bool allocdRawBlob, const CssmData *DescriptiveData,
CssmKey &WrappedKey,
CSSM_PRIVILEGE Privilege)
{
RSABinaryKey &rPubBinKey = dynamic_cast<RSABinaryKey &>(unwrappedBinKey);
RSA *rsa = rPubBinKey.mRsaKey;
CASSERT(rsa != NULL);
CSSM_SIZE wrappingKeyLen = 0;
uint8 *wrappingKey = NULL;
AppleCSPContext::symmetricKeyBits(context, *this,
CSSM_ALGID_OPENSSH1, CSSM_KEYUSE_WRAP,
wrappingKey, wrappingKeyLen);
if(wrappingKeyLen != CC_MD5_DIGEST_LENGTH) {
errorLog0("AppleCSPSession::WrapKeyOpenSSH1: bad wrapping key length\n");
CssmError::throwMe(CSSMERR_CSP_INVALID_KEY);
}
CFDataRef cfOut = NULL;
const UInt8 *comment = NULL;
unsigned commentLen = 0;
if((DescriptiveData != NULL) && (DescriptiveData->Length != 0)) {
comment = (const UInt8 *)DescriptiveData->Data;
commentLen = (unsigned)DescriptiveData->Length;
}
CSSM_RETURN crtn = encodeOpenSSHv1PrivKey(rsa, comment, commentLen, wrappingKey, &cfOut);
if(crtn) {
CssmError::throwMe(crtn);
}
CFIndex len = CFDataGetLength(cfOut);
setUpData(WrappedKey.KeyData, len, normAllocator);
memmove(WrappedKey.KeyData.Data, CFDataGetBytePtr(cfOut), len);
CFRelease(cfOut);
WrappedKey.KeyHeader.BlobType = CSSM_KEYBLOB_WRAPPED;
WrappedKey.KeyHeader.WrapMode = CSSM_ALGMODE_NONE;
WrappedKey.KeyHeader.Format = CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSH1;
}
#pragma mark --- Decode/Unwrap OpenSSHv1 private key ---
CSSM_RETURN decodeOpenSSHv1PrivKey(
const unsigned char *encodedKey,
unsigned encodedKeyLen,
RSA *rsa,
const uint8 *decryptKey,
uint8 **comment,
unsigned *commentLen)
{
unsigned len = (unsigned)strlen(authfile_id_string);
const unsigned char *cp = encodedKey;
unsigned remLen = encodedKeyLen;
CSSM_RETURN ourRtn = CSSM_OK;
if(remLen < (len + 6)) {
errorLog0("decodeOpenSSHv1PrivKey: short record(1)\n");
return CSSMERR_CSP_INVALID_KEY;
}
if(memcmp(authfile_id_string, cp, len)) {
errorLog0("decodeOpenSSHv1PrivKey: bad header\n");
return CSSMERR_CSP_INVALID_KEY;
}
cp += (len + 1);
remLen -= (len + 1);
unsigned char cipherSpec = *cp;
switch(cipherSpec) {
case SSH_CIPHER_NONE:
if(decryptKey != NULL) {
errorLog0("decodeOpenSSHv1PrivKey: Attempt to decrypt plaintext key\n");
return CSSMERR_CSP_INVALID_KEY;
}
break;
case SSH_CIPHER_3DES:
if(decryptKey == NULL) {
errorLog0("decodeOpenSSHv1PrivKey: Encrypted key with no decryptKey\n");
return CSSMERR_CSP_INVALID_KEY;
}
break;
default:
errorLog1("decodeOpenSSHv1PrivKey: unknown cipherSpec (%u)\n", cipherSpec);
return CSSMERR_CSP_INVALID_KEY;
}
cp += 5;
remLen -= 5;
if(remLen < sizeof(uint32_t)) {
errorLog0("decodeOpenSSHv1PrivKey: bad len(1)\n");
return CSSMERR_CSP_INVALID_KEY;
}
readUint32(cp, remLen);
rsa->n = readBigNum(cp, remLen);
if(rsa->n == NULL) {
errorLog0("decodeOpenSSHv1PrivKey: error decoding n\n");
return CSSMERR_CSP_INVALID_KEY;
}
rsa->e = readBigNum(cp, remLen);
if(rsa->e == NULL) {
errorLog0("decodeOpenSSHv1PrivKey: error decoding e\n");
return CSSMERR_CSP_INVALID_KEY;
}
if(remLen < sizeof(uint32_t)) {
errorLog0("decodeOpenSSHv1PrivKey: bad len(2)\n");
return CSSMERR_CSP_INVALID_KEY;
}
uint32_t commLen = readUint32(cp, remLen);
if(commLen > remLen) {
errorLog0("decodeOpenSSHv1PrivKey: bad len(3)\n");
return CSSMERR_CSP_INVALID_KEY;
}
if(comment) {
*comment = (uint8 *)malloc(commLen);
*commentLen = commLen;
memcpy(*comment, cp, commLen);
}
cp += commLen;
remLen -= commLen;
unsigned char *ptext = (unsigned char *)malloc(remLen);
unsigned ptextLen = 0;
ourRtn = ssh1DES3Crypt(cipherSpec, false, cp, remLen,
decryptKey, decryptKey ? CC_MD5_DIGEST_LENGTH : 0,
ptext, &ptextLen);
if(ourRtn) {
errorLog0("UnwrapKeyOpenSSH1: decrypt error\n");
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
cp = ptext;
remLen = ptextLen;
if(remLen < 4) {
errorLog0("UnwrapKeyOpenSSH1: bad len(4)\n");
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
if((cp[0] != cp[2]) || (cp[1] != cp[3])) {
errorLog0("UnwrapKeyOpenSSH1: check byte error\n");
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
cp += 4;
remLen -= 4;
rsa->d = readBigNum(cp, remLen);
if(rsa->d == NULL) {
errorLog0("UnwrapKeyOpenSSH1: error decoding d\n");
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
rsa->iqmp = readBigNum(cp, remLen);
if(rsa->iqmp == NULL) {
errorLog0("UnwrapKeyOpenSSH1: error decoding iqmp\n");
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
rsa->q = readBigNum(cp, remLen);
if(rsa->q == NULL) {
errorLog0("UnwrapKeyOpenSSH1: error decoding q\n");
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
rsa->p = readBigNum(cp, remLen);
if(rsa->p == NULL) {
errorLog0("UnwrapKeyOpenSSH1: error decoding p\n");
ourRtn = CSSMERR_CSP_INVALID_KEY;
goto errOut;
}
ourRtn = rsa_generate_additional_parameters(rsa);
errOut:
if(ptext) {
memset(ptext, 0, ptextLen);
free(ptext);
}
return ourRtn;
}
void AppleCSPSession::UnwrapKeyOpenSSH1(
CSSM_CC_HANDLE CCHandle,
const Context &context,
const CssmKey &WrappedKey,
const CSSM_RESOURCE_CONTROL_CONTEXT *CredAndAclEntry,
CssmKey &UnwrappedKey,
CssmData &DescriptiveData,
CSSM_PRIVILEGE Privilege,
cspKeyStorage keyStorage)
{
CSSM_SIZE unwrapKeyLen = 0;
uint8 *unwrapKey = NULL;
AppleCSPContext::symmetricKeyBits(context, *this,
CSSM_ALGID_OPENSSH1, CSSM_KEYUSE_UNWRAP,
unwrapKey, unwrapKeyLen);
if((unwrapKey == NULL) || (unwrapKeyLen != CC_MD5_DIGEST_LENGTH)) {
errorLog0("AppleCSPSession::UnwrapKeyOpenSSH1: bad unwrapping key length\n");
CssmError::throwMe(CSSMERR_CSP_INVALID_KEY);
}
RSA *rsa = RSA_new();
CSSM_RETURN ourRtn = CSSM_OK;
unsigned char *comment = NULL;
unsigned commentLen = 0;
RSABinaryKey *binKey = NULL;
ourRtn = decodeOpenSSHv1PrivKey((const unsigned char *)WrappedKey.KeyData.Data,
(unsigned)WrappedKey.KeyData.Length,
rsa, unwrapKey, &comment, &commentLen);
if(ourRtn) {
goto errOut;
}
if(comment) {
setUpCssmData(DescriptiveData, commentLen, normAllocator);
memcpy(DescriptiveData.Data, comment, commentLen);
}
binKey = new RSABinaryKey(rsa);
addRefKey(*binKey, UnwrappedKey);
errOut:
if(ourRtn) {
if(rsa) {
RSA_free(rsa);
}
CssmError::throwMe(ourRtn);
}
}