/*
 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please obtain
 * a copy of the License at http://www.apple.com/publicsource and read it before
 * using this file.
 * 
 * This 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.
 */


//
// connection - manage connections to clients.
//
// Note that Connection objects are single-threaded; only one request can be outstanding
// per connection. The various operational calls (e.g. generateMac) can be called by
// multiple threads, but each call will be for a different connection (the one the request
// came in on). Thus, locking happens elsewhere as needed.
//
#include "connection.h"
#include "key.h"
#include "server.h"
#include "session.h"
#include <Security/keyclient.h>
#include <Security/genkey.h>
#include <Security/wrapkey.h>
#include <Security/signclient.h>
#include <Security/macclient.h>
#include <Security/cryptoclient.h>


//
// Construct a Connection object.
//
Connection::Connection(Process &proc, Port rPort)
 : process(proc), mClientPort(rPort), state(idle), agentWait(NULL),
   aclUpdateTrigger(NULL)
{
	// bump the send-rights count on the reply port so we keep the right after replying
	mClientPort.modRefs(MACH_PORT_RIGHT_SEND, +1);
	
	debug("SS", "New connection %p for process %d clientport=%d",
		this, process.pid(), int(rPort));
}


//
// When a Connection's destructor executes, the connection must already have been
// terminated. All we have to do here is clean up a bit.
//
Connection::~Connection()
{
	debug("SS", "Connection %p destroyed", this);
	assert(!agentWait);
}


//
// Terminate a Connection normally.
// This is assumed to be properly sequenced, so no thread races are possible.
//
void Connection::terminate()
{
	// cleanly discard port rights
	assert(state == idle);
	mClientPort.modRefs(MACH_PORT_RIGHT_SEND, -1);	// discard surplus send right
	assert(mClientPort.getRefs(MACH_PORT_RIGHT_SEND) == 1);	// one left for final reply
	debug("SS", "Connection %p terminated", this);
}


//
// Abort a Connection.
// This may be called from thread A while thread B is working a request for the Connection,
// so we must be careful.
//
bool Connection::abort(bool keepReplyPort)
{
	StLock<Mutex> _(lock);
    if (!keepReplyPort)
        mClientPort.destroy();		// dead as a doornail already
	switch (state) {
	case idle:
		debug("SS", "Connection %p aborted", this);
		return true;				// just shoot me
	case busy:
		state = dying;				// shoot me soon, please
		if (agentWait)
			agentWait->cancel();
		debug("SS", "Connection %p abort deferred (busy)", this);
		return false;				// but not quite yet
	default:
		assert(false);				// impossible (we hope)
	}
}


//
// Service request framing.
// These are here so "hanging" connection service threads don't fall
// into the Big Bad Void as Connections and processes drop out from
// under them.
//
void Connection::beginWork()
{
	switch (state) {
	case idle:
		state = busy;
		process.beginConnection(*this);
		break;
	case busy:
		debug("SS", "Attempt to re-enter connection %p(port %d)", this, mClientPort.port());
		CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);	//@@@ some state-error code instead?
	default:
		assert(false);
	}
}

void Connection::checkWork()
{
	StLock<Mutex> _(lock);
	switch (state) {
	case busy:
		return;
	case dying:
		agentWait = NULL;	// obviously we're not waiting on this
		throw this;
	default:
		assert(false);
	}
}

bool Connection::endWork()
{
	switch (state) {
	case busy:
		// process the n-step aclUpdateTrigger
		if (aclUpdateTrigger) {
            if (--aclUpdateTriggerCount == 0) {
                aclUpdateTrigger = NULL;
                debug("kcacl", "acl update trigger expires");
            } else
                debug("kcacl", "acl update trigger armed for %d calls",
                    aclUpdateTriggerCount);
        }
		// end involvement
		state = idle;
		process.endConnection(*this);
		return false;
	case dying:
		debug("SS", "Connection %p abort resuming", this);
		if (process.endConnection(*this))
			delete &process;
		return true;
	default:
		assert(false);
	}
}


//
// Key creation and release
//
void Connection::releaseKey(Key::Handle key)
{
	delete &Server::key(key);
}


//
// Key inquiries
//
CSSM_KEY_SIZE Connection::queryKeySize(Key &key)
{
    CssmClient::Key theKey(Server::csp(), key);
    return theKey.sizeInBits();
}


