aboutsummaryrefslogblamecommitdiff
path: root/ks_volatile.c
blob: 363441ad553ee22fd7a2f8916538f2f39ddcae3a (plain) (tree)
1
2
3
4
5
6
7
8
9








                                                                         
                                                             



























                                                                           
                   
                   
 


                         

                                     

                                                             
      
 

                                               

      
  


                                                                     

   
                


                              

                                
                                
                                       
                                                                                         
           
 

                            
                              
       
 
  



                                                                     

   


                                                                      
                                                                 
       
 





                                                                      
                                
 
                        

                                           
 
                     

 
















                                                                      




                                                                            



                                                                  

 










                                                                       
                                                 


                                        
 
                  

                                


                                 
 





                                                                                                  
 





                                   









                                                                                          





                                                          
                                                    
                              
 
                                           

 
                                                                                          
 



                           
                                               


                                                                                     
 
                      
 
                                                               


                                                     
 

                  





                                                                             


                

                                                                         
 

                                       
                
 
 

                                                  


                













                                                                
                                                  
                                                                            
 
                                                                                                    

                                   
                            
                           
             
 





                                    
 
                                                                                         
              
 


                          
 

                            


                          

                                   
 
                                                                    



                                                                               


                         
                                                                                 
 

                  
             





                                                                                
                                 

                                   
                            
                           
             
 





                                    
 
                                                                                          
              
 
                                               
 



                                                                                   
 
                        

                         
 






                                     





                          
                                                                      


                                                                                    

   


                  
 
 
                                          
                                                   
 
                                 

                                   
                            
                           
             
 





                                    
 
                                                                                          
              
 



                                                                                                   

                                                                                            
              
 
                                                         
 


                  

 
                                         

                                                         

                                                         
                                                       
                                                        
                                                                   



                                                          
                                                                   
 
                                                                 







                                                                    
                           

             

                

                  




                                                                     
              
 
                                                               











                                                                   


                                                       
                                                                         








                                                   
                                                        


                                                                                        
                  
 
                                                             

                                                                      
                                                  











                                                                           
                                                     


                  




                  

 

                                                           
                                                                            





                                                                              
                           

             





































                                                                                                        
 
   
 


                  



                                                           
                                                                      




                                                                                
                                


                                   
                           

             
                
 




                                      
 

                                                                                            
 
                                                 
 



                                                                                     
 
                                                                              
 


                                                                                      
 
                                      
 










                                                                          
 








                                                                               
 






                                                    
 

   


                  

 


























                                                                                                            
                                                    








                                               

                                            

   
                                         
 




                        
/*
 * ks_volatile.c
 * -------------
 * Keystore implementation in normal volatile internal memory.
 *
 * NB: This is only suitable for cases where you do not want the keystore
 *     to survive library exit, eg, for storing PKCS #11 session keys.
 *
 * Authors: Rob Austein
 * Copyright (c) 2015-2017, NORDUnet A/S All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * - Neither the name of the NORDUnet nor the names of its contributors may
 *   be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <string.h>
#include <assert.h>

#include "hal.h"
#include "hal_internal.h"

#define KEK_LENGTH (bitsToBytes(256))

#ifndef STATIC_KS_VOLATILE_SLOTS
#define STATIC_KS_VOLATILE_SLOTS HAL_STATIC_PKEY_STATE_BLOCKS
#endif

#ifndef STATIC_KS_VOLATILE_ATTRIBUTE_SPACE
#define STATIC_KS_VOLATILE_ATTRIBUTE_SPACE 4096
#endif

/*
 * In-memory keystore database.  This should also be usable for
 * mmap(), if and when we get around to rewriting that driver (and in
 * which case this driver probably ought to be renamed ks_memory).
 */

typedef struct {
  hal_key_type_t        type;
  hal_curve_name_t      curve;
  hal_key_flags_t       flags;
  hal_client_handle_t   client;
  hal_session_handle_t  session;
  size_t                der_len;
  unsigned              attributes_len;
  uint8_t               der[HAL_KS_WRAPPED_KEYSIZE + STATIC_KS_VOLATILE_ATTRIBUTE_SPACE];
} ks_key_t;

