/* libp11, a simple layer on to of PKCS#11 API * Copyright (C) 2005 Olaf Kirch * Copyright (C) 2016-2018 MichaƂ Trojnara * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "libp11-int.h" #include #include #include #ifdef _WIN32 #define strncasecmp strnicmp #endif /* The maximum length of PIN */ #define MAX_PIN_LENGTH 32 static int pkcs11_find_keys(PKCS11_TOKEN *, unsigned int); static int pkcs11_next_key(PKCS11_CTX *ctx, PKCS11_TOKEN *token, CK_SESSION_HANDLE session, CK_OBJECT_CLASS type); static int pkcs11_init_key(PKCS11_CTX *ctx, PKCS11_TOKEN *token, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE o, CK_OBJECT_CLASS type, PKCS11_KEY **); static int pkcs11_store_key(PKCS11_TOKEN *, EVP_PKEY *, unsigned int, char *, unsigned char *, size_t, PKCS11_KEY **); /* Set UI method to allow retrieving CKU_CONTEXT_SPECIFIC PINs interactively */ int pkcs11_set_ui_method(PKCS11_CTX *ctx, UI_METHOD *ui_method, void *ui_user_data) { PKCS11_CTX_private *cpriv = PRIVCTX(ctx); if (cpriv == NULL) return -1; cpriv->ui_method = ui_method; cpriv->ui_user_data = ui_user_data; return 0; } /* * Find private key matching a certificate */ PKCS11_KEY *pkcs11_find_key(PKCS11_CERT *cert) { PKCS11_CERT_private *cpriv = PRIVCERT(cert); PKCS11_KEY *keys; unsigned int n, count; if (pkcs11_enumerate_keys(CERT2TOKEN(cert), CKO_PRIVATE_KEY, &keys, &count)) return NULL; for (n = 0; n < count; n++) { PKCS11_KEY_private *kpriv = PRIVKEY(&keys[n]); if (kpriv && cpriv->id_len == kpriv->id_len && !memcmp(cpriv->id, kpriv->id, cpriv->id_len)) return &keys[n]; } return NULL; } /* * Find key matching a key of the other type (public vs private) */ PKCS11_KEY *pkcs11_find_key_from_key(PKCS11_KEY *keyin) { PKCS11_KEY_private *kinpriv = PRIVKEY(keyin); PKCS11_KEY *keys; unsigned int n, count, type = keyin->isPrivate ? CKO_PUBLIC_KEY : CKO_PRIVATE_KEY; if (pkcs11_enumerate_keys(KEY2TOKEN(keyin), type, &keys, &count)) return NULL; for (n = 0; n < count; n++) { PKCS11_KEY_private *kpriv = PRIVKEY(&keys[n]); if (kpriv && kinpriv->id_len == kpriv->id_len && !memcmp(kinpriv->id, kpriv->id, kinpriv->id_len)) return &keys[n]; } return NULL; } /* * Reopens the object associated with the key */ int pkcs11_reload_key(PKCS11_KEY *key) { PKCS11_KEY_private *kpriv = PRIVKEY(key); PKCS11_SLOT *slot = KEY2SLOT(key); PKCS11_SLOT_private *spriv = PRIVSLOT(slot); PKCS11_CTX *ctx = SLOT2CTX(slot); CK_OBJECT_CLASS key_search_class = key->isPrivate ? CKO_PRIVATE_KEY : CKO_PUBLIC_KEY; CK_ATTRIBUTE key_search_attrs[2] = { {CKA_CLASS, &key_search_class, sizeof(key_search_class)}, {CKA_ID, kpriv->id, kpriv->id_len}, }; CK_ULONG count; int rv; /* this is already covered with a per-ctx lock */ rv = CRYPTOKI_call(ctx, C_FindObjectsInit(spriv->session, key_search_attrs, 2)); CRYPTOKI_checkerr(CKR_F_PKCS11_RELOAD_KEY, rv); rv = CRYPTOKI_call(ctx, C_FindObjects(spriv->session, &kpriv->object, 1, &count)); CRYPTOKI_checkerr(CKR_F_PKCS11_RELOAD_KEY, rv); CRYPTOKI_call(ctx, C_FindObjectsFinal(spriv->session)); return 0; } /** * Generate a keyPair directly on token */ int pkcs11_generate_key(PKCS11_TOKEN *token, int algorithm, unsigned int bits, char *label, unsigned char* id, size_t id_len) { PKCS11_SLOT *slot = TOKEN2SLOT(token); PKCS11_CTX *ctx = TOKEN2CTX(token); PKCS11_SLOT_private *spriv = PRIVSLOT(slot); CK_ATTRIBUTE pubkey_attrs[32]; CK_ATTRIBUTE privkey_attrs[32]; unsigned int n_pub = 0, n_priv = 0; CK_MECHANISM mechanism = { CKM_RSA_PKCS_KEY_PAIR_GEN, NULL_PTR, 0 }; CK_BYTE public_exponent[] = { 1, 0, 1 }; CK_OBJECT_HANDLE pub_key_obj, priv_key_obj; int rv; (void)algorithm; /* squash the unused parameter warning */ /* make sure we have a session */ if (!spriv->haveSession && PKCS11_open_session(slot, 1)) return -1; /* pubkey attributes */ pkcs11_addattr(pubkey_attrs + n_pub++, CKA_ID, id, id_len); if (label) pkcs11_addattr_s(pubkey_attrs + n_pub++, CKA_LABEL, label); pkcs11_addattr_bool(pubkey_attrs + n_pub++, CKA_TOKEN, TRUE); pkcs11_addattr_bool(pubkey_attrs + n_pub++, CKA_ENCRYPT, TRUE); pkcs11_addattr_bool(pubkey_attrs + n_pub++, CKA_VERIFY, TRUE); pkcs11_addattr_bool(pubkey_attrs + n_pub++, CKA_WRAP, TRUE); pkcs11_addattr_int(pubkey_attrs + n_pub++, CKA_MODULUS_BITS, bits); pkcs11_addattr(pubkey_attrs + n_pub++, CKA_PUBLIC_EXPONENT, public_exponent, 3); /* privkey attributes */ pkcs11_addattr(privkey_attrs + n_priv++, CKA_ID, id, id_len); if (label) pkcs11_addattr_s(privkey_attrs + n_priv++, CKA_LABEL, label); pkcs11_addattr_bool(privkey_attrs + n_priv++, CKA_TOKEN, TRUE); pkcs11_addattr_bool(privkey_attrs + n_priv++, CKA_PRIVATE, TRUE); pkcs11_addattr_bool(privkey_attrs + n_priv++, CKA_SENSITIVE, TRUE); pkcs11_addattr_bool(privkey_attrs + n_priv++, CKA_DECRYPT, TRUE); pkcs11_addattr_bool(privkey_attrs + n_priv++, CKA_SIGN, TRUE); pkcs11_addattr_bool(privkey_attrs + n_priv++, CKA_UNWRAP, TRUE); /* call the pkcs11 module to create the key pair */ rv = CRYPTOKI_call(ctx, C_GenerateKeyPair( spriv->session, &mechanism, pubkey_attrs, n_pub, privkey_attrs, n_priv, &pub_key_obj, &priv_key_obj )); /* zap all memory allocated when building the template */ pkcs11_zap_attrs(privkey_attrs, n_priv); pkcs11_zap_attrs(pubkey_attrs, n_pub); CRYPTOKI_checkerr(CKR_F_PKCS11_GENERATE_KEY, rv); return 0; } /* * Store a private key on the token */ int pkcs11_store_private_key(PKCS11_TOKEN *token, EVP_PKEY *pk, char *label, unsigned char *id, size_t id_len) { if (pkcs11_store_key(token, pk, CKO_PRIVATE_KEY, label, id, id_len, NULL)) return -1; return 0; } int pkcs11_store_public_key(PKCS11_TOKEN *token, EVP_PKEY *pk, char *label, unsigned char *id, size_t id_len) { if (pkcs11_store_key(token, pk, CKO_PUBLIC_KEY, label, id, id_len, NULL)) return -1; return 0; } /* * Store private key */ static int pkcs11_store_key(PKCS11_TOKEN *token, EVP_PKEY *pk, unsigned int type, char *label, unsigned char *id, size_t id_len, PKCS11_KEY ** ret_key) { PKCS11_SLOT *slot = TOKEN2SLOT(token); PKCS11_CTX *ctx = TOKEN2CTX(token); PKCS11_SLOT_private *spriv = PRIVSLOT(slot); CK_OBJECT_HANDLE object; CK_ATTRIBUTE attrs[32]; unsigned int n = 0; int rv; const BIGNUM *rsa_n, *rsa_e, *rsa_d, *rsa_p, *rsa_q, *rsa_dmp1, *rsa_dmq1, *rsa_iqmp; /* First, make sure we have a session */ if (!spriv->haveSession && PKCS11_open_session(slot, 1)) return -1; /* Now build the key attrs */ pkcs11_addattr_int(attrs + n++, CKA_CLASS, type); if (label) pkcs11_addattr_s(attrs + n++, CKA_LABEL, label); if (id && id_len) pkcs11_addattr(attrs + n++, CKA_ID, id, id_len); pkcs11_addattr_bool(attrs + n++, CKA_TOKEN, TRUE); if (type == CKO_PRIVATE_KEY) { pkcs11_addattr_bool(attrs + n++, CKA_PRIVATE, TRUE); pkcs11_addattr_bool(attrs + n++, CKA_SENSITIVE, TRUE); pkcs11_addattr_bool(attrs + n++, CKA_DECRYPT, TRUE); pkcs11_addattr_bool(attrs + n++, CKA_SIGN, TRUE); pkcs11_addattr_bool(attrs + n++, CKA_UNWRAP, TRUE); } else { /* CKO_PUBLIC_KEY */ pkcs11_addattr_bool(attrs + n++, CKA_ENCRYPT, TRUE); pkcs11_addattr_bool(attrs + n++, CKA_VERIFY, TRUE); pkcs11_addattr_bool(attrs + n++, CKA_WRAP, TRUE); } #if OPENSSL_VERSION_NUMBER >= 0x10100003L && !defined(LIBRESSL_VERSION_NUMBER) if (EVP_PKEY_base_id(pk) == EVP_PKEY_RSA) { RSA *rsa = EVP_PKEY_get1_RSA(pk); pkcs11_addattr_int(attrs + n++, CKA_KEY_TYPE, CKK_RSA); RSA_get0_key(rsa, &rsa_n, &rsa_e, &rsa_d); RSA_get0_factors(rsa, &rsa_p, &rsa_q); RSA_get0_crt_params(rsa, &rsa_dmp1, &rsa_dmq1, &rsa_iqmp); RSA_free(rsa); #else if (pk->type == EVP_PKEY_RSA) { RSA *rsa = pk->pkey.rsa; pkcs11_addattr_int(attrs + n++, CKA_KEY_TYPE, CKK_RSA); rsa_n=rsa->n; rsa_e=rsa->e; rsa_d=rsa->d; rsa_p=rsa->p; rsa_q=rsa->q; rsa_dmp1=rsa->dmp1; rsa_dmq1=rsa->dmq1; rsa_iqmp=rsa->iqmp; #endif pkcs11_addattr_bn(attrs + n++, CKA_MODULUS, rsa_n); pkcs11_addattr_bn(attrs + n++, CKA_PUBLIC_EXPONENT, rsa_e); if (type == CKO_PRIVATE_KEY) { pkcs11_addattr_bn(attrs + n++, CKA_PRIVATE_EXPONENT, rsa_d); pkcs11_addattr_bn(attrs + n++, CKA_PRIME_1, rsa_p); pkcs11_addattr_bn(attrs + n++, CKA_PRIME_2, rsa_q); if (rsa_dmp1) pkcs11_addattr_bn(attrs + n++, CKA_EXPONENT_1, rsa_dmp1); if (rsa_dmq1) pkcs11_addattr_bn(attrs + n++, CKA_EXPONENT_2, rsa_dmq1); if (rsa_iqmp) pkcs11_addattr_bn(attrs + n++, CKA_COEFFICIENT, rsa_iqmp); } } else { pkcs11_zap_attrs(attrs, n); P11err(P11_F_PKCS11_STORE_KEY, P11_R_NOT_SUPPORTED); return -1; } /* Now call the pkcs11 module to create the object */ rv = CRYPTOKI_call(ctx, C_CreateObject(spriv->session, attrs, n, &object)); /* Zap all memory allocated when building the template */ pkcs11_zap_attrs(attrs, n); CRYPTOKI_checkerr(CKR_F_PKCS11_STORE_KEY, rv); /* Gobble the key object */ return pkcs11_init_key(ctx, token, spriv->session, object, type, ret_key); } /* * Get the key type */ int pkcs11_get_key_type(PKCS11_KEY *key) { PKCS11_KEY_private *kpriv = PRIVKEY(key); return kpriv->ops->type; } /* * Create an EVP_PKEY OpenSSL object for a given key * Returns private or public key depending on isPrivate */ EVP_PKEY *pkcs11_get_key(PKCS11_KEY *key, int isPrivate) { if (key->isPrivate != isPrivate) key = pkcs11_find_key_from_key(key); if (key == NULL) return NULL; if (key->evp_key == NULL) { PKCS11_KEY_private *kpriv = PRIVKEY(key); key->evp_key = kpriv->ops->get_evp_key(key); if (key->evp_key == NULL) return NULL; kpriv->always_authenticate = CK_FALSE; if (isPrivate && key_getattr_val(key, CKA_ALWAYS_AUTHENTICATE, &kpriv->always_authenticate, sizeof(CK_BBOOL))) { #ifdef DEBUG fprintf(stderr, "Missing CKA_ALWAYS_AUTHENTICATE attribute\n"); #endif } } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) EVP_PKEY_up_ref(key->evp_key); #else CRYPTO_add(&key->evp_key->references, 1, CRYPTO_LOCK_EVP_PKEY); #endif return key->evp_key; } /* * Authenticate a private the key operation if needed * This function *only* handles CKU_CONTEXT_SPECIFIC logins. */ int pkcs11_authenticate(PKCS11_KEY *key) { PKCS11_TOKEN *token = KEY2TOKEN(key); PKCS11_SLOT *slot = TOKEN2SLOT(token); PKCS11_SLOT_private *spriv = PRIVSLOT(slot); PKCS11_CTX *ctx = SLOT2CTX(slot); PKCS11_CTX_private *cpriv = PRIVCTX(ctx); char pin[MAX_PIN_LENGTH+1]; char* prompt; UI *ui; int rv; /* Handle CKF_PROTECTED_AUTHENTICATION_PATH */ if (token->secureLogin) { rv = CRYPTOKI_call(ctx, C_Login(spriv->session, CKU_CONTEXT_SPECIFIC, NULL, 0)); return rv == CKR_USER_ALREADY_LOGGED_IN ? 0 : rv; } /* Call UI to ask for a PIN */ ui = UI_new_method(cpriv->ui_method); if (ui == NULL) return P11_R_UI_FAILED; if (cpriv->ui_user_data != NULL) UI_add_user_data(ui, cpriv->ui_user_data); memset(pin, 0, MAX_PIN_LENGTH+1); prompt = UI_construct_prompt(ui, "PKCS#11 key PIN", key->label); if (!prompt) { return P11_R_UI_FAILED; } if (!UI_dup_input_string(ui, prompt, UI_INPUT_FLAG_DEFAULT_PWD, pin, 4, MAX_PIN_LENGTH)) { UI_free(ui); OPENSSL_free(prompt); return P11_R_UI_FAILED; } OPENSSL_free(prompt); if (UI_process(ui)) { UI_free(ui); return P11_R_UI_FAILED; } UI_free(ui); /* Login with the PIN */ rv = CRYPTOKI_call(ctx, C_Login(spriv->session, CKU_CONTEXT_SPECIFIC, (CK_UTF8CHAR *)pin, strlen(pin))); OPENSSL_cleanse(pin, MAX_PIN_LENGTH+1); return rv == CKR_USER_ALREADY_LOGGED_IN ? 0 : rv; } /* * Return keys of a given type (public or private) * Use the cached values if available */ int pkcs11_enumerate_keys(PKCS11_TOKEN *token, unsigned int type, PKCS11_KEY ** keyp, unsigned int *countp) { PKCS11_SLOT *slot = TOKEN2SLOT(token); PKCS11_CTX *ctx = TOKEN2CTX(token); PKCS11_TOKEN_private *tpriv = PRIVTOKEN(token); PKCS11_SLOT_private *spriv = PRIVSLOT(slot); PKCS11_CTX_private *cpriv = PRIVCTX(ctx); PKCS11_keys *keys = (type == CKO_PRIVATE_KEY) ? &tpriv->prv : &tpriv->pub; PKCS11_KEY *first_key_prev = keys->keys; int rv; int i; /* Make sure we have a session */ if (!spriv->haveSession && PKCS11_open_session(slot, 0)) return -1; CRYPTO_THREAD_write_lock(cpriv->rwlock); rv = pkcs11_find_keys(token, type); CRYPTO_THREAD_unlock(cpriv->rwlock); if (rv < 0) { pkcs11_destroy_keys(token, type); return -1; } /* Always update key references if the keys pointer changed */ if (first_key_prev != NULL && first_key_prev != keys->keys) { for (i = 0; i < keys->num; ++i) { PKCS11_KEY *key = keys->keys + i; PKCS11_KEY_private *kpriv = PRIVKEY(key); kpriv->ops->update_ex_data(key); } } if (keyp) *keyp = keys->keys; if (countp) *countp = keys->num; return 0; } /** * Remove a key from the associated token */ int pkcs11_remove_key(PKCS11_KEY *key) { PKCS11_SLOT *slot = KEY2SLOT(key); PKCS11_CTX *ctx = KEY2CTX(key); PKCS11_SLOT_private *spriv = PRIVSLOT(slot); CK_OBJECT_HANDLE obj; CK_ULONG count; CK_ATTRIBUTE search_parameters[32]; unsigned int n = 0; int rv; /* First, make sure we have a session */ if (!spriv->haveSession && PKCS11_open_session(slot, 1)) return -1; if (key->isPrivate) pkcs11_addattr_int(search_parameters + n++, CKA_CLASS, CKO_PRIVATE_KEY); else pkcs11_addattr_int(search_parameters + n++, CKA_CLASS, CKO_PUBLIC_KEY); if (key->id && key->id_len) pkcs11_addattr(search_parameters + n++, CKA_ID, key->id, key->id_len); if (key->label) pkcs11_addattr_s(search_parameters + n++, CKA_LABEL, key->label); rv = CRYPTOKI_call(ctx, C_FindObjectsInit(spriv->session, search_parameters, n)); CRYPTOKI_checkerr(CKR_F_PKCS11_REMOVE_KEY, rv); rv = CRYPTOKI_call(ctx, C_FindObjects(spriv->session, &obj, 1, &count)); CRYPTOKI_checkerr(CKR_F_PKCS11_REMOVE_KEY, rv); CRYPTOKI_call(ctx, C_FindObjectsFinal(spriv->session)); if (count!=1) { pkcs11_zap_attrs(search_parameters, n); return -1; } rv = CRYPTOKI_call(ctx, C_DestroyObject(spriv->session, obj)); if (rv != CKR_OK) { pkcs11_zap_attrs(search_parameters, n); return -1; } pkcs11_zap_attrs(search_parameters, n); return 0; } /* * Find all keys of a given type (public or private) */ static int pkcs11_find_keys(PKCS11_TOKEN *token, unsigned int type) { PKCS11_SLOT *slot = TOKEN2SLOT(token); PKCS11_CTX *ctx = TOKEN2CTX(token); PKCS11_SLOT_private *spriv = PRIVSLOT(slot); CK_OBJECT_CLASS key_search_class; CK_ATTRIBUTE key_search_attrs[1] = { {CKA_CLASS, &key_search_class, sizeof(key_search_class)}, }; int rv, res = -1; /* Tell the PKCS11 lib to enumerate all matching objects */ key_search_class = type; rv = CRYPTOKI_call(ctx, C_FindObjectsInit(spriv->session, key_search_attrs, 1)); CRYPTOKI_checkerr(CKR_F_PKCS11_FIND_KEYS, rv); do { res = pkcs11_next_key(ctx, token, spriv->session, type); } while (res == 0); CRYPTOKI_call(ctx, C_FindObjectsFinal(spriv->session)); return (res < 0) ? -1 : 0; } static int pkcs11_next_key(PKCS11_CTX *ctx, PKCS11_TOKEN *token, CK_SESSION_HANDLE session, CK_OBJECT_CLASS type) { CK_OBJECT_HANDLE obj; CK_ULONG count; int rv; /* Get the next matching object */ rv = CRYPTOKI_call(ctx, C_FindObjects(session, &obj, 1, &count)); CRYPTOKI_checkerr(CKR_F_PKCS11_NEXT_KEY, rv); if (count == 0) return 1; if (pkcs11_init_key(ctx, token, session, obj, type, NULL)) return -1; return 0; } static int pkcs11_init_key(PKCS11_CTX *ctx, PKCS11_TOKEN *token, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE obj, CK_OBJECT_CLASS type, PKCS11_KEY ** ret) { PKCS11_TOKEN_private *tpriv = PRIVTOKEN(token); PKCS11_keys *keys = (type == CKO_PRIVATE_KEY) ? &tpriv->prv : &tpriv->pub; PKCS11_KEY_private *kpriv; PKCS11_KEY *key, *tmp; CK_KEY_TYPE key_type; PKCS11_KEY_ops *ops; size_t size; int i; (void)ctx; (void)session; /* Ignore unknown key types */ size = sizeof(CK_KEY_TYPE); if (pkcs11_getattr_var(token, obj, CKA_KEY_TYPE, (CK_BYTE *)&key_type, &size)) return -1; switch (key_type) { case CKK_RSA: ops = &pkcs11_rsa_ops; break; case CKK_EC: ops = pkcs11_ec_ops; if (ops == NULL) return 0; /* not supported */ break; default: /* Ignore any keys we don't understand */ return 0; } /* Prevent re-adding existing PKCS#11 object handles */ /* TODO: Rewrite the O(n) algorithm as O(log n), * or it may be too slow with a large number of keys */ for (i=0; i < keys->num; ++i) if (PRIVKEY(keys->keys + i)->object == obj) return 0; /* Allocate memory */ kpriv = OPENSSL_malloc(sizeof(PKCS11_KEY_private)); if (kpriv == NULL) return -1; memset(kpriv, 0, sizeof(PKCS11_KEY_private)); tmp = OPENSSL_realloc(keys->keys, (keys->num + 1) * sizeof(PKCS11_KEY)); if (tmp == NULL) return -1; keys->keys = tmp; key = keys->keys + keys->num++; memset(key, 0, sizeof(PKCS11_KEY)); /* Fill public properties */ pkcs11_getattr_alloc(token, obj, CKA_LABEL, (CK_BYTE **)&key->label, NULL); key->id_len = 0; pkcs11_getattr_alloc(token, obj, CKA_ID, &key->id, &key->id_len); key->isPrivate = (type == CKO_PRIVATE_KEY); /* Fill private properties */ key->_private = kpriv; kpriv->object = obj; kpriv->parent = token; kpriv->id_len = sizeof kpriv->id; if (pkcs11_getattr_var(token, obj, CKA_ID, kpriv->id, &kpriv->id_len)) kpriv->id_len = 0; kpriv->ops = ops; kpriv->forkid = get_forkid(); if (ret) *ret = key; return 0; } /* * Destroy all keys of a given type (public or private) */ void pkcs11_destroy_keys(PKCS11_TOKEN *token, unsigned int type) { PKCS11_TOKEN_private *tpriv = PRIVTOKEN(token); PKCS11_keys *keys = (type == CKO_PRIVATE_KEY) ? &tpriv->prv : &tpriv->pub; while (keys->num > 0) { PKCS11_KEY *key = &keys->keys[--(keys->num)]; if (key->evp_key) EVP_PKEY_free(key->evp_key); OPENSSL_free(key->label); if (key->id) OPENSSL_free(key->id); if (key->_private != NULL) OPENSSL_free(key->_private); } if (keys->keys) OPENSSL_free(keys->keys); keys->keys = NULL; keys->num = 0; } /* vim: set noexpandtab: */