--- /dev/null
+/*
+ * 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);
+ }
+}