/*
 * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved.
 * 
 * @APPLE_LICENSE_HEADER_START@
 * 
 * Copyright (c) 1999-2003 Apple Computer, Inc.  All Rights Reserved.
 * 
 * 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@
 */


//
// sstransit - SecurityServer client library transition code.
//
// These are the functions that implement CssmClient methods in terms of
// MIG IPC client calls, plus their supporting machinery.
//
#include "sstransit.h"
#include <security_cdsa_client/cspclient.h>
#include <security_utilities/ktracecodes.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>

namespace Security {

using MachPlusPlus::check;
using MachPlusPlus::VMGuard;


//
// DataOutput helper.
// This happens "at the end" of a glue method, via the DataOutput destructor.
//
DataOutput::~DataOutput()
{
	// @@@ Why are we setting up a VMGuard if mData is NULL?
	VMGuard _(mData, mLength);
	if (mData) {	// was assigned to; IPC returned OK
		if (argument) {	// buffer was provided
			if (argument.length() < mLength)
				CssmError::throwMe(CSSMERR_CSP_OUTPUT_LENGTH_ERROR);
			argument.length(mLength);
		} else {	// allocate buffer
			argument = CssmData(allocator.malloc(mLength), mLength);
		}
		memcpy(argument.data(), mData, mLength);
	}
}


CssmList chunkCopy(CssmList &list, Allocator &alloc)
{
	CssmList copy = list;
	ChunkCopyWalker w(alloc);
	walk(w, copy);
	return copy;
}


//
// Create a packaged-up Context for IPC transmission.
// In addition to collecting the context into a contiguous blob for transmission,
// we also evaluate CssmCryptoData callbacks at this time.
//
SendContext::SendContext(const Security::Context &ctx) : context(ctx)
{
	CssmCryptoData cryptoDataValue;	// holding area for CssmCryptoData element
	IFDEBUG(uint32 cryptoDataUsed = 0);
	Context::Builder builder(Allocator::standard());
	for (unsigned n = 0; n < ctx.attributesInUse(); n++) {
		switch (ctx[n].baseType()) {
		case CSSM_ATTRIBUTE_DATA_CRYPTO_DATA: {
			CssmCryptoData &data = ctx[n];	// extract CssmCryptoData value
			cryptoDataValue = data();		// evaluate callback (if any)
			builder.setup(&cryptoDataValue); // use evaluted value
			IFDEBUG(cryptoDataUsed++);
			break;
		}
		default:
			builder.setup(ctx[n]);
			break;
		}
	}
	attributeSize = builder.make();
	for (unsigned n = 0; n < ctx.attributesInUse(); n++) {
		const Context::Attr &attr = ctx[n];
		switch (attr.baseType()) {
		case CSSM_ATTRIBUTE_DATA_CRYPTO_DATA:
			builder.put(attr.type(), &cryptoDataValue);
			break;
		default:
			builder.put(attr);
			break;
		}
	}
	uint32 count;	// not needed
	builder.done(attributes, count);
	assert(cryptoDataUsed <= 1);	// no more than one slot converted
}


//
// Copy an AccessCredentials for shipment.
// In addition, scan the samples for "special" database locking samples
// and translate certain items for safe shipment. Note that this overwrites
// part of the CssmList value (CSPHandle -> SS/KeyHandle), but we do it on
// the COPY, so that's okay.
//
DatabaseAccessCredentials::DatabaseAccessCredentials(const AccessCredentials *creds, Allocator &alloc)
	: Copier<AccessCredentials>(creds, alloc)
{
	if (creds) {
		for (uint32 n = 0; n < value()->samples().length(); n++) {
			TypedList sample = value()->samples()[n];
			sample.checkProper();
			switch (sample.type()) {
			case CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK:
			case CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK:
				sample.snip();	// skip sample type
				sample.checkProper();
				if (sample.type() == CSSM_WORDID_SYMMETRIC_KEY) {
					secdebug("SSclient", "key sample encountered");
					// proper form is sample[1] = DATA:CSPHandle, sample[2] = DATA:CSSM_KEY
					if (sample.length() != 3
						|| sample[1].type() != CSSM_LIST_ELEMENT_DATUM
						|| sample[2].type() != CSSM_LIST_ELEMENT_DATUM)
							CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
					mapKeySample(
						*sample[1].data().interpretedAs<CSSM_CSP_HANDLE>(CSSM_ERRCODE_INVALID_SAMPLE_VALUE),
						*sample[2].data().interpretedAs<CssmKey>(CSSM_ERRCODE_INVALID_SAMPLE_VALUE));
				}
				break;
			default:
				break;
			}
		}
	}
}

void DatabaseAccessCredentials::mapKeySample(CSSM_CSP_HANDLE &cspHandle, CssmKey &key)
{
	// if the key belongs to the AppleCSPDL, look it up and write the SS KeyHandle
	// into the CSPHandle element for transmission
	if (key.header().cspGuid() == gGuidAppleCSPDL) {
		// @@@ can't use CssmClient (it makes its own attachments)
		CSSM_CC_HANDLE ctx;
		if (CSSM_RETURN err = CSSM_CSP_CreatePassThroughContext(cspHandle, &key, &ctx))
			CssmError::throwMe(err);
		KeyHandle ssKey;
		CSSM_RETURN passthroughError =
			CSSM_CSP_PassThrough(ctx, CSSM_APPLESCPDL_CSP_GET_KEYHANDLE, NULL, (void **)&ssKey);
		CSSM_DeleteContext(ctx);	// ignore error
		if (passthroughError)
			CssmError::throwMe(passthroughError);
		// we happen to know that they're both uint32 values
		assert(sizeof(CSSM_CSP_HANDLE) >= sizeof(KeyHandle));
		cspHandle = ssKey;
		secdebug("SSclient", "key sample mapped to key 0x%lx", ssKey);
	}
}


namespace SecurityServer
{


//
// DL Interface
//

AttachmentHandle
ClientSession::attach(const CssmSubserviceUid &ssuid)
{
	AttachmentHandle attachment;

	Copier<CssmSubserviceUid> in_ssuid(&ssuid, internalAllocator);
	IPC(ucsp_client_attach(UCSP_ARGS, COPY(in_ssuid), &attachment));
	return attachment;
}

void
ClientSession::detach(AttachmentHandle attachment)
{
	IPC(ucsp_client_detach(UCSP_ARGS, attachment));
}

DbHandle
ClientSession::openDb(AttachmentHandle attachment,
				const char *dbName,
				const CssmNetAddress *dbLocation,
				CSSM_DB_ACCESS_TYPE accessType,
				const AccessCredentials *cred,
				const CssmData &openParameters)
{
	DbHandle db;
	Copier<CssmNetAddress> location(dbLocation, internalAllocator);
	DatabaseAccessCredentials creds(cred, internalAllocator);
	IPC(ucsp_client_openDb(UCSP_ARGS, attachment, const_cast<char *>(dbName), strlen(dbName) + 1, COPY(location),
		accessType, COPY(creds), DATA(openParameters), &db));
	return db;
}

DbHandle
ClientSession::createDb(AttachmentHandle attachment,
				  const char *dbName,
				  const CssmNetAddress *dbLocation,
				  const CSSM_DBINFO &dbInfo,
				  CSSM_DB_ACCESS_TYPE accessType,
				  const AccessCredentials *cred,
				  const AclEntryInput *owner,
				  const CssmData &openParameters)
{
	Copier<CssmNetAddress> location(dbLocation, internalAllocator);
	Copier<CSSM_DBINFO> info(&dbInfo, internalAllocator);
	DatabaseAccessCredentials creds(cred, internalAllocator);
	Copier<AclEntryPrototype> proto(owner ? &owner->proto() : NULL, internalAllocator);
	DbHandle db;
	IPC(ucsp_client_createDb2(UCSP_ARGS, attachment, const_cast<char *>(dbName), strlen(dbName) + 1,
		COPY(location), COPY(info), accessType, COPY(creds), COPY(proto), DATA(openParameters), &db));
	return db;
}

void
ClientSession::deleteDb(AttachmentHandle attachment,
			  const char *dbName,
			  const CssmNetAddress *dbLocation,
			  const AccessCredentials *cred)
{
	Copier<CssmNetAddress> location(dbLocation, internalAllocator);
	DatabaseAccessCredentials creds(cred, internalAllocator);
	IPC(ucsp_client_deleteDb(UCSP_ARGS, attachment, const_cast<char *>(dbName), strlen(dbName) + 1,
		COPY(location), COPY(creds)));
}

CSSM_NAME_LIST_PTR
ClientSession::getDbNames(AttachmentHandle attachment)
{
	CSSM_NAME_LIST_PTR nameList, nameListBase;
	mach_msg_type_number_t nameListLength;
	IPC(ucsp_client_getDbNames(UCSP_ARGS, attachment, COPY_OUT(nameList)));
	VMGuard _(nameList, nameListLength);
	relocate(nameList, nameListBase);
	return copy(nameList, returnAllocator);
}

void
ClientSession::freeNameList(AttachmentHandle attachment,
				  CSSM_NAME_LIST_PTR nameList)
{
	returnAllocator.free(nameList);
}

// CSSM_DL_GetDbNameFromHandle result is allocated using the returnAllocator.
char *
ClientSession::getDbNameFromHandle(DbHandle db)
{
	CssmData name;
	DataOutput nameOut(name, returnAllocator);
	IPC(ucsp_client_getDbNameFromHandle(UCSP_ARGS, db, DATA(nameOut)));
	return reinterpret_cast<char *>(name.Data);
}

void
ClientSession::closeDb(DbHandle db)
{
	IPC(ucsp_client_closeDb(UCSP_ARGS, db));
}

// @@@ cred should be non optional.
void ClientSession::authenticateDb(DbHandle db, CSSM_DB_ACCESS_TYPE type,
	const AccessCredentials *cred)
{
    DatabaseAccessCredentials creds(cred, internalAllocator);
	IPC(ucsp_client_authenticateDb(UCSP_ARGS, db, type, COPY(creds)));
}

void
ClientSession::createRelation(DbHandle db,
					CSSM_DB_RECORDTYPE recordType,
					const char &relationName,
					uint32 attributeCount,
					const CSSM_DB_SCHEMA_ATTRIBUTE_INFO *attributes,
					uint32 indexCount,
					const CSSM_DB_SCHEMA_INDEX_INFO *indices)
{
	Copier<CSSM_DB_SCHEMA_ATTRIBUTE_INFO> attrs(attributes, attributeCount, internalAllocator);
	Copier<CSSM_DB_SCHEMA_INDEX_INFO> idxs(indices, indexCount, internalAllocator);
	IPC(ucsp_client_createRelation(UCSP_ARGS, db, recordType, &relationName, attributeCount, COPY(attrs), indexCount, COPY(idxs)));
}

void
ClientSession::destroyRelation(DbHandle db, CSSM_DB_RECORDTYPE recordType)
{
	IPC(ucsp_client_destroyRelation(UCSP_ARGS, db, recordType));
}

RecordHandle
ClientSession::insertRecord(DbHandle db,
						  CSSM_DB_RECORDTYPE recordType,
						  const CssmDbRecordAttributeData *attributes,
						  const CssmData *data)
{
	RecordHandle record;
	Copier<CssmDbRecordAttributeData> attrs(attributes, internalAllocator);
	IPC(ucsp_client_insertRecord(UCSP_ARGS, db, recordType, COPY(attrs), OPTIONALDATA(data), &record));
	return record;
}

void
ClientSession::deleteRecord(RecordHandle record)
{
	IPC(ucsp_client_deleteRecord(UCSP_ARGS, record));
}

void
ClientSession::modifyRecord(RecordHandle record,
				  CSSM_DB_RECORDTYPE recordType,
				  const CssmDbRecordAttributeData *attributesToBeModified,
				  const CssmData *dataToBeModified,
				  CSSM_DB_MODIFY_MODE modifyMode)
{
	Copier<CssmDbRecordAttributeData> attrs(attributesToBeModified, internalAllocator);
	IPC(ucsp_client_modifyRecord(UCSP_ARGS, record, recordType, COPY(attrs), OPTIONALDATA(dataToBeModified), modifyMode));
}

void
ClientSession::returnAttrsAndData(CssmDbRecordAttributeData *inOutAttributes,
	CssmDbRecordAttributeData *attrs, CssmDbRecordAttributeData *attrsBase, mach_msg_type_number_t attrsLength,
	CssmData *inOutData, void *dataPtr, mach_msg_type_number_t dataLength)
{
	VMGuard _a(attrs, attrsLength);
	VMGuard _d(dataPtr, dataLength);
	if (inOutAttributes)
	{
		relocate(attrs, attrsBase);
		if (inOutAttributes->NumberOfAttributes != attrs->NumberOfAttributes)
			CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);

		inOutAttributes->DataRecordType = attrs->DataRecordType;
		inOutAttributes->SemanticInformation = attrs->SemanticInformation;
		// Copy the returned values.
		for (uint32 ix = 0; ix < inOutAttributes->NumberOfAttributes; ++ix)
		{
			inOutAttributes->at(ix).set(attrs->at(ix), returnAllocator);
		}
	}
	if (inOutData)
	{
		if (dataPtr == NULL || dataLength == 0)
		{
			// Deal with 0 out data length.
			inOutData->clear();
		}
		else
		{
			inOutData->Data = reinterpret_cast<uint8 *>(returnAllocator.malloc(dataLength));
			inOutData->Length = dataLength;
			memcpy(inOutData->Data, dataPtr, dataLength);
		}
	}
}

RecordHandle
ClientSession::getFirstRecord(DbHandle db,
							  const CssmQuery &query,
							  SearchHandle &outSearchHandle,
							  CssmDbRecordAttributeData *inOutAttributes,
							  CssmData *inOutData)
{
	RecordHandle record;
	Copier<CssmQuery> theQuery(&query, internalAllocator);
	Copier<CssmDbRecordAttributeData> inAttrs(inOutAttributes, internalAllocator);
	CssmDbRecordAttributeData *attrs, *attrsBase;
	mach_msg_type_number_t attrsLength;
	void *dataPtr;
	mach_msg_type_number_t dataLength;
	IPC(ucsp_client_findFirstRecord(UCSP_ARGS, db, COPY(theQuery), &outSearchHandle, COPY(inAttrs), COPY_OUT(attrs), inOutData != NULL, &dataPtr, &dataLength, &record));
	returnAttrsAndData(inOutAttributes, attrs, attrsBase, attrsLength, inOutData, dataPtr, dataLength);
	return record;
}

RecordHandle
ClientSession::getNextRecord(SearchHandle searchHandle,
							 CssmDbRecordAttributeData *inOutAttributes,
							 CssmData *inOutData)
{
	RecordHandle record;
	Copier<CssmDbRecordAttributeData> inAttrs(inOutAttributes, internalAllocator);
	CssmDbRecordAttributeData *attrs, *attrsBase;
	mach_msg_type_number_t attrsLength;
	void *dataPtr;
	mach_msg_type_number_t dataLength;
	IPC(ucsp_client_findNextRecord(UCSP_ARGS, searchHandle, COPY(inAttrs), COPY_OUT(attrs), inOutData != NULL, &dataPtr, &dataLength, &record));
	returnAttrsAndData(inOutAttributes, attrs, attrsBase, attrsLength, inOutData, dataPtr, dataLength);
	return record;
}

void
ClientSession::abortFind(SearchHandle searchHandle)
{
	IPC(ucsp_client_abortFind(UCSP_ARGS, searchHandle));
}

void
ClientSession::getRecordFromHandle(RecordHandle record,
								   CssmDbRecordAttributeData *inOutAttributes,
								   CssmData *inOutData)
{
	Copier<CssmDbRecordAttributeData> inAttrs(inOutAttributes, internalAllocator);
	CssmDbRecordAttributeData *attrs, *attrsBase;
	mach_msg_type_number_t attrsLength;
	void *dataPtr;
	mach_msg_type_number_t dataLength;
	IPC(ucsp_client_getRecordFromHandle(UCSP_ARGS, record, COPY(inAttrs), COPY_OUT(attrs), inOutData != NULL, &dataPtr, &dataLength));
	returnAttrsAndData(inOutAttributes, attrs, attrsBase, attrsLength, inOutData, dataPtr, dataLength);
}

void
ClientSession::freeRecordHandle(RecordHandle record)
{
	IPC(ucsp_client_freeRecordHandle(UCSP_ARGS, record));
}


//
// Database control
//
DbHandle ClientSession::createDb(const DLDbIdentifier &dbId,
    const AccessCredentials *cred, const AclEntryInput *owner,
    const DBParameters &params)
{
	DatabaseAccessCredentials creds(cred, internalAllocator);
	Copier<AclEntryPrototype> proto(owner ? &owner->proto() : NULL, internalAllocator);
    DataWalkers::DLDbFlatIdentifier ident(dbId);
    Copier<DataWalkers::DLDbFlatIdentifier> id(&ident, internalAllocator);
	DbHandle db;
	IPC(ucsp_client_createDb(UCSP_ARGS, &db, COPY(id), COPY(creds), COPY(proto), params));
	return db;
}

DbHandle ClientSession::decodeDb(const DLDbIdentifier &dbId,
    const AccessCredentials *cred, const CssmData &blob)
{
	DatabaseAccessCredentials creds(cred, internalAllocator);
    DataWalkers::DLDbFlatIdentifier ident(dbId);
    Copier<DataWalkers::DLDbFlatIdentifier> id(&ident, internalAllocator);
	DbHandle db;
	IPC(ucsp_client_decodeDb(UCSP_ARGS, &db, COPY(id), COPY(creds), DATA(blob)));
	return db;
}

void ClientSession::encodeDb(DbHandle db, CssmData &blob, Allocator &alloc)
{
	DataOutput outBlob(blob, alloc);
	IPC(ucsp_client_encodeDb(UCSP_ARGS, db, DATA(outBlob)));
}

void ClientSession::releaseDb(DbHandle db)
{
	IPC(ucsp_client_releaseDb(UCSP_ARGS, db));
}

void ClientSession::getDbSuggestedIndex(DbHandle db, CssmData &index, Allocator &alloc)
{
	DataOutput outBlob(index, alloc);
	IPC(ucsp_client_getDbIndex(UCSP_ARGS, db, DATA(outBlob)));
}

void ClientSession::setDbParameters(DbHandle db, const DBParameters &params)
{
	IPC(ucsp_client_setDbParameters(UCSP_ARGS, db, params));
}

void ClientSession::getDbParameters(DbHandle db, DBParameters &params)
{
	IPC(ucsp_client_getDbParameters(UCSP_ARGS, db, &params));
}

void ClientSession::changePassphrase(DbHandle db, const AccessCredentials *cred)
{
    Copier<AccessCredentials> creds(cred, internalAllocator);
    IPC(ucsp_client_changePassphrase(UCSP_ARGS, db, COPY(creds)));
}


void ClientSession::lock(DbHandle db)
{
	IPC(ucsp_client_lockDb(UCSP_ARGS, db));
}

void ClientSession::lockAll (bool forSleep)
{
        IPC(ucsp_client_lockAll (UCSP_ARGS, forSleep));
}

void ClientSession::unlock(DbHandle db)
{
	IPC(ucsp_client_unlockDb(UCSP_ARGS, db));
}

void ClientSession::unlock(DbHandle db, const CssmData &passphrase)
{
	IPC(ucsp_client_unlockDbWithPassphrase(UCSP_ARGS, db, DATA(passphrase)));
}

bool ClientSession::isLocked(DbHandle db)
{
    boolean_t locked;
	IPC(ucsp_client_isLocked(UCSP_ARGS, db, &locked));
    return locked;
}


//
// Key control
//
void ClientSession::encodeKey(KeyHandle key, CssmData &blob,
    KeyUID *uid, Allocator &alloc)
{
	DataOutput oBlob(blob, alloc);
    void *uidp;
    mach_msg_type_number_t uidLength;
	IPC(ucsp_client_encodeKey(UCSP_ARGS, key, oBlob.data(), oBlob.length(),
        (uid != NULL), &uidp, &uidLength));
    // return key uid if requested
    if (uid) {
        assert(uidLength == sizeof(KeyUID));
        memcpy(uid, uidp, sizeof(KeyUID));
    }
}


KeyHandle ClientSession::decodeKey(DbHandle db, const CssmData &blob, CssmKey::Header &header)
{
	KeyHandle key;
	IPC(ucsp_client_decodeKey(UCSP_ARGS, &key, &header, db, blob.data(), blob.length()));
	return key;
}

void ClientSession::releaseKey(KeyHandle key)
{
	IPC(ucsp_client_releaseKey(UCSP_ARGS, key));
}


CssmKeySize ClientSession::queryKeySizeInBits(KeyHandle key)
{
    CssmKeySize length;
    IPC(ucsp_client_queryKeySizeInBits(UCSP_ARGS, key, &length));
    return length;
}


uint32 ClientSession::getOutputSize(const Context &context, KeyHandle key,
    uint32 inputSize, bool encrypt)
{
	SendContext ctx(context);
    uint32 outputSize;
    IPC(ucsp_client_getOutputSize(UCSP_ARGS, CONTEXT(ctx), key, inputSize, encrypt, &outputSize));
    return outputSize;
}


//
// Random number generation.
// This interfaces to the secure RNG inside the SecurityServer; it does not access
// a PRNG in its CSP. If you need a reproducible PRNG, attach a local CSP and use it.
// Note that this function does not allocate a buffer; it always fills the buffer provided.
//
void ClientSession::generateRandom(CssmData &data)
{
	void *result;
	mach_msg_type_number_t resultLength;
	IPC(ucsp_client_generateRandom(UCSP_ARGS, data.length(), &result, &resultLength));
	assert(resultLength == data.length());
	memcpy(data.data(), result, data.length());
}


//
// Signatures and MACs
//
void ClientSession::generateSignature(const Context &context, KeyHandle key,
	const CssmData &data, CssmData &signature, Allocator &alloc, CSSM_ALGORITHMS signOnlyAlgorithm)
{
	SendContext ctx(context);
	DataOutput sig(signature, alloc);
	IPCKEY(ucsp_client_generateSignature(UCSP_ARGS, CONTEXT(ctx), key, signOnlyAlgorithm,
		DATA(data), DATA(sig)),
		key, CSSM_ACL_AUTHORIZATION_SIGN);
}

void ClientSession::verifySignature(const Context &context, KeyHandle key,
	const CssmData &data, const CssmData &signature, CSSM_ALGORITHMS verifyOnlyAlgorithm)
{
	SendContext ctx(context);
	IPC(ucsp_client_verifySignature(UCSP_ARGS, CONTEXT(ctx), key, verifyOnlyAlgorithm,
		DATA(data), DATA(signature)));
}


void ClientSession::generateMac(const Context &context, KeyHandle key,
	const CssmData &data, CssmData &signature, Allocator &alloc)
{
	SendContext ctx(context);
	DataOutput sig(signature, alloc);
	IPCKEY(ucsp_client_generateMac(UCSP_ARGS, CONTEXT(ctx), key,
		DATA(data), DATA(sig)),
		key, CSSM_ACL_AUTHORIZATION_MAC);
}

void ClientSession::verifyMac(const Context &context, KeyHandle key,
	const CssmData &data, const CssmData &signature)
{
	SendContext ctx(context);
	IPCKEY(ucsp_client_verifyMac(UCSP_ARGS, CONTEXT(ctx), key,
		DATA(data), DATA(signature)),
		key, CSSM_ACL_AUTHORIZATION_MAC);
}


//
// Encryption/Decryption
//
	
void ClientSession::encrypt(const Context &context, KeyHandle key,
	const CssmData &clear, CssmData &cipher, Allocator &alloc)
{
	SendContext ctx(context);
	DataOutput cipherOut(cipher, alloc);
	IPCKEY(ucsp_client_encrypt(UCSP_ARGS, CONTEXT(ctx), key, DATA(clear), DATA(cipherOut)),
		key, CSSM_ACL_AUTHORIZATION_ENCRYPT);
}

void ClientSession::decrypt(const Context &context, KeyHandle key,
	const CssmData &cipher, CssmData &clear, Allocator &alloc)
{
    Debug::trace (kSecTraceUCSPServerDecryptBegin);
    
	SendContext ctx(context);
	DataOutput clearOut(clear, alloc);
	IPCKEY(ucsp_client_decrypt(UCSP_ARGS, CONTEXT(ctx), key, DATA(cipher), DATA(clearOut)),
		key, CSSM_ACL_AUTHORIZATION_DECRYPT);
}


//
// Key generation
//
void ClientSession::generateKey(DbHandle db, const Context &context, uint32 keyUsage, uint32 keyAttr,
    const AccessCredentials *cred, const AclEntryInput *owner,
    KeyHandle &newKey, CssmKey::Header &newHeader)
{
	SendContext ctx(context);
	Copier<AccessCredentials> creds(cred, internalAllocator);
	Copier<AclEntryPrototype> proto(&owner->proto(), internalAllocator);
	IPC(ucsp_client_generateKey(UCSP_ARGS, db, CONTEXT(ctx),
		COPY(creds), COPY(proto), keyUsage, keyAttr, &newKey, &newHeader));
}

void ClientSession::generateKey(DbHandle db, const Context &context,
    uint32 pubKeyUsage, uint32 pubKeyAttr,
    uint32 privKeyUsage, uint32 privKeyAttr,
    const AccessCredentials *cred, const AclEntryInput *owner,
    KeyHandle &pubKey, CssmKey::Header &pubHeader,
    KeyHandle &privKey, CssmKey::Header &privHeader)
{
	SendContext ctx(context);
	Copier<AccessCredentials> creds(cred, internalAllocator);
	Copier<AclEntryPrototype> proto(&owner->proto(), internalAllocator);
	IPC(ucsp_client_generateKeyPair(UCSP_ARGS, db, CONTEXT(ctx),
		COPY(creds), COPY(proto),
		pubKeyUsage, pubKeyAttr, privKeyUsage, privKeyAttr,
		&pubKey, &pubHeader, &privKey, &privHeader));
}


//
// Key derivation
// This is a bit strained; the incoming 'param' value may have structure
// and needs to be handled on a per-algorithm basis, which means we have to
// know which key derivation algorithms we support for passing to our CSP(s).
// The default behavior is to handle "flat" data blobs, which is as good
// a default as we can manage.
// NOTE: The param-specific handling must be synchronized with the server
// transition layer code (in transition.cpp).
//
void ClientSession::deriveKey(DbHandle db, const Context &context, KeyHandle baseKey,
    uint32 keyUsage, uint32 keyAttr, CssmData &param,
    const AccessCredentials *cred, const AclEntryInput *owner,
    KeyHandle &newKey, CssmKey::Header &newHeader, Allocator &allocator)
{
    SendContext ctx(context);
	Copier<AccessCredentials> creds(cred, internalAllocator);
	Copier<AclEntryPrototype> proto(&owner->proto(), internalAllocator);
    DataOutput paramOutput(param, allocator);
    switch (context.algorithm()) {
    case CSSM_ALGID_PKCS5_PBKDF2: {
        typedef CSSM_PKCS5_PBKDF2_PARAMS Params;
        Copier<Params> params(param.interpretedAs<Params>(CSSM_ERRCODE_INVALID_INPUT_POINTER),
			internalAllocator);
        IPCKEY(ucsp_client_deriveKey(UCSP_ARGS, db, CONTEXT(ctx), baseKey,
            COPY(creds), COPY(proto), COPY(params), DATA(paramOutput),
            keyUsage, keyAttr, &newKey, &newHeader),
			baseKey, CSSM_ACL_AUTHORIZATION_DERIVE);
        break; }
    default: {
        IPCKEY(ucsp_client_deriveKey(UCSP_ARGS, db, CONTEXT(ctx), baseKey,
            COPY(creds), COPY(proto),
            param.data(), param.length(), param.data(),
            DATA(paramOutput),
            keyUsage, keyAttr, &newKey, &newHeader),
			baseKey, CSSM_ACL_AUTHORIZATION_DERIVE);
        break; }
    }
}


//
// Digest generation
//
void ClientSession::getKeyDigest(KeyHandle key, CssmData &digest, Allocator &allocator)
{
	DataOutput dig(digest, allocator);
	IPC(ucsp_client_getKeyDigest(UCSP_ARGS, key, DATA(dig)));
}


//
// Key wrapping and unwrapping
//
void ClientSession::wrapKey(const Context &context, KeyHandle wrappingKey,
    KeyHandle keyToBeWrapped, const AccessCredentials *cred,
	const CssmData *descriptiveData, CssmWrappedKey &wrappedKey, Allocator &alloc)
{
	SendContext ctx(context);
	Copier<AccessCredentials> creds(cred, internalAllocator);
	DataOutput keyData(wrappedKey, alloc);
	/* @@@ When <rdar://problem/3525664>: CSSM_WrapKey doesn't check the acl of the key doing the wrapping. is fixed, we need to potentially edit the CSSM_ACL_AUTHORIZATION_ENCRYPT acl of the wrapping key as opposed to the key being wrapped.  We need to know which of the 2 keys to edit though somehow. */
	IPCKEY(ucsp_client_wrapKey(UCSP_ARGS, CONTEXT(ctx), wrappingKey, COPY(creds),
		keyToBeWrapped, OPTIONALDATA(descriptiveData),
		&wrappedKey, DATA(keyData)),
		keyToBeWrapped,
		context.algorithm() == CSSM_ALGID_NONE
			? CSSM_ACL_AUTHORIZATION_EXPORT_CLEAR : CSSM_ACL_AUTHORIZATION_EXPORT_WRAPPED);
	wrappedKey = CssmData();	// null out data section (force allocation for key data)
}

void ClientSession::unwrapKey(DbHandle db, const Context &context, KeyHandle key,
    KeyHandle publicKey, const CssmWrappedKey &wrappedKey,
	uint32 usage, uint32 attr,
	const AccessCredentials *cred, const AclEntryInput *acl,
	CssmData &descriptiveData,
    KeyHandle &newKey, CssmKey::Header &newHeader, Allocator &alloc)
{
	SendContext ctx(context);
	DataOutput descriptor(descriptiveData, alloc);
	Copier<AccessCredentials> creds(cred, internalAllocator);
	Copier<AclEntryPrototype> proto(&acl->proto(), internalAllocator);
	IPCKEY(ucsp_client_unwrapKey(UCSP_ARGS, db, CONTEXT(ctx), key,
		COPY(creds), COPY(proto),
		publicKey, wrappedKey, DATA(wrappedKey), usage, attr, DATA(descriptor),
        &newKey, &newHeader),
		key, CSSM_ACL_AUTHORIZATION_DECRYPT);
}


//
// ACL management
//
void ClientSession::getAcl(AclKind kind, KeyHandle key, const char *tag,
	uint32 &infoCount, AclEntryInfo * &infoArray, Allocator &alloc)
{
	uint32 count;
	AclEntryInfo *info, *infoBase;
	mach_msg_type_number_t infoLength;
	IPC(ucsp_client_getAcl(UCSP_ARGS, kind, key,
		(tag != NULL), tag ? tag : "",
		&count, COPY_OUT(info)));
	VMGuard _(info, infoLength);
	infoCount = count;

	// relocate incoming AclEntryInfo array
	ReconstituteWalker relocator(info, infoBase);
	for (uint32 n = 0; n < count; n++)
		walk(relocator, info[n]);

	// copy AclEntryInfo array into discrete memory nodes
	infoArray = alloc.alloc<AclEntryInfo>(count);
	ChunkCopyWalker chunker(alloc);
	for (uint32 n = 0; n < count; n++) {
		infoArray[n] = info[n];
		walk(chunker, infoArray[n]);
	}
}

void ClientSession::changeAcl(AclKind kind, KeyHandle key, const AccessCredentials &cred,
	const AclEdit &edit)
{
	Copier<AccessCredentials> creds(&cred, internalAllocator);
	//@@@ ignoring callback
	Copier<AclEntryInput> newEntry(edit.newEntry(), internalAllocator);
	IPCKEY(ucsp_client_changeAcl(UCSP_ARGS, kind, key, COPY(creds),
		edit.mode(), edit.handle(), COPY(newEntry)),
		key, CSSM_ACL_AUTHORIZATION_CHANGE_ACL);
}

void ClientSession::getOwner(AclKind kind, KeyHandle key, AclOwnerPrototype &owner,
    Allocator &alloc)
{
	AclOwnerPrototype *proto, *protoBase;
	mach_msg_type_number_t protoLength;
	IPC(ucsp_client_getOwner(UCSP_ARGS, kind, key, COPY_OUT(proto)));
	// turn the returned AclOwnerPrototype into its proper output form
	// @@@ What here checks protoLength for sanity?
	relocate(proto, protoBase);
	owner.TypedSubject = chunkCopy(proto->subject(), alloc);
	owner.Delegate = proto->delegate();
}

void ClientSession::changeOwner(AclKind kind, KeyHandle key,
	const AccessCredentials &cred, const AclOwnerPrototype &proto)
{
	Copier<AccessCredentials> creds(&cred, internalAllocator);
	Copier<AclOwnerPrototype> protos(&proto, internalAllocator);
	IPCKEY(ucsp_client_setOwner(UCSP_ARGS, kind, key, COPY(creds), COPY(protos)),
		key, CSSM_ACL_AUTHORIZATION_CHANGE_OWNER);
}


void ClientSession::getKeyAcl(DbHandle db, const char *tag,
	uint32 &count, AclEntryInfo * &info, Allocator &alloc)
{ getAcl(keyAcl, db, tag, count, info, alloc); }

void ClientSession::changeKeyAcl(DbHandle db, const AccessCredentials &cred,
	const AclEdit &edit)
{ changeAcl(keyAcl, db, cred, edit); }

void ClientSession::getKeyOwner(DbHandle db, AclOwnerPrototype &owner, Allocator &alloc)
{ getOwner(keyAcl, db, owner, alloc); }

void ClientSession::changeKeyOwner(DbHandle db, const AccessCredentials &cred,
	const AclOwnerPrototype &edit)
{ changeOwner(keyAcl, db, cred, edit); }

void ClientSession::getDbAcl(DbHandle db, const char *tag,
	uint32 &count, AclEntryInfo * &info, Allocator &alloc)
{ getAcl(dbAcl, db, tag, count, info, alloc); }

void ClientSession::changeDbAcl(DbHandle db, const AccessCredentials &cred,
	const AclEdit &edit)
{ changeAcl(dbAcl, db, cred, edit); }

void ClientSession::getDbOwner(DbHandle db, AclOwnerPrototype &owner, Allocator &alloc)
{ getOwner(dbAcl, db, owner, alloc); }

void ClientSession::changeDbOwner(DbHandle db, const AccessCredentials &cred,
	const AclOwnerPrototype &edit)
{ changeOwner(dbAcl, db, cred, edit); }


//
// Database key management
//
void ClientSession::extractMasterKey(DbHandle db, const Context &context, DbHandle sourceDb,
	uint32 keyUsage, uint32 keyAttr,
	const AccessCredentials *cred, const AclEntryInput *owner,
	KeyHandle &newKey, CssmKey::Header &newHeader, Allocator &alloc)
{
    SendContext ctx(context);
	Copier<AccessCredentials> creds(cred, internalAllocator);
	Copier<AclEntryPrototype> proto(&owner->proto(), internalAllocator);
	IPC(ucsp_client_extractMasterKey(UCSP_ARGS, db, CONTEXT(ctx), sourceDb,
		COPY(creds), COPY(proto),
		keyUsage, keyAttr, &newKey, &newHeader));
}


//
// Authorization subsystem entry
//
void ClientSession::authCreate(const AuthorizationItemSet *rights,
	const AuthorizationItemSet *environment, AuthorizationFlags flags,
	AuthorizationBlob &result)
{
	Copier<AuthorizationItemSet> rightSet(rights, internalAllocator);
	Copier<AuthorizationItemSet> environ(environment, internalAllocator);
	IPC(ucsp_client_authorizationCreate(UCSP_ARGS,
		COPY(rightSet), flags, COPY(environ), &result));
}

void ClientSession::authRelease(const AuthorizationBlob &auth, 
	AuthorizationFlags flags)
{
	IPC(ucsp_client_authorizationRelease(UCSP_ARGS, auth, flags));
}

void ClientSession::authCopyRights(const AuthorizationBlob &auth,
	const AuthorizationItemSet *rights, const AuthorizationItemSet *environment,
	AuthorizationFlags flags,
	AuthorizationItemSet **grantedRights)
{
	Copier<AuthorizationItemSet> rightSet(rights, internalAllocator);
	Copier<AuthorizationItemSet> environ(environment, internalAllocator);
	COPY_OUT_DECL(AuthorizationItemSet, result);
	IPC(ucsp_client_authorizationCopyRights(UCSP_ARGS, auth, COPY(rightSet),
		flags | (grantedRights ? 0 : kAuthorizationFlagNoData),
		COPY(environ), COPY_OUT(result)));
	VMGuard _(result, resultLength);
	// return rights vector (only) if requested
	if (grantedRights) {
		relocate(result, resultBase);
		*grantedRights = copy(result, returnAllocator);
	}
}

void ClientSession::authCopyInfo(const AuthorizationBlob &auth,
	const char *tag,
	AuthorizationItemSet * &info)
{
	COPY_OUT_DECL(AuthorizationItemSet, result);
    if (tag == NULL)
        tag = "";
    else if (tag[0] == '\0')
        MacOSError::throwMe(errAuthorizationInvalidTag);
	IPC(ucsp_client_authorizationCopyInfo(UCSP_ARGS, auth, tag, COPY_OUT(result)));
	VMGuard _(result, resultLength);
	relocate(result, resultBase);
	info = copy(result, returnAllocator);
}

void ClientSession::authExternalize(const AuthorizationBlob &auth,
	AuthorizationExternalForm &extForm)
{
	IPC(ucsp_client_authorizationExternalize(UCSP_ARGS, auth, &extForm));
}

void ClientSession::authInternalize(const AuthorizationExternalForm &extForm,
	AuthorizationBlob &auth)
{
	IPC(ucsp_client_authorizationInternalize(UCSP_ARGS, extForm, &auth));
}


//
// Get session information (security session status)
//
void ClientSession::getSessionInfo(SecuritySessionId &sessionId, SessionAttributeBits &attrs)
{
    IPC(ucsp_client_getSessionInfo(UCSP_ARGS, &sessionId, &attrs));
}


//
// Create a new session.
//
// Caveat: This discards all SecurityServer held state for this process, including
// authorizations, database handles, etc. If you are multi-threaded at this point,
// and other threads have talked to SecurityServer, they will leak a few resources
// (mach ports and the like). Nothing horrendous, unless you create masses of sessions
// that way (which we wouldn't exactly recommend for other reasons).
//
// Hacker's note: This engages in an interesting dance with SecurityServer's state tracking.
// If you don't know the choreography, don't change things here until talking to an expert.
//
// Yes, if the client had multiple threads each of which has talked to SecurityServer,
// the reply ports for all but the calling thread will leak. If that ever turns out to
// be a real problem, we can fix it by keeping a (locked) set of client replyPorts to ditch.
// Hardly worth it, though. This is a rare call.
//
void ClientSession::setupSession(SessionCreationFlags flags, SessionAttributeBits attrs)
{
	mGlobal().thread().replyPort.destroy(); // kill this thread's reply port
	mGlobal.reset();			// kill existing cache (leak all other threads)
	mSetupSession = true;		// global flag to Global constructor
	IPC(ucsp_client_setupSession(UCSP_ARGS, flags, attrs)); // reinitialize and call
}


//
// Notification subsystem
//
void ClientSession::requestNotification(Port receiver, NotificationDomain domain, NotificationMask events)
{
    IPC(ucsp_client_requestNotification(UCSP_ARGS, receiver, domain, events));
}

void ClientSession::stopNotification(Port port)
{
    IPC(ucsp_client_stopNotification(UCSP_ARGS, port.port()));
}

void ClientSession::postNotification(NotificationDomain domain, NotificationEvent event, const CssmData &data)
{
    IPC(ucsp_client_postNotification(UCSP_ARGS, domain, event, DATA(data)));
}

OSStatus ClientSession::dispatchNotification(const mach_msg_header_t *message,
    ConsumeNotification *consumer, void *context) throw()
{
    struct Message {
        mach_msg_header_t Head;
        /* start of the kernel processed data */
        mach_msg_body_t msgh_body;
        mach_msg_ool_descriptor_t data;
        /* end of the kernel processed data */
        NDR_record_t NDR;
        uint32 domain;
        uint32 event;
        mach_msg_type_number_t dataCnt;
        uint32 sender;
    } *msg = (Message *)message;

	OSStatus status;
	try
	{
		status = consumer(msg->domain, msg->event, msg->data.address, msg->dataCnt, context);
	}
	catch (const CommonError &err) { status = err.osStatus(); }
	catch (const std::bad_alloc &) { status = memFullErr; }
	catch (...) { status = internalComponentErr; }

    mig_deallocate((vm_offset_t) msg->data.address, msg->dataCnt);
    msg->data.address = (vm_offset_t) 0;
    msg->data.size = (mach_msg_size_t) 0;

    return status;
}


//
// authorizationdbGet/Set/Remove
//
void ClientSession::authorizationdbGet(const AuthorizationString rightname, CssmData &rightDefinition, Allocator &alloc)
{
	DataOutput definition(rightDefinition, alloc);
	IPC(ucsp_client_authorizationdbGet(UCSP_ARGS, rightname, DATA(definition)));
}

void ClientSession::authorizationdbSet(const AuthorizationBlob &auth, const AuthorizationString rightname, uint32_t rightDefinitionLength, const void *rightDefinition)
{
	// @@@ DATA_IN in transition.cpp is not const void *
	IPC(ucsp_client_authorizationdbSet(UCSP_ARGS, auth, rightname, const_cast<void *>(rightDefinition), rightDefinitionLength));
}

void ClientSession::authorizationdbRemove(const AuthorizationBlob &auth, const AuthorizationString rightname)
{
	IPC(ucsp_client_authorizationdbRemove(UCSP_ARGS, auth, rightname));
}


//
// Miscellaneous administrative calls
//
void ClientSession::addCodeEquivalence(const CssmData &oldHash, const CssmData &newHash,
	const char *name, bool forSystem /* = false */)
{
	IPC(ucsp_client_addCodeEquivalence(UCSP_ARGS, DATA(oldHash), DATA(newHash),
		name, forSystem));
}

void ClientSession::removeCodeEquivalence(const CssmData &hash, const char *name, bool forSystem /* = false */)
{
	IPC(ucsp_client_removeCodeEquivalence(UCSP_ARGS, DATA(hash), name, forSystem));
}

void ClientSession::setAlternateSystemRoot(const char *path)
{
	IPC(ucsp_client_setAlternateSystemRoot(UCSP_ARGS, path));
}


} // end namespace SecurityServer
} // end namespace Security
