/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef CKMK_H
#include "ckmk.h"
#endif /* CKMK_H */

/*
 * nssmkey/mfind.c
 *
 * This file implements the NSSCKMDFindObjects object for the
 * "nssmkey" cryptoki module.
 */

struct ckmkFOStr {
  NSSArena *arena;
  CK_ULONG n;
  CK_ULONG i;
  ckmkInternalObject **objs;
};

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

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

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

  return;
}

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

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

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

  return nss_ckmk_CreateMDObject(arena, io, pError);
}

static CK_BBOOL
ckmk_attrmatch
(
  CK_ATTRIBUTE_PTR a,
  ckmkInternalObject *o
)
{
  PRBool prb;
  const NSSItem *b;
  CK_RV error;

  b = nss_ckmk_FetchAttribute(o, a->type,  &error);
  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_ckmk_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
ckmk_match
(
  CK_ATTRIBUTE_PTR pTemplate,
  CK_ULONG ulAttributeCount,
  ckmkInternalObject *o
)
{
  CK_ULONG i;

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

  /* Every attribute passed */
  return CK_TRUE;
}

#define CKMK_ITEM_CHUNK  20

#define PUT_OBJECT(obj, err, size, count, list) \
  { \
    if (count >= size) { \
    (list) = (list) ? \
		nss_ZREALLOCARRAY(list, ckmkInternalObject *, \
		               ((size)+CKMK_ITEM_CHUNK) ) : \
		nss_ZNEWARRAY(NULL, ckmkInternalObject *, \
		               ((size)+CKMK_ITEM_CHUNK) ) ; \
      if ((ckmkInternalObject **)NULL == list) { \
        err = CKR_HOST_MEMORY; \
        goto loser; \
      } \
      (size) += CKMK_ITEM_CHUNK; \
    } \
    (list)[ count ] = (obj); \
    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,
  SecItemClass itemClass,
  CK_ATTRIBUTE_PTR pTemplate, 
  CK_ULONG ulAttributeCount, 
  ckmkInternalObject ***listp,
  PRUint32 *sizep,
  PRUint32 count,
  CK_RV *pError
)
{
  ckmkInternalObject *next = NULL;
  SecKeychainSearchRef searchRef = 0;
  SecKeychainItemRef itemRef = 0;
  OSStatus error;

  /* future, build the attribute list based on the template
   * so we can refine the search */
  error = SecKeychainSearchCreateFromAttributes( 
		NULL, itemClass, NULL, &searchRef);

  while (noErr == SecKeychainSearchCopyNext(searchRef, &itemRef)) {
    /* if we don't have an internal object structure, get one */
    if ((ckmkInternalObject *)NULL == next) {
      next = nss_ZNEW(NULL, ckmkInternalObject);
      if ((ckmkInternalObject *)NULL == next) {
        *pError = CKR_HOST_MEMORY;
        goto loser;
      }
    }
    /* fill in the relevant object data */
    next->type = ckmkItem;
    next->objClass = objClass;
    next->u.item.itemRef = itemRef;
    next->u.item.itemClass = itemClass;

    /* see if this is one of the objects we are looking for */
    if( CK_TRUE == ckmk_match(pTemplate, ulAttributeCount, next) ) {
      /* yes, put it on the list */
      PUT_OBJECT(next, *pError, *sizep, count, *listp);
      next = NULL; /* this one is on the list, need to allocate a new one now */
    } else {
      /* no , release the current item and clear out the structure for reuse */
      CFRelease(itemRef);
      /* don't cache the values we just loaded */
      nsslibc_memset(next, 0, sizeof(*next));
    }
  }
loser:
  if (searchRef) {
    CFRelease(searchRef);
  }
  nss_ZFreeIf(next);
  return count;
}

static PRUint32
collect_objects(
  CK_ATTRIBUTE_PTR pTemplate, 
  CK_ULONG ulAttributeCount, 
  ckmkInternalObject ***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_ckmk_nObjects; i++ ) {
    ckmkInternalObject *o = (ckmkInternalObject *)&nss_ckmk_data[i];

    if( CK_TRUE == ckmk_match(pTemplate, ulAttributeCount, o) ) {
      PUT_OBJECT(o, *pError, size, count, *listp);
    }
  }

  /*
   * now handle the various object types
   */
  objClass = nss_ckmk_GetULongAttribute(CKA_CLASS, 
		pTemplate, ulAttributeCount, pError);
  if (CKR_OK != *pError) {
    objClass = CK_INVALID_HANDLE;
  }
  *pError = CKR_OK;
  switch (objClass) {
  case CKO_CERTIFICATE:
    count = collect_class(objClass, kSecCertificateItemClass,
                              pTemplate, ulAttributeCount, listp, 
                              &size, count, pError);
    break;
  case CKO_PUBLIC_KEY:
    count = collect_class(objClass,  CSSM_DL_DB_RECORD_PUBLIC_KEY,
                              pTemplate, ulAttributeCount, listp, 
                              &size, count, pError);
    break;
  case CKO_PRIVATE_KEY:
    count = collect_class(objClass,  CSSM_DL_DB_RECORD_PRIVATE_KEY,
                              pTemplate, ulAttributeCount, listp, 
                              &size, count, pError);
    break;
  /* all of them */
  case CK_INVALID_HANDLE:
    count = collect_class(CKO_CERTIFICATE, kSecCertificateItemClass,
                              pTemplate, ulAttributeCount, listp, 
                              &size, count, pError);
    count = collect_class(CKO_PUBLIC_KEY,  CSSM_DL_DB_RECORD_PUBLIC_KEY,
                              pTemplate, ulAttributeCount, listp, 
                              &size, count, pError);
    count = collect_class(CKO_PUBLIC_KEY,  CSSM_DL_DB_RECORD_PRIVATE_KEY,
                              pTemplate, ulAttributeCount, listp, 
                              &size, count, pError);
    break;
  default:
    break;
  }
  if (CKR_OK != *pError) {
    goto loser;
  }

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


NSS_IMPLEMENT NSSCKMDFindObjects *
nss_ckmk_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 ckmkFOStr *fo = (struct ckmkFOStr *)NULL;
  ckmkInternalObject **temp = (ckmkInternalObject **)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 ckmkFOStr);
  if( (struct ckmkFOStr *)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 = ckmk_mdFindObjects_Final;
  rv->Next = ckmk_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, ckmkInternalObject *, fo->n);
  if( (ckmkInternalObject **)NULL == fo->objs ) {
    *pError = CKR_HOST_MEMORY;
    goto loser;
  }

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

  return rv;

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

