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

/*
	File:		 cuDbUtils.cpp
	
	Description: CDSA DB access utilities

	Author:		 dmitch
*/

#include "cuCdsaUtils.h"
#include "cuTimeStr.h"
#include "cuDbUtils.h"
#include "cuPrintCert.h"
#include <stdlib.h>
#include <stdio.h>
#include <Security/SecCertificate.h>
#include <Security/SecCertificatePriv.h>	/* private SecInferLabelFromX509Name() */
#include <Security/cssmapple.h>				/* for cssmPerror() */
#include <Security/oidscert.h>
#include <Security/oidscrl.h>
#include <Security/oidsattr.h>
#include <strings.h>
#include <security_cdsa_utilities/Schema.h>			/* private API */

/*
 * Add a certificate to an open DLDB.
 */
CSSM_RETURN cuAddCertToDb(
	CSSM_DL_DB_HANDLE	dlDbHand,
	const CSSM_DATA		*cert,
	CSSM_CERT_TYPE		certType,
	CSSM_CERT_ENCODING	certEncoding,
	const char			*printName,		// C string
	const CSSM_DATA		*publicKeyHash)		
{
	CSSM_DB_ATTRIBUTE_DATA			attrs[6];
	CSSM_DB_RECORD_ATTRIBUTE_DATA	recordAttrs;
	CSSM_DB_ATTRIBUTE_DATA_PTR		attr = &attrs[0];
	CSSM_DATA						certTypeData;
	CSSM_DATA						certEncData;
	CSSM_DATA						printNameData;
	CSSM_RETURN						crtn;
	CSSM_DB_UNIQUE_RECORD_PTR		recordPtr;
	
	/* issuer and serial number required, fake 'em */
	CSSM_DATA						issuer = {6, (uint8 *)"issuer"};
	CSSM_DATA						serial = {6, (uint8 *)"serial"};
	
	/* we spec six attributes, skipping alias */
	certTypeData.Data = (uint8 *)&certType;
	certTypeData.Length = sizeof(CSSM_CERT_TYPE);
	certEncData.Data = (uint8 *)&certEncoding;
	certEncData.Length = sizeof(CSSM_CERT_ENCODING);
	printNameData.Data = (uint8 *)printName;
	printNameData.Length = strlen(printName) + 1;
	
	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	attr->Info.Label.AttributeName = "CertType";
	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_UINT32;
	attr->NumberOfValues = 1;
	attr->Value = &certTypeData;
	
	attr++;
	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	attr->Info.Label.AttributeName = "CertEncoding";
	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_UINT32;
	attr->NumberOfValues = 1;
	attr->Value = &certEncData;
	
	attr++;
	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	attr->Info.Label.AttributeName = "PrintName";
	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
	attr->NumberOfValues = 1;
	attr->Value = &printNameData;
	
	attr++;
	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	attr->Info.Label.AttributeName = "PublicKeyHash";
	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
	attr->NumberOfValues = 1;
	attr->Value = (CSSM_DATA_PTR)publicKeyHash;
	
	attr++;
	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	attr->Info.Label.AttributeName = "Issuer";
	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
	attr->NumberOfValues = 1;
	attr->Value = &issuer;
	
	attr++;
	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	attr->Info.Label.AttributeName = "SerialNumber";
	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
	attr->NumberOfValues = 1;
	attr->Value = &serial;
	
	recordAttrs.DataRecordType = CSSM_DL_DB_RECORD_X509_CERTIFICATE;
	recordAttrs.SemanticInformation = 0;
	recordAttrs.NumberOfAttributes = 6;
	recordAttrs.AttributeData = attrs;
	
	crtn = CSSM_DL_DataInsert(dlDbHand,
		CSSM_DL_DB_RECORD_X509_CERTIFICATE,
		&recordAttrs,
		cert,
		&recordPtr);
	if(crtn) {
		cuPrintError("CSSM_DL_DataInsert", crtn);
	}
	else {
		CSSM_DL_FreeUniqueRecord(dlDbHand, recordPtr);
	}
	return crtn;
}

