/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Netscape security libraries.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1994-2000
 * the Initial Developer. All Rights Reserved.
 * Portions created by Red Hat, Inc, are Copyright (C) 2005
 *
 * Contributor(s):
 *   Bob Relyea (rrelyea@redhat.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
#ifdef DEBUG
static const char CVS_ID[] = "@(#) $RCSfile: cfind.c,v $ $Revision: 1.3 $ $Date: 2005/12/16 00:48:02 $";
#endif /* DEBUG */

#ifndef CKCAPI_H
#include "ckcapi.h"
#endif /* CKCAPI_H */

/*
 * ckcapi/cfind.c
 *
 * This file implements the NSSCKMDFindObjects object for the
 * "capi" cryptoki module.
 */

struct ckcapiFOStr {
  NSSArena *arena;
  CK_ULONG n;
  CK_ULONG i;
  ckcapiInternalObject **objs;
};

static void
ckcapi_mdFindObjects_Final
(
  NSSCKMDFindObjects *mdFindObjects,
  NSSCKFWFindObjects *fwFindObjects,
  NSSCKMDSession *mdSession,
  NSSCKFWSession *fwSession,
  NSSCKMDToken *mdToken,
  NSSCKFWToken *fwToken,
  NSSCKMDInstance *mdInstance,
  NSSCKFWInstance *fwInstance
)
{
  struct ckcapiFOStr *fo = (struct ckcapiFOStr *)mdFindObjects->etc;
  NSSArena *arena = fo->arena;
  PRUint32 i;

  /* walk down an free the unused 'objs' */
  for (i=fo->i; i < fo->n ; i++) {
    nss_ckcapi_DestroyInternalObject(fo->objs[i]);
  }

  nss_ZFreeIf(fo->objs);
  nss_ZFreeIf(fo);
  nss_ZFreeIf(mdFindObjects);
  if ((NSSArena *)NULL != arena) {
    NSSArena_Destroy(arena);
  }

  return;
}

static NSSCKMDObject *
ckcapi_mdFindObjects_Next
(
  NSSCKMDFindObjects *mdFindObjects,
  NSSCKFWFindObjects *fwFindObjects,
  NSSCKMDSession *mdSession,
  NSSCKFWSession *fwSession,
  NSSCKMDToken *mdToken,
  NSSCKFWToken *fwToken,
  NSSCKMDInstance *mdInstance,
  NSSCKFWInstance *fwInstance,
  NSSArena *arena,
  CK_RV *pError
)
{
  struct ckcapiFOStr *fo = (struct ckcapiFOStr *)mdFindObjects->etc;
  ckcapiInternalObject *io;

  if( fo->i == fo->n ) {
    *pError = CKR_OK;
    return (NSSCKMDObject *)NULL;
  }

  io = fo->objs[ fo->i ];
  fo->i++;

  return nss_ckcapi_CreateMDObject(arena, io, pError);
}

static CK_BBOOL
ckcapi_attrmatch
(
  CK_ATTRIBUTE_PTR a,
  ckcapiInternalObject *o
)
{
  PRBool prb;
  const NSSItem *b;

  b = nss_ckcapi_FetchAttribute(o, a->type);
  if (b == NULL) {
    return CK_FALSE;
  }

  if( a->ulValueLen != b->size ) {
    /* match a decoded serial number */
    if ((a->type == CKA_SERIAL_NUMBER) && (a->ulValueLen < b->size)) {
	int len;
	unsigned char *data;

	data = nss_ckcapi_DERUnwrap(b->data, b->size, &len, NULL);
	if ((len == a->ulValueLen) && 
		nsslibc_memequal(a->pValue, data, len, (PRStatus *)NULL)) {
	    return CK_TRUE;
	}
    }
    return CK_FALSE;
  }

  prb = nsslibc_memequal(a->pValue, b->data, b->size, (PRStatus *)NULL);

  if( PR_TRUE == prb ) {
    return CK_TRUE;
  } else {
    return CK_FALSE;
  }
}


static CK_BBOOL
ckcapi_match
(
  CK_ATTRIBUTE_PTR pTemplate,
  CK_ULONG ulAttributeCount,
  ckcapiInternalObject *o
)
{
  CK_ULONG i;

  for( i = 0; i < ulAttributeCount; i++ ) {
    if (CK_FALSE == ckcapi_attrmatch(&pTemplate[i], o)) {
      return CK_FALSE;
    }
  }

  /* Every attribute passed */
  return CK_TRUE;
}

#define CKAPI_ITEM_CHUNK  20

#define PUT_Object(obj,err) \
  { \
    if (count >= size) { \
    *listp = *listp ? \
		nss_ZREALLOCARRAY(*listp, ckcapiInternalObject *, \
		               (size+CKAPI_ITEM_CHUNK) ) : \
		nss_ZNEWARRAY(NULL, ckcapiInternalObject *, \
		               (size+CKAPI_ITEM_CHUNK) ) ; \
      if ((ckcapiInternalObject **)NULL == *listp) { \
        err = CKR_HOST_MEMORY; \
        goto loser; \
      } \
      size += CKAPI_ITEM_CHUNK; \
    } \
    (*listp)[ count ] = (obj); \
    count++; \
  }


/*
 * pass parameters back through the callback.
 */
typedef struct BareCollectParamsStr {
  CK_OBJECT_CLASS objClass;
  CK_ATTRIBUTE_PTR pTemplate;
  CK_ULONG ulAttributeCount;
  ckcapiInternalObject ***listp;
  PRUint32 size;
  PRUint32 count;
} BareCollectParams;

/* collect_bare's callback. Called for each object that
 * supposedly has a PROVINDER_INFO property */
static BOOL WINAPI
doBareCollect
(
  const CRYPT_HASH_BLOB *msKeyID,
  DWORD flags,
  void *reserved,
  void *args,
  DWORD cProp,
  DWORD *propID,
  void **propData,
  DWORD *propSize
)
{
  BareCollectParams *bcp = (BareCollectParams *) args;
  PRUint32 size = bcp->size;
  PRUint32 count = bcp->count;
  ckcapiInternalObject ***listp = bcp->listp;
  ckcapiInternalObject *io = NULL;
  DWORD i;
  CRYPT_KEY_PROV_INFO *keyProvInfo = NULL;
  void *idData;
  CK_RV error;
 
  /* make sure there is a Key Provider Info property */
  for (i=0; i < cProp; i++) {
    if (CERT_KEY_PROV_INFO_PROP_ID == propID[i]) {
	keyProvInfo = (CRYPT_KEY_PROV_INFO *)propData[i];
	break;
    }
  }
  if ((CRYPT_KEY_PROV_INFO *)NULL == keyProvInfo) {
    return 1;
  }

  /* copy the key ID */
  idData = nss_ZNEWARRAY(NULL, char, msKeyID->cbData);
  if ((void *)NULL == idData) {
     goto loser;
  }
  nsslibc_memcpy(idData, msKeyID->pbData, msKeyID->cbData);

  /* build a bare internal object */  
  io = nss_ZNEW(NULL, ckcapiInternalObject);
  if ((ckcapiInternalObject *)NULL == io) {
     goto loser;
  }
  io->type = ckcapiBareKey;
  io->objClass = bcp->objClass;
  io->u.key.provInfo = *keyProvInfo;
  io->u.key.provInfo.pwszContainerName = 
			nss_ckcapi_WideDup(keyProvInfo->pwszContainerName);
  io->u.key.provInfo.pwszProvName = 
			nss_ckcapi_WideDup(keyProvInfo->pwszProvName);
  io->u.key.provName = nss_ckcapi_WideToUTF8(keyProvInfo->pwszProvName);
  io->u.key.containerName = 
                        nss_ckcapi_WideToUTF8(keyProvInfo->pwszContainerName);
  io->u.key.hProv = 0;
  io->idData = idData;
  io->id.data = idData;
  io->id.size = msKeyID->cbData;
  idData = NULL;

  /* see if it matches */ 
  if( CK_FALSE == ckcapi_match(bcp->pTemplate, bcp->ulAttributeCount, io) ) {
    goto loser;
  }
  PUT_Object(io, error);
  bcp->size = size;
  bcp->count = count;
  return 1;

loser:
  if (io) {
    nss_ckcapi_DestroyInternalObject(io);
  }
  nss_ZFreeIf(idData);
  return 1;
}

/*
 * collect the bare keys running around
 */
static PRUint32
collect_bare(
  CK_OBJECT_CLASS objClass,
  CK_ATTRIBUTE_PTR pTemplate, 
  CK_ULONG ulAttributeCount, 
  ckcapiInternalObject ***listp,
  PRUint32 *sizep,
  PRUint32 count,
  CK_RV *pError
)
{
  BOOL rc;
  BareCollectParams bareCollectParams;

  bareCollectParams.objClass = objClass;
  bareCollectParams.pTemplate = pTemplate;
  bareCollectParams.ulAttributeCount = ulAttributeCount;
  bareCollectParams.listp = listp;
  bareCollectParams.size = *sizep;
  bareCollectParams.count = count;

  rc = CryptEnumKeyIdentifierProperties(NULL, CERT_KEY_PROV_INFO_PROP_ID, 0,
       NULL, NULL, &bareCollectParams, doBareCollect);

  *sizep = bareCollectParams.size;
  return bareCollectParams.count;
}

/* find all the certs that represent the appropriate object (cert, priv key, or
 *  pub key) in the cert store.
 */
static PRUint32
collect_class(
  CK_OBJECT_CLASS objClass,
  LPCSTR storeStr,
  PRBool hasID,
  CK_ATTRIBUTE_PTR pTemplate, 
  CK_ULONG ulAttributeCount, 
  ckcapiInternalObject ***listp,
  PRUint32 *sizep,
  PRUint32 count,
  CK_RV *pError
)
{
  PRUint32 size = *sizep;
  ckcapiInternalObject *next = NULL;
  HCERTSTORE hStore;
  PCCERT_CONTEXT certContext = NULL;
  PRBool  isKey = 
     (objClass == CKO_PUBLIC_KEY) | (objClass == CKO_PRIVATE_KEY);

  hStore = CertOpenSystemStore((HCRYPTPROV)NULL, storeStr);
  if (NULL == hStore) {
     return count; /* none found does not imply an error */
  }

  /* FUTURE: use CertFindCertificateInStore to filter better -- so we don't
   * have to enumerate all the certificates */
  while ((PCERT_CONTEXT) NULL != 
         (certContext= CertEnumCertificatesInStore(hStore, certContext))) {
    /* first filter out non user certs if we are looking for keys */
    if (isKey) {
      /* make sure there is a Key Provider Info property */
      CRYPT_KEY_PROV_INFO *keyProvInfo;
      DWORD size = 0;
      BOOL rv;
      rv =CertGetCertificateContextProperty(certContext,
        CERT_KEY_PROV_INFO_PROP_ID, NULL, &size);
      if (!rv) {
	int reason = GetLastError();
        /* we only care if it exists, we don't really need to fetch it yet */
	if (reason == CRYPT_E_NOT_FOUND) {
	  continue;
	}
      }
      /* filter out the non-microsoft providers */
      keyProvInfo = (CRYPT_KEY_PROV_INFO *)nss_ZAlloc(NULL, size);
      if (keyProvInfo) {
        rv =CertGetCertificateContextProperty(certContext,
          CERT_KEY_PROV_INFO_PROP_ID, keyProvInfo, &size);
        if (rv) {
	  char *provName = nss_ckcapi_WideToUTF8(keyProvInfo->pwszProvName);
          nss_ZFreeIf(keyProvInfo);

	  if (provName && 
		(strncmp(provName, "Microsoft", sizeof("Microsoft")-1) != 0)) {
	    continue;
	  }
	} else {
	  int reason = GetLastError();
          /* we only care if it exists, we don't really need to fetch it yet */
          nss_ZFreeIf(keyProvInfo);
	  if (reason == CRYPT_E_NOT_FOUND) {
	   continue;
	  }
	  
        }
      }
    }
    
    if ((ckcapiInternalObject *)NULL == next) {
      next = nss_ZNEW(NULL, ckcapiInternalObject);
      if ((ckcapiInternalObject *)NULL == next) {
        *pError = CKR_HOST_MEMORY;
        goto loser;
      }
    }
    next->type = ckcapiCert;
    next->objClass = objClass;
    next->u.cert.certContext = certContext;
    next->u.cert.hasID = hasID;
    next->u.cert.certStore = storeStr;
    if( CK_TRUE == ckcapi_match(pTemplate, ulAttributeCount, next) ) {
      /* clear cached values that may be dependent on our old certContext */
      memset(&next->u.cert, 0, sizeof(next->u.cert));
      /* get a 'permanent' context */
      next->u.cert.certContext = CertDuplicateCertificateContext(certContext);
      next->objClass = objClass;
      next->u.cert.certContext = certContext;
      next->u.cert.hasID = hasID;
      next->u.cert.certStore = storeStr;
      PUT_Object(next, *pError);
      next = NULL; /* need to allocate a new one now */
    } else {
      /* don't cache the values we just loaded */
      memset(&next->u.cert, 0, sizeof(next->u.cert));
    }
  }
loser:
  CertCloseStore(hStore, 0);
  nss_ZFreeIf(next);
  *sizep = size;
  return count;
}

NSS_IMPLEMENT PRUint32
nss_ckcapi_collect_all_certs(
  CK_ATTRIBUTE_PTR pTemplate, 
  CK_ULONG ulAttributeCount, 
  ckcapiInternalObject ***listp,
  PRUint32 *sizep,
  PRUint32 count,
  CK_RV *pError
)
{
  count = collect_class(CKO_CERTIFICATE, "My", PR_TRUE, pTemplate, 
			ulAttributeCount, listp, sizep, count, pError);
  /*count = collect_class(CKO_CERTIFICATE, "AddressBook", PR_FALSE, pTemplate, 
                        ulAttributeCount, listp, sizep, count, pError); */
  count = collect_class(CKO_CERTIFICATE, "CA", PR_FALSE, pTemplate, 
			ulAttributeCount, listp, sizep, count, pError);
  count = collect_class(CKO_CERTIFICATE, "Root", PR_FALSE, pTemplate, 
			ulAttributeCount, listp, sizep, count, pError);
  count = collect_class(CKO_CERTIFICATE, "Trust", PR_FALSE, pTemplate, 
			ulAttributeCount, listp, sizep, count, pError);
  count = collect_class(CKO_CERTIFICATE, "TrustedPeople", PR_FALSE, pTemplate, 
                        ulAttributeCount, listp, sizep, count, pError);
  count = collect_class(CKO_CERTIFICATE, "AuthRoot", PR_FALSE, pTemplate, 
                        ulAttributeCount, listp, sizep, count, pError);
  return count;
}

CK_OBJECT_CLASS
ckcapi_GetObjectClass(CK_ATTRIBUTE_PTR pTemplate, 
                      CK_ULONG ulAttributeCount)
{
  CK_ULONG i;

  for (i=0; i < ulAttributeCount; i++)
  {
    if (pTemplate[i].type == CKA_CLASS) {
      return *(CK_OBJECT_CLASS *) pTemplate[i].pValue;
    }
  }
  /* need to return a value that says 'fetch them all' */
  return CK_INVALID_HANDLE;
}

static PRUint32
collect_objects(
  CK_ATTRIBUTE_PTR pTemplate, 
  CK_ULONG ulAttributeCount, 
  ckcapiInternalObject ***listp,
  CK_RV *pError
)
{
  PRUint32 i;
  PRUint32 count = 0;
  PRUint32 size = 0;
  CK_OBJECT_CLASS objClass;

  /*
   * first handle the static build in objects (if any)
   */
  for( i = 0; i < nss_ckcapi_nObjects; i++ ) {
    ckcapiInternalObject *o = (ckcapiInternalObject *)&nss_ckcapi_data[i];

    if( CK_TRUE == ckcapi_match(pTemplate, ulAttributeCount, o) ) {
      PUT_Object(o, *pError);
    }
  }

  /*
   * now handle the various object types
   */
  objClass = ckcapi_GetObjectClass(pTemplate, ulAttributeCount);
  *pError = CKR_OK;
  switch (objClass) {
  case CKO_CERTIFICATE:
    count = nss_ckcapi_collect_all_certs(pTemplate, ulAttributeCount, listp, 
                              &size, count, pError);
    break;
  case CKO_PUBLIC_KEY:
    count = collect_class(objClass, "My", PR_TRUE, pTemplate, 
			ulAttributeCount, listp, &size, count, pError);
    count = collect_bare(objClass, pTemplate, ulAttributeCount, listp,
			&size, count, pError);
    break;
  case CKO_PRIVATE_KEY:
    count = collect_class(objClass, "My", PR_TRUE, pTemplate, 
			ulAttributeCount, listp, &size, count, pError);
    count = collect_bare(objClass, pTemplate, ulAttributeCount, listp,
			&size, count, pError);
    break;
  /* all of them */
  case CK_INVALID_HANDLE:
    count = nss_ckcapi_collect_all_certs(pTemplate, ulAttributeCount, listp, 
                              &size, count, pError);
    count = collect_class(CKO_PUBLIC_KEY, "My", PR_TRUE, pTemplate, 
			ulAttributeCount, listp, &size, count, pError);
    count = collect_bare(CKO_PUBLIC_KEY, pTemplate, ulAttributeCount, listp,
			&size, count, pError);
    count = collect_class(CKO_PRIVATE_KEY, "My", PR_TRUE, pTemplate, 
			ulAttributeCount, listp, &size, count, pError);
    count = collect_bare(CKO_PRIVATE_KEY, pTemplate, ulAttributeCount, listp,
			&size, count, pError);
    break;
  default:
    goto done; /* no other object types we understand in this module */
  }
  if (CKR_OK != *pError) {
    goto loser;
  }


done:
  return count;
loser:
  nss_ZFreeIf(*listp);
  return 0;
}



NSS_IMPLEMENT NSSCKMDFindObjects *
nss_ckcapi_FindObjectsInit
(
  NSSCKFWSession *fwSession,
  CK_ATTRIBUTE_PTR pTemplate,
  CK_ULONG ulAttributeCount,
  CK_RV *pError
)
{
  /* This could be made more efficient.  I'm rather rushed. */
  NSSArena *arena;
  NSSCKMDFindObjects *rv = (NSSCKMDFindObjects *)NULL;
  struct ckcapiFOStr *fo = (struct ckcapiFOStr *)NULL;
  ckcapiInternalObject **temp = (ckcapiInternalObject **)NULL;

  arena = NSSArena_Create();
  if( (NSSArena *)NULL == arena ) {
    goto loser;
  }

  rv = nss_ZNEW(arena, NSSCKMDFindObjects);
  if( (NSSCKMDFindObjects *)NULL == rv ) {
    *pError = CKR_HOST_MEMORY;
    goto loser;
  }

  fo = nss_ZNEW(arena, struct ckcapiFOStr);
  if( (struct ckcapiFOStr *)NULL == fo ) {
    *pError = CKR_HOST_MEMORY;
    goto loser;
  }

  fo->arena = arena;
  /* fo->n and fo->i are already zero */

  rv->etc = (void *)fo;
  rv->Final = ckcapi_mdFindObjects_Final;
  rv->Next = ckcapi_mdFindObjects_Next;
  rv->null = (void *)NULL;

  fo->n = collect_objects(pTemplate, ulAttributeCount, &temp, pError);
  if (*pError != CKR_OK) {
    goto loser;
  }

  fo->objs = nss_ZNEWARRAY(arena, ckcapiInternalObject *, fo->n);
  if( (ckcapiInternalObject **)NULL == fo->objs ) {
    *pError = CKR_HOST_MEMORY;
    goto loser;
  }

  (void)nsslibc_memcpy(fo->objs, temp, sizeof(ckcapiInternalObject *) * fo->n);
  nss_ZFreeIf(temp);
  temp = (ckcapiInternalObject **)NULL;

  return rv;

 loser:
  nss_ZFreeIf(temp);
  nss_ZFreeIf(fo);
  nss_ZFreeIf(rv);
  if ((NSSArena *)NULL != arena) {
     NSSArena_Destroy(arena);
  }
  return (NSSCKMDFindObjects *)NULL;
}

