<commit_before>#include "ykcs11.h"
//#include "pkcs11.h"
#include <stdlib.h>
#include <ykpiv.h>
#include <string.h>
#include "obj_types.h"
#include "objects.h"
#include "utils.h"
#include "mechanisms.h"
#include "openssl_types.h"
#include "debug.h"

#define YKCS11_MANUFACTURER "Yubico (www.yubico.com)"
#define YKCS11_LIBDESC      "PKCS#11 PIV Library (SP-800-73)"

#define PIV_MIN_PIN_LEN 6
#define PIV_MAX_PIN_LEN 8

#define YKCS11_MAX_SLOTS       16
#define YKCS11_MAX_SIG_BUF_LEN 1024

#define YKCS11_SESSION_ID 5355104

static ykpiv_state *piv_state = NULL;

static ykcs11_slot_t slots[YKCS11_MAX_SLOTS]; // TODO: build at runtime?
static CK_ULONG      n_slots = 0;
static CK_ULONG      n_slots_with_token = 0;

static ykcs11_session_t session; // TODO: support multiple sessions?

static struct {
  CK_BBOOL        active;
  CK_ULONG        num;
  CK_ULONG        idx;
  piv_obj_id_t    *objects;
} find_obj;

op_info_t op_info;

extern CK_FUNCTION_LIST function_list; // TODO: check all return values

/* General Purpose */

CK_DEFINE_FUNCTION(CK_RV, C_Initialize)(
  CK_VOID_PTR pInitArgs
)
{
  DIN;
  char readers[2048];
  CK_ULONG len = sizeof(readers);

  // TODO: check for locks and mutexes

  if (piv_state != NULL)
    return CKR_CRYPTOKI_ALREADY_INITIALIZED;

  if (ykpiv_init(&piv_state, YKCS11_DBG) != YKPIV_OK) {
    DBG(("Unable to initialize libykpiv"));
    return CKR_FUNCTION_FAILED; // TODO: better error?
  }

  if (ykpiv_list_readers(piv_state, readers, &len) != YKPIV_OK) {
    DBG(("Unable to list readers"));
    return CKR_FUNCTION_FAILED;
  }

  if(ykpiv_connect(piv_state, NULL) != YKPIV_OK) {
    DBG(("Unable to connect to reader"));
    return CKR_FUNCTION_FAILED;
  }

  if (parse_readers(readers, len, slots, &n_slots, &n_slots_with_token) != CK_TRUE)
    CKR_FUNCTION_FAILED;

  DBG(("Found %lu slot(s) of which %lu tokenless/unsupported", n_slots, n_slots - n_slots_with_token));

  find_obj.active = CK_FALSE;
  // TODO: FILL OUT INIT ARGS;

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_Finalize)(
  CK_VOID_PTR pReserved
)
{
  DIN;
  if (pReserved != NULL_PTR) {
    DBG(("Finalized called with pReserved != NULL"));
    return CKR_ARGUMENTS_BAD;
  }

  if (piv_state == NULL) {
    DBG(("libykpiv is not initialized or already finalized"));
    return CKR_CRYPTOKI_NOT_INITIALIZED;
  }

  memset(slots, 0, sizeof(slots));

  ykpiv_done(piv_state); // TODO: this calls disconnect...
  piv_state = NULL;

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_GetInfo)(
  CK_INFO_PTR pInfo
)
{
  DIN;
  CK_VERSION ver = {0, 0}; // TODO: set version number
  pInfo->cryptokiVersion = function_list.version;

  memset(pInfo->manufacturerID, ' ', sizeof(pInfo->manufacturerID));
  strcpy((char *)pInfo->manufacturerID, YKCS11_MANUFACTURER);

  pInfo->flags = 0;

  memset(pInfo->libraryDescription, ' ', sizeof(pInfo->libraryDescription));
  strcpy((char *)pInfo->libraryDescription, YKCS11_LIBDESC);

  pInfo->libraryVersion = ver;

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_GetFunctionList)(
  CK_FUNCTION_LIST_PTR_PTR ppFunctionList
)
{
  DIN;
  if(ppFunctionList == NULL_PTR) {
    DBG(("GetFunctionList called with ppFunctionList = NULL"));
    return CKR_ARGUMENTS_BAD;
  }
  *ppFunctionList = &function_list; // TODO: filter out unsupported functions

  DOUT;
  return CKR_OK;
}