typedef struct {
  hal_ks_index_t        ksi;
  ks_key_t              *keys;
} db_t;

/*
 * "Subclass" (well, what one can do in C) of hal_ks_t.  This is
 * separate from db_t primarily to simplify things like rewriting the
 * old ks_mmap driver to piggy-back on the ks_volatile driver: we
 * wouldn't want the hal_ks_t into the mmap()ed file.
 */

typedef struct {
  hal_ks_t ks;              /* Must be first */
  db_t *db;                 /* Which memory-based keystore database */
  int per_session;          /* Whether objects are per-session */
} ks_t;

/*
 * If we also supported mmap, there would be a separate definition for
 * HAL_KS_MMAP_SLOTS above, and the bulk of the code would be under a
 * conditional testing whether either HAL_KS_*_SLOTS were nonzero.
 */

#if STATIC_KS_VOLATILE_SLOTS > 0

static ks_t volatile_ks;

static inline ks_t *ks_to_ksv(hal_ks_t *ks)
{
  return (ks_t *) ks;
}

/*
 * Check whether the current session can see a particular key.  One
 * might expect this to be based on whether the session matches, and
 * indeed it would be in a sane world, but in the world of PKCS #11,
 * keys belong to sessions, are visible to other sessions, and may
 * even be modifiable by other sessions, but softly and silently
 * vanish away when the original creating session is destroyed.
 *
 * In our terms, this means that visibility of session objects is
 * determined only by the client handle, so taking the session handle
 * as an argument here isn't really necessary, but we've flipflopped
 * on that enough times that at least for now I'd prefer to leave the
 * session handle here and not have to revise all the RPC calls again.
 * Remove it at some later date and redo the RPC calls if we manage to
 * avoid revising this yet again.
 */

static inline int key_visible_to_session(const ks_t * const ksv,
                                         const hal_client_handle_t client,
                                         const hal_session_handle_t session,
                                         const ks_key_t * const k)
{
  return (!ksv->per_session ||
          client.handle == HAL_HANDLE_NONE ||
          k->client.handle  == client.handle ||
          hal_rpc_is_logged_in(client, HAL_USER_WHEEL) == HAL_OK);
}

static inline void *gnaw(uint8_t **mem, size_t *len, const size_t size)
{
  if (mem == NULL || *mem == NULL || len == NULL || size > *len)
    return NULL;
  void *ret = *mem;
  *mem += size;
  *len -= size;
  return ret;
}

static hal_error_t ks_init(const hal_ks_driver_t * const driver,
                           const int per_session,
                           ks_t *ksv,
                           uint8_t *mem,
                           size_t len)
{
  if (ksv == NULL)
    return HAL_ERROR_IMPOSSIBLE;

  if (mem != NULL) {
    memset(ksv, 0, sizeof(*ksv));
    memset(mem, 0, len);

    ksv->db            = gnaw(&mem, &len, sizeof(*ksv->db));
    ksv->db->ksi.index = gnaw(&mem, &len, sizeof(*ksv->db->ksi.index) * STATIC_KS_VOLATILE_SLOTS);
    ksv->db->ksi.names = gnaw(&mem, &len, sizeof(*ksv->db->ksi.names) * STATIC_KS_VOLATILE_SLOTS);
    ksv->db->keys      = gnaw(&mem, &len, sizeof(*ksv->db->keys)      * STATIC_KS_VOLATILE_SLOTS);
    ksv->db->ksi.size  = STATIC_KS_VOLATILE_SLOTS;
  }

  if (ksv->db            == NULL ||
      ksv->db->ksi.index == NULL ||
      ksv->db->ksi.names == NULL ||
      ksv->db->keys      == NULL)
    return HAL_ERROR_IMPOSSIBLE;

  if (mem == NULL) {
    memset(ksv->db->ksi.index, 0, sizeof(*ksv->db->ksi.index) * STATIC_KS_VOLATILE_SLOTS);
    memset(ksv->db->ksi.names, 0, sizeof(*ksv->db->ksi.names) * STATIC_KS_VOLATILE_SLOTS);
    memset(ksv->db->keys,      0, sizeof(*ksv->db->keys)      * STATIC_KS_VOLATILE_SLOTS);
  }

  ksv->ks.driver     = driver;
  ksv->per_session   = per_session;
  ksv->db->ksi.used  = 0;

  /*
   * Set up keystore with empty index and full free list.
   * Since this driver doesn't care about wear leveling,
   * just populate the free list in block numerical order.
   */

  for (int i = 0; i < STATIC_KS_VOLATILE_SLOTS; i++)
    ksv->db->ksi.index[i] = i;

  return hal_ks_index_setup(&ksv->db->ksi);
}

