/*
 * Copyright (c) 2002-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@
 */

//
// TrustedApplication.cpp
//
#include <security_keychain/TrustedApplication.h>
#include <security_keychain/ACL.h>
#include <security_utilities/osxcode.h>
#include <security_cdsa_client/osxsigner.h>
#include <security_utilities/trackingallocator.h>
#include <sys/syslimits.h>
#include <memory>

using namespace KeychainCore;
using namespace CodeSigning;


//
// Create a TrustedApplication from a code-signing ACL subject.
// Throws ACL::ParseError if the subject is unexpected.
//
TrustedApplication::TrustedApplication(const TypedList &subject)
	: mSignature(Allocator::standard()),
	  mData(Allocator::standard())
{
	if (subject.type() != CSSM_ACL_SUBJECT_TYPE_CODE_SIGNATURE)
		throw ACL::ParseError();
	if (subject[1] != CSSM_ACL_CODE_SIGNATURE_OSX)
		throw ACL::ParseError();
	mSignature = subject[2].data();
	mData = subject[3].data();
}


TrustedApplication::TrustedApplication(const CssmData &signature, const CssmData &data) :
	mSignature(Allocator::standard(), signature),
	mData(Allocator::standard(), data)
{
}

TrustedApplication::TrustedApplication(const char *path)
	: mSignature(Allocator::standard()),
	  mData(Allocator::standard())
{
	OSXSigner signer;
	RefPointer<OSXCode> object(OSXCode::at(path));
	auto_ptr<OSXSigner::OSXSignature> signature(signer.sign(*object));
	mSignature = *signature;
	string basePath = object->canonicalPath();
	mData = CssmData(const_cast<char *>(basePath.c_str()), basePath.length() + 1);
}

TrustedApplication::TrustedApplication()
	: mSignature(Allocator::standard()),
	  mData(Allocator::standard())
{
	OSXSigner signer;
	RefPointer<OSXCode> object(OSXCode::main());
	auto_ptr<OSXSigner::OSXSignature> signature(signer.sign(*object));
	mSignature = *signature;
	string path = object->canonicalPath();
	mData.copy(path.c_str(), path.length() + 1);	// including trailing null
}

TrustedApplication::~TrustedApplication() throw()
{
}

const CssmData &
TrustedApplication::signature() const
{
	return mSignature;
}

const char *
TrustedApplication::path() const
{
	if (mData)
		return mData.get().interpretedAs<const char>();
	else
		return NULL;
}

bool
TrustedApplication::sameSignature(const char *path)
{
	// return true if object at given path has same signature
    CssmAutoData otherSignature(Allocator::standard());
    calcSignature(path, otherSignature);
	return (mSignature.get() == otherSignature);
}

void
TrustedApplication::calcSignature(const char *path, CssmOwnedData &signature)
{
	// generate a signature for the given object
    RefPointer<OSXCode> objToVerify(OSXCode::at(path));
	CodeSigning::OSXSigner signer;
    auto_ptr<CodeSigning::OSXSigner::OSXSignature> osxSignature(signer.sign(*objToVerify));
    signature.copy(osxSignature->data(), osxSignature->length());
}


//
// Produce a TypedList representing a code-signing ACL subject
// for this application.
// Memory is allocated from the allocator given, and belongs to
// the caller.
//
TypedList TrustedApplication::makeSubject(Allocator &allocator)
{
	return TypedList(allocator,
		CSSM_ACL_SUBJECT_TYPE_CODE_SIGNATURE,
		new(allocator) ListElement(CSSM_ACL_CODE_SIGNATURE_OSX),
		new(allocator) ListElement(allocator, mSignature.get()),
		new(allocator) ListElement(allocator, mData.get()));
}


//
// On a completely different note...
// Read a simple text file from disk and cache the lines in a set.
// This is used during re-prebinding to cut down on the number of
// equivalency records being generated.
// This feature is otherwise completely unconnected to anything else here.
//
PathDatabase::PathDatabase(const char *path)
{
    if (FILE *f = fopen(path, "r")) {
        mQualifyAll = false;
        char path[PATH_MAX+1];
        while (fgets(path, sizeof(path), f)) {
            path[strlen(path)-1] = '\0';	// strip NL
            mPaths.insert(path);
        }
		fclose(f);
        secdebug("equivdb", "read %ld paths from %s", mPaths.size(), path);
    } else {
        mQualifyAll = true;
        secdebug("equivdb", "cannot open %s, will qualify all application paths", path);
    }
}


bool PathDatabase::lookup(const string &inPath)
{
	string path = inPath;
	string::size_type lastSlash = path.rfind('/');
	string::size_type bundleCore = path.find("/Contents/MacOS/");
	if (lastSlash != string::npos && bundleCore != string::npos)
		if (bundleCore + 15 == lastSlash)
			path = path.substr(0, bundleCore); // @@@ path is being modified here so it can't be const.
	return mPaths.find(path) != mPaths.end();
}
