/*
 * Copyright (c) 2004 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@
 *
 * SecExport.cpp - high-level facility for exporting Sec layer objects. 
 */

#include "SecImportExport.h"
#include "SecImportExportAgg.h"
#include "SecImportExportPem.h"
#include "SecExternalRep.h"
#include "SecImportExportUtils.h"
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include <security_utilities/errors.h>
#include <Security/SecIdentity.h>

using namespace Security;
using namespace KeychainCore;


/*
 * Convert Sec item to one or two SecExportReps, append to exportReps array.
 * The "one or two" clause exists for SecIdentityRefs, which we split into 
 * a cert and a key.
 * Throws a MacOSError if incoming CFTypeRef is of type other than SecKeyRef,
 * SecCertRef, or SecIdentityRef.
 */
static void impExpAddToExportReps(
	CFTypeRef			thing,				// Key, Cert, Identity
	CFMutableArrayRef   exportReps,
	unsigned			&numCerts,			// IN/OUT - accumulated
	unsigned			&numKeys)			// IN/OUT - accumulated
{
	if(CFGetTypeID(thing) == SecIdentityGetTypeID()) {
		/* special case for SecIdentities, creates two SecExportReps */
		OSStatus ortn;
		SecIdentityRef idRef = (SecIdentityRef)thing;
		SecCertificateRef certRef;
		SecKeyRef keyRef;
		SecExportRep *rep;
		
		/* cert */
		SecImpExpDbg("impExpAddToExportReps: adding identity cert and key");
		ortn = SecIdentityCopyCertificate(idRef, &certRef);
		if(ortn) {
			Security::MacOSError::throwMe(ortn);
		}
		rep = SecExportRep::vend(certRef);
		CFArrayAppendValue(exportReps, rep);
		CFRelease(certRef);			// SecExportRep holds a reference
		numCerts++;
		
		/* private key */
		ortn = SecIdentityCopyPrivateKey(idRef, &keyRef);
		if(ortn) {
			Security::MacOSError::throwMe(ortn);
		}
		rep = SecExportRep::vend(keyRef);
		CFArrayAppendValue(exportReps, rep);
		CFRelease(keyRef);			// SecExportRep holds a reference
		numKeys++;
	}
	else {
		/* this throws if 'thing' is an unacceptable type */
		SecExportRep *rep = SecExportRep::vend(thing);
		SecImpExpDbg("impExpAddToExportReps: adding single type %d",
			(int)rep->externType());
		CFArrayAppendValue(exportReps, rep);
		if(rep->externType() == kSecItemTypeCertificate) {
			numCerts++;
		}
		else {
			numKeys++;
		}
	}
}

#pragma mark --- public export function ---

