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


//
// testacls - ACL-related test cases.
// 
#include "testclient.h"
#include "testutils.h"


//
// Blob tests.
// Encodes and decodes Db and Key blobs and all that jazz.
//
void blobs()
{
	printf("* Database blob encryption test\n");
	ClientSession ss(CssmAllocator::standard(), CssmAllocator::standard());
	
	DbTester db1(ss, "/tmp/one", NULL, 60, true);
	DbTester db2(ss, "/tmp/two", NULL, 30, false);
	
	// encode db1, purge it, decode it again
	CssmData dbBlob;
	ss.encodeDb(db1, dbBlob);
	DbHandle db1a = ss.decodeDb(db1.dbId, &nullCred, dbBlob);
	ss.releaseDb(db1);
	if (db1 == db1a)
		detail("REUSED DB HANDLE ON DECODEDB (probably wrong)");
	DBParameters savedParams;
	ss.getDbParameters(db1a, savedParams);
	assert(savedParams.idleTimeout == db1.params.idleTimeout);
	assert(savedParams.lockOnSleep == db1.params.lockOnSleep);
	detail("Database encode/decode passed");
	
	// make sure the old handle isn't valid anymore
	try {
		ss.getDbParameters(db1, savedParams);
		printf("OLD DATABASE HANDLE NOT PURGED (possibly wrong)\n");
	} catch (const CssmCommonError &err) {
		detail(err, "old DB handle rejected");
	}
    
    // open db1 a second time (so now there's two db handles for db1)
    DbHandle db1b = ss.decodeDb(db1.dbId, &nullCred, dbBlob);
    
    // release both db1 handles and db2
    ss.releaseDb(db1a);
    ss.releaseDb(db1b);
    ss.releaseDb(db2);
}


//
// Database tests.
// Database locks/unlocks etc.
//
void databases()
{
	printf("* Database manipulation test\n");
    CssmAllocator &alloc = CssmAllocator::standard();
	ClientSession ss(alloc, alloc);
    
    AutoCredentials pwCred(alloc);
    StringData passphrase("two");
    StringData badPassphrase("three");
    pwCred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK,
        new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD),
        new(alloc) ListElement(passphrase));
    pwCred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK,
        new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD),
        new(alloc) ListElement(badPassphrase));
	// pwCred = (NEW: two, OLD: three)
	
	DbTester db1(ss, "/tmp/one", NULL, 30, true);
	DbTester db2(ss, "/tmp/two", &pwCred, 60, false);
	// db2.passphrase = two
	
	// encode db1 and re-open it
	CssmData dbBlob;
	ss.encodeDb(db1, dbBlob);
	DbHandle db1b = ss.decodeDb(db1.dbId, &nullCred, dbBlob);
	if (db1b == db1.dbRef)
		detail("REUSED DB HANDLE ON DECODEDB (probably wrong)");
	
    // open db1 a third time (so now there's three db handles for db1)
    DbHandle db1c = ss.decodeDb(db1.dbId, &nullCred, dbBlob);
    
    // lock them to get started
    ss.lock(db1);
    ss.lock(db2);
    
    // unlock it through user
    prompt("unlock");
    ss.unlock(db1);
	prompt();
    ss.unlock(db1b);		// 2nd unlock should not prompt
    ss.lock(db1c);			// lock it again
    prompt("unlock");
    ss.unlock(db1);		// and that should prompt again
	prompt();
    
    // db2 has a passphrase lock credentials - it'll work without U/I
	db2.unlock("wrong passphrase");		// pw=two, cred=three
    AutoCredentials pwCred2(alloc);
    pwCred2 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK,
        new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD),
        new(alloc) ListElement(passphrase));
	// pwCred2 = (OLD: two)
    ss.authenticateDb(db2, CSSM_DB_ACCESS_WRITE, &pwCred2); // set it
	db2.unlock();
    ss.lock(db2);
    
    // now change db2's passphrase
    ss.lock(db2);
    pwCred2 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK,
        new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD),
        new(alloc) ListElement(badPassphrase));
	// pwCred2 = (OLD: two, NEW: three)
	db2.changePassphrase(&pwCred2);
	// passphrase = three, cred = (OLD: two)
	
    // encode and re-decode to make sure new data is there
    CssmData blob2;
    ss.encodeDb(db2, blob2);
    DbHandle db2a = ss.decodeDb(db2.dbId, &pwCred, blob2);
	// db2a cred = (OLD: two, NEW: three)
    
    // now, the *old* cred won't work anymore
	db2.unlock("old passphrase accepted");

    // back to the old credentials, which *do* have the (old bad, now good) passphrase
    ss.lock(db2a);
    ss.unlock(db2a);
    detail("New passphrase accepted");
	
	// clear the credentials (this will prompt; cancel it)
	ss.authenticateDb(db2, CSSM_DB_ACCESS_WRITE, NULL);
	prompt("cancel");
	db2.unlock("null credential accepted");
	prompt();
	
	// fell-swoop from-to change password operation
	StringData newPassphrase("hollerith");
	AutoCredentials pwCred3(alloc);
	pwCred3 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK,
		new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD),
		new(alloc) ListElement(newPassphrase));
	pwCred3 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK,
		new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD),
		new(alloc) ListElement(passphrase));
	db2.changePassphrase(&pwCred3, "accepting original (unchanged) passphrase");
	
	AutoCredentials pwCred4(alloc);
	pwCred4 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK,
		new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD),
		new(alloc) ListElement(newPassphrase));
	pwCred4 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK,
		new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD),
		new(alloc) ListElement(badPassphrase));
	db2.changePassphrase(&pwCred4);
	
	// final status check
	AutoCredentials pwCred5(alloc);
	pwCred5 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK,
		new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD),
		new(alloc) ListElement(newPassphrase));	
	ss.authenticateDb(db2, CSSM_DB_ACCESS_WRITE, &pwCred5);
	db2.unlock();
	detail("Final passphrase change verified");
}


