opensshWrap.cpp   [plain text]


/*
 * Copyright (c) 2006 Apple Computer, Inc. All Rights Reserved.
 * 
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/*
 * opensshCoding.h - Encoding and decoding of OpenSSH format public keys.
 *
 * Created 8/29/2006 by dmitch.
 */

#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";

/* default comment on encode if app doesn't provide DescriptiveData */
#define OPENSSH1_COMMENT		"Encoded by Mac OS X Security.framework"

/* 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

#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,		// IN/OUT
	unsigned &len)					// IN/OUT 
{
	uint16_t r = *cp++;
	r <<= 8;
	r |= *cp++;
	len -= 2;
	return r;
}

/* Write BIGNUM, OpenSSH-1 version */
static CSSM_RETURN 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) {
		errorLog0("appendBigNum: BN_bn2bin() screwup\n");
		return CSSMERR_CSP_INTERNAL_ERROR;
	}
	CFDataAppendBytes(cfOut, (UInt8 *)outBytes, numBytes);
	return CSSM_OK;
}

/* Read BIGNUM, OpenSSH-1 version */
static BIGNUM *readBigNum(
	const unsigned char *&cp,	// IN/OUT
	unsigned &remLen)			// IN/OUT
{
	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;
}

/* 
 * Calculate d mod{p-1,q-1}
 * Used when decoding OpenSSH-1 private RSA key.
 */
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 ---

/* 
 * Encrypt/decrypt the secret portion of an OpenSSHv1 format RSA private key.
 */
static CSSM_RETURN ssh1DES3Crypt(
	unsigned char cipher,
	bool doEncrypt,
	const unsigned char *inText,
	unsigned inTextLen,
	const uint8 *key,			// MD5(password)
	CSSM_SIZE keyLen,
	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 CSSM_OK;
		default:
			/* who knows how we're going to figure these out */
			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;
	}
	
	/* three keys from that, like so: */
	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;
	}
	
	/* 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) {
		/* should never happen */
		errorLog1("***ssh1DES3Crypt: CCCrypt()(1) returned %u\n", (unsigned)cstat);
		return CSSMERR_CSP_INTERNAL_ERROR;
	}
	cstat = CCCrypt(op2, kCCAlgorithmDES, 
		0,						// no padding - SSH does that itself 
		k2, kCCKeySizeDES, 
		NULL,		// IV
		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,						// no padding - SSH does that itself 
		k3, kCCKeySizeDES, 
		NULL,		// IV
		outText, moved,
		outText, inTextLen, &moved);
	if(cstat) {
		errorLog1("***ssh1DES3Crypt: CCCrypt()(3) returned %u\n", (unsigned)cstat);
		return CSSMERR_CSP_INTERNAL_ERROR;
	}

	*outTextLen = moved;	
	return CSSM_OK;
}

#pragma mark --- DeriveKey ---

/*
 * Key derivation for OpenSSH1 private key wrap/unwrap.
 * This is pretty trivial, it's just an MD5() operation. The main 
 * purpose for doing this in a DeriveKey operation is to enable the 
 * use of either Secure Passphrases, obtained by securityd/SecurityAgent,
 * or app-specified data.
 */
void AppleCSPSession::DeriveKey_OpenSSH1(
	const Context &context,
	CSSM_ALGORITHMS algId,
	const CssmData &Param,			// IV optional, mallocd by app to indicate
									//   size 
	CSSM_DATA *keyData)				// mallocd by caller to indicate size - must be
									// size of MD5 digest!
{
	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);
	}
	
	/* password from either Seed.Param or from base key */
	CssmCryptoData *cryptData = 
		context.get<CssmCryptoData>(CSSM_ATTRIBUTE_SEED);
	if((cryptData != NULL) && (cryptData->Param.Length != 0)) {
		pwd = cryptData->Param;
	}
	else {
		/* Get secure passphrase from base key */
		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);
	}

	/* here it is */
	CC_MD5(pwd.Data, pwd.Length, keyData->Data);

}

#pragma mark --- Encode/Wrap OpenSSHv1 private key ---

/* 
 * Encode OpenSSHv1 private key, with or without encryption.
 * This used for generating key blobs of format CSSM_KEYBLOB_RAW_FORMAT_OPENSSH
 * as well as wrapping keys in format CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSH1.
 */