static CSSM_RETURN cuAddCrlSchema(
	CSSM_DL_DB_HANDLE	dlDbHand);
	
static void cuInferCrlLabel(
	const CSSM_X509_NAME 	*x509Name,
	CSSM_DATA				*label)	 // not mallocd; contents are from the x509Name
{
	/* use private API for common "infer label" logic */
	const CSSM_DATA *printValue = SecInferLabelFromX509Name(x509Name);
	if(printValue == NULL) {
		/* punt! */
		label->Data = (uint8 *)"X509 CRL";
		label->Length = 8;
	}
	else {
		*label = *printValue;
	}
}

/*
 * Search extensions for specified OID, assumed to have underlying
 * value type of uint32; returns the value and true if found.
 */
static bool cuSearchNumericExtension(
	const CSSM_X509_EXTENSIONS	*extens,
	const CSSM_OID				*oid,
	uint32						*val)
{
	for(uint32 dex=0; dex<extens->numberOfExtensions; dex++) {
		const CSSM_X509_EXTENSION *exten = &extens->extensions[dex];
		if(!cuCompareOid(&exten->extnId, oid)) {
			continue;
		}
		if(exten->format != CSSM_X509_DATAFORMAT_PARSED) {
			printf("***Malformed extension\n");
			continue;
		}
		*val = *((uint32 *)exten->value.parsedValue);
		return true;
	}
	return false;
}

/*
 * Add a CRL to an existing DL/DB.
 */
#define MAX_CRL_ATTRS			9