static hal_error_t ks_volatile_init(const hal_ks_driver_t * const driver, const int alloc)
{
  hal_error_t err = HAL_OK;

  hal_ks_lock();

  const size_t len = (sizeof(*volatile_ks.db) +
                      sizeof(*volatile_ks.db->ksi.index) * STATIC_KS_VOLATILE_SLOTS +
                      sizeof(*volatile_ks.db->ksi.names) * STATIC_KS_VOLATILE_SLOTS +
                      sizeof(*volatile_ks.db->keys)      * STATIC_KS_VOLATILE_SLOTS);

  uint8_t *mem = NULL;

  if (alloc && (mem = hal_allocate_static_memory(len)) == NULL)
    err = HAL_ERROR_ALLOCATION_FAILURE;
  else
    err = ks_init(driver, 1, &volatile_ks, mem, len);

  hal_ks_unlock();
  return err;
}

static hal_error_t ks_volatile_shutdown(const hal_ks_driver_t * const driver)
{
  if (volatile_ks.ks.driver != driver)
    return HAL_ERROR_KEYSTORE_ACCESS;
  return HAL_OK;
}

static hal_error_t ks_volatile_open(const hal_ks_driver_t * const driver,
                                    hal_ks_t **ks)
{
  assert(driver != NULL && ks != NULL);
  *ks = &volatile_ks.ks;
  return HAL_OK;
}

static hal_error_t ks_volatile_close(hal_ks_t *ks)
{
  return HAL_OK;
}

static inline int acceptable_key_type(const hal_key_type_t type)
{
  switch (type) {
  case HAL_KEY_TYPE_RSA_PRIVATE:
  case HAL_KEY_TYPE_EC_PRIVATE:
  case HAL_KEY_TYPE_RSA_PUBLIC:
  case HAL_KEY_TYPE_EC_PUBLIC:
    return 1;
  default:
    return 0;
  }
}

static hal_error_t ks_store(hal_ks_t *ks,
                            hal_pkey_slot_t *slot,
                            const uint8_t * const der, const size_t der_len)
{
  if (ks == NULL || slot == NULL || der == NULL || der_len == 0 || !acceptable_key_type(slot->type))
    return HAL_ERROR_BAD_ARGUMENTS;

  ks_t *ksv = ks_to_ksv(ks);
  hal_error_t err = HAL_OK;
  unsigned b;

  hal_ks_lock();

  if (ksv->db == NULL) {
    err = HAL_ERROR_KEYSTORE_ACCESS;
    goto done;
  }

  if ((err = hal_ks_index_add(&ksv->db->ksi, &slot->name, 0, &b, &slot->hint)) != HAL_OK)
    goto done;

  uint8_t kek[KEK_LENGTH];
  size_t kek_len;
  ks_key_t k;

  memset(&k, 0, sizeof(k));
  k.der_len = sizeof(k.der);
  k.type    = slot->type;
  k.curve   = slot->curve;
  k.flags   = slot->flags;
  k.client  = slot->client_handle;
  k.session = slot->session_handle;

  if ((err = hal_mkm_get_kek(kek, &kek_len, sizeof(kek))) == HAL_OK)
    err = hal_aes_keywrap(NULL, kek, kek_len, der, der_len, k.der, &k.der_len);

  memset(kek, 0, sizeof(kek));

  if (err == HAL_OK)
    ksv->db->keys[b] = k;
  else
    (void) hal_ks_index_delete(&ksv->db->ksi, &slot->name, 0, NULL, &slot->hint);

 done:
  hal_ks_unlock();
  return err;
}

