1/*
2 * Copyright (c) 2006,2011-2012,2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24/*
25 * opensshCoding.h - Encoding and decoding of OpenSSH format public keys.
26 *
27 */
28
29#include "AppleCSPSession.h"
30#include "AppleCSPContext.h"
31#include "AppleCSPUtils.h"
32#include "AppleCSPKeys.h"
33#include "RSA_DSA_Keys.h"
34#include "opensshCoding.h"
35#include "cspdebugging.h"
36#include <CommonCrypto/CommonDigest.h>
37#include <CommonCrypto/CommonCryptor.h>
38#include <openssl/rsa.h>
39#include <openssl/bn.h>
40#include <security_utilities/devrandom.h>
41
42static const char *authfile_id_string = "SSH PRIVATE KEY FILE FORMAT 1.1\n";
43
44/* default comment on encode if app doesn't provide DescriptiveData */
45#define OPENSSH1_COMMENT		"Encoded by Mac OS X Security.framework"
46
47/* from openssh cipher.h */
48#define SSH_CIPHER_NONE			0	/* no encryption */
49#define SSH_CIPHER_IDEA			1	/* IDEA CFB */
50#define SSH_CIPHER_DES			2	/* DES CBC */
51#define SSH_CIPHER_3DES			3	/* 3DES CBC */
52#define SSH_CIPHER_BROKEN_TSS	4	/* TRI's Simple Stream encryption CBC */
53#define SSH_CIPHER_BROKEN_RC4	5	/* Alleged RC4 */
54#define SSH_CIPHER_BLOWFISH		6
55#define SSH_CIPHER_RESERVED		7
56
57#pragma mark --- utilities ---
58
59static void appendUint16(
60	CFMutableDataRef cfOut,
61	uint16_t ui)
62{
63	UInt8 buf[sizeof(uint16_t)];
64
65	buf[1] = ui & 0xff;
66	ui >>= 8;
67	buf[0] = ui;
68	CFDataAppendBytes(cfOut, buf, sizeof(uint16_t));
69}
70
71static uint16_t readUint16(
72	const unsigned char *&cp,		// IN/OUT
73	unsigned &len)					// IN/OUT
74{
75	uint16_t r = *cp++;
76	r <<= 8;
77	r |= *cp++;
78	len -= 2;
79	return r;
80}
81
82/* Write BIGNUM, OpenSSH-1 version */
83static CSSM_RETURN appendBigNum(
84	CFMutableDataRef cfOut,
85	const BIGNUM *bn)
86{
87	/* 16 bits of numbits */
88	unsigned numBits = BN_num_bits(bn);
89	appendUint16(cfOut, numBits);
90
91	/* serialize the bytes */
92	int numBytes = (numBits + 7) / 8;
93	unsigned char outBytes[numBytes];	// gcc is so cool...
94	int moved = BN_bn2bin(bn, outBytes);
95	if(moved != numBytes) {
96		errorLog0("appendBigNum: BN_bn2bin() screwup\n");
97		return CSSMERR_CSP_INTERNAL_ERROR;
98	}
99	CFDataAppendBytes(cfOut, (UInt8 *)outBytes, numBytes);
100	return CSSM_OK;
101}
102
103/* Read BIGNUM, OpenSSH-1 version */
104static BIGNUM *readBigNum(
105	const unsigned char *&cp,	// IN/OUT
106	unsigned &remLen)			// IN/OUT
107{
108	if(remLen < sizeof(uint16_t)) {
109		errorLog0("readBigNum: short record(1)\n");
110		return NULL;
111	}
112	uint16_t numBits = readUint16(cp, remLen);
113	unsigned bytes = (numBits + 7) / 8;
114	if(remLen < bytes) {
115		errorLog0("readBigNum: short record(2)\n");
116		return NULL;
117	}
118	BIGNUM *bn = BN_bin2bn(cp, bytes, NULL);
119	if(bn == NULL) {
120		errorLog0("readBigNum: BN_bin2bn error\n");
121		return NULL;
122	}
123	cp += bytes;
124	remLen -= bytes;
125	return bn;
126}
127
128/*
129 * Calculate d mod{p-1,q-1}
130 * Used when decoding OpenSSH-1 private RSA key.
131 */
132static CSSM_RETURN rsa_generate_additional_parameters(RSA *rsa)
133{
134	BIGNUM *aux;
135	BN_CTX *ctx;
136
137	if((rsa->dmq1 = BN_new()) == NULL) {
138		errorLog0("rsa_generate_additional_parameters: BN_new failed");
139		return CSSMERR_CSP_INTERNAL_ERROR;
140	}
141	if((rsa->dmp1 = BN_new()) == NULL) {
142		errorLog0("rsa_generate_additional_parameters: BN_new failed");
143		return CSSMERR_CSP_INTERNAL_ERROR;
144	}
145	if ((aux = BN_new()) == NULL) {
146		errorLog0("rsa_generate_additional_parameters: BN_new failed");
147		return CSSMERR_CSP_INTERNAL_ERROR;
148	}
149	if ((ctx = BN_CTX_new()) == NULL) {
150		errorLog0("rsa_generate_additional_parameters: BN_CTX_new failed");
151		BN_clear_free(aux);
152		return CSSMERR_CSP_INTERNAL_ERROR;
153	}
154
155	BN_sub(aux, rsa->q, BN_value_one());
156	BN_mod(rsa->dmq1, rsa->d, aux, ctx);
157
158	BN_sub(aux, rsa->p, BN_value_one());
159	BN_mod(rsa->dmp1, rsa->d, aux, ctx);
160
161	BN_clear_free(aux);
162	BN_CTX_free(ctx);
163	return CSSM_OK;
164}
165
166#pragma mark --- encrypt/decrypt ---
167
168/*
169 * Encrypt/decrypt the secret portion of an OpenSSHv1 format RSA private key.
170 */
171static CSSM_RETURN ssh1DES3Crypt(
172	unsigned char cipher,
173	bool doEncrypt,
174	const unsigned char *inText,
175	unsigned inTextLen,
176	const uint8 *key,			// MD5(password)
177	CSSM_SIZE keyLen,
178	unsigned char *outText,		// data RETURNED here, caller mallocs.
179	unsigned *outTextLen)		// RETURNED
180{
181	switch(cipher) {
182		case SSH_CIPHER_3DES:
183			break;
184		case SSH_CIPHER_NONE:
185			/* cleartext RSA private key, e.g. host key. */
186			memmove(outText, inText, inTextLen);
187			*outTextLen = inTextLen;
188			return CSSM_OK;
189		default:
190			/* who knows how we're going to figure these out */
191			errorLog1("***ssh1DES3Crypt: Unsupported cipher (%u)\n", cipher);
192			return CSSMERR_CSP_INVALID_KEY;
193	}
194
195	if(keyLen != CC_MD5_DIGEST_LENGTH) {
196		errorLog0("ssh1DES3Crypt: bad key length\n");
197		return CSSMERR_CSP_INVALID_KEY;
198	}
199
200	/* three keys from that, like so: */
201	unsigned char k1[kCCKeySizeDES];
202	unsigned char k2[kCCKeySizeDES];
203	unsigned char k3[kCCKeySizeDES];
204	memmove(k1, key, kCCKeySizeDES);
205	memmove(k2, key + kCCKeySizeDES, kCCKeySizeDES);
206	memmove(k3, key, kCCKeySizeDES);
207
208	CCOperation op1_3;
209	CCOperation op2;
210	if(doEncrypt) {
211		op1_3 = kCCEncrypt;
212		op2   = kCCDecrypt;
213	}
214	else {
215		op1_3 = kCCDecrypt;
216		op2   = kCCEncrypt;
217	}
218
219	/* the openssh v1 pseudo triple DES. Each DES pass has its own CBC. */
220	size_t moved = 0;
221
222	CCCryptorStatus cstat = CCCrypt(op1_3, kCCAlgorithmDES,
223		0,						// no padding
224		k1, kCCKeySizeDES,
225		NULL,		// IV
226		inText, inTextLen,
227		outText, inTextLen, &moved);
228	if(cstat) {
229		/* should never happen */
230		errorLog1("***ssh1DES3Crypt: CCCrypt()(1) returned %u\n", (unsigned)cstat);
231		return CSSMERR_CSP_INTERNAL_ERROR;
232	}
233	cstat = CCCrypt(op2, kCCAlgorithmDES,
234		0,						// no padding - SSH does that itself
235		k2, kCCKeySizeDES,
236		NULL,		// IV
237		outText, moved,
238		outText, inTextLen, &moved);
239	if(cstat) {
240		errorLog1("***ssh1DES3Crypt: CCCrypt()(2) returned %u\n", (unsigned)cstat);
241		return CSSMERR_CSP_INTERNAL_ERROR;
242	}
243	cstat = CCCrypt(op1_3, kCCAlgorithmDES,
244		0,						// no padding - SSH does that itself
245		k3, kCCKeySizeDES,
246		NULL,		// IV
247		outText, moved,
248		outText, inTextLen, &moved);
249	if(cstat) {
250		errorLog1("***ssh1DES3Crypt: CCCrypt()(3) returned %u\n", (unsigned)cstat);
251		return CSSMERR_CSP_INTERNAL_ERROR;
252	}
253
254	*outTextLen = (unsigned)moved;
255	return CSSM_OK;
256}
257
258#pragma mark --- DeriveKey ---
259
260/*
261 * Key derivation for OpenSSH1 private key wrap/unwrap.
262 * This is pretty trivial, it's just an MD5() operation. The main
263 * purpose for doing this in a DeriveKey operation is to enable the
264 * use of either Secure Passphrases, obtained by securityd/SecurityAgent,
265 * or app-specified data.
266 */
267void AppleCSPSession::DeriveKey_OpenSSH1(
268	const Context &context,
269	CSSM_ALGORITHMS algId,
270	const CssmData &Param,			// IV optional, mallocd by app to indicate
271									//   size
272	CSSM_DATA *keyData)				// mallocd by caller to indicate size - must be
273									// size of MD5 digest!
274{
275	CSSM_DATA pwd = {0, NULL};
276
277	if(keyData->Length != CC_MD5_DIGEST_LENGTH) {
278		errorLog0("DeriveKey_OpenSSH1: invalid key length\n");
279		CssmError::throwMe(CSSMERR_CSP_UNSUPPORTED_KEY_SIZE);
280	}
281
282	/* password from either Seed.Param or from base key */
283	CssmCryptoData *cryptData =
284		context.get<CssmCryptoData>(CSSM_ATTRIBUTE_SEED);
285	if((cryptData != NULL) && (cryptData->Param.Length != 0)) {
286		pwd = cryptData->Param;
287	}
288	else {
289		/* Get secure passphrase from base key */
290		CssmKey *passKey = context.get<CssmKey>(CSSM_ATTRIBUTE_KEY);
291		if (passKey != NULL) {
292			AppleCSPContext::symmetricKeyBits(context, *this,
293				CSSM_ALGID_SECURE_PASSPHRASE, CSSM_KEYUSE_DERIVE,
294				pwd.Data, pwd.Length);
295		}
296	}
297
298	if(pwd.Data == NULL) {
299		errorLog0("DeriveKey_PKCS5_V1_5: null Passphrase\n");
300		CssmError::throwMe(CSSMERR_CSP_INVALID_DATA);
301	}
302	if(pwd.Length == 0) {
303		errorLog0("DeriveKey_PKCS5_V1_5: zero length passphrase\n");
304		CssmError::throwMe(CSSMERR_CSP_INVALID_INPUT_POINTER);
305	}
306
307	/* here it is */
308	CC_MD5(pwd.Data, (CC_LONG)pwd.Length, keyData->Data);
309
310}
311
312#pragma mark --- Encode/Wrap OpenSSHv1 private key ---
313
314/*
315 * Encode OpenSSHv1 private key, with or without encryption.
316 * This used for generating key blobs of format CSSM_KEYBLOB_RAW_FORMAT_OPENSSH
317 * as well as wrapping keys in format CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSH1.
318 */
319CSSM_RETURN encodeOpenSSHv1PrivKey(
320	RSA *rsa,
321	const uint8 *comment,		/* optional */
322	unsigned commentLen,
323	const uint8 *encryptKey,	/* optional; if present, it's 16 bytes of MD5(password) */
324	CFDataRef *encodedKey)		/* RETURNED */
325{
326	CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0);
327	CSSM_RETURN ourRtn = CSSM_OK;
328
329	/* ID string including NULL */
330	CFDataAppendBytes(cfOut, (const UInt8 *)authfile_id_string, strlen(authfile_id_string) + 1);
331
332	/* one byte cipher */
333	UInt8 cipherSpec = encryptKey ? SSH_CIPHER_3DES : SSH_CIPHER_NONE;
334	CFDataAppendBytes(cfOut, &cipherSpec, 1);
335
336	/* spares */
337	UInt8 spares[4] = {0};
338	CFDataAppendBytes(cfOut, spares, 4);
339
340	/*
341	 * Clear text public key:
342	 * uint32 bits
343	 * bignum n
344	 * bignum e
345	 */
346	uint32_t keybits = RSA_size(rsa) * 8;
347	appendUint32(cfOut, keybits);
348	appendBigNum(cfOut, rsa->n);
349	appendBigNum(cfOut, rsa->e);
350
351	/*
352	 * Comment string.
353	 * The format appears to require this, or else we wouldn't know
354	 * when we've got to the ciphertext on decode.
355	 */
356	if((comment == NULL) || (commentLen == 0)) {
357		comment = (const UInt8 *)OPENSSH1_COMMENT;
358		commentLen = strlen(OPENSSH1_COMMENT);
359	}
360	appendUint32(cfOut, commentLen);
361	CFDataAppendBytes(cfOut, comment, commentLen);
362
363	/*
364	 * Remainder is encrypted, consisting of
365	 *
366	 * [0-1]		-- random bytes
367	 * [2-3]		-- copy of [01] for passphrase validity checking
368	 * buffer_put_bignum(d)
369	 * buffer_put_bignum(iqmp)
370	 * buffer_put_bignum(q)
371	 * buffer_put_bignum(p)
372	 * pad to block size
373	 */
374	CFMutableDataRef ptext = CFDataCreateMutable(NULL, 0);
375
376	/* [0..3] check bytes */
377	UInt8 checkBytes[4];
378	DevRandomGenerator rng = DevRandomGenerator();
379	rng.random(checkBytes, 2);
380	checkBytes[2] = checkBytes[0];
381	checkBytes[3] = checkBytes[1];
382	CFDataAppendBytes(ptext, checkBytes, 4);
383
384	/* d, iqmp, q, p */
385	appendBigNum(ptext, rsa->d);
386	appendBigNum(ptext, rsa->iqmp);
387	appendBigNum(ptext, rsa->q);
388	appendBigNum(ptext, rsa->p);
389
390	/* pad to block boundary */
391	CFIndex ptextLen = CFDataGetLength(ptext);
392	unsigned padding = 0;
393	unsigned rem = (unsigned)ptextLen & 0x7;
394	if(rem) {
395		padding = 8 - rem;
396	}
397	UInt8 padByte = 0;
398	for(unsigned dex=0; dex<padding; dex++) {
399		CFDataAppendBytes(ptext, &padByte, 1);
400	}
401
402	/* encrypt it */
403	ptextLen = CFDataGetLength(ptext);
404	unsigned char ctext[ptextLen];
405	unsigned ctextLen;
406	ourRtn = ssh1DES3Crypt(cipherSpec, true,
407		(unsigned char *)CFDataGetBytePtr(ptext), (unsigned)ptextLen,
408		encryptKey, encryptKey ? CC_MD5_DIGEST_LENGTH : 0,
409		ctext, &ctextLen);
410	if(ourRtn != 0) {
411		goto errOut;
412	}
413
414	/* appended encrypted portion */
415	CFDataAppendBytes(cfOut, ctext, ctextLen);
416	*encodedKey = cfOut;
417errOut:
418	/* it would be proper to zero out ptext here, but we can't do that to a CFData */
419	CFRelease(ptext);
420	return ourRtn;
421}
422
423void AppleCSPSession::WrapKeyOpenSSH1(
424	CSSM_CC_HANDLE CCHandle,
425	const Context &context,
426	const AccessCredentials &AccessCred,
427	BinaryKey &unwrappedBinKey,
428	CssmData &rawBlob,
429	bool allocdRawBlob,			// callee has to free rawBlob
430	const CssmData *DescriptiveData,
431	CssmKey &WrappedKey,
432	CSSM_PRIVILEGE Privilege)
433{
434	/*
435	 * The job here is to convert the RSA key in binKey to the OpenSSHv1 private
436	 * key format, and drop that into WrappedKey.KeyData (allocated by the session).
437	 *
438	 * This cast throws an exception if the key is not an RSA key, which
439	 * would be a major bogon, since our caller verified that the unwrapped key
440	 * is a private RSA key.
441	 */
442	RSABinaryKey &rPubBinKey = dynamic_cast<RSABinaryKey &>(unwrappedBinKey);
443	RSA *rsa = rPubBinKey.mRsaKey;
444	CASSERT(rsa != NULL);
445
446	/*
447	 * Get the raw password bits from the wrapping key.
448	 * Our caller verified that the context has a symmetric key; this call
449	 * ensures that the key is of algorithm CSSM_ALGID_OPENSSH1.
450	 * Key length 0 means no encryption.
451	 */
452	CSSM_SIZE	wrappingKeyLen = 0;
453	uint8		*wrappingKey = NULL;
454
455	AppleCSPContext::symmetricKeyBits(context, *this,
456		CSSM_ALGID_OPENSSH1, CSSM_KEYUSE_WRAP,
457		wrappingKey, wrappingKeyLen);
458	if(wrappingKeyLen != CC_MD5_DIGEST_LENGTH) {
459		errorLog0("AppleCSPSession::WrapKeyOpenSSH1: bad wrapping key length\n");
460		CssmError::throwMe(CSSMERR_CSP_INVALID_KEY);
461	}
462
463	CFDataRef cfOut = NULL;
464
465	/*
466	 * Optional comment string from DescriptiveData.
467	 */
468	const UInt8 *comment = NULL;
469	unsigned commentLen = 0;
470	if((DescriptiveData != NULL) && (DescriptiveData->Length != 0)) {
471		comment = (const UInt8 *)DescriptiveData->Data;
472		commentLen = (unsigned)DescriptiveData->Length;
473	}
474
475	/* generate the encrypted blob */
476	CSSM_RETURN crtn = encodeOpenSSHv1PrivKey(rsa, comment, commentLen, wrappingKey, &cfOut);
477	if(crtn) {
478		CssmError::throwMe(crtn);
479	}
480
481	/* allocate key data in session's memory space */
482	CFIndex len = CFDataGetLength(cfOut);
483	setUpData(WrappedKey.KeyData, len, normAllocator);
484	memmove(WrappedKey.KeyData.Data, CFDataGetBytePtr(cfOut), len);
485	CFRelease(cfOut);
486
487	/* outgoing header */
488	WrappedKey.KeyHeader.BlobType = CSSM_KEYBLOB_WRAPPED;
489	// OK to be zero or not present
490	WrappedKey.KeyHeader.WrapMode = CSSM_ALGMODE_NONE;
491	WrappedKey.KeyHeader.Format = CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSH1;
492}
493
494#pragma mark --- Decode/Unwrap OpenSSHv1 private key ---
495
496/*
497 * Decode OpenSSHv1 private, optionally decrypting the secret portion.
498 * This used for decoding key blobs of format CSSM_KEYBLOB_RAW_FORMAT_OPENSSH
499 * as well as unwrapping keys in format CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSH1.
500 */
501CSSM_RETURN decodeOpenSSHv1PrivKey(
502	const unsigned char *encodedKey,
503	unsigned encodedKeyLen,
504	RSA *rsa,
505	const uint8 *decryptKey,	/* optional; if present, it's 16 bytes of MD5(password) */
506	uint8 **comment,			/* optional, mallocd and RETURNED */
507	unsigned *commentLen)		/* RETURNED */
508{
509	unsigned len = (unsigned)strlen(authfile_id_string);
510	const unsigned char *cp = encodedKey;
511	unsigned remLen = encodedKeyLen;
512	CSSM_RETURN ourRtn = CSSM_OK;
513
514	/* length: ID string, NULL, Cipher, 4-byte spare */
515	if(remLen < (len + 6)) {
516		errorLog0("decodeOpenSSHv1PrivKey: short record(1)\n");
517		return CSSMERR_CSP_INVALID_KEY;
518	}
519
520	/* ID string plus a NULL */
521	if(memcmp(authfile_id_string, cp, len)) {
522		errorLog0("decodeOpenSSHv1PrivKey: bad header\n");
523		return CSSMERR_CSP_INVALID_KEY;
524	}
525	cp += (len + 1);
526	remLen -= (len + 1);
527
528	/* cipher */
529	unsigned char cipherSpec = *cp;
530	switch(cipherSpec) {
531		case SSH_CIPHER_NONE:
532			if(decryptKey != NULL) {
533				errorLog0("decodeOpenSSHv1PrivKey: Attempt to decrypt plaintext key\n");
534				return CSSMERR_CSP_INVALID_KEY;
535			}
536			break;
537		case SSH_CIPHER_3DES:
538			if(decryptKey == NULL) {
539				errorLog0("decodeOpenSSHv1PrivKey: Encrypted key with no decryptKey\n");
540				return CSSMERR_CSP_INVALID_KEY;
541			}
542			break;
543		default:
544			/* I hope we don't see any other values here */
545			errorLog1("decodeOpenSSHv1PrivKey: unknown cipherSpec (%u)\n", cipherSpec);
546			return CSSMERR_CSP_INVALID_KEY;
547	}
548
549	/* skip cipher, spares */
550	cp += 5;
551	remLen -= 5;
552
553	/*
554	 * Clear text public key:
555	 * uint32 bits
556	 * bignum n
557	 * bignum e
558	 */
559	if(remLen < sizeof(uint32_t)) {
560		errorLog0("decodeOpenSSHv1PrivKey: bad len(1)\n");
561		return CSSMERR_CSP_INVALID_KEY;
562	}
563	/* skip over bits */
564	readUint32(cp, remLen);
565	rsa->n = readBigNum(cp, remLen);
566	if(rsa->n == NULL) {
567		errorLog0("decodeOpenSSHv1PrivKey: error decoding n\n");
568		return CSSMERR_CSP_INVALID_KEY;
569	}
570	rsa->e = readBigNum(cp, remLen);
571	if(rsa->e == NULL) {
572		errorLog0("decodeOpenSSHv1PrivKey: error decoding e\n");
573		return CSSMERR_CSP_INVALID_KEY;
574	}
575
576	/* comment string: 4-byte length and the string w/o NULL */
577	if(remLen < sizeof(uint32_t)) {
578		errorLog0("decodeOpenSSHv1PrivKey: bad len(2)\n");
579		return CSSMERR_CSP_INVALID_KEY;
580	}
581	uint32_t commLen = readUint32(cp, remLen);
582	if(commLen > remLen) {
583		errorLog0("decodeOpenSSHv1PrivKey: bad len(3)\n");
584		return CSSMERR_CSP_INVALID_KEY;
585	}
586	if(comment) {
587		*comment = (uint8 *)malloc(commLen);
588		*commentLen = commLen;
589		memcpy(*comment, cp, commLen);
590	}
591
592	cp += commLen;
593	remLen -= commLen;
594
595	/* everything that remains is ciphertext */
596	unsigned char *ptext = (unsigned char *)malloc(remLen);
597	unsigned ptextLen = 0;
598	ourRtn = ssh1DES3Crypt(cipherSpec, false, cp, remLen,
599		decryptKey, decryptKey ? CC_MD5_DIGEST_LENGTH : 0,
600		ptext, &ptextLen);
601	if(ourRtn) {
602		errorLog0("UnwrapKeyOpenSSH1: decrypt error\n");
603		ourRtn = CSSMERR_CSP_INVALID_KEY;
604		goto errOut;
605	}
606
607	/* plaintext contents:
608
609	[0-1]		-- random bytes
610	[2-3]		-- copy of [01] for passphrase validity checking
611	buffer_put_bignum(d)
612	buffer_put_bignum(iqmp)
613	buffer_put_bignum(q)
614	buffer_put_bignum(p)
615	pad to block size
616	*/
617	cp = ptext;
618	remLen = ptextLen;
619	if(remLen < 4) {
620		errorLog0("UnwrapKeyOpenSSH1: bad len(4)\n");
621		ourRtn = CSSMERR_CSP_INVALID_KEY;
622		goto errOut;
623	}
624	if((cp[0] != cp[2]) || (cp[1] != cp[3])) {
625		/* decrypt fail */
626		errorLog0("UnwrapKeyOpenSSH1: check byte error\n");
627		ourRtn = CSSMERR_CSP_INVALID_KEY;
628		goto errOut;
629	}
630	cp += 4;
631	remLen -= 4;
632
633	/* remainder comprises private portion of RSA key */
634	rsa->d = readBigNum(cp, remLen);
635	if(rsa->d == NULL) {
636		errorLog0("UnwrapKeyOpenSSH1: error decoding d\n");
637		ourRtn = CSSMERR_CSP_INVALID_KEY;
638		goto errOut;
639	}
640	rsa->iqmp = readBigNum(cp, remLen);
641	if(rsa->iqmp == NULL) {
642		errorLog0("UnwrapKeyOpenSSH1: error decoding iqmp\n");
643		ourRtn = CSSMERR_CSP_INVALID_KEY;
644		goto errOut;
645	}
646	rsa->q = readBigNum(cp, remLen);
647	if(rsa->q == NULL) {
648		errorLog0("UnwrapKeyOpenSSH1: error decoding q\n");
649		ourRtn = CSSMERR_CSP_INVALID_KEY;
650		goto errOut;
651	}
652	rsa->p = readBigNum(cp, remLen);
653	if(rsa->p == NULL) {
654		errorLog0("UnwrapKeyOpenSSH1: error decoding p\n");
655		ourRtn = CSSMERR_CSP_INVALID_KEY;
656		goto errOut;
657	}
658
659	/* calculate d mod{p-1,q-1} */
660	ourRtn = rsa_generate_additional_parameters(rsa);
661
662errOut:
663	if(ptext) {
664		memset(ptext, 0, ptextLen);
665		free(ptext);
666	}
667	return ourRtn;
668}
669
670void AppleCSPSession::UnwrapKeyOpenSSH1(
671	CSSM_CC_HANDLE CCHandle,
672	const Context &context,
673	const CssmKey &WrappedKey,
674	const CSSM_RESOURCE_CONTROL_CONTEXT *CredAndAclEntry,
675	CssmKey &UnwrappedKey,
676	CssmData &DescriptiveData,
677	CSSM_PRIVILEGE Privilege,
678	cspKeyStorage keyStorage)
679{
680	/*
681	 * Get the raw password bits from the unwrapping key.
682	 * Our caller verified that the context has a symmetric key; this call
683	 * ensures that the key is of algorithm CSSM_ALGID_OPENSSH1.
684	 */
685	CSSM_SIZE		unwrapKeyLen = 0;
686	uint8			*unwrapKey = NULL;
687
688	AppleCSPContext::symmetricKeyBits(context, *this,
689		CSSM_ALGID_OPENSSH1, CSSM_KEYUSE_UNWRAP,
690		unwrapKey, unwrapKeyLen);
691	if((unwrapKey == NULL) || (unwrapKeyLen != CC_MD5_DIGEST_LENGTH)) {
692		errorLog0("AppleCSPSession::UnwrapKeyOpenSSH1: bad unwrapping key length\n");
693		CssmError::throwMe(CSSMERR_CSP_INVALID_KEY);
694	}
695
696	RSA *rsa = RSA_new();
697	CSSM_RETURN ourRtn = CSSM_OK;
698	unsigned char *comment = NULL;
699	unsigned commentLen = 0;
700	RSABinaryKey *binKey = NULL;
701
702	ourRtn = decodeOpenSSHv1PrivKey((const unsigned char *)WrappedKey.KeyData.Data,
703		(unsigned)WrappedKey.KeyData.Length,
704		rsa, unwrapKey, &comment, &commentLen);
705	if(ourRtn) {
706		goto errOut;
707	}
708	if(comment) {
709		setUpCssmData(DescriptiveData, commentLen, normAllocator);
710		memcpy(DescriptiveData.Data, comment, commentLen);
711	}
712
713	/*
714	 * Our caller ensured that we're only generating a reference key,
715	 * which we do like so:
716	 */
717	binKey = new RSABinaryKey(rsa);
718	addRefKey(*binKey, UnwrappedKey);
719
720errOut:
721	if(ourRtn) {
722		if(rsa) {
723			RSA_free(rsa);
724		}
725		CssmError::throwMe(ourRtn);
726	}
727}
728