CSSM_RETURN cuAddCrlToDb(
	CSSM_DL_DB_HANDLE	dlDbHand,
	CSSM_CL_HANDLE		clHand,
	const CSSM_DATA		*crl,
	const CSSM_DATA		*URI)		// optional
{
	CSSM_DB_ATTRIBUTE_DATA			attrs[MAX_CRL_ATTRS];
	CSSM_DB_RECORD_ATTRIBUTE_DATA	recordAttrs;
	CSSM_DB_ATTRIBUTE_DATA_PTR		attr = &attrs[0];
	CSSM_DATA						crlTypeData;
	CSSM_DATA						crlEncData;
	CSSM_DATA						printNameData;
	CSSM_RETURN						crtn;
	CSSM_DB_UNIQUE_RECORD_PTR		recordPtr;
	CSSM_DATA_PTR					issuer;		// mallocd by CL
	CSSM_DATA_PTR					crlValue;	// ditto
	uint32							numFields;
	CSSM_HANDLE						result;
	CSSM_CRL_ENCODING 				crlEnc = CSSM_CRL_ENCODING_DER;
	const CSSM_X509_SIGNED_CRL 		*signedCrl;
	const CSSM_X509_TBS_CERTLIST 	*tbsCrl;
	CSSM_CRL_TYPE 					crlType;
	CSSM_DATA 						thisUpdateData = {0, NULL};
	CSSM_DATA 						nextUpdateData = {0, NULL};
	char							*thisUpdate, *nextUpdate;
	unsigned						timeLen;
	uint32							crlNumber;
	uint32							deltaCrlNumber;
	CSSM_DATA						crlNumberData;
	CSSM_DATA						deltaCrlNumberData;
	bool							crlNumberPresent = false;
	bool							deltaCrlPresent = false;
	CSSM_DATA						attrUri;
	
	/* get normalized issuer name as Issuer attr */
	crtn = CSSM_CL_CrlGetFirstFieldValue(clHand,
		crl,
		&CSSMOID_X509V1IssuerName,
		&result,
		&numFields,
		&issuer);
	if(crtn) {
		cuPrintError("CSSM_CL_CrlGetFirstFieldValue(Issuer)", crtn);
		return crtn;
	}
	CSSM_CL_CrlAbortQuery(clHand, result);
	
	/* get parsed CRL from the CL */
	crtn = CSSM_CL_CrlGetFirstFieldValue(clHand,
		crl,
		&CSSMOID_X509V2CRLSignedCrlCStruct,
		&result,
		&numFields,
		&crlValue);
	if(crtn) {
		cuPrintError("CSSM_CL_CrlGetFirstFieldValue(Issuer)", crtn);
		return crtn;
	}
	CSSM_CL_CrlAbortQuery(clHand, result);
	if(crlValue == NULL) {
		printf("***CSSM_CL_CrlGetFirstFieldValue: value error (1)\n");
		return CSSMERR_CL_INVALID_CRL_POINTER;
	}
	if((crlValue->Data == NULL) || 
	   (crlValue->Length != sizeof(CSSM_X509_SIGNED_CRL))) {
		printf("***CSSM_CL_CrlGetFirstFieldValue: value error (2)\n");
		return CSSMERR_CL_INVALID_CRL_POINTER;
	}
	signedCrl = (const CSSM_X509_SIGNED_CRL *)crlValue->Data;
	tbsCrl = &signedCrl->tbsCertList;
	
	/* CrlType inferred from version */
	if(tbsCrl->version.Length == 0) {
		/* should never happen... */
		crlType = CSSM_CRL_TYPE_X_509v1;
	}
	else {
		uint8 vers = tbsCrl->version.Data[tbsCrl->version.Length - 1];
		switch(vers) {
			case 0:
				crlType = CSSM_CRL_TYPE_X_509v1;
				break;
			case 1:
				crlType = CSSM_CRL_TYPE_X_509v2;
				break;
			default:
				printf("***Unknown version in CRL (%u)\n", vers);
				crlType = CSSM_CRL_TYPE_X_509v1;
				break;
		}
	}
	crlTypeData.Data = (uint8 *)&crlType;
	crlTypeData.Length = sizeof(CSSM_CRL_TYPE);
	/* encoding more-or-less assumed here */
	crlEncData.Data = (uint8 *)&crlEnc;
	crlEncData.Length = sizeof(CSSM_CRL_ENCODING);
	
	/* printName inferred from issuer */
	cuInferCrlLabel(&tbsCrl->issuer, &printNameData);
	
	/* cook up CSSM_TIMESTRING versions of this/next update */
	thisUpdate = cuX509TimeToCssmTimestring(&tbsCrl->thisUpdate, &timeLen);
	if(thisUpdate == NULL) {
		printf("***Badly formatted thisUpdate\n");
	}
	else {
		thisUpdateData.Data = (uint8 *)thisUpdate;
		thisUpdateData.Length = timeLen;
	}
	if(tbsCrl->nextUpdate.time.Data != NULL) {
		nextUpdate = cuX509TimeToCssmTimestring(&tbsCrl->nextUpdate, &timeLen);
		if(nextUpdate == NULL) {
			printf("***Badly formatted nextUpdate\n");
		}
		else {
			nextUpdateData.Data = (uint8 *)nextUpdate;
			nextUpdateData.Length = timeLen;
		}
	}
	else {
		/*
		 * NextUpdate not present; fake it by using "virtual end of time"
		 */
		CSSM_X509_TIME tempTime = {	0,		// timeType, not used
			{ strlen(CSSM_APPLE_CRL_END_OF_TIME), 
			  (uint8 *)CSSM_APPLE_CRL_END_OF_TIME} };
		nextUpdate = cuX509TimeToCssmTimestring(&tempTime, &timeLen);
		nextUpdateData.Data = (uint8 *)nextUpdate;
		nextUpdateData.Length = CSSM_TIME_STRLEN;
	}
	
	/* optional CrlNumber and DeltaCrlNumber */
	if(cuSearchNumericExtension(&tbsCrl->extensions,
			&CSSMOID_CrlNumber,
			&crlNumber)) {
		crlNumberData.Data = (uint8 *)&crlNumber;
		crlNumberData.Length = sizeof(uint32);
		crlNumberPresent = true;
	}
	if(cuSearchNumericExtension(&tbsCrl->extensions,
			&CSSMOID_DeltaCrlIndicator,
			&deltaCrlNumber)) {
		deltaCrlNumberData.Data = (uint8 *)&deltaCrlNumber;
		deltaCrlNumberData.Length = sizeof(uint32);
		deltaCrlPresent = true;
	}

	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	attr->Info.Label.AttributeName = "CrlType";
	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_UINT32;
	attr->NumberOfValues = 1;
	attr->Value = &crlTypeData;
	attr++;
	
	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	attr->Info.Label.AttributeName = "CrlEncoding";
	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_UINT32;
	attr->NumberOfValues = 1;
	attr->Value = &crlEncData;
	attr++;
	
	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	attr->Info.Label.AttributeName = "PrintName";
	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
	attr->NumberOfValues = 1;
	attr->Value = &printNameData;
	attr++;
	
	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	attr->Info.Label.AttributeName = "Issuer";
	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
	attr->NumberOfValues = 1;
	attr->Value = issuer;
	attr++;
	
	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	attr->Info.Label.AttributeName = "ThisUpdate";
	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
	attr->NumberOfValues = 1;
	attr->Value = &thisUpdateData;
	attr++;
	
	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
	attr->Info.Label.AttributeName = "NextUpdate";
	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
	attr->NumberOfValues = 1;
	attr->Value = &nextUpdateData;
	attr++;
	
	/* now the optional attributes */
	if(crlNumberPresent) {
		attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
		attr->Info.Label.AttributeName = "CrlNumber";
		attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_UINT32;
		attr->NumberOfValues = 1;
		attr->Value = &crlNumberData;
		attr++;
	}
	if(deltaCrlPresent) {
		attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
		attr->Info.Label.AttributeName = "DeltaCrlNumber";
		attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_UINT32;
		attr->NumberOfValues = 1;
		attr->Value = &deltaCrlNumberData;
		attr++;
	}
	if(URI) {
		/* ensure URI string does not contain NULL */
		attrUri = *URI;
		if((attrUri.Length != 0) && 
		   (attrUri.Data[attrUri.Length - 1] == 0)) {
			attrUri.Length--;
		}
		attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
		attr->Info.Label.AttributeName = "URI";
		attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
		attr->NumberOfValues = 1;
		attr->Value = &attrUri;
		attr++;
	}
	recordAttrs.DataRecordType = CSSM_DL_DB_RECORD_X509_CRL;
	recordAttrs.SemanticInformation = 0;
	recordAttrs.NumberOfAttributes = attr - attrs;
	recordAttrs.AttributeData = attrs;
	
	crtn = CSSM_DL_DataInsert(dlDbHand,
		CSSM_DL_DB_RECORD_X509_CRL,
		&recordAttrs,
		crl,
		&recordPtr);
	if(crtn == CSSMERR_DL_INVALID_RECORDTYPE) {
		/* gross hack of inserting this "new" schema that Keychain didn't specify */
		crtn = cuAddCrlSchema(dlDbHand);
		if(crtn == CSSM_OK) {
			/* Retry with a fully capable DLDB */
			crtn = CSSM_DL_DataInsert(dlDbHand,
				CSSM_DL_DB_RECORD_X509_CRL,
				&recordAttrs,
				crl,
				&recordPtr);
		}
	}
	if(crtn == CSSM_OK) {
		CSSM_DL_FreeUniqueRecord(dlDbHand, recordPtr);
	}
	
	/* free all the stuff we allocated to get here */
  	CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V1IssuerName, issuer);
  	CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V2CRLSignedCrlCStruct, crlValue);
	free(thisUpdate);
	free(nextUpdate);
	return crtn;
}