static hal_error_t ks_fetch(hal_ks_t *ks,
                            hal_pkey_slot_t *slot,
                            uint8_t *der, size_t *der_len, const size_t der_max)
{
  if (ks == NULL || slot == NULL)
    return HAL_ERROR_BAD_ARGUMENTS;

  ks_t *ksv = ks_to_ksv(ks);
  hal_error_t err = HAL_OK;
  unsigned b;

  hal_ks_lock();

  if (ksv->db == NULL) {
    err = HAL_ERROR_KEYSTORE_ACCESS;
    goto done;
  }

  if ((err = hal_ks_index_find(&ksv->db->ksi, &slot->name, 0, &b, &slot->hint)) != HAL_OK)
    goto done;

  const ks_key_t * const k = &ksv->db->keys[b];

  if (!key_visible_to_session(ksv, slot->client_handle, slot->session_handle, k)) {
    err = HAL_ERROR_KEY_NOT_FOUND;
    goto done;
  }

  slot->type  = k->type;
  slot->curve = k->curve;
  slot->flags = k->flags;

  if (der == NULL && der_len != NULL)
    *der_len = k->der_len;

  if (der != NULL) {

    uint8_t kek[KEK_LENGTH];
    size_t kek_len, der_len_;

    if (der_len == NULL)
      der_len = &der_len_;

    *der_len = der_max;

    if ((err = hal_mkm_get_kek(kek, &kek_len, sizeof(kek))) == HAL_OK)
      err = hal_aes_keyunwrap(NULL, kek, kek_len, k->der, k->der_len, der, der_len);

    memset(kek, 0, sizeof(kek));
  }

 done:
  hal_ks_unlock();
  return err;
}

static hal_error_t ks_delete(hal_ks_t *ks,
                             hal_pkey_slot_t *slot)
{
  if (ks == NULL || slot == NULL)
    return HAL_ERROR_BAD_ARGUMENTS;

  ks_t *ksv = ks_to_ksv(ks);
  hal_error_t err = HAL_OK;
  unsigned b;

  hal_ks_lock();

  if (ksv->db == NULL) {
    err = HAL_ERROR_KEYSTORE_ACCESS;
    goto done;
  }

  if ((err = hal_ks_index_find(&ksv->db->ksi, &slot->name, 0, &b, &slot->hint)) != HAL_OK)
    goto done;

  if (!key_visible_to_session(ksv, slot->client_handle, slot->session_handle, &ksv->db->keys[b])) {
    err = HAL_ERROR_KEY_NOT_FOUND;
    goto done;
  }

  if ((err = hal_ks_index_delete(&ksv->db->ksi, &slot->name, 0, &b, &slot->hint)) != HAL_OK)
    goto done;

  memset(&ksv->db->keys[b], 0, sizeof(ksv->db->keys[b]));

 done:
  hal_ks_unlock();
  return err;
}

