]> git.saurik.com Git - apple/security.git/blobdiff - libsecurity_apple_csp/lib/opensshWrap.cpp
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_apple_csp / lib / opensshWrap.cpp
diff --git a/libsecurity_apple_csp/lib/opensshWrap.cpp b/libsecurity_apple_csp/lib/opensshWrap.cpp
new file mode 100644 (file)
index 0000000..9338d52
--- /dev/null
@@ -0,0 +1,728 @@
+/*
+ * 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);
+       }
+}