/* Slot and token management */

CK_DEFINE_FUNCTION(CK_RV, C_GetSlotList)(
  CK_BBOOL tokenPresent,
  CK_SLOT_ID_PTR pSlotList,
  CK_ULONG_PTR pulCount
)
{
  DIN;
  CK_ULONG i;
  int j;

  if (piv_state == NULL) {
    DBG(("libykpiv is not initialized or already finalized"));
    return CKR_CRYPTOKI_NOT_INITIALIZED;
  }

  if (pSlotList == NULL_PTR) {
    // Just return the number of slots
    *pulCount = n_slots;

    if (tokenPresent)
      *pulCount = n_slots_with_token;
    else
      *pulCount = n_slots;

    DOUT;
    return CKR_OK;
  }

  if ((tokenPresent && *pulCount < n_slots_with_token) || (!tokenPresent && *pulCount < n_slots)) {
    DBG(("Buffer too small: needed %lu, provided %lu", n_slots, *pulCount));
    return CKR_BUFFER_TOO_SMALL;
  }

  for (j = 0, i = 0; i < n_slots; i++) {
    if (tokenPresent) {
      if (has_token(slots + i)) // TODO: use more to check if TOKEN_REMOVED
        pSlotList[j++] = i;
    }
    else
      pSlotList[i] = i;
  }

  DBG(("token present is %d", tokenPresent));
  DBG(("number of slot(s) is %lu", *pulCount));

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_GetSlotInfo)(
  CK_SLOT_ID slotID,
  CK_SLOT_INFO_PTR pInfo
)
{
  DIN;

  if (piv_state == NULL) {
    DBG(("libykpiv is not initialized or already finalized"));
    return CKR_CRYPTOKI_NOT_INITIALIZED;
  }

  if (slotID >= n_slots) {
    DBG(("Invalid slot ID %lu", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  memcpy(pInfo, &slots[slotID].info, sizeof(CK_SLOT_INFO));

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_GetTokenInfo)(
  CK_SLOT_ID slotID,
  CK_TOKEN_INFO_PTR pInfo
)
{
  DIN;

  if (piv_state == NULL) {
    DBG(("libykpiv is not initialized or already finalized"));
    return CKR_CRYPTOKI_NOT_INITIALIZED;
  }

  if (slotID >= n_slots) {
    DBG(("Invalid slot ID %lu", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  if (slots[slotID].vid == UNKNOWN) {
    DBG(("No support for slot %lu", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  if (!has_token(slots + slotID)) {
    DBG(("Slot %lu has no token inserted", slotID));
    return CKR_TOKEN_NOT_PRESENT;
  }

  if (slots[slotID].token->vid == UNKNOWN) {
    DBG(("No support for token in slot %lu", slotID));
    return CKR_TOKEN_NOT_RECOGNIZED;
  }

  memcpy(pInfo, &slots[slotID].token->info, sizeof(CK_TOKEN_INFO));

  // Overwrite values that are application specific
  pInfo->ulMaxSessionCount = CK_UNAVAILABLE_INFORMATION; // TODO: should this be 1?
  pInfo->ulSessionCount = CK_UNAVAILABLE_INFORMATION; // number of sessions that this application currently has open with the token

  pInfo->ulMaxRwSessionCount = CK_UNAVAILABLE_INFORMATION; // maximum number of read/write sessions that can be opened with the token at one time by a single TODO: should this be 1?

  pInfo->ulRwSessionCount =  CK_UNAVAILABLE_INFORMATION; // number of read/write sessions that this application currently has open with the token
  pInfo->ulMaxPinLen = PIV_MAX_PIN_LEN; // maximum length in bytes of the PIN
  pInfo->ulMinPinLen = PIV_MIN_PIN_LEN; // minimum length in bytes of the PIN
  pInfo->ulTotalPublicMemory = CK_UNAVAILABLE_INFORMATION;
  pInfo->ulFreePublicMemory = CK_UNAVAILABLE_INFORMATION;
  pInfo->ulTotalPrivateMemory = CK_UNAVAILABLE_INFORMATION;
  pInfo->ulFreePrivateMemory = CK_UNAVAILABLE_INFORMATION;

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_WaitForSlotEvent)(
  CK_FLAGS flags,
  CK_SLOT_ID_PTR pSlot,
  CK_VOID_PTR pReserved
)
{
  DIN;
  DBG(("TODO!!!"));
  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_GetMechanismList)(
  CK_SLOT_ID slotID,
  CK_MECHANISM_TYPE_PTR pMechanismList,
  CK_ULONG_PTR pulCount
)
{
  DIN;
  token_vendor_t token;
  CK_ULONG count;

  if (piv_state == NULL) {
    DBG(("libykpiv is not initialized or already finalized"));
    return CKR_CRYPTOKI_NOT_INITIALIZED;
  }

  if (slotID >= n_slots) {
    DBG(("Invalid slot ID %lu", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  if (pulCount == NULL_PTR) {
    DBG(("Wrong/Missing parameter"));
    return CKR_ARGUMENTS_BAD;
  }

  if (slots[slotID].vid == UNKNOWN) {
    DBG(("Slot %lu is tokenless/unsupported", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  // TODO: check more return values

  token = get_token_vendor(slots[slotID].vid);

  if (token.get_token_mechanisms_num(&count) != CKR_OK)
    return CKR_FUNCTION_FAILED;

  if (pMechanismList == NULL_PTR) {
    *pulCount = count;
    DBG(("Found %lu mechanisms", *pulCount));
    DOUT;
    return CKR_OK;
  }

  if (*pulCount < count) {
    DBG(("Buffer too small: needed %lu, provided %lu", count, *pulCount));
    return CKR_BUFFER_TOO_SMALL;
  }

  if (token.get_token_mechanism_list(pMechanismList, *pulCount) != CKR_OK) {
    DBG(("Unable to retrieve mechanism list"));
    return CKR_FUNCTION_FAILED;
  }

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_GetMechanismInfo)(
  CK_SLOT_ID slotID,
  CK_MECHANISM_TYPE type,
  CK_MECHANISM_INFO_PTR pInfo
)
{
  DIN;
  token_vendor_t token;

  if (piv_state == NULL) {
    DBG(("libykpiv is not initialized or already finalized"));
    return CKR_CRYPTOKI_NOT_INITIALIZED;
  }

  if (slotID >= n_slots) {
    DBG(("Invalid slot ID %lu", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  if (pInfo == NULL_PTR) {
    DBG(("Wrong/Missing parameter"));
    return CKR_ARGUMENTS_BAD;
  }

  if (slots[slotID].vid == UNKNOWN) {
    DBG(("Slot %lu is tokenless/unsupported", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  token = get_token_vendor(slots[slotID].vid);

  if (token.get_token_mechanism_info(type, pInfo) != CKR_OK) {
    DBG(("Unable to retrieve mechanism information"));
    return CKR_MECHANISM_INVALID;
  }

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_InitToken)(
  CK_SLOT_ID slotID,
  CK_UTF8CHAR_PTR pPin,
  CK_ULONG ulPinLen,
  CK_UTF8CHAR_PTR pLabel
)
{
  DIN;
  DBG(("Token initialization unsupported"));
  DOUT;
  return CKR_FUNCTION_FAILED;
}

CK_DEFINE_FUNCTION(CK_RV, C_InitPIN)(
  CK_SESSION_HANDLE hSession,
  CK_UTF8CHAR_PTR pPin,
  CK_ULONG ulPinLen
)
{
  DIN;
  DBG(("PIN initialization unsupported"));
  DOUT;
  return CKR_FUNCTION_FAILED;
}

CK_DEFINE_FUNCTION(CK_RV, C_SetPIN)(
  CK_SESSION_HANDLE hSession,
  CK_UTF8CHAR_PTR pOldPin,
  CK_ULONG ulOldLen,
  CK_UTF8CHAR_PTR pNewPin,
  CK_ULONG ulNewLen
)
{
  DIN;
  DBG(("TODO!!!"));
  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_OpenSession)(
  CK_SLOT_ID slotID,
  CK_FLAGS flags,
  CK_VOID_PTR pApplication,
  CK_NOTIFY Notify,
  CK_SESSION_HANDLE_PTR phSession
)
{
  DIN; // TODO: pApplication and Notify

  token_vendor_t token;
  CK_RV          rv;
  piv_obj_id_t   *cert_ids;
  CK_ULONG       i;
  CK_BYTE        cert_data[2100];  // Max cert value for ykpiv
  CK_ULONG       cert_len = sizeof(cert_data);

  if (piv_state == NULL) {
    DBG(("libykpiv is not initialized or already finalized"));
    return CKR_CRYPTOKI_NOT_INITIALIZED;
  }

  if (slotID >= n_slots) {
    DBG(("Invalid slot ID %lu", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  if (phSession == NULL_PTR) {
    DBG(("Wrong/Missing parameter"));
    return CKR_ARGUMENTS_BAD;
  }

  if (slots[slotID].vid == UNKNOWN) {
    DBG(("No support for slot %lu", slotID));
    return CKR_TOKEN_NOT_RECOGNIZED;
  }

  if (slots[slotID].token->vid == UNKNOWN) {
    DBG(("No support for token in slot %lu", slotID));
    return CKR_TOKEN_NOT_RECOGNIZED;
  }

  if (!has_token(slots + slotID)) {
    DBG(("Slot %lu has no token inserted", slotID));
    return CKR_TOKEN_NOT_PRESENT;
  }

  if (session.handle != CK_INVALID_HANDLE) {
    DBG(("A session with this or another token already exists"));
    return CKR_SESSION_COUNT;
  }

  if ((flags & CKF_SERIAL_SESSION) == 0) {
    DBG(("Open session called without CKF_SERIAL_SESSION set")); // Reuired by specs
    return CKR_SESSION_PARALLEL_NOT_SUPPORTED;
  }

  token = get_token_vendor(slots[slotID].token->vid);

  // Store the slot
  session.slot = slots + slotID;
  //session.slot->info.slotID = slotID; // Redundant but required in CK_SESSION_INFO

  // Store session state
  session.info.state = 0;

  if ((flags & CKF_RW_SESSION)) {
    // R/W Session
    session.info.state = CKS_RW_PUBLIC_SESSION; // Nobody has logged in, default RO session
  }
  else {
    // R/O Session
    session.info.state = CKS_RO_PUBLIC_SESSION; // Nobody has logged in, default RW session
  }

  session.info.flags = flags;
  session.info.ulDeviceError = 0;

  // Get the number of token objects
  rv = token.get_token_objects_num(piv_state, &session.slot->token->n_objects, &session.slot->token->n_certs);
  if (rv != CKR_OK) {
    DBG(("Unable to retrieve number of token objects"));
    return rv;
  }

  // Get memory for the objects
  session.slot->token->objects = malloc(sizeof(piv_obj_id_t) * session.slot->token->n_objects);
  if (session.slot->token->objects == NULL) {
    DBG(("Unable to allocate memory for token object ids"));
    return CKR_HOST_MEMORY;
  }

  // Get memory for the certificates
  cert_ids = malloc(sizeof(piv_obj_id_t) * session.slot->token->n_certs);
  if (cert_ids == NULL) {
    DBG(("Unable to allocate memory for token certificate ids"));
    return CKR_HOST_MEMORY;
  }

  // Save a list of all the available objects in the token
  rv = token.get_token_object_list(piv_state, session.slot->token->objects, session.slot->token->n_objects);
  if (rv != CKR_OK) {
    DBG(("Unable to retrieve token objects"));
    goto failure;
  }

  // Get a list of object ids for available certificates object from the session
  rv = get_available_certificate_ids(&session, cert_ids, session.slot->token->n_certs);
  if (rv != CKR_OK) {
    DBG(("Unable to retrieve certificate ids from the session"));
    goto failure;
  }

  // Get the actual certificate data from the token and store it as an X509 object
  for (i = 0; i < session.slot->token->n_certs; i++) {
    cert_len = sizeof(cert_data);
    rv = token.get_token_raw_certificate(piv_state, cert_ids[i], cert_data, &cert_len);
    if (rv != CKR_OK) {
      DBG(("Unable to get certificate data from token"));
      goto failure;
    }

    rv = store_cert(cert_ids[i], cert_data, cert_len);
    if (rv != CKR_OK) {
      DBG(("Unable to store certificate data"));
      goto failure;
    }
  }

  session.handle = YKCS11_SESSION_ID;
  // TODO: KEEP TRACK OF THE APPLICATION (possble to steal a session?)

  *phSession = session.handle;

  DOUT;
  return CKR_OK;
<commit_msg>Free more.<commit_after>#include "ykcs11.h"
//#include "pkcs11.h"
#include <stdlib.h>
#include <ykpiv.h>
#include <string.h>
#include "obj_types.h"
#include "objects.h"
#include "utils.h"
#include "mechanisms.h"
#include "openssl_types.h"
#include "debug.h"

#define YKCS11_MANUFACTURER "Yubico (www.yubico.com)"
#define YKCS11_LIBDESC      "PKCS#11 PIV Library (SP-800-73)"

#define PIV_MIN_PIN_LEN 6
#define PIV_MAX_PIN_LEN 8

#define YKCS11_MAX_SLOTS       16
#define YKCS11_MAX_SIG_BUF_LEN 1024

#define YKCS11_SESSION_ID 5355104

static ykpiv_state *piv_state = NULL;

static ykcs11_slot_t slots[YKCS11_MAX_SLOTS]; // TODO: build at runtime?
static CK_ULONG      n_slots = 0;
static CK_ULONG      n_slots_with_token = 0;

static ykcs11_session_t session; // TODO: support multiple sessions?

static struct {
  CK_BBOOL        active;
  CK_ULONG        num;
  CK_ULONG        idx;
  piv_obj_id_t    *objects;
} find_obj;

op_info_t op_info;

extern CK_FUNCTION_LIST function_list; // TODO: check all return values

/* General Purpose */

CK_DEFINE_FUNCTION(CK_RV, C_Initialize)(
  CK_VOID_PTR pInitArgs
)
{
  DIN;
  char readers[2048];
  CK_ULONG len = sizeof(readers);

  // TODO: check for locks and mutexes

  if (piv_state != NULL)
    return CKR_CRYPTOKI_ALREADY_INITIALIZED;

  if (ykpiv_init(&piv_state, YKCS11_DBG) != YKPIV_OK) {
    DBG(("Unable to initialize libykpiv"));
    return CKR_FUNCTION_FAILED; // TODO: better error?
  }

  if (ykpiv_list_readers(piv_state, readers, &len) != YKPIV_OK) {
    DBG(("Unable to list readers"));
    return CKR_FUNCTION_FAILED;
  }

  if(ykpiv_connect(piv_state, NULL) != YKPIV_OK) {
    DBG(("Unable to connect to reader"));
    return CKR_FUNCTION_FAILED;
  }

  if (parse_readers(readers, len, slots, &n_slots, &n_slots_with_token) != CK_TRUE)
    CKR_FUNCTION_FAILED;

  DBG(("Found %lu slot(s) of which %lu tokenless/unsupported", n_slots, n_slots - n_slots_with_token));

  find_obj.active = CK_FALSE;
  // TODO: FILL OUT INIT ARGS;

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_Finalize)(
  CK_VOID_PTR pReserved
)
{
  DIN;
  CK_ULONG i;

  if (pReserved != NULL_PTR) {
    DBG(("Finalized called with pReserved != NULL"));
    return CKR_ARGUMENTS_BAD;
  }

  if (piv_state == NULL) {
    DBG(("libykpiv is not initialized or already finalized"));
    return CKR_CRYPTOKI_NOT_INITIALIZED;
  }

  for (i = 0; i < n_slots; i++) {
    destroy_token(slots + i);
  }
  memset(slots, 0, sizeof(slots));

  ykpiv_done(piv_state); // TODO: this calls disconnect...
  piv_state = NULL;

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_GetInfo)(
  CK_INFO_PTR pInfo
)
{
  DIN;
  CK_VERSION ver = {0, 0}; // TODO: set version number
  pInfo->cryptokiVersion = function_list.version;

  memset(pInfo->manufacturerID, ' ', sizeof(pInfo->manufacturerID));
  strcpy((char *)pInfo->manufacturerID, YKCS11_MANUFACTURER);

  pInfo->flags = 0;

  memset(pInfo->libraryDescription, ' ', sizeof(pInfo->libraryDescription));
  strcpy((char *)pInfo->libraryDescription, YKCS11_LIBDESC);

  pInfo->libraryVersion = ver;

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_GetFunctionList)(
  CK_FUNCTION_LIST_PTR_PTR ppFunctionList
)
{
  DIN;
  if(ppFunctionList == NULL_PTR) {
    DBG(("GetFunctionList called with ppFunctionList = NULL"));
    return CKR_ARGUMENTS_BAD;
  }
  *ppFunctionList = &function_list; // TODO: filter out unsupported functions

  DOUT;
  return CKR_OK;
}

/* Slot and token management */

CK_DEFINE_FUNCTION(CK_RV, C_GetSlotList)(
  CK_BBOOL tokenPresent,
  CK_SLOT_ID_PTR pSlotList,
  CK_ULONG_PTR pulCount
)
{
  DIN;
  CK_ULONG i;
  int j;

  if (piv_state == NULL) {
    DBG(("libykpiv is not initialized or already finalized"));
    return CKR_CRYPTOKI_NOT_INITIALIZED;
  }

  if (pSlotList == NULL_PTR) {
    // Just return the number of slots
    *pulCount = n_slots;

    if (tokenPresent)
      *pulCount = n_slots_with_token;
    else
      *pulCount = n_slots;

    DOUT;
    return CKR_OK;
  }

  if ((tokenPresent && *pulCount < n_slots_with_token) || (!tokenPresent && *pulCount < n_slots)) {
    DBG(("Buffer too small: needed %lu, provided %lu", n_slots, *pulCount));
    return CKR_BUFFER_TOO_SMALL;
  }

  for (j = 0, i = 0; i < n_slots; i++) {
    if (tokenPresent) {
      if (has_token(slots + i)) // TODO: use more to check if TOKEN_REMOVED
        pSlotList[j++] = i;
    }
    else
      pSlotList[i] = i;
  }

  DBG(("token present is %d", tokenPresent));
  DBG(("number of slot(s) is %lu", *pulCount));

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_GetSlotInfo)(
  CK_SLOT_ID slotID,
  CK_SLOT_INFO_PTR pInfo
)
{
  DIN;

  if (piv_state == NULL) {
    DBG(("libykpiv is not initialized or already finalized"));
    return CKR_CRYPTOKI_NOT_INITIALIZED;
  }

  if (slotID >= n_slots) {
    DBG(("Invalid slot ID %lu", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  memcpy(pInfo, &slots[slotID].info, sizeof(CK_SLOT_INFO));

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_GetTokenInfo)(
  CK_SLOT_ID slotID,
  CK_TOKEN_INFO_PTR pInfo
)
{
  DIN;

  if (piv_state == NULL) {
    DBG(("libykpiv is not initialized or already finalized"));
    return CKR_CRYPTOKI_NOT_INITIALIZED;
  }

  if (slotID >= n_slots) {
    DBG(("Invalid slot ID %lu", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  if (slots[slotID].vid == UNKNOWN) {
    DBG(("No support for slot %lu", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  if (!has_token(slots + slotID)) {
    DBG(("Slot %lu has no token inserted", slotID));
    return CKR_TOKEN_NOT_PRESENT;
  }

  if (slots[slotID].token->vid == UNKNOWN) {
    DBG(("No support for token in slot %lu", slotID));
    return CKR_TOKEN_NOT_RECOGNIZED;
  }

  memcpy(pInfo, &slots[slotID].token->info, sizeof(CK_TOKEN_INFO));

  // Overwrite values that are application specific
  pInfo->ulMaxSessionCount = CK_UNAVAILABLE_INFORMATION; // TODO: should this be 1?
  pInfo->ulSessionCount = CK_UNAVAILABLE_INFORMATION; // number of sessions that this application currently has open with the token

  pInfo->ulMaxRwSessionCount = CK_UNAVAILABLE_INFORMATION; // maximum number of read/write sessions that can be opened with the token at one time by a single TODO: should this be 1?

  pInfo->ulRwSessionCount =  CK_UNAVAILABLE_INFORMATION; // number of read/write sessions that this application currently has open with the token
  pInfo->ulMaxPinLen = PIV_MAX_PIN_LEN; // maximum length in bytes of the PIN
  pInfo->ulMinPinLen = PIV_MIN_PIN_LEN; // minimum length in bytes of the PIN
  pInfo->ulTotalPublicMemory = CK_UNAVAILABLE_INFORMATION;
  pInfo->ulFreePublicMemory = CK_UNAVAILABLE_INFORMATION;
  pInfo->ulTotalPrivateMemory = CK_UNAVAILABLE_INFORMATION;
  pInfo->ulFreePrivateMemory = CK_UNAVAILABLE_INFORMATION;

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_WaitForSlotEvent)(
  CK_FLAGS flags,
  CK_SLOT_ID_PTR pSlot,
  CK_VOID_PTR pReserved
)
{
  DIN;
  DBG(("TODO!!!"));
  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_GetMechanismList)(
  CK_SLOT_ID slotID,
  CK_MECHANISM_TYPE_PTR pMechanismList,
  CK_ULONG_PTR pulCount
)
{
  DIN;
  token_vendor_t token;
  CK_ULONG count;

  if (piv_state == NULL) {
    DBG(("libykpiv is not initialized or already finalized"));
    return CKR_CRYPTOKI_NOT_INITIALIZED;
  }

  if (slotID >= n_slots) {
    DBG(("Invalid slot ID %lu", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  if (pulCount == NULL_PTR) {
    DBG(("Wrong/Missing parameter"));
    return CKR_ARGUMENTS_BAD;
  }

  if (slots[slotID].vid == UNKNOWN) {
    DBG(("Slot %lu is tokenless/unsupported", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  // TODO: check more return values

  token = get_token_vendor(slots[slotID].vid);

  if (token.get_token_mechanisms_num(&count) != CKR_OK)
    return CKR_FUNCTION_FAILED;

  if (pMechanismList == NULL_PTR) {
    *pulCount = count;
    DBG(("Found %lu mechanisms", *pulCount));
    DOUT;
    return CKR_OK;
  }

  if (*pulCount < count) {
    DBG(("Buffer too small: needed %lu, provided %lu", count, *pulCount));
    return CKR_BUFFER_TOO_SMALL;
  }

  if (token.get_token_mechanism_list(pMechanismList, *pulCount) != CKR_OK) {
    DBG(("Unable to retrieve mechanism list"));
    return CKR_FUNCTION_FAILED;
  }

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_GetMechanismInfo)(
  CK_SLOT_ID slotID,
  CK_MECHANISM_TYPE type,
  CK_MECHANISM_INFO_PTR pInfo
)
{
  DIN;
  token_vendor_t token;

  if (piv_state == NULL) {
    DBG(("libykpiv is not initialized or already finalized"));
    return CKR_CRYPTOKI_NOT_INITIALIZED;
  }

  if (slotID >= n_slots) {
    DBG(("Invalid slot ID %lu", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  if (pInfo == NULL_PTR) {
    DBG(("Wrong/Missing parameter"));
    return CKR_ARGUMENTS_BAD;
  }

  if (slots[slotID].vid == UNKNOWN) {
    DBG(("Slot %lu is tokenless/unsupported", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  token = get_token_vendor(slots[slotID].vid);

  if (token.get_token_mechanism_info(type, pInfo) != CKR_OK) {
    DBG(("Unable to retrieve mechanism information"));
    return CKR_MECHANISM_INVALID;
  }

  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_InitToken)(
  CK_SLOT_ID slotID,
  CK_UTF8CHAR_PTR pPin,
  CK_ULONG ulPinLen,
  CK_UTF8CHAR_PTR pLabel
)
{
  DIN;
  DBG(("Token initialization unsupported"));
  DOUT;
  return CKR_FUNCTION_FAILED;
}

CK_DEFINE_FUNCTION(CK_RV, C_InitPIN)(
  CK_SESSION_HANDLE hSession,
  CK_UTF8CHAR_PTR pPin,
  CK_ULONG ulPinLen
)
{
  DIN;
  DBG(("PIN initialization unsupported"));
  DOUT;
  return CKR_FUNCTION_FAILED;
}

CK_DEFINE_FUNCTION(CK_RV, C_SetPIN)(
  CK_SESSION_HANDLE hSession,
  CK_UTF8CHAR_PTR pOldPin,
  CK_ULONG ulOldLen,
  CK_UTF8CHAR_PTR pNewPin,
  CK_ULONG ulNewLen
)
{
  DIN;
  DBG(("TODO!!!"));
  DOUT;
  return CKR_OK;
}

CK_DEFINE_FUNCTION(CK_RV, C_OpenSession)(
  CK_SLOT_ID slotID,
  CK_FLAGS flags,
  CK_VOID_PTR pApplication,
  CK_NOTIFY Notify,
  CK_SESSION_HANDLE_PTR phSession
)
{
  DIN; // TODO: pApplication and Notify

  token_vendor_t token;
  CK_RV          rv;
  piv_obj_id_t   *cert_ids;
  CK_ULONG       i;
  CK_BYTE        cert_data[2100];  // Max cert value for ykpiv
  CK_ULONG       cert_len = sizeof(cert_data);

  if (piv_state == NULL) {
    DBG(("libykpiv is not initialized or already finalized"));
    return CKR_CRYPTOKI_NOT_INITIALIZED;
  }

  if (slotID >= n_slots) {
    DBG(("Invalid slot ID %lu", slotID));
    return CKR_SLOT_ID_INVALID;
  }

  if (phSession == NULL_PTR) {
    DBG(("Wrong/Missing parameter"));
    return CKR_ARGUMENTS_BAD;
  }

  if (slots[slotID].vid == UNKNOWN) {
    DBG(("No support for slot %lu", slotID));
    return CKR_TOKEN_NOT_RECOGNIZED;
  }

  if (slots[slotID].token->vid == UNKNOWN) {
    DBG(("No support for token in slot %lu", slotID));
    return CKR_TOKEN_NOT_RECOGNIZED;
  }

  if (!has_token(slots + slotID)) {
    DBG(("Slot %lu has no token inserted", slotID));
    return CKR_TOKEN_NOT_PRESENT;
  }

  if (session.handle != CK_INVALID_HANDLE) {
    DBG(("A session with this or another token already exists"));
    return CKR_SESSION_COUNT;
  }

  if ((flags & CKF_SERIAL_SESSION) == 0) {
    DBG(("Open session called without CKF_SERIAL_SESSION set")); // Reuired by specs
    return CKR_SESSION_PARALLEL_NOT_SUPPORTED;
  }

  token = get_token_vendor(slots[slotID].token->vid);

  // Store the slot
  session.slot = slots + slotID;
  //session.slot->info.slotID = slotID; // Redundant but required in CK_SESSION_INFO

  // Store session state
  session.info.state = 0;

  if ((flags & CKF_RW_SESSION)) {
    // R/W Session
    session.info.state = CKS_RW_PUBLIC_SESSION; // Nobody has logged in, default RO session
  }
  else {
    // R/O Session
    session.info.state = CKS_RO_PUBLIC_SESSION; // Nobody has logged in, default RW session
  }

  session.info.flags = flags;
  session.info.ulDeviceError = 0;

  // Get the number of token objects
  rv = token.get_token_objects_num(piv_state, &session.slot->token->n_objects, &session.slot->token->n_certs);
  if (rv != CKR_OK) {
    DBG(("Unable to retrieve number of token objects"));
    return rv;
  }

  // Get memory for the objects
  session.slot->token->objects = malloc(sizeof(piv_obj_id_t) * session.slot->token->n_objects);
  if (session.slot->token->objects == NULL) {
    DBG(("Unable to allocate memory for token object ids"));
    return CKR_HOST_MEMORY;
  }

  // Get memory for the certificates
  cert_ids = malloc(sizeof(piv_obj_id_t) * session.slot->token->n_certs);
  if (cert_ids == NULL) {
    DBG(("Unable to allocate memory for token certificate ids"));
    return CKR_HOST_MEMORY;
  }

  // Save a list of all the available objects in the token
  rv = token.get_token_object_list(piv_state, session.slot->token->objects, session.slot->token->n_objects);
  if (rv != CKR_OK) {
    DBG(("Unable to retrieve token objects"));
    goto failure;
  }

  // Get a list of object ids for available certificates object from the session
  rv = get_available_certificate_ids(&session, cert_ids, session.slot->token->n_certs);
  if (rv != CKR_OK) {
    DBG(("Unable to retrieve certificate ids from the session"));
    goto failure;
  }

  // Get the actual certificate data from the token and store it as an X509 object
  for (i = 0; i < session.slot->token->n_certs; i++) {
    cert_len = sizeof(cert_data);
    rv = token.get_token_raw_certificate(piv_state, cert_ids[i], cert_data, &cert_len);
    if (rv != CKR_OK) {
      DBG(("Unable to get certificate data from token"));
      goto failure;
    }

    rv = store_cert(cert_ids[i], cert_data, cert_len);
    if (rv != CKR_OK) {
      DBG(("Unable to store certificate data"));
      goto failure;
    }
  }

  free(cert_ids);
  cert_ids = NULL;

  session.handle = YKCS11_SESSION_ID;
  // TODO: KEEP TRACK OF THE APPLICATION (possble to steal a session?)

  *phSession = session.handle;

  DOUT;
  return CKR_OK;

{'fraction_non_alphanumeric': 0.11494793173271622, 'fraction_numerical': 0.005821521550477293, 'mean_word_length': 3.8436077057793345, 'pattern_counts': {'":': 0, '<': 20, '<?xml version=': 0, '>': 107, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1824280', 'n_tokens_mistral': 11140, 'n_tokens_neox': 10441, 'n_words': 2380}