static hal_error_t ks_match(hal_ks_t *ks,
                            hal_client_handle_t client,
                            hal_session_handle_t session,
                            const hal_key_type_t type,
                            const hal_curve_name_t curve,
                            const hal_key_flags_t mask,
                            const hal_key_flags_t flags,
                            const hal_pkey_attribute_t *attributes,
                            const unsigned attributes_len,
                            hal_uuid_t *result,
                            unsigned *result_len,
                            const unsigned result_max,
                            const hal_uuid_t * const previous_uuid)
{
  if (ks == NULL || (attributes == NULL && attributes_len > 0) ||
      result == NULL || result_len == NULL || previous_uuid == NULL)
    return HAL_ERROR_BAD_ARGUMENTS;

  ks_t *ksv = ks_to_ksv(ks);

  if (ksv->db == NULL)
    return HAL_ERROR_KEYSTORE_ACCESS;

  hal_error_t err = HAL_OK;
  int i = -1;

  hal_ks_lock();

  *result_len = 0;

  err = hal_ks_index_find(&ksv->db->ksi, previous_uuid, 0, NULL, &i);

  if (err == HAL_ERROR_KEY_NOT_FOUND)
    i--;
  else if (err != HAL_OK)
    goto done;

  while (*result_len < result_max && ++i < ksv->db->ksi.used) {

    unsigned b = ksv->db->ksi.index[i];

    if (ksv->db->ksi.names[b].chunk > 0)
      continue;

    if (type != HAL_KEY_TYPE_NONE && type != ksv->db->keys[b].type)
      continue;

    if (curve != HAL_CURVE_NONE && curve != ksv->db->keys[b].curve)
      continue;

    if (((flags ^ ksv->db->keys[b].flags) & mask) != 0)
      continue;

    if (!key_visible_to_session(ksv, client, session, &ksv->db->keys[b]))
      continue;

    if (attributes_len > 0) {
      const ks_key_t * const k = &ksv->db->keys[b];
      int ok = 1;

      if (k->attributes_len == 0)
        continue;

      hal_pkey_attribute_t key_attrs[k->attributes_len];

      if ((err = hal_ks_attribute_scan(k->der + k->der_len, sizeof(k->der) - k->der_len,
                                       key_attrs, k->attributes_len, NULL)) != HAL_OK)
        goto done;

      for (const hal_pkey_attribute_t *required = attributes;
           ok && required < attributes + attributes_len; required++) {

        hal_pkey_attribute_t *present = key_attrs;
        while (ok && present->type != required->type)
          ok = ++present < key_attrs + k->attributes_len;

        if (ok)
          ok = (present->length == required->length &&
                !memcmp(present->value, required->value, present->length));
      }

      if (!ok)
        continue;
    }

    result[*result_len] = ksv->db->ksi.names[b].name;
    ++*result_len;
  }

  err = HAL_OK;

 done:
  hal_ks_unlock();
  return err;
}

static hal_error_t ks_set_attributes(hal_ks_t *ks,
                                     hal_pkey_slot_t *slot,
                                     const hal_pkey_attribute_t *attributes,
                                     const unsigned attributes_len)
{
  if (ks == NULL || slot == NULL || attributes == NULL || attributes_len == 0)
    return HAL_ERROR_BAD_ARGUMENTS;

  ks_t *ksv = ks_to_ksv(ks);
  hal_error_t err = HAL_OK;
  unsigned b;

  hal_ks_lock();

  {
    if (ksv->db == NULL) {
      err = HAL_ERROR_KEYSTORE_ACCESS;
      goto done;
    }

    if ((err = hal_ks_index_find(&ksv->db->ksi, &slot->name, 0, &b, &slot->hint)) != HAL_OK)
      goto done;

    ks_key_t * const k = &ksv->db->keys[b];

    if (!key_visible_to_session(ksv, slot->client_handle, slot->session_handle, k)) {
      err = HAL_ERROR_KEY_NOT_FOUND;
      goto done;
    }

    hal_pkey_attribute_t attrs[k->attributes_len + attributes_len];
    uint8_t *bytes = k->der + k->der_len;
    size_t bytes_len = sizeof(k->der) - k->der_len;
    size_t total_len;

    if ((err = hal_ks_attribute_scan(bytes, bytes_len, attrs, k->attributes_len, &total_len)) != HAL_OK)
      goto done;

    for (const hal_pkey_attribute_t *a = attributes; a < attributes + attributes_len; a++) {
      if (a->length == HAL_PKEY_ATTRIBUTE_NIL)
        err =  hal_ks_attribute_delete(bytes, bytes_len, attrs, &k->attributes_len, &total_len,
                                       a->type);
      else
        err =  hal_ks_attribute_insert(bytes, bytes_len, attrs, &k->attributes_len, &total_len,
                                       a->type, a->value, a->length);
      if (err != HAL_OK)
        goto done;
    }

    err = HAL_OK;

  }

 done:
  hal_ks_unlock();
  return err;
}