CSSM_RETURN encodeOpenSSHv1PrivKey(
	RSA *rsa,
	const uint8 *comment,		/* optional */
	unsigned commentLen,
	const uint8 *encryptKey,	/* optional; if present, it's 16 bytes of MD5(password) */
	CFDataRef *encodedKey)		/* RETURNED */
{
	CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
	CSSM_RETURN ourRtn = CSSM_OK;
	
	/* ID string including NULL */
	CFDataAppendBytes(cfOut, (const UInt8 *)authfile_id_string, strlen(authfile_id_string) + 1);
	
	/* one byte cipher */
	UInt8 cipherSpec = encryptKey ? SSH_CIPHER_3DES : SSH_CIPHER_NONE;
	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.
	 * The format appears to require this, or else we wouldn't know
	 * when we've got to the ciphertext on decode.
	 */
	if((comment == NULL) || (commentLen == 0)) {
		comment = (const UInt8 *)OPENSSH1_COMMENT;
		commentLen = strlen(OPENSSH1_COMMENT);
	}
	appendUint32(cfOut, commentLen);
	CFDataAppendBytes(cfOut, comment, commentLen);
	
	/* 
	 * 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);
	
	/* pad to block boundary */
	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);
	}
	
	/* encrypt it */
	ptextLen = CFDataGetLength(ptext);
	unsigned char ctext[ptextLen];
	unsigned ctextLen;
	ourRtn = ssh1DES3Crypt(cipherSpec, true, 
		(unsigned char *)CFDataGetBytePtr(ptext), ptextLen, 
		encryptKey, encryptKey ? CC_MD5_DIGEST_LENGTH : 0,
		ctext, &ctextLen);
	if(ourRtn != 0) {
		goto errOut;
	}
	
	/* appended encrypted portion */
	CFDataAppendBytes(cfOut, ctext, ctextLen);
	*encodedKey = cfOut;
errOut:
	/* it would be proper to zero out ptext here, but we can't do that to a CFData */
	CFRelease(ptext);
	return ourRtn;
}

void AppleCSPSession::WrapKeyOpenSSH1(
	CSSM_CC_HANDLE CCHandle,
	const Context &context,
	const AccessCredentials &AccessCred,
	BinaryKey &unwrappedBinKey,
	CssmData &rawBlob,
	bool allocdRawBlob,			// callee has to free rawBlob
	const CssmData *DescriptiveData,
	CssmKey &WrappedKey,
	CSSM_PRIVILEGE Privilege)
{
	/*
	 * The job here is to convert the RSA key in binKey to the OpenSSHv1 private
	 * key format, and drop that into WrappedKey.KeyData (allocated by the session).
	 *
	 * This cast throws an exception if the key is not an RSA key, which 
	 * would be a major bogon, since our caller verified that the unwrapped key
	 * is a private RSA key.
	 */
	RSABinaryKey &rPubBinKey = dynamic_cast<RSABinaryKey &>(unwrappedBinKey);
	RSA *rsa = rPubBinKey.mRsaKey;
	CASSERT(rsa != NULL);
	
	/*
	 * Get the raw password bits from the wrapping key.
	 * Our caller verified that the context has a symmetric key; this call
	 * ensures that the key is of algorithm CSSM_ALGID_OPENSSH1.
	 * Key length 0 means no encryption. 
	 */ 
	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;
	
	/* 
	 * Optional comment string from DescriptiveData.
	 */
	const UInt8 *comment = NULL;
	unsigned commentLen = 0;
	if((DescriptiveData != NULL) && (DescriptiveData->Length != 0)) {
		comment = (const UInt8 *)DescriptiveData->Data;
		commentLen = DescriptiveData->Length;
	}

	/* generate the encrypted blob */
	CSSM_RETURN crtn = encodeOpenSSHv1PrivKey(rsa, comment, commentLen, wrappingKey, &cfOut);
	if(crtn) {	
		CssmError::throwMe(crtn);
	}
	
	/* allocate key data in session's memory space */
	unsigned len = CFDataGetLength(cfOut);
	setUpData(WrappedKey.KeyData, len, normAllocator);
	memmove(WrappedKey.KeyData.Data, CFDataGetBytePtr(cfOut), len);
	CFRelease(cfOut);
	
	/* outgoing header */
	WrappedKey.KeyHeader.BlobType = CSSM_KEYBLOB_WRAPPED;
	// OK to be zero or not present 
	WrappedKey.KeyHeader.WrapMode = CSSM_ALGMODE_NONE;
	WrappedKey.KeyHeader.Format = CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSH1;
}

