/*
 * 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@
 */


//
// localdatabase - locally implemented database using internal CSP cryptography
//
#include "localdatabase.h"
#include "agentquery.h"
#include "localkey.h"
#include "server.h"
#include "session.h"
#include <security_agent_client/agentclient.h>
#include <security_cdsa_utilities/acl_any.h>	// for default owner ACLs
#include <security_cdsa_client/wrapkey.h>
#include <security_cdsa_client/genkey.h>
#include <security_cdsa_client/signclient.h>
#include <security_cdsa_client/cryptoclient.h>
#include <security_cdsa_client/macclient.h>
#include <security_utilities/endian.h>


//
// Create a Database object from initial parameters (create operation)
//
LocalDatabase::LocalDatabase(Process &proc)
	: Database(proc)
{
}


static inline LocalKey &myKey(Key &key)
{
	return safer_cast<LocalKey &>(key);
}


//
// Key inquiries
//
void LocalDatabase::queryKeySizeInBits(Key &key, CssmKeySize &result)
{
    CssmClient::Key theKey(Server::csp(), myKey(key));
    result = theKey.sizeInBits();
}


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

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

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

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


//
// Encryption/decryption
//
void LocalDatabase::encrypt(const Context &context, Key &key,
	const CssmData &clear, CssmData &cipher)
{
	context.replace(CSSM_ATTRIBUTE_KEY, myKey(key).cssmKey());
	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 LocalDatabase::decrypt(const Context &context, Key &key,
	const CssmData &cipher, CssmData &clear)
{
	context.replace(CSSM_ATTRIBUTE_KEY, myKey(key).cssmKey());
	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 LocalDatabase::generateKey(const Context &context,
		const AccessCredentials *cred, const AclEntryPrototype *owner,
		uint32 usage, uint32 attrs, RefPointer<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, LocalKey::KeySpec(usage, attrs));
		
	// register and return the generated key
    newKey = makeKey(key, attrs & LocalKey::managedAttributes, owner);
}

void LocalDatabase::generateKey(const Context &context,
	const AccessCredentials *cred, const AclEntryPrototype *owner,
	uint32 pubUsage, uint32 pubAttrs, uint32 privUsage, uint32 privAttrs,
    RefPointer<Key> &publicKey, RefPointer<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, LocalKey::KeySpec(pubUsage, pubAttrs),
		privKey, LocalKey::KeySpec(privUsage, privAttrs));
		
	// register and return the generated keys
	publicKey = makeKey(pubKey, pubAttrs & LocalKey::managedAttributes, owner);
	privateKey = makeKey(privKey, privAttrs & LocalKey::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 LocalDatabase::wrapKey(const Context &context, const AccessCredentials *cred,
	Key *wrappingKey, Key &keyToBeWrapped,
	const CssmData &descriptiveData, CssmKey &wrappedKey)
{
    keyToBeWrapped.validate(context.algorithm() == CSSM_ALGID_NONE ?
            CSSM_ACL_AUTHORIZATION_EXPORT_CLEAR : CSSM_ACL_AUTHORIZATION_EXPORT_WRAPPED,
        cred);
    if (wrappingKey) {
        context.replace(CSSM_ATTRIBUTE_KEY, myKey(*wrappingKey).cssmKey());
		wrappingKey->validate(CSSM_ACL_AUTHORIZATION_ENCRYPT, context);
	}
    CssmClient::WrapKey wrap(Server::csp(), context.algorithm());
    wrap.override(context);
    wrap.cred(cred);
    wrap(myKey(keyToBeWrapped), wrappedKey, &descriptiveData);
}

void LocalDatabase::unwrapKey(const Context &context,
	const AccessCredentials *cred, const AclEntryPrototype *owner,
	Key *wrappingKey, Key *publicKey, CSSM_KEYUSE usage, CSSM_KEYATTR_FLAGS attrs,
	const CssmKey wrappedKey, RefPointer<Key> &unwrappedKey, CssmData &descriptiveData)
{
    if (wrappingKey) {
        context.replace(CSSM_ATTRIBUTE_KEY, myKey(*wrappingKey).cssmKey());
		wrappingKey->validate(CSSM_ACL_AUTHORIZATION_DECRYPT, context);
	}
	// we are not checking access on the public key, if any
	
    CssmClient::UnwrapKey unwrap(Server::csp(), context.algorithm());
    unwrap.override(context);
    unwrap.cred(cred);
	
	// the AclEntryInput will have to live until unwrap is done
	AclEntryInput ownerInput;
    if (owner) {
		ownerInput.proto() = *owner;
        unwrap.owner(ownerInput);
	}
	
    CssmKey result;
	unwrap(wrappedKey, LocalKey::KeySpec(usage, attrs), result, &descriptiveData,
		publicKey ? &myKey(*publicKey).cssmKey() : NULL);
    unwrappedKey = makeKey(result, attrs & LocalKey::managedAttributes, owner);
}


//
// Key derivation
//
void LocalDatabase::deriveKey(const Context &context, Key *key,
	const AccessCredentials *cred, const AclEntryPrototype *owner,
	CssmData *param, uint32 usage, uint32 attrs, RefPointer<Key> &derivedKey)
{
    if (key) {
		key->validate(CSSM_ACL_AUTHORIZATION_DERIVE, cred);
        context.replace(CSSM_ATTRIBUTE_KEY, myKey(*key).cssmKey());
	}
	CssmClient::DeriveKey derive(Server::csp(), context.algorithm(), CSSM_ALGID_NONE);
	derive.override(context);
	
	// derive key
	// @@@ turn "none" return into reference if permanent (only)
	CssmKey dKey;
	derive(param, LocalKey::KeySpec(usage, attrs), dKey);

	// register and return the generated key
    derivedKey = makeKey(dKey, attrs & LocalKey::managedAttributes, owner);
}


//
// Miscellaneous CSSM functions
//
void LocalDatabase::getOutputSize(const Context &context, Key &key, uint32 inputSize,
	bool encrypt, uint32 &result)
{
    // 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, myKey(key).cssmKey());
    CssmClient::Digest ctx(Server::csp(), context.algorithm());
    ctx.override(context);
    result = ctx.getOutputSize(inputSize, encrypt);
}