static hal_error_t ks_get_attributes(hal_ks_t *ks,
                                     hal_pkey_slot_t *slot,
                                     hal_pkey_attribute_t *attributes,
                                     const unsigned attributes_len,
                                     uint8_t *attributes_buffer,
                                     const size_t attributes_buffer_len)
{
  if (ks == NULL || slot == NULL || attributes == NULL || attributes_len == 0 ||
      attributes_buffer == NULL)
    return HAL_ERROR_BAD_ARGUMENTS;

  ks_t *ksv = ks_to_ksv(ks);
  hal_error_t err = HAL_OK;
  unsigned b;

  hal_ks_lock();

  {
    if (ksv->db == NULL) {
      err = HAL_ERROR_KEYSTORE_ACCESS;
      goto done;
    }

    if ((err = hal_ks_index_find(&ksv->db->ksi, &slot->name, 0, &b, &slot->hint)) != HAL_OK)
      goto done;

    const ks_key_t * const k = &ksv->db->keys[b];

    if (!key_visible_to_session(ksv, slot->client_handle, slot->session_handle, k)) {
      err = HAL_ERROR_KEY_NOT_FOUND;
      goto done;
    }

    hal_pkey_attribute_t attrs[k->attributes_len > 0 ? k->attributes_len : 1];

    if ((err = hal_ks_attribute_scan(k->der + k->der_len, sizeof(k->der) - k->der_len,
                                     attrs, k->attributes_len, NULL)) != HAL_OK)
      goto done;

    uint8_t *abuf = attributes_buffer;

    for (int i = 0; i < attributes_len; i++) {
      int j = 0;
      while (j < k->attributes_len && attrs[j].type != attributes[i].type)
        j++;
      const int found = j < k->attributes_len;

      if (attributes_buffer_len == 0) {
        attributes[i].value  = NULL;
        attributes[i].length = found ? attrs[j].length : 0;
        continue;
      }

      if (!found) {
        err = HAL_ERROR_ATTRIBUTE_NOT_FOUND;
        goto done;
      }

      if (attrs[j].length > attributes_buffer + attributes_buffer_len - abuf) {
        err = HAL_ERROR_RESULT_TOO_LONG;
        goto done;
      }

      memcpy(abuf, attrs[j].value, attrs[j].length);
      attributes[i].value  = abuf;
      attributes[i].length = attrs[j].length;
      abuf += attrs[j].length;
    }

    err = HAL_OK;

  }

 done:
  hal_ks_unlock();
  return err;
}

static void ks_client_cleanup(hal_ks_t *ks,
                              hal_client_handle_t client)
{
  if (ks == NULL || client.handle = HAL_HANDLE_NONE)
    return HAL_ERROR_BAD_ARGUMENTS;

  ks_t *ksv = ks_to_ksv(ks);
  hal_error_t err = HAL_OK;

  hal_ks_lock();

  for (int i = 0; i < ksv->db->ksi.used; i++) {
    unsigned b = ksv->db->ksi.index[i];
    if (ksv->db->keys[b].client.handle == client.handle) {
      int hint = i;
      if ((err = hal_ks_index_delete(&ksv->db->ksi, &ksv->db->ksi.names[b].name, 0, NULL, &hint)) != HAL_OK)
        goto done;
      memset(&ksv->db->keys[b], 0, sizeof(ksv->db->keys[b]));
      i--;
    }
  }

 done:
  hal_ks_unlock();
  return err;
}

const hal_ks_driver_t hal_ks_volatile_driver[1] = {{
  .init                 = ks_volatile_init,
  .shutdown             = ks_volatile_shutdown,
  .open                 = ks_volatile_open,
  .close                = ks_volatile_close,
  .store                = ks_store,
  .fetch                = ks_fetch,
  .delete               = ks_delete,
  .match                = ks_match,
  .set_attributes       = ks_set_attributes,
  .get_attributes       = ks_get_attributes,
  .client_cleanup	= ks_client_cleanup
}};

#endif /* STATIC_KS_VOLATILE_SLOTS > 0 */

/*
 * Local variables:
 * indent-tabs-mode: nil
 * End:
 */