#pragma mark --- Decode/Unwrap OpenSSHv1 private key ---

/* 
 * Decode OpenSSHv1 private, optionally decrypting the secret portion. 
 * This used for decoding key blobs of format CSSM_KEYBLOB_RAW_FORMAT_OPENSSH
 * as well as unwrapping keys in format CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSH1.
 */
CSSM_RETURN decodeOpenSSHv1PrivKey(
	const unsigned char *encodedKey,
	unsigned encodedKeyLen,
	RSA *rsa,
	const uint8 *decryptKey,	/* optional; if present, it's 16 bytes of MD5(password) */
	uint8 **comment,			/* optional, mallocd and RETURNED */
	unsigned *commentLen)		/* RETURNED */
{
	unsigned len = strlen(authfile_id_string);
	const unsigned char *cp = encodedKey;
	unsigned remLen = encodedKeyLen;
	CSSM_RETURN ourRtn = CSSM_OK;
	
	/* length: ID string, NULL, Cipher, 4-byte spare */
	if(remLen < (len + 6)) {
		errorLog0("decodeOpenSSHv1PrivKey: short record(1)\n");
		return CSSMERR_CSP_INVALID_KEY;
	}
	
	/* ID string plus a NULL */
	if(memcmp(authfile_id_string, cp, len)) {
		errorLog0("decodeOpenSSHv1PrivKey: bad header\n");
		return CSSMERR_CSP_INVALID_KEY;
	}
	cp += (len + 1);
	remLen -= (len + 1);
	
	/* cipher */
	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:
			/* I hope we don't see any other values here */
			errorLog1("decodeOpenSSHv1PrivKey: unknown cipherSpec (%u)\n", cipherSpec);
			return CSSMERR_CSP_INVALID_KEY;
	}
		
	/* skip cipher, spares */
	cp += 5;
	remLen -= 5;
	
	/*
	 * Clear text public key:
	 * uint32 bits
	 * bignum n
	 * bignum e
	 */
	if(remLen < sizeof(uint32_t)) {
		errorLog0("decodeOpenSSHv1PrivKey: bad len(1)\n");
		return CSSMERR_CSP_INVALID_KEY;
	}
	/* skip over bits */
	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;
	}
	
	/* comment string: 4-byte length and the string w/o NULL */
	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;
	
	/* everything that remains is ciphertext */
	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;
	}
		
	/* 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) {
		errorLog0("UnwrapKeyOpenSSH1: bad len(4)\n");
		ourRtn = CSSMERR_CSP_INVALID_KEY;
		goto errOut;
	}
	if((cp[0] != cp[2]) || (cp[1] != cp[3])) {
		/* decrypt fail */
		errorLog0("UnwrapKeyOpenSSH1: check byte error\n");
		ourRtn = CSSMERR_CSP_INVALID_KEY;
		goto errOut;
	}
	cp += 4;
	remLen -= 4;
	
	/* remainder comprises private portion of RSA key */
	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;
	}

	/* calculate d mod{p-1,q-1} */
	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)
{
	/*
	 * Get the raw password bits from the unwrapping key.
	 * Our caller verified that the context has a symmetric key; this call
	 * ensures that the key is of algorithm CSSM_ALGID_OPENSSH1.
	 */ 
	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,
		WrappedKey.KeyData.Length,
		rsa, unwrapKey, &comment, &commentLen);
	if(ourRtn) {
		goto errOut;
	}
	if(comment) {
		setUpCssmData(DescriptiveData, commentLen, normAllocator);
		memcpy(DescriptiveData.Data, comment, commentLen);
	}

	/* 
	 * Our caller ensured that we're only generating a reference key,
	 * which we do like so:
	 */
	binKey = new RSABinaryKey(rsa);
	addRefKey(*binKey, UnwrappedKey);
	
errOut:
	if(ourRtn) {
		if(rsa) {
			RSA_free(rsa);
		}
		CssmError::throwMe(ourRtn);
	}
}