//
// Key encryption tests.
//
void keyBlobs()
{
	printf("* Keyblob encryption test\n");
    CssmAllocator &alloc = CssmAllocator::standard();
	ClientSession ss(alloc, alloc);
    
    DLDbIdentifier dbId1(ssuid, "/tmp/one", NULL);
	DBParameters initialParams1 = { 3600, false };
    
    // create a new database
	DbHandle db = ss.createDb(dbId1, NULL, NULL, initialParams1);
	detail("Database created");
	
	// establish an ACL for the key
	StringData theAclPassword("Strenge Geheimsache");
	AclEntryPrototype initialAcl;
	initialAcl.TypedSubject = TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_PASSWORD,
		new(alloc) ListElement(theAclPassword));
	AclEntryInput initialAclInput(initialAcl);
    
    AutoCredentials cred(alloc);
    cred += TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD,
        new(alloc) ListElement(theAclPassword));
    
    // generate a key
	const CssmCryptoData seed(StringData("Farmers' day"));
	FakeContext genContext(CSSM_ALGCLASS_KEYGEN, CSSM_ALGID_DES,
		&::Context::Attr(CSSM_ATTRIBUTE_KEY_LENGTH, 64),
		&::Context::Attr(CSSM_ATTRIBUTE_SEED, seed),
		NULL);
    KeyHandle key;
    CssmKey::Header header;
    ss.generateKey(db, genContext, CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT,
        CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_PERMANENT,
        /*cred*/NULL, &initialAclInput, key, header);
	detail("Key generated");
    
    // encrypt with the key
    StringData clearText("Yet another boring cleartext sample string text sequence.");
    StringData iv("Aardvark");
    CssmKey nullKey; memset(&nullKey, 0, sizeof(nullKey));
	FakeContext cryptoContext(CSSM_ALGCLASS_SYMMETRIC, CSSM_ALGID_DES,
		&::Context::Attr(CSSM_ATTRIBUTE_KEY, nullKey),
		&::Context::Attr(CSSM_ATTRIBUTE_INIT_VECTOR, iv),
		&::Context::Attr(CSSM_ATTRIBUTE_MODE, CSSM_ALGMODE_CBC_IV8),
		&::Context::Attr(CSSM_ATTRIBUTE_PADDING, CSSM_PADDING_PKCS1),
        &::Context::Attr(CSSM_ATTRIBUTE_ACCESS_CREDENTIALS, cred),
		NULL);
	CssmData cipherText;
	ss.encrypt(cryptoContext, key, clearText, cipherText);
	detail("Plaintext encrypted with original key");
    
    // encode the key and release it
    CssmData blob;
    ss.encodeKey(key, blob);
    ss.releaseKey(key);
    detail("Key encoded and released");
    
    // decode it again, re-introducing it
    CssmKey::Header decodedHeader;
    KeyHandle key2 = ss.decodeKey(db, blob, decodedHeader);
    detail("Key decoded");
    
    // decrypt with decoded key
    CssmData recovered;
    ss.decrypt(cryptoContext, key2, cipherText, recovered);
    assert(recovered == clearText);
    detail("Decoded key correctly decrypts ciphertext");
    
    // check a few header fields
    if (!memcmp(&header, &decodedHeader, sizeof(header))) {
        detail("All header fields match");
    } else {
        assert(header.algorithm() == decodedHeader.algorithm());
        assert(header.blobType() == decodedHeader.blobType());
        assert(header.blobFormat() == decodedHeader.blobFormat());
        assert(header.keyClass() == decodedHeader.keyClass());
        assert(header.attributes() == decodedHeader.attributes());
        assert(header.usage() == decodedHeader.usage());
        printf("Some header fields differ (probably okay)\n");
    }
    
    // make sure we need the credentials (destructive)
    memset(&cred, 0, sizeof(cred));
    try {
        ss.decrypt(cryptoContext, key2, cipherText, recovered);
        error("RESTORED ACL FAILS TO RESTRICT");
    } catch (CssmError &err) {
        detail(err, "Restored key restricts access properly");
    }
}