/*
 * Update an existing DLDB to be CRL-capable.
 */
static CSSM_RETURN cuAddCrlSchema(
	CSSM_DL_DB_HANDLE	dlDbHand)
{
	return CSSM_DL_CreateRelation(dlDbHand,
		CSSM_DL_DB_RECORD_X509_CRL,
		"CSSM_DL_DB_RECORD_X509_CRL",
		Security::KeychainCore::Schema::X509CrlSchemaAttributeCount,
		Security::KeychainCore::Schema::X509CrlSchemaAttributeList,
		Security::KeychainCore::Schema::X509CrlSchemaIndexCount,
		Security::KeychainCore::Schema::X509CrlSchemaIndexList);		
}

/*
 * Search DB for all records of type CRL or cert, calling appropriate
 * parse/print routine for each record. 
 */ 
CSSM_RETURN cuDumpCrlsCerts(
	CSSM_DL_DB_HANDLE	dlDbHand,
	CSSM_CL_HANDLE		clHand,
	CSSM_BOOL			isCert,
	unsigned			&numItems,		// returned
	CSSM_BOOL			verbose)
{
	CSSM_QUERY					query;
	CSSM_DB_UNIQUE_RECORD_PTR	record = NULL;
	CSSM_HANDLE					resultHand;
	CSSM_RETURN					crtn;
	CSSM_DATA					certCrl;
	const char					*itemStr;
	
	numItems = 0;
	itemStr = isCert ? "Certificate" : "CRL";
	
	/* just search by recordType, no predicates, no attributes */
	if(isCert) {
		query.RecordType = CSSM_DL_DB_RECORD_X509_CERTIFICATE;
	}
	else {
		query.RecordType = CSSM_DL_DB_RECORD_X509_CRL;
	}
	query.Conjunctive = CSSM_DB_NONE;
	query.NumSelectionPredicates = 0;
	query.SelectionPredicate = NULL;
	query.QueryLimits.TimeLimit = 0;			// FIXME - meaningful?
	query.QueryLimits.SizeLimit = 1;			// FIXME - meaningful?
	query.QueryFlags = 0;		// CSSM_QUERY_RETURN_DATA...FIXME - used?

	certCrl.Data = NULL;
	certCrl.Length = 0;
	crtn = CSSM_DL_DataGetFirst(dlDbHand,
		&query,
		&resultHand,
		NULL,			// no attrs 
		&certCrl,
		&record);
	switch(crtn) {
		case CSSM_OK:
			break;		// proceed
		case CSSMERR_DL_ENDOFDATA:
			/* no data, otherwise OK */
			return CSSM_OK;
		case CSSMERR_DL_INVALID_RECORDTYPE:
			/* invalid record type just means "this hasn't been set up
			* for certs yet". */
			return crtn;
		default:
			cuPrintError("DataGetFirst", crtn);
			return crtn;
	}

	/* got one; print it */
	printf("%s %u:\n", itemStr, numItems);
	if(isCert) {
		printCert(certCrl.Data, certCrl.Length, verbose);
	}
	else {
		printCrl(certCrl.Data, certCrl.Length, verbose);
	}
	CSSM_DL_FreeUniqueRecord(dlDbHand, record);
	APP_FREE(certCrl.Data);
	certCrl.Data = NULL;
	certCrl.Length = 0;
	numItems++;
	
	/* get the rest */
	for(;;) {
		crtn = CSSM_DL_DataGetNext(dlDbHand,
			resultHand, 
			NULL,
			&certCrl,
			&record);
		switch(crtn) {
			case CSSM_OK:
				printf("%s %u:\n", itemStr, numItems);
				if(isCert) {
					printCert(certCrl.Data, certCrl.Length, verbose);
				}
				else {
					printCrl(certCrl.Data, certCrl.Length, verbose);
				}
				CSSM_DL_FreeUniqueRecord(dlDbHand, record);
				APP_FREE(certCrl.Data);
				certCrl.Data = NULL;
				certCrl.Length = 0;
				numItems++;
				break;		// and go again 
			case CSSMERR_DL_ENDOFDATA:
				/* normal termination */
				return CSSM_OK;
			default:
				cuPrintError("DataGetNext", crtn);
				return crtn;
		}
	}
	/* NOT REACHED */
}
	
