| /* $OpenBSD: ssh-pkcs11.c,v 1.46 2019/10/01 10:22:53 djm Exp $ */ |
| /* |
| * Copyright (c) 2010 Markus Friedl. All rights reserved. |
| * Copyright (c) 2014 Pedro Martelletto. All rights reserved. |
| * |
| * Permission to use, copy, modify, and distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| #include "includes.h" |
| |
| #ifdef ENABLE_PKCS11 |
| |
| #ifdef HAVE_SYS_TIME_H |
| # include <sys/time.h> |
| #endif |
| |
| #include <sys/types.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| |
| #include <ctype.h> |
| #include <string.h> |
| #include <dlfcn.h> |
| |
| #include "openbsd-compat/sys-queue.h" |
| #include "openbsd-compat/openssl-compat.h" |
| |
| #include <openssl/ecdsa.h> |
| #include <openssl/x509.h> |
| #include <openssl/err.h> |
| |
| #define CRYPTOKI_COMPAT |
| #include "pkcs11.h" |
| |
| #include "log.h" |
| #include "misc.h" |
| #include "sshkey.h" |
| #include "ssh-pkcs11.h" |
| #include "xmalloc.h" |
| |
| struct pkcs11_slotinfo { |
| CK_TOKEN_INFO token; |
| CK_SESSION_HANDLE session; |
| int logged_in; |
| }; |
| |
| struct pkcs11_provider { |
| char *name; |
| void *handle; |
| CK_FUNCTION_LIST *function_list; |
| CK_INFO info; |
| CK_ULONG nslots; |
| CK_SLOT_ID *slotlist; |
| struct pkcs11_slotinfo *slotinfo; |
| int valid; |
| int refcount; |
| TAILQ_ENTRY(pkcs11_provider) next; |
| }; |
| |
| TAILQ_HEAD(, pkcs11_provider) pkcs11_providers; |
| |
| struct pkcs11_key { |
| struct pkcs11_provider *provider; |
| CK_ULONG slotidx; |
| char *keyid; |
| int keyid_len; |
| }; |
| |
| int pkcs11_interactive = 0; |
| |
| #ifdef HAVE_EC_KEY_METHOD_NEW |
| static void |
| ossl_error(const char *msg) |
| { |
| unsigned long e; |
| |
| error("%s: %s", __func__, msg); |
| while ((e = ERR_get_error()) != 0) |
| error("%s: libcrypto error: %.100s", __func__, |
| ERR_error_string(e, NULL)); |
| } |
| #endif /* HAVE_EC_KEY_METHOD_NEW */ |
| |
| int |
| pkcs11_init(int interactive) |
| { |
| pkcs11_interactive = interactive; |
| TAILQ_INIT(&pkcs11_providers); |
| return (0); |
| } |
| |
| /* |
| * finalize a provider shared library, it's no longer usable. |
| * however, there might still be keys referencing this provider, |
| * so the actual freeing of memory is handled by pkcs11_provider_unref(). |
| * this is called when a provider gets unregistered. |
| */ |
| static void |
| pkcs11_provider_finalize(struct pkcs11_provider *p) |
| { |
| CK_RV rv; |
| CK_ULONG i; |
| |
| debug("pkcs11_provider_finalize: %p refcount %d valid %d", |
| p, p->refcount, p->valid); |
| if (!p->valid) |
| return; |
| for (i = 0; i < p->nslots; i++) { |
| if (p->slotinfo[i].session && |
| (rv = p->function_list->C_CloseSession( |
| p->slotinfo[i].session)) != CKR_OK) |
| error("C_CloseSession failed: %lu", rv); |
| } |
| if ((rv = p->function_list->C_Finalize(NULL)) != CKR_OK) |
| error("C_Finalize failed: %lu", rv); |
| p->valid = 0; |
| p->function_list = NULL; |
| dlclose(p->handle); |
| } |
| |
| /* |
| * remove a reference to the provider. |
| * called when a key gets destroyed or when the provider is unregistered. |
| */ |
| static void |
| pkcs11_provider_unref(struct pkcs11_provider *p) |
| { |
| debug("pkcs11_provider_unref: %p refcount %d", p, p->refcount); |
| if (--p->refcount <= 0) { |
| if (p->valid) |
| error("pkcs11_provider_unref: %p still valid", p); |
| free(p->name); |
| free(p->slotlist); |
| free(p->slotinfo); |
| free(p); |
| } |
| } |
| |
| /* unregister all providers, keys might still point to the providers */ |
| void |
| pkcs11_terminate(void) |
| { |
| struct pkcs11_provider *p; |
| |
| while ((p = TAILQ_FIRST(&pkcs11_providers)) != NULL) { |
| TAILQ_REMOVE(&pkcs11_providers, p, next); |
| pkcs11_provider_finalize(p); |
| pkcs11_provider_unref(p); |
| } |
| } |
| |
| /* lookup provider by name */ |
| static struct pkcs11_provider * |
| pkcs11_provider_lookup(char *provider_id) |
| { |
| struct pkcs11_provider *p; |
| |
| TAILQ_FOREACH(p, &pkcs11_providers, next) { |
| debug("check %p %s", p, p->name); |
| if (!strcmp(provider_id, p->name)) |
| return (p); |
| } |
| return (NULL); |
| } |
| |
| /* unregister provider by name */ |
| int |
| pkcs11_del_provider(char *provider_id) |
| { |
| struct pkcs11_provider *p; |
| |
| if ((p = pkcs11_provider_lookup(provider_id)) != NULL) { |
| TAILQ_REMOVE(&pkcs11_providers, p, next); |
| pkcs11_provider_finalize(p); |
| pkcs11_provider_unref(p); |
| return (0); |
| } |
| return (-1); |
| } |
| |
| static RSA_METHOD *rsa_method; |
| static int rsa_idx = 0; |
| #ifdef HAVE_EC_KEY_METHOD_NEW |
| static EC_KEY_METHOD *ec_key_method; |
| static int ec_key_idx = 0; |
| #endif |
| |
| /* release a wrapped object */ |
| static void |
| pkcs11_k11_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad, int idx, |
| long argl, void *argp) |
| { |
| struct pkcs11_key *k11 = ptr; |
| |
| debug("%s: parent %p ptr %p idx %d", __func__, parent, ptr, idx); |
| if (k11 == NULL) |
| return; |
| if (k11->provider) |
| pkcs11_provider_unref(k11->provider); |
| free(k11->keyid); |
| free(k11); |
| } |
| |
| /* find a single 'obj' for given attributes */ |
| static int |
| pkcs11_find(struct pkcs11_provider *p, CK_ULONG slotidx, CK_ATTRIBUTE *attr, |
| CK_ULONG nattr, CK_OBJECT_HANDLE *obj) |
| { |
| CK_FUNCTION_LIST *f; |
| CK_SESSION_HANDLE session; |
| CK_ULONG nfound = 0; |
| CK_RV rv; |
| int ret = -1; |
| |
| f = p->function_list; |
| session = p->slotinfo[slotidx].session; |
| if ((rv = f->C_FindObjectsInit(session, attr, nattr)) != CKR_OK) { |
| error("C_FindObjectsInit failed (nattr %lu): %lu", nattr, rv); |
| return (-1); |
| } |
| if ((rv = f->C_FindObjects(session, obj, 1, &nfound)) != CKR_OK || |
| nfound != 1) { |
| debug("C_FindObjects failed (nfound %lu nattr %lu): %lu", |
| nfound, nattr, rv); |
| } else |
| ret = 0; |
| if ((rv = f->C_FindObjectsFinal(session)) != CKR_OK) |
| error("C_FindObjectsFinal failed: %lu", rv); |
| return (ret); |
| } |
| |
| static int |
| pkcs11_login_slot(struct pkcs11_provider *provider, struct pkcs11_slotinfo *si, |
| CK_USER_TYPE type) |
| { |
| char *pin = NULL, prompt[1024]; |
| CK_RV rv; |
| |
| if (provider == NULL || si == NULL || !provider->valid) { |
| error("no pkcs11 (valid) provider found"); |
| return (-1); |
| } |
| |
| if (!pkcs11_interactive) { |
| error("need pin entry%s", |
| (si->token.flags & CKF_PROTECTED_AUTHENTICATION_PATH) ? |
| " on reader keypad" : ""); |
| return (-1); |
| } |
| if (si->token.flags & CKF_PROTECTED_AUTHENTICATION_PATH) |
| verbose("Deferring PIN entry to reader keypad."); |
| else { |
| snprintf(prompt, sizeof(prompt), "Enter PIN for '%s': ", |
| si->token.label); |
| if ((pin = read_passphrase(prompt, RP_ALLOW_EOF)) == NULL) { |
| debug("%s: no pin specified", __func__); |
| return (-1); /* bail out */ |
| } |
| } |
| rv = provider->function_list->C_Login(si->session, type, (u_char *)pin, |
| (pin != NULL) ? strlen(pin) : 0); |
| if (pin != NULL) |
| freezero(pin, strlen(pin)); |
| if (rv != CKR_OK && rv != CKR_USER_ALREADY_LOGGED_IN) { |
| error("C_Login failed: %lu", rv); |
| return (-1); |
| } |
| si->logged_in = 1; |
| return (0); |
| } |
| |
| static int |
| pkcs11_login(struct pkcs11_key *k11, CK_USER_TYPE type) |
| { |
| if (k11 == NULL || k11->provider == NULL || !k11->provider->valid) { |
| error("no pkcs11 (valid) provider found"); |
| return (-1); |
| } |
| |
| return pkcs11_login_slot(k11->provider, |
| &k11->provider->slotinfo[k11->slotidx], type); |
| } |
| |
| |
| static int |
| pkcs11_check_obj_bool_attrib(struct pkcs11_key *k11, CK_OBJECT_HANDLE obj, |
| CK_ATTRIBUTE_TYPE type, int *val) |
| { |
| struct pkcs11_slotinfo *si; |
| CK_FUNCTION_LIST *f; |
| CK_BBOOL flag = 0; |
| CK_ATTRIBUTE attr; |
| CK_RV rv; |
| |
| *val = 0; |
| |
| if (!k11->provider || !k11->provider->valid) { |
| error("no pkcs11 (valid) provider found"); |
| return (-1); |
| } |
| |
| f = k11->provider->function_list; |
| si = &k11->provider->slotinfo[k11->slotidx]; |
| |
| attr.type = type; |
| attr.pValue = &flag; |
| attr.ulValueLen = sizeof(flag); |
| |
| rv = f->C_GetAttributeValue(si->session, obj, &attr, 1); |
| if (rv != CKR_OK) { |
| error("C_GetAttributeValue failed: %lu", rv); |
| return (-1); |
| } |
| *val = flag != 0; |
| debug("%s: provider %p slot %lu object %lu: attrib %lu = %d", |
| __func__, k11->provider, k11->slotidx, obj, type, *val); |
| return (0); |
| } |
| |
| static int |
| pkcs11_get_key(struct pkcs11_key *k11, CK_MECHANISM_TYPE mech_type) |
| { |
| struct pkcs11_slotinfo *si; |
| CK_FUNCTION_LIST *f; |
| CK_OBJECT_HANDLE obj; |
| CK_RV rv; |
| CK_OBJECT_CLASS private_key_class; |
| CK_BBOOL true_val; |
| CK_MECHANISM mech; |
| CK_ATTRIBUTE key_filter[3]; |
| int always_auth = 0; |
| int did_login = 0; |
| |
| if (!k11->provider || !k11->provider->valid) { |
| error("no pkcs11 (valid) provider found"); |
| return (-1); |
| } |
| |
| f = k11->provider->function_list; |
| si = &k11->provider->slotinfo[k11->slotidx]; |
| |
| if ((si->token.flags & CKF_LOGIN_REQUIRED) && !si->logged_in) { |
| if (pkcs11_login(k11, CKU_USER) < 0) { |
| error("login failed"); |
| return (-1); |
| } |
| did_login = 1; |
| } |
| |
| memset(&key_filter, 0, sizeof(key_filter)); |
| private_key_class = CKO_PRIVATE_KEY; |
| key_filter[0].type = CKA_CLASS; |
| key_filter[0].pValue = &private_key_class; |
| key_filter[0].ulValueLen = sizeof(private_key_class); |
| |
| key_filter[1].type = CKA_ID; |
| key_filter[1].pValue = k11->keyid; |
| key_filter[1].ulValueLen = k11->keyid_len; |
| |
| true_val = CK_TRUE; |
| key_filter[2].type = CKA_SIGN; |
| key_filter[2].pValue = &true_val; |
| key_filter[2].ulValueLen = sizeof(true_val); |
| |
| /* try to find object w/CKA_SIGN first, retry w/o */ |
| if (pkcs11_find(k11->provider, k11->slotidx, key_filter, 3, &obj) < 0 && |
| pkcs11_find(k11->provider, k11->slotidx, key_filter, 2, &obj) < 0) { |
| error("cannot find private key"); |
| return (-1); |
| } |
| |
| memset(&mech, 0, sizeof(mech)); |
| mech.mechanism = mech_type; |
| mech.pParameter = NULL_PTR; |
| mech.ulParameterLen = 0; |
| |
| if ((rv = f->C_SignInit(si->session, &mech, obj)) != CKR_OK) { |
| error("C_SignInit failed: %lu", rv); |
| return (-1); |
| } |
| |
| pkcs11_check_obj_bool_attrib(k11, obj, CKA_ALWAYS_AUTHENTICATE, |
| &always_auth); /* ignore errors here */ |
| if (always_auth && !did_login) { |
| debug("%s: always-auth key", __func__); |
| if (pkcs11_login(k11, CKU_CONTEXT_SPECIFIC) < 0) { |
| error("login failed for always-auth key"); |
| return (-1); |
| } |
| } |
| |
| return (0); |
| } |
| |
| /* openssl callback doing the actual signing operation */ |
| static int |
| pkcs11_rsa_private_encrypt(int flen, const u_char *from, u_char *to, RSA *rsa, |
| int padding) |
| { |
| struct pkcs11_key *k11; |
| struct pkcs11_slotinfo *si; |
| CK_FUNCTION_LIST *f; |
| CK_ULONG tlen = 0; |
| CK_RV rv; |
| int rval = -1; |
| |
| if ((k11 = RSA_get_ex_data(rsa, rsa_idx)) == NULL) { |
| error("RSA_get_ex_data failed for rsa %p", rsa); |
| return (-1); |
| } |
| |
| if (pkcs11_get_key(k11, CKM_RSA_PKCS) == -1) { |
| error("pkcs11_get_key failed"); |
| return (-1); |
| } |
| |
| f = k11->provider->function_list; |
| si = &k11->provider->slotinfo[k11->slotidx]; |
| tlen = RSA_size(rsa); |
| |
| /* XXX handle CKR_BUFFER_TOO_SMALL */ |
| rv = f->C_Sign(si->session, (CK_BYTE *)from, flen, to, &tlen); |
| if (rv == CKR_OK) |
| rval = tlen; |
| else |
| error("C_Sign failed: %lu", rv); |
| |
| return (rval); |
| } |
| |
| static int |
| pkcs11_rsa_private_decrypt(int flen, const u_char *from, u_char *to, RSA *rsa, |
| int padding) |
| { |
| return (-1); |
| } |
| |
| static int |
| pkcs11_rsa_start_wrapper(void) |
| { |
| if (rsa_method != NULL) |
| return (0); |
| rsa_method = RSA_meth_dup(RSA_get_default_method()); |
| if (rsa_method == NULL) |
| return (-1); |
| rsa_idx = RSA_get_ex_new_index(0, "ssh-pkcs11-rsa", |
| NULL, NULL, pkcs11_k11_free); |
| if (rsa_idx == -1) |
| return (-1); |
| if (!RSA_meth_set1_name(rsa_method, "pkcs11") || |
| !RSA_meth_set_priv_enc(rsa_method, pkcs11_rsa_private_encrypt) || |
| !RSA_meth_set_priv_dec(rsa_method, pkcs11_rsa_private_decrypt)) { |
| error("%s: setup pkcs11 method failed", __func__); |
| return (-1); |
| } |
| return (0); |
| } |
| |
| /* redirect private key operations for rsa key to pkcs11 token */ |
| static int |
| pkcs11_rsa_wrap(struct pkcs11_provider *provider, CK_ULONG slotidx, |
| CK_ATTRIBUTE *keyid_attrib, RSA *rsa) |
| { |
| struct pkcs11_key *k11; |
| |
| if (pkcs11_rsa_start_wrapper() == -1) |
| return (-1); |
| |
| k11 = xcalloc(1, sizeof(*k11)); |
| k11->provider = provider; |
| provider->refcount++; /* provider referenced by RSA key */ |
| k11->slotidx = slotidx; |
| /* identify key object on smartcard */ |
| k11->keyid_len = keyid_attrib->ulValueLen; |
| if (k11->keyid_len > 0) { |
| k11->keyid = xmalloc(k11->keyid_len); |
| memcpy(k11->keyid, keyid_attrib->pValue, k11->keyid_len); |
| } |
| |
| RSA_set_method(rsa, rsa_method); |
| RSA_set_ex_data(rsa, rsa_idx, k11); |
| return (0); |
| } |
| |
| #ifdef HAVE_EC_KEY_METHOD_NEW |
| /* openssl callback doing the actual signing operation */ |
| static ECDSA_SIG * |
| ecdsa_do_sign(const unsigned char *dgst, int dgst_len, const BIGNUM *inv, |
| const BIGNUM *rp, EC_KEY *ec) |
| { |
| struct pkcs11_key *k11; |
| struct pkcs11_slotinfo *si; |
| CK_FUNCTION_LIST *f; |
| CK_ULONG siglen = 0, bnlen; |
| CK_RV rv; |
| ECDSA_SIG *ret = NULL; |
| u_char *sig; |
| BIGNUM *r = NULL, *s = NULL; |
| |
| if ((k11 = EC_KEY_get_ex_data(ec, ec_key_idx)) == NULL) { |
| ossl_error("EC_KEY_get_key_method_data failed for ec"); |
| return (NULL); |
| } |
| |
| if (pkcs11_get_key(k11, CKM_ECDSA) == -1) { |
| error("pkcs11_get_key failed"); |
| return (NULL); |
| } |
| |
| f = k11->provider->function_list; |
| si = &k11->provider->slotinfo[k11->slotidx]; |
| |
| siglen = ECDSA_size(ec); |
| sig = xmalloc(siglen); |
| |
| /* XXX handle CKR_BUFFER_TOO_SMALL */ |
| rv = f->C_Sign(si->session, (CK_BYTE *)dgst, dgst_len, sig, &siglen); |
| if (rv != CKR_OK) { |
| error("C_Sign failed: %lu", rv); |
| goto done; |
| } |
| if (siglen < 64 || siglen > 132 || siglen % 2) { |
| ossl_error("d2i_ECDSA_SIG failed"); |
| goto done; |
| } |
| bnlen = siglen/2; |
| if ((ret = ECDSA_SIG_new()) == NULL) { |
| error("ECDSA_SIG_new failed"); |
| goto done; |
| } |
| if ((r = BN_bin2bn(sig, bnlen, NULL)) == NULL || |
| (s = BN_bin2bn(sig+bnlen, bnlen, NULL)) == NULL) { |
| ossl_error("d2i_ECDSA_SIG failed"); |
| ECDSA_SIG_free(ret); |
| ret = NULL; |
| goto done; |
| } |
| if (!ECDSA_SIG_set0(ret, r, s)) { |
| error("%s: ECDSA_SIG_set0 failed", __func__); |
| ECDSA_SIG_free(ret); |
| ret = NULL; |
| goto done; |
| } |
| r = s = NULL; /* now owned by ret */ |
| /* success */ |
| done: |
| BN_free(r); |
| BN_free(s); |
| free(sig); |
| |
| return (ret); |
| } |
| |
| static int |
| pkcs11_ecdsa_start_wrapper(void) |
| { |
| int (*orig_sign)(int, const unsigned char *, int, unsigned char *, |
| unsigned int *, const BIGNUM *, const BIGNUM *, EC_KEY *) = NULL; |
| |
| if (ec_key_method != NULL) |
| return (0); |
| ec_key_idx = EC_KEY_get_ex_new_index(0, "ssh-pkcs11-ecdsa", |
| NULL, NULL, pkcs11_k11_free); |
| if (ec_key_idx == -1) |
| return (-1); |
| ec_key_method = EC_KEY_METHOD_new(EC_KEY_OpenSSL()); |
| if (ec_key_method == NULL) |
| return (-1); |
| EC_KEY_METHOD_get_sign(ec_key_method, &orig_sign, NULL, NULL); |
| EC_KEY_METHOD_set_sign(ec_key_method, orig_sign, NULL, ecdsa_do_sign); |
| return (0); |
| } |
| |
| static int |
| pkcs11_ecdsa_wrap(struct pkcs11_provider *provider, CK_ULONG slotidx, |
| CK_ATTRIBUTE *keyid_attrib, EC_KEY *ec) |
| { |
| struct pkcs11_key *k11; |
| |
| if (pkcs11_ecdsa_start_wrapper() == -1) |
| return (-1); |
| |
| k11 = xcalloc(1, sizeof(*k11)); |
| k11->provider = provider; |
| provider->refcount++; /* provider referenced by ECDSA key */ |
| k11->slotidx = slotidx; |
| /* identify key object on smartcard */ |
| k11->keyid_len = keyid_attrib->ulValueLen; |
| k11->keyid = xmalloc(k11->keyid_len); |
| memcpy(k11->keyid, keyid_attrib->pValue, k11->keyid_len); |
| |
| EC_KEY_set_method(ec, ec_key_method); |
| EC_KEY_set_ex_data(ec, ec_key_idx, k11); |
| |
| return (0); |
| } |
| #endif /* HAVE_EC_KEY_METHOD_NEW */ |
| |
| /* remove trailing spaces */ |
| static void |
| rmspace(u_char *buf, size_t len) |
| { |
| size_t i; |
| |
| if (!len) |
| return; |
| for (i = len - 1; i > 0; i--) |
| if (i == len - 1 || buf[i] == ' ') |
| buf[i] = '\0'; |
| else |
| break; |
| } |
| |
| /* |
| * open a pkcs11 session and login if required. |
| * if pin == NULL we delay login until key use |
| */ |
| static int |
| pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin, |
| CK_ULONG user) |
| { |
| struct pkcs11_slotinfo *si; |
| CK_FUNCTION_LIST *f; |
| CK_RV rv; |
| CK_SESSION_HANDLE session; |
| int login_required, ret; |
| |
| f = p->function_list; |
| si = &p->slotinfo[slotidx]; |
| |
| login_required = si->token.flags & CKF_LOGIN_REQUIRED; |
| |
| /* fail early before opening session */ |
| if (login_required && !pkcs11_interactive && |
| (pin == NULL || strlen(pin) == 0)) { |
| error("pin required"); |
| return (-SSH_PKCS11_ERR_PIN_REQUIRED); |
| } |
| if ((rv = f->C_OpenSession(p->slotlist[slotidx], CKF_RW_SESSION| |
| CKF_SERIAL_SESSION, NULL, NULL, &session)) != CKR_OK) { |
| error("C_OpenSession failed: %lu", rv); |
| return (-1); |
| } |
| if (login_required && pin != NULL && strlen(pin) != 0) { |
| rv = f->C_Login(session, user, (u_char *)pin, strlen(pin)); |
| if (rv != CKR_OK && rv != CKR_USER_ALREADY_LOGGED_IN) { |
| error("C_Login failed: %lu", rv); |
| ret = (rv == CKR_PIN_LOCKED) ? |
| -SSH_PKCS11_ERR_PIN_LOCKED : |
| -SSH_PKCS11_ERR_LOGIN_FAIL; |
| if ((rv = f->C_CloseSession(session)) != CKR_OK) |
| error("C_CloseSession failed: %lu", rv); |
| return (ret); |
| } |
| si->logged_in = 1; |
| } |
| si->session = session; |
| return (0); |
| } |
| |
| static int |
| pkcs11_key_included(struct sshkey ***keysp, int *nkeys, struct sshkey *key) |
| { |
| int i; |
| |
| for (i = 0; i < *nkeys; i++) |
| if (sshkey_equal(key, (*keysp)[i])) |
| return (1); |
| return (0); |
| } |
| |
| #ifdef HAVE_EC_KEY_METHOD_NEW |
| static struct sshkey * |
| pkcs11_fetch_ecdsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx, |
| CK_OBJECT_HANDLE *obj) |
| { |
| CK_ATTRIBUTE key_attr[3]; |
| CK_SESSION_HANDLE session; |
| CK_FUNCTION_LIST *f = NULL; |
| CK_RV rv; |
| ASN1_OCTET_STRING *octet = NULL; |
| EC_KEY *ec = NULL; |
| EC_GROUP *group = NULL; |
| struct sshkey *key = NULL; |
| const unsigned char *attrp = NULL; |
| int i; |
| int nid; |
| |
| memset(&key_attr, 0, sizeof(key_attr)); |
| key_attr[0].type = CKA_ID; |
| key_attr[1].type = CKA_EC_POINT; |
| key_attr[2].type = CKA_EC_PARAMS; |
| |
| session = p->slotinfo[slotidx].session; |
| f = p->function_list; |
| |
| /* figure out size of the attributes */ |
| rv = f->C_GetAttributeValue(session, *obj, key_attr, 3); |
| if (rv != CKR_OK) { |
| error("C_GetAttributeValue failed: %lu", rv); |
| return (NULL); |
| } |
| |
| /* |
| * Allow CKA_ID (always first attribute) to be empty, but |
| * ensure that none of the others are zero length. |
| * XXX assumes CKA_ID is always first. |
| */ |
| if (key_attr[1].ulValueLen == 0 || |
| key_attr[2].ulValueLen == 0) { |
| error("invalid attribute length"); |
| return (NULL); |
| } |
| |
| /* allocate buffers for attributes */ |
| for (i = 0; i < 3; i++) |
| if (key_attr[i].ulValueLen > 0) |
| key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen); |
| |
| /* retrieve ID, public point and curve parameters of EC key */ |
| rv = f->C_GetAttributeValue(session, *obj, key_attr, 3); |
| if (rv != CKR_OK) { |
| error("C_GetAttributeValue failed: %lu", rv); |
| goto fail; |
| } |
| |
| ec = EC_KEY_new(); |
| if (ec == NULL) { |
| error("EC_KEY_new failed"); |
| goto fail; |
| } |
| |
| attrp = key_attr[2].pValue; |
| group = d2i_ECPKParameters(NULL, &attrp, key_attr[2].ulValueLen); |
| if (group == NULL) { |
| ossl_error("d2i_ECPKParameters failed"); |
| goto fail; |
| } |
| |
| if (EC_KEY_set_group(ec, group) == 0) { |
| ossl_error("EC_KEY_set_group failed"); |
| goto fail; |
| } |
| |
| if (key_attr[1].ulValueLen <= 2) { |
| error("CKA_EC_POINT too small"); |
| goto fail; |
| } |
| |
| attrp = key_attr[1].pValue; |
| octet = d2i_ASN1_OCTET_STRING(NULL, &attrp, key_attr[1].ulValueLen); |
| if (octet == NULL) { |
| ossl_error("d2i_ASN1_OCTET_STRING failed"); |
| goto fail; |
| } |
| attrp = octet->data; |
| if (o2i_ECPublicKey(&ec, &attrp, octet->length) == NULL) { |
| ossl_error("o2i_ECPublicKey failed"); |
| goto fail; |
| } |
| |
| nid = sshkey_ecdsa_key_to_nid(ec); |
| if (nid < 0) { |
| error("couldn't get curve nid"); |
| goto fail; |
| } |
| |
| if (pkcs11_ecdsa_wrap(p, slotidx, &key_attr[0], ec)) |
| goto fail; |
| |
| key = sshkey_new(KEY_UNSPEC); |
| if (key == NULL) { |
| error("sshkey_new failed"); |
| goto fail; |
| } |
| |
| key->ecdsa = ec; |
| key->ecdsa_nid = nid; |
| key->type = KEY_ECDSA; |
| key->flags |= SSHKEY_FLAG_EXT; |
| ec = NULL; /* now owned by key */ |
| |
| fail: |
| for (i = 0; i < 3; i++) |
| free(key_attr[i].pValue); |
| if (ec) |
| EC_KEY_free(ec); |
| if (group) |
| EC_GROUP_free(group); |
| if (octet) |
| ASN1_OCTET_STRING_free(octet); |
| |
| return (key); |
| } |
| #endif /* HAVE_EC_KEY_METHOD_NEW */ |
| |
| static struct sshkey * |
| pkcs11_fetch_rsa_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx, |
| CK_OBJECT_HANDLE *obj) |
| { |
| CK_ATTRIBUTE key_attr[3]; |
| CK_SESSION_HANDLE session; |
| CK_FUNCTION_LIST *f = NULL; |
| CK_RV rv; |
| RSA *rsa = NULL; |
| BIGNUM *rsa_n, *rsa_e; |
| struct sshkey *key = NULL; |
| int i; |
| |
| memset(&key_attr, 0, sizeof(key_attr)); |
| key_attr[0].type = CKA_ID; |
| key_attr[1].type = CKA_MODULUS; |
| key_attr[2].type = CKA_PUBLIC_EXPONENT; |
| |
| session = p->slotinfo[slotidx].session; |
| f = p->function_list; |
| |
| /* figure out size of the attributes */ |
| rv = f->C_GetAttributeValue(session, *obj, key_attr, 3); |
| if (rv != CKR_OK) { |
| error("C_GetAttributeValue failed: %lu", rv); |
| return (NULL); |
| } |
| |
| /* |
| * Allow CKA_ID (always first attribute) to be empty, but |
| * ensure that none of the others are zero length. |
| * XXX assumes CKA_ID is always first. |
| */ |
| if (key_attr[1].ulValueLen == 0 || |
| key_attr[2].ulValueLen == 0) { |
| error("invalid attribute length"); |
| return (NULL); |
| } |
| |
| /* allocate buffers for attributes */ |
| for (i = 0; i < 3; i++) |
| if (key_attr[i].ulValueLen > 0) |
| key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen); |
| |
| /* retrieve ID, modulus and public exponent of RSA key */ |
| rv = f->C_GetAttributeValue(session, *obj, key_attr, 3); |
| if (rv != CKR_OK) { |
| error("C_GetAttributeValue failed: %lu", rv); |
| goto fail; |
| } |
| |
| rsa = RSA_new(); |
| if (rsa == NULL) { |
| error("RSA_new failed"); |
| goto fail; |
| } |
| |
| rsa_n = BN_bin2bn(key_attr[1].pValue, key_attr[1].ulValueLen, NULL); |
| rsa_e = BN_bin2bn(key_attr[2].pValue, key_attr[2].ulValueLen, NULL); |
| if (rsa_n == NULL || rsa_e == NULL) { |
| error("BN_bin2bn failed"); |
| goto fail; |
| } |
| if (!RSA_set0_key(rsa, rsa_n, rsa_e, NULL)) |
| fatal("%s: set key", __func__); |
| rsa_n = rsa_e = NULL; /* transferred */ |
| |
| if (pkcs11_rsa_wrap(p, slotidx, &key_attr[0], rsa)) |
| goto fail; |
| |
| key = sshkey_new(KEY_UNSPEC); |
| if (key == NULL) { |
| error("sshkey_new failed"); |
| goto fail; |
| } |
| |
| key->rsa = rsa; |
| key->type = KEY_RSA; |
| key->flags |= SSHKEY_FLAG_EXT; |
| rsa = NULL; /* now owned by key */ |
| |
| fail: |
| for (i = 0; i < 3; i++) |
| free(key_attr[i].pValue); |
| RSA_free(rsa); |
| |
| return (key); |
| } |
| |
| static struct sshkey * |
| pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx, |
| CK_OBJECT_HANDLE *obj) |
| { |
| CK_ATTRIBUTE cert_attr[3]; |
| CK_SESSION_HANDLE session; |
| CK_FUNCTION_LIST *f = NULL; |
| CK_RV rv; |
| X509 *x509 = NULL; |
| EVP_PKEY *evp; |
| RSA *rsa = NULL; |
| #ifdef OPENSSL_HAS_ECC |
| EC_KEY *ec = NULL; |
| #endif |
| struct sshkey *key = NULL; |
| int i; |
| #ifdef HAVE_EC_KEY_METHOD_NEW |
| int nid; |
| #endif |
| const u_char *cp; |
| |
| memset(&cert_attr, 0, sizeof(cert_attr)); |
| cert_attr[0].type = CKA_ID; |
| cert_attr[1].type = CKA_SUBJECT; |
| cert_attr[2].type = CKA_VALUE; |
| |
| session = p->slotinfo[slotidx].session; |
| f = p->function_list; |
| |
| /* figure out size of the attributes */ |
| rv = f->C_GetAttributeValue(session, *obj, cert_attr, 3); |
| if (rv != CKR_OK) { |
| error("C_GetAttributeValue failed: %lu", rv); |
| return (NULL); |
| } |
| |
| /* |
| * Allow CKA_ID (always first attribute) to be empty, but |
| * ensure that none of the others are zero length. |
| * XXX assumes CKA_ID is always first. |
| */ |
| if (cert_attr[1].ulValueLen == 0 || |
| cert_attr[2].ulValueLen == 0) { |
| error("invalid attribute length"); |
| return (NULL); |
| } |
| |
| /* allocate buffers for attributes */ |
| for (i = 0; i < 3; i++) |
| if (cert_attr[i].ulValueLen > 0) |
| cert_attr[i].pValue = xcalloc(1, cert_attr[i].ulValueLen); |
| |
| /* retrieve ID, subject and value of certificate */ |
| rv = f->C_GetAttributeValue(session, *obj, cert_attr, 3); |
| if (rv != CKR_OK) { |
| error("C_GetAttributeValue failed: %lu", rv); |
| goto fail; |
| } |
| |
| x509 = X509_new(); |
| if (x509 == NULL) { |
| error("x509_new failed"); |
| goto fail; |
| } |
| |
| cp = cert_attr[2].pValue; |
| if (d2i_X509(&x509, &cp, cert_attr[2].ulValueLen) == NULL) { |
| error("d2i_x509 failed"); |
| goto fail; |
| } |
| |
| evp = X509_get_pubkey(x509); |
| if (evp == NULL) { |
| error("X509_get_pubkey failed"); |
| goto fail; |
| } |
| |
| if (EVP_PKEY_base_id(evp) == EVP_PKEY_RSA) { |
| if (EVP_PKEY_get0_RSA(evp) == NULL) { |
| error("invalid x509; no rsa key"); |
| goto fail; |
| } |
| if ((rsa = RSAPublicKey_dup(EVP_PKEY_get0_RSA(evp))) == NULL) { |
| error("RSAPublicKey_dup failed"); |
| goto fail; |
| } |
| |
| if (pkcs11_rsa_wrap(p, slotidx, &cert_attr[0], rsa)) |
| goto fail; |
| |
| key = sshkey_new(KEY_UNSPEC); |
| if (key == NULL) { |
| error("sshkey_new failed"); |
| goto fail; |
| } |
| |
| key->rsa = rsa; |
| key->type = KEY_RSA; |
| key->flags |= SSHKEY_FLAG_EXT; |
| rsa = NULL; /* now owned by key */ |
| #ifdef HAVE_EC_KEY_METHOD_NEW |
| } else if (EVP_PKEY_base_id(evp) == EVP_PKEY_EC) { |
| if (EVP_PKEY_get0_EC_KEY(evp) == NULL) { |
| error("invalid x509; no ec key"); |
| goto fail; |
| } |
| if ((ec = EC_KEY_dup(EVP_PKEY_get0_EC_KEY(evp))) == NULL) { |
| error("EC_KEY_dup failed"); |
| goto fail; |
| } |
| |
| nid = sshkey_ecdsa_key_to_nid(ec); |
| if (nid < 0) { |
| error("couldn't get curve nid"); |
| goto fail; |
| } |
| |
| if (pkcs11_ecdsa_wrap(p, slotidx, &cert_attr[0], ec)) |
| goto fail; |
| |
| key = sshkey_new(KEY_UNSPEC); |
| if (key == NULL) { |
| error("sshkey_new failed"); |
| goto fail; |
| } |
| |
| key->ecdsa = ec; |
| key->ecdsa_nid = nid; |
| key->type = KEY_ECDSA; |
| key->flags |= SSHKEY_FLAG_EXT; |
| ec = NULL; /* now owned by key */ |
| #endif /* HAVE_EC_KEY_METHOD_NEW */ |
| } else |
| error("unknown certificate key type"); |
| |
| fail: |
| for (i = 0; i < 3; i++) |
| free(cert_attr[i].pValue); |
| X509_free(x509); |
| RSA_free(rsa); |
| #ifdef OPENSSL_HAS_ECC |
| EC_KEY_free(ec); |
| #endif |
| |
| return (key); |
| } |
| |
| #if 0 |
| static int |
| have_rsa_key(const RSA *rsa) |
| { |
| const BIGNUM *rsa_n, *rsa_e; |
| |
| RSA_get0_key(rsa, &rsa_n, &rsa_e, NULL); |
| return rsa_n != NULL && rsa_e != NULL; |
| } |
| #endif |
| |
| /* |
| * lookup certificates for token in slot identified by slotidx, |
| * add 'wrapped' public keys to the 'keysp' array and increment nkeys. |
| * keysp points to an (possibly empty) array with *nkeys keys. |
| */ |
| static int |
| pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx, |
| struct sshkey ***keysp, int *nkeys) |
| { |
| struct sshkey *key = NULL; |
| CK_OBJECT_CLASS key_class; |
| CK_ATTRIBUTE key_attr[1]; |
| CK_SESSION_HANDLE session; |
| CK_FUNCTION_LIST *f = NULL; |
| CK_RV rv; |
| CK_OBJECT_HANDLE obj; |
| CK_ULONG n = 0; |
| int ret = -1; |
| |
| memset(&key_attr, 0, sizeof(key_attr)); |
| memset(&obj, 0, sizeof(obj)); |
| |
| key_class = CKO_CERTIFICATE; |
| key_attr[0].type = CKA_CLASS; |
| key_attr[0].pValue = &key_class; |
| key_attr[0].ulValueLen = sizeof(key_class); |
| |
| session = p->slotinfo[slotidx].session; |
| f = p->function_list; |
| |
| rv = f->C_FindObjectsInit(session, key_attr, 1); |
| if (rv != CKR_OK) { |
| error("C_FindObjectsInit failed: %lu", rv); |
| goto fail; |
| } |
| |
| while (1) { |
| CK_CERTIFICATE_TYPE ck_cert_type; |
| |
| rv = f->C_FindObjects(session, &obj, 1, &n); |
| if (rv != CKR_OK) { |
| error("C_FindObjects failed: %lu", rv); |
| goto fail; |
| } |
| if (n == 0) |
| break; |
| |
| memset(&ck_cert_type, 0, sizeof(ck_cert_type)); |
| memset(&key_attr, 0, sizeof(key_attr)); |
| key_attr[0].type = CKA_CERTIFICATE_TYPE; |
| key_attr[0].pValue = &ck_cert_type; |
| key_attr[0].ulValueLen = sizeof(ck_cert_type); |
| |
| rv = f->C_GetAttributeValue(session, obj, key_attr, 1); |
| if (rv != CKR_OK) { |
| error("C_GetAttributeValue failed: %lu", rv); |
| goto fail; |
| } |
| |
| switch (ck_cert_type) { |
| case CKC_X_509: |
| key = pkcs11_fetch_x509_pubkey(p, slotidx, &obj); |
| break; |
| default: |
| /* XXX print key type? */ |
| key = NULL; |
| error("skipping unsupported certificate type"); |
| } |
| |
| if (key == NULL) { |
| error("failed to fetch key"); |
| continue; |
| } |
| |
| if (pkcs11_key_included(keysp, nkeys, key)) { |
| sshkey_free(key); |
| } else { |
| /* expand key array and add key */ |
| *keysp = xrecallocarray(*keysp, *nkeys, |
| *nkeys + 1, sizeof(struct sshkey *)); |
| (*keysp)[*nkeys] = key; |
| *nkeys = *nkeys + 1; |
| debug("have %d keys", *nkeys); |
| } |
| } |
| |
| ret = 0; |
| fail: |
| rv = f->C_FindObjectsFinal(session); |
| if (rv != CKR_OK) { |
| error("C_FindObjectsFinal failed: %lu", rv); |
| ret = -1; |
| } |
| |
| return (ret); |
| } |
| |
| /* |
| * lookup public keys for token in slot identified by slotidx, |
| * add 'wrapped' public keys to the 'keysp' array and increment nkeys. |
| * keysp points to an (possibly empty) array with *nkeys keys. |
| */ |
| static int |
| pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx, |
| struct sshkey ***keysp, int *nkeys) |
| { |
| struct sshkey *key = NULL; |
| CK_OBJECT_CLASS key_class; |
| CK_ATTRIBUTE key_attr[1]; |
| CK_SESSION_HANDLE session; |
| CK_FUNCTION_LIST *f = NULL; |
| CK_RV rv; |
| CK_OBJECT_HANDLE obj; |
| CK_ULONG n = 0; |
| int ret = -1; |
| |
| memset(&key_attr, 0, sizeof(key_attr)); |
| memset(&obj, 0, sizeof(obj)); |
| |
| key_class = CKO_PUBLIC_KEY; |
| key_attr[0].type = CKA_CLASS; |
| key_attr[0].pValue = &key_class; |
| key_attr[0].ulValueLen = sizeof(key_class); |
| |
| session = p->slotinfo[slotidx].session; |
| f = p->function_list; |
| |
| rv = f->C_FindObjectsInit(session, key_attr, 1); |
| if (rv != CKR_OK) { |
| error("C_FindObjectsInit failed: %lu", rv); |
| goto fail; |
| } |
| |
| while (1) { |
| CK_KEY_TYPE ck_key_type; |
| |
| rv = f->C_FindObjects(session, &obj, 1, &n); |
| if (rv != CKR_OK) { |
| error("C_FindObjects failed: %lu", rv); |
| goto fail; |
| } |
| if (n == 0) |
| break; |
| |
| memset(&ck_key_type, 0, sizeof(ck_key_type)); |
| memset(&key_attr, 0, sizeof(key_attr)); |
| key_attr[0].type = CKA_KEY_TYPE; |
| key_attr[0].pValue = &ck_key_type; |
| key_attr[0].ulValueLen = sizeof(ck_key_type); |
| |
| rv = f->C_GetAttributeValue(session, obj, key_attr, 1); |
| if (rv != CKR_OK) { |
| error("C_GetAttributeValue failed: %lu", rv); |
| goto fail; |
| } |
| |
| switch (ck_key_type) { |
| case CKK_RSA: |
| key = pkcs11_fetch_rsa_pubkey(p, slotidx, &obj); |
| break; |
| #ifdef HAVE_EC_KEY_METHOD_NEW |
| case CKK_ECDSA: |
| key = pkcs11_fetch_ecdsa_pubkey(p, slotidx, &obj); |
| break; |
| #endif /* HAVE_EC_KEY_METHOD_NEW */ |
| default: |
| /* XXX print key type? */ |
| key = NULL; |
| error("skipping unsupported key type"); |
| } |
| |
| if (key == NULL) { |
| error("failed to fetch key"); |
| continue; |
| } |
| |
| if (pkcs11_key_included(keysp, nkeys, key)) { |
| sshkey_free(key); |
| } else { |
| /* expand key array and add key */ |
| *keysp = xrecallocarray(*keysp, *nkeys, |
| *nkeys + 1, sizeof(struct sshkey *)); |
| (*keysp)[*nkeys] = key; |
| *nkeys = *nkeys + 1; |
| debug("have %d keys", *nkeys); |
| } |
| } |
| |
| ret = 0; |
| fail: |
| rv = f->C_FindObjectsFinal(session); |
| if (rv != CKR_OK) { |
| error("C_FindObjectsFinal failed: %lu", rv); |
| ret = -1; |
| } |
| |
| return (ret); |
| } |
| |
| #ifdef WITH_PKCS11_KEYGEN |
| #define FILL_ATTR(attr, idx, typ, val, len) \ |
| { (attr[idx]).type=(typ); (attr[idx]).pValue=(val); (attr[idx]).ulValueLen=len; idx++; } |
| |
| static struct sshkey * |
| pkcs11_rsa_generate_private_key(struct pkcs11_provider *p, CK_ULONG slotidx, |
| char *label, CK_ULONG bits, CK_BYTE keyid, u_int32_t *err) |
| { |
| struct pkcs11_slotinfo *si; |
| char *plabel = label ? label : ""; |
| int npub = 0, npriv = 0; |
| CK_RV rv; |
| CK_FUNCTION_LIST *f; |
| CK_SESSION_HANDLE session; |
| CK_BBOOL true_val = CK_TRUE, false_val = CK_FALSE; |
| CK_OBJECT_HANDLE pubKey, privKey; |
| CK_ATTRIBUTE tpub[16], tpriv[16]; |
| CK_MECHANISM mech = { |
| CKM_RSA_PKCS_KEY_PAIR_GEN, NULL_PTR, 0 |
| }; |
| CK_BYTE pubExponent[] = { |
| 0x01, 0x00, 0x01 /* RSA_F4 in bytes */ |
| }; |
| pubkey_filter[0].pValue = &pubkey_class; |
| cert_filter[0].pValue = &cert_class; |
| |
| *err = 0; |
| |
| FILL_ATTR(tpub, npub, CKA_TOKEN, &true_val, sizeof(true_val)); |
| FILL_ATTR(tpub, npub, CKA_LABEL, plabel, strlen(plabel)); |
| FILL_ATTR(tpub, npub, CKA_ENCRYPT, &false_val, sizeof(false_val)); |
| FILL_ATTR(tpub, npub, CKA_VERIFY, &true_val, sizeof(true_val)); |
| FILL_ATTR(tpub, npub, CKA_VERIFY_RECOVER, &false_val, |
| sizeof(false_val)); |
| FILL_ATTR(tpub, npub, CKA_WRAP, &false_val, sizeof(false_val)); |
| FILL_ATTR(tpub, npub, CKA_DERIVE, &false_val, sizeof(false_val)); |
| FILL_ATTR(tpub, npub, CKA_MODULUS_BITS, &bits, sizeof(bits)); |
| FILL_ATTR(tpub, npub, CKA_PUBLIC_EXPONENT, pubExponent, |
| sizeof(pubExponent)); |
| FILL_ATTR(tpub, npub, CKA_ID, &keyid, sizeof(keyid)); |
| |
| FILL_ATTR(tpriv, npriv, CKA_TOKEN, &true_val, sizeof(true_val)); |
| FILL_ATTR(tpriv, npriv, CKA_LABEL, plabel, strlen(plabel)); |
| FILL_ATTR(tpriv, npriv, CKA_PRIVATE, &true_val, sizeof(true_val)); |
| FILL_ATTR(tpriv, npriv, CKA_SENSITIVE, &true_val, sizeof(true_val)); |
| FILL_ATTR(tpriv, npriv, CKA_DECRYPT, &false_val, sizeof(false_val)); |
| FILL_ATTR(tpriv, npriv, CKA_SIGN, &true_val, sizeof(true_val)); |
| FILL_ATTR(tpriv, npriv, CKA_SIGN_RECOVER, &false_val, |
| sizeof(false_val)); |
| FILL_ATTR(tpriv, npriv, CKA_UNWRAP, &false_val, sizeof(false_val)); |
| FILL_ATTR(tpriv, npriv, CKA_DERIVE, &false_val, sizeof(false_val)); |
| FILL_ATTR(tpriv, npriv, CKA_ID, &keyid, sizeof(keyid)); |
| |
| f = p->function_list; |
| si = &p->slotinfo[slotidx]; |
| session = si->session; |
| |
| if ((rv = f->C_GenerateKeyPair(session, &mech, tpub, npub, tpriv, npriv, |
| &pubKey, &privKey)) != CKR_OK) { |
| error("%s: key generation failed: error 0x%lx", __func__, rv); |
| *err = rv; |
| return NULL; |
| } |
| |
| return pkcs11_fetch_rsa_pubkey(p, slotidx, &pubKey); |
| } |
| |
| static int |
| pkcs11_decode_hex(const char *hex, unsigned char **dest, size_t *rlen) |
| { |
| size_t i, len; |
| char ptr[3]; |
| |
| if (dest) |
| *dest = NULL; |
| if (rlen) |
| *rlen = 0; |
| |
| if ((len = strlen(hex)) % 2) |
| return -1; |
| len /= 2; |
| |
| *dest = xmalloc(len); |
| |
| ptr[2] = '\0'; |
| for (i = 0; i < len; i++) { |
| ptr[0] = hex[2 * i]; |
| ptr[1] = hex[(2 * i) + 1]; |
| if (!isxdigit(ptr[0]) || !isxdigit(ptr[1])) |
| return -1; |
| (*dest)[i] = (unsigned char)strtoul(ptr, NULL, 16); |
| } |
| |
| if (rlen) |
| *rlen = len; |
| |
| return 0; |
| } |
| |
| static struct ec_curve_info { |
| const char *name; |
| const char *oid; |
| const char *oid_encoded; |
| size_t size; |
| } ec_curve_infos[] = { |
| {"prime256v1", "1.2.840.10045.3.1.7", "06082A8648CE3D030107", 256}, |
| {"secp384r1", "1.3.132.0.34", "06052B81040022", 384}, |
| {"secp521r1", "1.3.132.0.35", "06052B81040023", 521}, |
| {NULL, NULL, NULL, 0}, |
| }; |
| |
| static struct sshkey * |
| pkcs11_ecdsa_generate_private_key(struct pkcs11_provider *p, CK_ULONG slotidx, |
| char *label, CK_ULONG bits, CK_BYTE keyid, u_int32_t *err) |
| { |
| struct pkcs11_slotinfo *si; |
| char *plabel = label ? label : ""; |
| int i; |
| size_t ecparams_size; |
| unsigned char *ecparams = NULL; |
| int npub = 0, npriv = 0; |
| CK_RV rv; |
| CK_FUNCTION_LIST *f; |
| CK_SESSION_HANDLE session; |
| CK_BBOOL true_val = CK_TRUE, false_val = CK_FALSE; |
| CK_OBJECT_HANDLE pubKey, privKey; |
| CK_MECHANISM mech = { |
| CKM_EC_KEY_PAIR_GEN, NULL_PTR, 0 |
| }; |
| CK_ATTRIBUTE tpub[16], tpriv[16]; |
| |
| *err = 0; |
| |
| for (i = 0; ec_curve_infos[i].name; i++) { |
| if (ec_curve_infos[i].size == bits) |
| break; |
| } |
| if (!ec_curve_infos[i].name) { |
| error("%s: invalid key size %lu", __func__, bits); |
| return NULL; |
| } |
| if (pkcs11_decode_hex(ec_curve_infos[i].oid_encoded, &ecparams, |
| &ecparams_size) == -1) { |
| error("%s: invalid oid", __func__); |
| return NULL; |
| } |
| |
| FILL_ATTR(tpub, npub, CKA_TOKEN, &true_val, sizeof(true_val)); |
| FILL_ATTR(tpub, npub, CKA_LABEL, plabel, strlen(plabel)); |
| FILL_ATTR(tpub, npub, CKA_ENCRYPT, &false_val, sizeof(false_val)); |
| FILL_ATTR(tpub, npub, CKA_VERIFY, &true_val, sizeof(true_val)); |
| FILL_ATTR(tpub, npub, CKA_VERIFY_RECOVER, &false_val, |
| sizeof(false_val)); |
| FILL_ATTR(tpub, npub, CKA_WRAP, &false_val, sizeof(false_val)); |
| FILL_ATTR(tpub, npub, CKA_DERIVE, &false_val, sizeof(false_val)); |
| FILL_ATTR(tpub, npub, CKA_EC_PARAMS, ecparams, ecparams_size); |
| FILL_ATTR(tpub, npub, CKA_ID, &keyid, sizeof(keyid)); |
| |
| FILL_ATTR(tpriv, npriv, CKA_TOKEN, &true_val, sizeof(true_val)); |
| FILL_ATTR(tpriv, npriv, CKA_LABEL, plabel, strlen(plabel)); |
| FILL_ATTR(tpriv, npriv, CKA_PRIVATE, &true_val, sizeof(true_val)); |
| FILL_ATTR(tpriv, npriv, CKA_SENSITIVE, &true_val, sizeof(true_val)); |
| FILL_ATTR(tpriv, npriv, CKA_DECRYPT, &false_val, sizeof(false_val)); |
| FILL_ATTR(tpriv, npriv, CKA_SIGN, &true_val, sizeof(true_val)); |
| FILL_ATTR(tpriv, npriv, CKA_SIGN_RECOVER, &false_val, |
| sizeof(false_val)); |
| FILL_ATTR(tpriv, npriv, CKA_UNWRAP, &false_val, sizeof(false_val)); |
| FILL_ATTR(tpriv, npriv, CKA_DERIVE, &false_val, sizeof(false_val)); |
| FILL_ATTR(tpriv, npriv, CKA_ID, &keyid, sizeof(keyid)); |
| |
| f = p->function_list; |
| si = &p->slotinfo[slotidx]; |
| session = si->session; |
| |
| if ((rv = f->C_GenerateKeyPair(session, &mech, tpub, npub, tpriv, npriv, |
| &pubKey, &privKey)) != CKR_OK) { |
| error("%s: key generation failed: error 0x%lx", __func__, rv); |
| *err = rv; |
| return NULL; |
| } |
| |
| return pkcs11_fetch_ecdsa_pubkey(p, slotidx, &pubKey); |
| } |
| #endif /* WITH_PKCS11_KEYGEN */ |
| |
| /* |
| * register a new provider, fails if provider already exists. if |
| * keyp is provided, fetch keys. |
| */ |
| static int |
| pkcs11_register_provider(char *provider_id, char *pin, struct sshkey ***keyp, |
| struct pkcs11_provider **providerp, CK_ULONG user) |
| { |
| int nkeys, need_finalize = 0; |
| int ret = -1; |
| struct pkcs11_provider *p = NULL; |
| void *handle = NULL; |
| CK_RV (*getfunctionlist)(CK_FUNCTION_LIST **); |
| CK_RV rv; |
| CK_FUNCTION_LIST *f = NULL; |
| CK_TOKEN_INFO *token; |
| CK_ULONG i; |
| |
| if (providerp == NULL) |
| goto fail; |
| *providerp = NULL; |
| |
| if (keyp != NULL) |
| *keyp = NULL; |
| |
| if (pkcs11_provider_lookup(provider_id) != NULL) { |
| debug("%s: provider already registered: %s", |
| __func__, provider_id); |
| goto fail; |
| } |
| /* open shared pkcs11-library */ |
| if ((handle = dlopen(provider_id, RTLD_NOW)) == NULL) { |
| error("dlopen %s failed: %s", provider_id, dlerror()); |
| goto fail; |
| } |
| if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL) { |
| error("dlsym(C_GetFunctionList) failed: %s", dlerror()); |
| goto fail; |
| } |
| p = xcalloc(1, sizeof(*p)); |
| p->name = xstrdup(provider_id); |
| p->handle = handle; |
| /* setup the pkcs11 callbacks */ |
| if ((rv = (*getfunctionlist)(&f)) != CKR_OK) { |
| error("C_GetFunctionList for provider %s failed: %lu", |
| provider_id, rv); |
| goto fail; |
| } |
| p->function_list = f; |
| if ((rv = f->C_Initialize(NULL)) != CKR_OK) { |
| error("C_Initialize for provider %s failed: %lu", |
| provider_id, rv); |
| goto fail; |
| } |
| need_finalize = 1; |
| if ((rv = f->C_GetInfo(&p->info)) != CKR_OK) { |
| error("C_GetInfo for provider %s failed: %lu", |
| provider_id, rv); |
| goto fail; |
| } |
| rmspace(p->info.manufacturerID, sizeof(p->info.manufacturerID)); |
| rmspace(p->info.libraryDescription, sizeof(p->info.libraryDescription)); |
| debug("provider %s: manufacturerID <%s> cryptokiVersion %d.%d" |
| " libraryDescription <%s> libraryVersion %d.%d", |
| provider_id, |
| p->info.manufacturerID, |
| p->info.cryptokiVersion.major, |
| p->info.cryptokiVersion.minor, |
| p->info.libraryDescription, |
| p->info.libraryVersion.major, |
| p->info.libraryVersion.minor); |
| if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &p->nslots)) != CKR_OK) { |
| error("C_GetSlotList failed: %lu", rv); |
| goto fail; |
| } |
| if (p->nslots == 0) { |
| debug("%s: provider %s returned no slots", __func__, |
| provider_id); |
| ret = -SSH_PKCS11_ERR_NO_SLOTS; |
| goto fail; |
| } |
| p->slotlist = xcalloc(p->nslots, sizeof(CK_SLOT_ID)); |
| if ((rv = f->C_GetSlotList(CK_TRUE, p->slotlist, &p->nslots)) |
| != CKR_OK) { |
| error("C_GetSlotList for provider %s failed: %lu", |
| provider_id, rv); |
| goto fail; |
| } |
| p->slotinfo = xcalloc(p->nslots, sizeof(struct pkcs11_slotinfo)); |
| p->valid = 1; |
| nkeys = 0; |
| for (i = 0; i < p->nslots; i++) { |
| token = &p->slotinfo[i].token; |
| if ((rv = f->C_GetTokenInfo(p->slotlist[i], token)) |
| != CKR_OK) { |
| error("C_GetTokenInfo for provider %s slot %lu " |
| "failed: %lu", provider_id, (unsigned long)i, rv); |
| continue; |
| } |
| if ((token->flags & CKF_TOKEN_INITIALIZED) == 0) { |
| debug2("%s: ignoring uninitialised token in " |
| "provider %s slot %lu", __func__, |
| provider_id, (unsigned long)i); |
| continue; |
| } |
| rmspace(token->label, sizeof(token->label)); |
| rmspace(token->manufacturerID, sizeof(token->manufacturerID)); |
| rmspace(token->model, sizeof(token->model)); |
| rmspace(token->serialNumber, sizeof(token->serialNumber)); |
| debug("provider %s slot %lu: label <%s> manufacturerID <%s> " |
| "model <%s> serial <%s> flags 0x%lx", |
| provider_id, (unsigned long)i, |
| token->label, token->manufacturerID, token->model, |
| token->serialNumber, token->flags); |
| /* |
| * open session, login with pin and retrieve public |
| * keys (if keyp is provided) |
| */ |
| if ((ret = pkcs11_open_session(p, i, pin, user)) != 0 || |
| keyp == NULL) |
| continue; |
| pkcs11_fetch_keys(p, i, keyp, &nkeys); |
| pkcs11_fetch_certs(p, i, keyp, &nkeys); |
| if (nkeys == 0 && !p->slotinfo[i].logged_in && |
| pkcs11_interactive) { |
| /* |
| * Some tokens require login before they will |
| * expose keys. |
| */ |
| if (pkcs11_login_slot(p, &p->slotinfo[i], |
| CKU_USER) < 0) { |
| error("login failed"); |
| continue; |
| } |
| pkcs11_fetch_keys(p, i, keyp, &nkeys); |
| pkcs11_fetch_certs(p, i, keyp, &nkeys); |
| } |
| } |
| |
| /* now owned by caller */ |
| *providerp = p; |
| |
| TAILQ_INSERT_TAIL(&pkcs11_providers, p, next); |
| p->refcount++; /* add to provider list */ |
| |
| return (nkeys); |
| fail: |
| if (need_finalize && (rv = f->C_Finalize(NULL)) != CKR_OK) |
| error("C_Finalize for provider %s failed: %lu", |
| provider_id, rv); |
| if (p) { |
| free(p->name); |
| free(p->slotlist); |
| free(p->slotinfo); |
| free(p); |
| } |
| if (handle) |
| dlclose(handle); |
| return (ret); |
| } |
| |
| /* |
| * register a new provider and get number of keys hold by the token, |
| * fails if provider already exists |
| */ |
| int |
| pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp) |
| { |
| struct pkcs11_provider *p = NULL; |
| int nkeys; |
| |
| nkeys = pkcs11_register_provider(provider_id, pin, keyp, &p, CKU_USER); |
| |
| /* no keys found or some other error, de-register provider */ |
| if (nkeys <= 0 && p != NULL) { |
| TAILQ_REMOVE(&pkcs11_providers, p, next); |
| pkcs11_provider_finalize(p); |
| pkcs11_provider_unref(p); |
| } |
| if (nkeys == 0) |
| debug("%s: provider %s returned no keys", __func__, |
| provider_id); |
| |
| return (nkeys); |
| } |
| |
| #ifdef WITH_PKCS11_KEYGEN |
| struct sshkey * |
| pkcs11_gakp(char *provider_id, char *pin, unsigned int slotidx, char *label, |
| unsigned int type, unsigned int bits, unsigned char keyid, u_int32_t *err) |
| { |
| struct pkcs11_provider *p = NULL; |
| struct pkcs11_slotinfo *si; |
| CK_FUNCTION_LIST *f; |
| CK_SESSION_HANDLE session; |
| struct sshkey *k = NULL; |
| int ret = -1, reset_pin = 0, reset_provider = 0; |
| CK_RV rv; |
| |
| *err = 0; |
| |
| if ((p = pkcs11_provider_lookup(provider_id)) != NULL) |
| debug("%s: provider \"%s\" available", __func__, provider_id); |
| else if ((ret = pkcs11_register_provider(provider_id, pin, NULL, &p, |
| CKU_SO)) < 0) { |
| debug("%s: could not register provider %s", __func__, |
| provider_id); |
| goto out; |
| } else |
| reset_provider = 1; |
| |
| f = p->function_list; |
| si = &p->slotinfo[slotidx]; |
| session = si->session; |
| |
| if ((rv = f->C_SetOperationState(session , pin, strlen(pin), |
| CK_INVALID_HANDLE, CK_INVALID_HANDLE)) != CKR_OK) { |
| debug("%s: could not supply SO pin: %lu", __func__, rv); |
| reset_pin = 0; |
| } else |
| reset_pin = 1; |
| |
| switch (type) { |
| case KEY_RSA: |
| if ((k = pkcs11_rsa_generate_private_key(p, slotidx, label, |
| bits, keyid, err)) == NULL) { |
| debug("%s: failed to generate RSA key", __func__); |
| goto out; |
| } |
| break; |
| case KEY_ECDSA: |
| if ((k = pkcs11_ecdsa_generate_private_key(p, slotidx, label, |
| bits, keyid, err)) == NULL) { |
| debug("%s: failed to generate ECDSA key", __func__); |
| goto out; |
| } |
| break; |
| default: |
| *err = SSH_PKCS11_ERR_GENERIC; |
| debug("%s: unknown type %d", __func__, type); |
| goto out; |
| } |
| |
| out: |
| if (reset_pin) |
| f->C_SetOperationState(session , NULL, 0, CK_INVALID_HANDLE, |
| CK_INVALID_HANDLE); |
| |
| if (reset_provider) |
| pkcs11_del_provider(provider_id); |
| |
| return (k); |
| } |
| |
| struct sshkey * |
| pkcs11_destroy_keypair(char *provider_id, char *pin, unsigned long slotidx, |
| unsigned char keyid, u_int32_t *err) |
| { |
| struct pkcs11_provider *p = NULL; |
| struct pkcs11_slotinfo *si; |
| struct sshkey *k = NULL; |
| int reset_pin = 0, reset_provider = 0; |
| CK_ULONG nattrs; |
| CK_FUNCTION_LIST *f; |
| CK_SESSION_HANDLE session; |
| CK_ATTRIBUTE attrs[16]; |
| CK_OBJECT_CLASS key_class; |
| CK_KEY_TYPE key_type; |
| CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE; |
| CK_RV rv; |
| |
| *err = 0; |
| |
| if ((p = pkcs11_provider_lookup(provider_id)) != NULL) { |
| debug("%s: using provider \"%s\"", __func__, provider_id); |
| } else if (pkcs11_register_provider(provider_id, pin, NULL, &p, |
| CKU_SO) < 0) { |
| debug("%s: could not register provider %s", __func__, |
| provider_id); |
| goto out; |
| } else |
| reset_provider = 1; |
| |
| f = p->function_list; |
| si = &p->slotinfo[slotidx]; |
| session = si->session; |
| |
| if ((rv = f->C_SetOperationState(session , pin, strlen(pin), |
| CK_INVALID_HANDLE, CK_INVALID_HANDLE)) != CKR_OK) { |
| debug("%s: could not supply SO pin: %lu", __func__, rv); |
| reset_pin = 0; |
| } else |
| reset_pin = 1; |
| |
| /* private key */ |
| nattrs = 0; |
| key_class = CKO_PRIVATE_KEY; |
| FILL_ATTR(attrs, nattrs, CKA_CLASS, &key_class, sizeof(key_class)); |
| FILL_ATTR(attrs, nattrs, CKA_ID, &keyid, sizeof(keyid)); |
| |
| if (pkcs11_find(p, slotidx, attrs, nattrs, &obj) == 0 && |
| obj != CK_INVALID_HANDLE) { |
| if ((rv = f->C_DestroyObject(session, obj)) != CKR_OK) { |
| debug("%s: could not destroy private key 0x%hhx", |
| __func__, keyid); |
| *err = rv; |
| goto out; |
| } |
| } |
| |
| /* public key */ |
| nattrs = 0; |
| key_class = CKO_PUBLIC_KEY; |
| FILL_ATTR(attrs, nattrs, CKA_CLASS, &key_class, sizeof(key_class)); |
| FILL_ATTR(attrs, nattrs, CKA_ID, &keyid, sizeof(keyid)); |
| |
| if (pkcs11_find(p, slotidx, attrs, nattrs, &obj) == 0 && |
| obj != CK_INVALID_HANDLE) { |
| |
| /* get key type */ |
| nattrs = 0; |
| FILL_ATTR(attrs, nattrs, CKA_KEY_TYPE, &key_type, |
| sizeof(key_type)); |
| rv = f->C_GetAttributeValue(session, obj, attrs, nattrs); |
| if (rv != CKR_OK) { |
| debug("%s: could not get key type of public key 0x%hhx", |
| __func__, keyid); |
| *err = rv; |
| key_type = -1; |
| } |
| if (key_type == CKK_RSA) |
| k = pkcs11_fetch_rsa_pubkey(p, slotidx, &obj); |
| else if (key_type == CKK_ECDSA) |
| k = pkcs11_fetch_ecdsa_pubkey(p, slotidx, &obj); |
| |
| if ((rv = f->C_DestroyObject(session, obj)) != CKR_OK) { |
| debug("%s: could not destroy public key 0x%hhx", |
| __func__, keyid); |
| *err = rv; |
| goto out; |
| } |
| } |
| |
| out: |
| if (reset_pin) |
| f->C_SetOperationState(session , NULL, 0, CK_INVALID_HANDLE, |
| CK_INVALID_HANDLE); |
| |
| if (reset_provider) |
| pkcs11_del_provider(provider_id); |
| |
| return (k); |
| } |
| #endif /* WITH_PKCS11_KEYGEN */ |
| #else /* ENABLE_PKCS11 */ |
| |
| #include <sys/types.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| |
| #include "log.h" |
| #include "sshkey.h" |
| |
| int |
| pkcs11_init(int interactive) |
| { |
| error("%s: dlopen() not supported", __func__); |
| return (-1); |
| } |
| |
| int |
| pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp) |
| { |
| error("%s: dlopen() not supported", __func__); |
| return (-1); |
| } |
| |
| void |
| pkcs11_terminate(void) |
| { |
| error("%s: dlopen() not supported", __func__); |
| } |
| #endif /* ENABLE_PKCS11 */ |