openssh/ssh-pkcs11.c
djm@openbsd.org 41923ce06a upstream: Correct some bugs in PKCS#11 token PIN handling at
initial login, the attempt at reading the PIN could be skipped in some cases
especially on devices with integrated PIN readers.

based on patch from Daniel Kucera in bz#2652; ok markus@

OpenBSD-Commit-ID: fad70a61c60610afe8bb0db538c90e343e75e58e
2019-01-22 23:04:54 +11:00

1791 lines
45 KiB
C

/* $OpenBSD: ssh-pkcs11.c,v 1.41 2019/01/22 12:03:58 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(struct pkcs11_key *k11, CK_USER_TYPE type)
{
struct pkcs11_slotinfo *si;
CK_FUNCTION_LIST *f;
char *pin = NULL, prompt[1024];
CK_RV rv;
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 (!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 = f->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_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, have_pinpad, ret;
f = p->function_list;
si = &p->slotinfo[slotidx];
have_pinpad = si->token.flags & CKF_PROTECTED_AUTHENTICATION_PATH;
login_required = si->token.flags & CKF_LOGIN_REQUIRED;
/* fail early before opening session */
if (login_required && !have_pinpad && 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) {
if (have_pinpad && (pin == NULL || strlen(pin) == 0)) {
/* defer PIN entry to the reader keypad */
rv = f->C_Login(session, CKU_USER, NULL_PTR, 0);
} else {
rv = f->C_Login(session, CKU_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;
EC_KEY *ec = NULL;
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);
EC_KEY_free(ec);
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? */
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? */
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) {
error("%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) {
if (keyp == NULL)
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 */
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 */