//
// Signatures and MACs
//
void Connection::generateSignature(const Context &context, Key &key,
	CSSM_ALGORITHMS signOnlyAlgorithm, const CssmData &data, CssmData &signature)
{
	context.replace(CSSM_ATTRIBUTE_KEY, (CSSM_KEY &)key);
	key.validate(CSSM_ACL_AUTHORIZATION_SIGN, context);
	CssmClient::Sign signer(Server::csp(), context.algorithm(), signOnlyAlgorithm);
	signer.override(context);
	signer.sign(data, signature);
}

void Connection::verifySignature(const Context &context, Key &key,
	CSSM_ALGORITHMS verifyOnlyAlgorithm, const CssmData &data, const CssmData &signature)
{
	context.replace(CSSM_ATTRIBUTE_KEY, (CSSM_KEY &)key);
	CssmClient::Verify verifier(Server::csp(), context.algorithm(), verifyOnlyAlgorithm);
	verifier.override(context);
	verifier.verify(data, signature);
}

void Connection::generateMac(const Context &context, Key &key,
	const CssmData &data, CssmData &mac)
{
	context.replace(CSSM_ATTRIBUTE_KEY, (CSSM_KEY &)key);
	key.validate(CSSM_ACL_AUTHORIZATION_MAC, context);
	CssmClient::GenerateMac signer(Server::csp(), context.algorithm());
	signer.override(context);
	signer.sign(data, mac);
}

void Connection::verifyMac(const Context &context, Key &key,
	const CssmData &data, const CssmData &mac)
{
	context.replace(CSSM_ATTRIBUTE_KEY, (CSSM_KEY &)key);
	key.validate(CSSM_ACL_AUTHORIZATION_MAC, context);
	CssmClient::VerifyMac verifier(Server::csp(), context.algorithm());
	verifier.override(context);
	verifier.verify(data, mac);
}


//
// Encryption/decryption
//
void Connection::encrypt(const Context &context, Key &key,
	const CssmData &clear, CssmData &cipher)
{
	context.replace(CSSM_ATTRIBUTE_KEY, (CSSM_KEY &)key);
	key.validate(CSSM_ACL_AUTHORIZATION_ENCRYPT, context);
	CssmClient::Encrypt cryptor(Server::csp(), context.algorithm());
	cryptor.override(context);
	CssmData remData;
	size_t totalLength = cryptor.encrypt(clear, cipher, remData);
	// shouldn't need remData - if an algorithm REQUIRES this, we'd have to ship it
	if (remData)
		CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
	cipher.length(totalLength);
}

void Connection::decrypt(const Context &context, Key &key,
	const CssmData &cipher, CssmData &clear)
{
	context.replace(CSSM_ATTRIBUTE_KEY, (CSSM_KEY &)key);
	key.validate(CSSM_ACL_AUTHORIZATION_DECRYPT, context);
	CssmClient::Decrypt cryptor(Server::csp(), context.algorithm());
	cryptor.override(context);
	CssmData remData;
	size_t totalLength = cryptor.decrypt(cipher, clear, remData);
	// shouldn't need remData - if an algorithm REQUIRES this, we'd have to ship it
	if (remData)
		CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
	clear.length(totalLength);
}


//
// Key generation and derivation.
// Currently, we consider symmetric key generation to be fast, but
// asymmetric key generation to be (potentially) slow.
//
void Connection::generateKey(Database *db, const Context &context,
		const AccessCredentials *cred, const AclEntryPrototype *owner,
		uint32 usage, uint32 attrs, Key * &newKey)
{
	// prepare a context
	CssmClient::GenerateKey generate(Server::csp(), context.algorithm());
	generate.override(context);
	
	// generate key
	// @@@ turn "none" return into reference if permanent (only)
	CssmKey key;
	generate(key, Key::KeySpec(usage, attrs));
		
	// register and return the generated key
    newKey = new Key(db, key, attrs & Key::managedAttributes, owner);
}