OSStatus SecKeychainItemExport(
	CFTypeRef							keychainItemOrArray,
	SecExternalFormat					outputFormat,	// a SecExternalFormat 
	SecItemImportExportFlags			flags,			// kSecItemPemArmour, etc. 	
	const SecKeyImportExportParameters  *keyParams,		// optional 
	CFDataRef							*exportedData)	// external representation 
														//    returned here
{
	BEGIN_IMP_EXP_SECAPI
	
	/* some basic input validation */
	if(keychainItemOrArray == NULL) {
		return paramErr;
	}
	if(keyParams != NULL) {
		/* can't specify explicit passphrase and ask for secure one */
		if( (keyParams->passphrase != NULL) &&
		    (keyParams->flags & kSecKeySecurePassphrase != 0)) {
			return paramErr;
		}
	}
	
	unsigned numKeys			= 0;
	unsigned numCerts			= 0;
	unsigned numTotalExports	= 0;
	OSStatus ortn				= noErr;
	SecExportRep *rep			= NULL;				// common temp variable
	CFMutableDataRef outputData = NULL;
	const char *pemHeader		= "UNKNOWN";
	
	/* convert keychainItemOrArray to CFArray of SecExportReps */
	CFMutableArrayRef exportReps = CFArrayCreateMutable(NULL, 0, NULL);
	/* subsequent errors to errOut: */
	
	try {
		if(CFGetTypeID(keychainItemOrArray) == CFArrayGetTypeID()) {
			CFArrayRef arr = (CFArrayRef)keychainItemOrArray;
			CFIndex arraySize = CFArrayGetCount(arr);
			for(CFIndex dex=0; dex<arraySize; dex++) {
				impExpAddToExportReps(CFArrayGetValueAtIndex(arr, dex), 
					exportReps, numCerts, numKeys);
			}
		}
		else {
			impExpAddToExportReps(keychainItemOrArray, exportReps, numCerts, numKeys);
		}
	}
	catch(const Security::MacOSError osErr) {
		ortn = osErr.error;
		goto errOut;
	}
	catch(...) {
		ortn = paramErr;
		goto errOut;
	}
	numTotalExports = CFArrayGetCount(exportReps);
	assert((numCerts + numKeys) == numTotalExports);
	if((numTotalExports > 1) && (outputFormat == kSecFormatUnknown)) {
		/* default aggregate format is PEM sequence */
		outputFormat = kSecFormatPEMSequence;
	}
	
	/*
	 * Break out to SecExternalFormat-specific code, appending all data to outputData 
	 */
	outputData = CFDataCreateMutable(NULL, 0);
	switch(outputFormat) {
		case kSecFormatPKCS7:
			ortn = impExpPkcs7Export(exportReps, flags, keyParams, outputData);
			pemHeader = PEM_STRING_PKCS7;
			break;
		case kSecFormatPKCS12:
			ortn = impExpPkcs12Export(exportReps, flags, keyParams, outputData);
			pemHeader = PEM_STRING_PKCS12;
			break;
		case kSecFormatPEMSequence:
			{
				/* 
				 * A bit of a special case. Create an intermediate DER encoding 
				 * of each SecExportRef, in the default format for that item;
				 * PEM encode the result, and append the PEM encoding to 
				 * outputData.
				 */
				CFIndex numReps = CFArrayGetCount(exportReps);
				for(CFIndex dex=0; dex<numReps; dex++) {
				
					rep = (SecExportRep *)CFArrayGetValueAtIndex(exportReps, dex);
					
					/* default DER encoding */
					CFMutableDataRef tmpData = CFDataCreateMutable(NULL, 0);
					ortn = rep->exportRep(kSecFormatUnknown, flags, keyParams,
						tmpData, &pemHeader);
					if(ortn) {
						SecImpExpDbg("ItemExport: releasing tmpData %p", tmpData);
						CFRelease(tmpData);
						goto errOut;
					}
					
					/* PEM to accumulating output */
					assert(rep->pemParamLines() == NULL);
					ortn = impExpPemEncodeExportRep((CFDataRef)tmpData, 
							pemHeader, NULL,		/* no pemParamLines, right? */
							outputData);
					CFRelease(tmpData);
					if(ortn) {
						goto errOut;
					}
				}
				break;
			}
		
		/* Enumerate remainder explicitly for clarity; all are single-item forms */
		case kSecFormatOpenSSL:
		case kSecFormatSSH:
		case kSecFormatBSAFE:
		case kSecFormatRawKey:
		case kSecFormatWrappedPKCS8:
		case kSecFormatWrappedOpenSSL:
		case kSecFormatWrappedSSH:
		case kSecFormatWrappedLSH:
		case kSecFormatX509Cert:
		case kSecFormatUnknown:		// i.e., default, handled by SecExportRep
			{
				unsigned foundCount = 0;
				
				/* verify that we have exactly one of specified item */
				if(outputFormat == kSecFormatX509Cert) {
					foundCount = numCerts;
				}
				else if(outputFormat == kSecFormatUnknown) {
					/* can't go wrong */
					foundCount = numTotalExports;
				}
				else {
					foundCount = numKeys;
				}
				if((numTotalExports != 1) || (foundCount != 1)) {
					SecImpExpDbg("Export single item format with other than one item");
					ortn = paramErr;
					goto errOut;
				}
				assert(CFArrayGetCount(exportReps) == 1);
				rep = (SecExportRep *)CFArrayGetValueAtIndex(exportReps, 0);
				ortn = rep->exportRep(outputFormat, flags, 
						keyParams, outputData, &pemHeader);
				break;
			}
		default:
			SecImpExpDbg("SecKeychainItemExport: bad format (%u)", 
				(unsigned)outputFormat);
			ortn = paramErr;
			goto errOut;
	}
	
	/* 
	 * Final step: possible PEM encode. Skip for kSecFormatPEMSequence (in which
	 * case outputData is all ready to ship out to the caller); mandatory
	 * if exportRep has a non-NULL pemParamLines (which can only happen if we're
	 * exporting a single item). 
	 */
	if(ortn == noErr) {
		if(outputFormat == kSecFormatPEMSequence) {
			*exportedData = outputData;
			outputData = NULL;		
		}
		else {
			rep = (SecExportRep *)CFArrayGetValueAtIndex(exportReps, 0);
			if((flags & kSecItemPemArmour) || (rep->pemParamLines() != NULL)) {
				/* PEM encode a single item */
				CFMutableDataRef tmpData = CFDataCreateMutable(NULL, 0);
				ortn = impExpPemEncodeExportRep((CFDataRef)outputData, pemHeader, 
					rep->pemParamLines(), tmpData);
				CFRelease(outputData);		// done with this
				outputData = NULL;			
				*exportedData = tmpData;	// caller gets PEM
			}
			else {
				*exportedData = outputData;
				outputData = NULL;		
			}
		}
	}
errOut:
	if(exportReps != NULL) {
		/* CFArray of our own classes, no auto release */
		CFIndex num = CFArrayGetCount(exportReps);
		for(CFIndex dex=0; dex<num; dex++) {
			rep = (SecExportRep *)CFArrayGetValueAtIndex(exportReps, dex);
			delete rep;
		}
		CFRelease(exportReps);
	}
	if(outputData != NULL) {
		CFRelease(outputData);
		outputData = NULL;
	}
	return ortn;
	
	END_IMP_EXP_SECAPI
}