void Connection::generateKey(Database *db, const Context &context,
	const AccessCredentials *cred, const AclEntryPrototype *owner,
	uint32 pubUsage, uint32 pubAttrs, uint32 privUsage, uint32 privAttrs,
    Key * &publicKey, Key * &privateKey)
{
	// prepare a context
	CssmClient::GenerateKey generate(Server::csp(), context.algorithm());
	generate.override(context);
	
	// this may take a while; let our server object know
	Server::active().longTermActivity();
	
	// generate keys
	// @@@ turn "none" return into reference if permanent (only)
	CssmKey pubKey, privKey;
	generate(pubKey, Key::KeySpec(pubUsage, pubAttrs),
		privKey, Key::KeySpec(privUsage, privAttrs));
		
	// register and return the generated keys
	publicKey = new Key(db, pubKey, pubAttrs & Key::managedAttributes, owner);
	privateKey = new Key(db, privKey, privAttrs & Key::managedAttributes, owner);
}

Key &Connection::deriveKey(Database *db, const Context &context, Key *baseKey,
		const AccessCredentials *cred, const AclEntryPrototype *owner,
        CssmData *param, uint32 usage, uint32 attrs)
{
	// prepare a key-derivation context
    if (baseKey) {
		baseKey->validate(CSSM_ACL_AUTHORIZATION_DERIVE, cred);
        context.replace(CSSM_ATTRIBUTE_KEY, (CSSM_KEY &)*baseKey);
	}
	CssmClient::DeriveKey derive(Server::csp(), context.algorithm(), CSSM_ALGID_NONE);
	derive.override(context);
	
	// derive key
	// @@@ turn "none" return into reference if permanent (only)
	CssmKey key;
	derive(param, Key::KeySpec(usage, attrs), key);
		
	// register and return the generated key
    return *new Key(db, key, attrs & Key::managedAttributes, owner);
}


//
// Key wrapping and unwrapping.
// Note that the key argument (the key in the context) is optional because of the special
// case of "cleartext" (null algorithm) wrapping for import/export.
//
void Connection::wrapKey(const Context &context, Key *key,
    Key &keyToBeWrapped, const AccessCredentials *cred,
    const CssmData &descriptiveData, CssmKey &wrappedKey)
{
    keyToBeWrapped.validate(context.algorithm() == CSSM_ALGID_NONE ?
            CSSM_ACL_AUTHORIZATION_EXPORT_CLEAR : CSSM_ACL_AUTHORIZATION_EXPORT_WRAPPED,
        cred);
    if (key)
        context.replace(CSSM_ATTRIBUTE_KEY, (CSSM_KEY &)*key);
    CssmClient::WrapKey wrap(Server::csp(), context.algorithm());
    wrap.override(context);
    wrap.cred(const_cast<AccessCredentials *>(cred));	//@@@ const madness - fix in client/pod
    wrap(keyToBeWrapped, wrappedKey, &descriptiveData);
}

Key &Connection::unwrapKey(Database *db, const Context &context, Key *key,
	const AccessCredentials *cred, const AclEntryPrototype *owner,
	uint32 usage, uint32 attrs, const CssmKey wrappedKey,
    Key *publicKey, CssmData *descriptiveData)
{
    if (key)
        context.replace(CSSM_ATTRIBUTE_KEY, (CSSM_KEY &)*key);
    CssmClient::UnwrapKey unwrap(Server::csp(), context.algorithm());
    unwrap.override(context);
    CssmKey unwrappedKey;
    unwrap.cred(const_cast<AccessCredentials *>(cred));	//@@@ const madness - fix in client/pod
    if (owner) {
        AclEntryInput ownerInput(*owner);	//@@@ const trouble - fix in client/pod
        unwrap.aclEntry(ownerInput);
    }

    // @@@ Invoking conversion operator to CssmKey & on *publicKey and take the address of the result.
    unwrap(wrappedKey, Key::KeySpec(usage, attrs), unwrappedKey,
        descriptiveData, publicKey ? &static_cast<CssmKey &>(*publicKey) : NULL);

    return *new Key(db, unwrappedKey, attrs & Key::managedAttributes, owner);
}


//
// Miscellaneous CSSM functions
//
uint32 Connection::getOutputSize(const Context &context, Key &key, uint32 inputSize, bool encrypt)
{
    // We're fudging here somewhat, since the context can be any type.
    // ctx.override will fix the type, and no-one's the wiser.
	context.replace(CSSM_ATTRIBUTE_KEY, (CSSM_KEY &)key);
    CssmClient::Digest ctx(Server::csp(), context.algorithm());
    ctx.override(context);
    return ctx.getOutputSize(inputSize, encrypt);
}

