version 1.27, 2018/09/13 02:08:33 |
version 1.28, 2019/01/20 22:51:37 |
|
|
/* $OpenBSD$ */ |
/* $OpenBSD$ */ |
/* |
/* |
* Copyright (c) 2010 Markus Friedl. All rights reserved. |
* 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 |
* Permission to use, copy, modify, and distribute this software for any |
* purpose with or without fee is hereby granted, provided that the above |
* purpose with or without fee is hereby granted, provided that the above |
|
|
#include <stdarg.h> |
#include <stdarg.h> |
#include <stdio.h> |
#include <stdio.h> |
|
|
|
#include <ctype.h> |
#include <string.h> |
#include <string.h> |
#include <dlfcn.h> |
#include <dlfcn.h> |
|
|
|
#include <openssl/ecdsa.h> |
#include <openssl/x509.h> |
#include <openssl/x509.h> |
|
#include <openssl/err.h> |
|
|
#define CRYPTOKI_COMPAT |
#define CRYPTOKI_COMPAT |
#include "pkcs11.h" |
#include "pkcs11.h" |
|
|
CK_ULONG slotidx; |
CK_ULONG slotidx; |
int (*orig_finish)(RSA *rsa); |
int (*orig_finish)(RSA *rsa); |
RSA_METHOD *rsa_method; |
RSA_METHOD *rsa_method; |
|
EC_KEY_METHOD *ec_key_method; |
char *keyid; |
char *keyid; |
int keyid_len; |
int keyid_len; |
}; |
}; |
|
|
int pkcs11_interactive = 0; |
int pkcs11_interactive = 0; |
|
|
|
#ifdef HAVE_DLOPEN |
|
static void |
|
ossl_error(const char *msg) |
|
{ |
|
unsigned long e; |
|
|
|
while ((e = ERR_get_error()) != 0) |
|
error("%s: %s: %.100s", __func__, msg, |
|
ERR_error_string(e, NULL)); |
|
} |
|
#endif |
|
|
int |
int |
pkcs11_init(int interactive) |
pkcs11_init(int interactive) |
{ |
{ |
|
|
} |
} |
|
|
/* |
/* |
* finalize a provider shared libarary, it's no longer usable. |
* finalize a provider shared library, it's no longer usable. |
* however, there might still be keys referencing this provider, |
* however, there might still be keys referencing this provider, |
* so the actuall freeing of memory is handled by pkcs11_provider_unref(). |
* so the actual freeing of memory is handled by pkcs11_provider_unref(). |
* this is called when a provider gets unregistered. |
* this is called when a provider gets unregistered. |
*/ |
*/ |
static void |
static void |
|
|
if (--p->refcount <= 0) { |
if (--p->refcount <= 0) { |
if (p->valid) |
if (p->valid) |
error("pkcs11_provider_unref: %p still valid", p); |
error("pkcs11_provider_unref: %p still valid", p); |
|
free(p->name); |
free(p->slotlist); |
free(p->slotlist); |
free(p->slotinfo); |
free(p->slotinfo); |
free(p); |
free(p); |
|
|
return (-1); |
return (-1); |
} |
} |
|
|
|
#ifdef HAVE_DLOPEN |
/* openssl callback for freeing an RSA key */ |
/* openssl callback for freeing an RSA key */ |
static int |
static int |
pkcs11_rsa_finish(RSA *rsa) |
pkcs11_rsa_finish(RSA *rsa) |
|
|
return (ret); |
return (ret); |
} |
} |
|
|
/* openssl callback doing the actual signing operation */ |
|
static int |
static int |
pkcs11_rsa_private_encrypt(int flen, const u_char *from, u_char *to, RSA *rsa, |
pkcs11_get_key(struct pkcs11_key *k11, CK_MECHANISM_TYPE mech_type) |
int padding) |
|
{ |
{ |
struct pkcs11_key *k11; |
|
struct pkcs11_slotinfo *si; |
struct pkcs11_slotinfo *si; |
CK_FUNCTION_LIST *f; |
CK_FUNCTION_LIST *f; |
CK_OBJECT_HANDLE obj; |
CK_OBJECT_HANDLE obj; |
CK_ULONG tlen = 0; |
CK_RV rv; |
CK_RV rv; |
CK_OBJECT_CLASS private_key_class; |
CK_OBJECT_CLASS private_key_class = CKO_PRIVATE_KEY; |
CK_BBOOL true_val; |
CK_BBOOL true_val = CK_TRUE; |
CK_MECHANISM mech; |
CK_MECHANISM mech = { |
CK_ATTRIBUTE key_filter[3]; |
CKM_RSA_PKCS, NULL_PTR, 0 |
|
}; |
|
CK_ATTRIBUTE key_filter[] = { |
|
{CKA_CLASS, &private_key_class, sizeof(private_key_class) }, |
|
{CKA_ID, NULL, 0}, |
|
{CKA_SIGN, &true_val, sizeof(true_val) } |
|
}; |
|
char *pin = NULL, prompt[1024]; |
char *pin = NULL, prompt[1024]; |
int rval = -1; |
|
|
|
if ((k11 = RSA_get_app_data(rsa)) == NULL) { |
|
error("RSA_get_app_data failed for rsa %p", rsa); |
|
return (-1); |
|
} |
|
if (!k11->provider || !k11->provider->valid) { |
if (!k11->provider || !k11->provider->valid) { |
error("no pkcs11 (valid) provider for rsa %p", rsa); |
error("no pkcs11 (valid) provider found"); |
return (-1); |
return (-1); |
} |
} |
|
|
f = k11->provider->function_list; |
f = k11->provider->function_list; |
si = &k11->provider->slotinfo[k11->slotidx]; |
si = &k11->provider->slotinfo[k11->slotidx]; |
|
|
if ((si->token.flags & CKF_LOGIN_REQUIRED) && !si->logged_in) { |
if ((si->token.flags & CKF_LOGIN_REQUIRED) && !si->logged_in) { |
if (!pkcs11_interactive) { |
if (!pkcs11_interactive) { |
error("need pin entry%s", (si->token.flags & |
error("need pin entry%s", (si->token.flags & |
|
|
} |
} |
si->logged_in = 1; |
si->logged_in = 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].pValue = k11->keyid; |
key_filter[1].ulValueLen = k11->keyid_len; |
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 */ |
/* try to find object w/CKA_SIGN first, retry w/o */ |
if (pkcs11_find(k11->provider, k11->slotidx, key_filter, 3, &obj) < 0 && |
if (pkcs11_find(k11->provider, k11->slotidx, key_filter, 3, &obj) < 0 && |
pkcs11_find(k11->provider, k11->slotidx, key_filter, 2, &obj) < 0) { |
pkcs11_find(k11->provider, k11->slotidx, key_filter, 2, &obj) < 0) { |
error("cannot find private key"); |
error("cannot find private key"); |
} else if ((rv = f->C_SignInit(si->session, &mech, obj)) != CKR_OK) { |
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); |
error("C_SignInit failed: %lu", rv); |
} else { |
return (-1); |
/* XXX handle CKR_BUFFER_TOO_SMALL */ |
|
tlen = RSA_size(rsa); |
|
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 (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_app_data(rsa)) == NULL) { |
|
error("RSA_get_app_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); |
return (rval); |
} |
} |
|
|
|
|
return (0); |
return (0); |
} |
} |
|
|
|
/* 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; |
|
const u_char *cp; |
|
|
|
if ((k11 = EC_KEY_get_ex_data(ec, 0)) == 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; |
|
} |
|
cp = sig; |
|
ret = d2i_ECDSA_SIG(NULL, &cp, siglen); |
|
if (ret == NULL) { |
|
/* |
|
* d2i_ECDSA_SIG failed, so sig does not point to a DER-encoded |
|
* sequence, but to the concatenation r|s. |
|
*/ |
|
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 (BN_bin2bn(sig, bnlen, ret->r) == NULL || |
|
BN_bin2bn(sig+bnlen, bnlen, ret->s) == NULL) { |
|
ossl_error("d2i_ECDSA_SIG failed"); |
|
ECDSA_SIG_free(ret); |
|
ret = NULL; |
|
goto done; |
|
} |
|
} |
|
done: |
|
free(sig); |
|
|
|
return (ret); |
|
} |
|
|
|
static EC_KEY_METHOD *ec_key_method; |
|
|
|
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_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); |
|
k11->ec_key_method = ec_key_method; |
|
|
|
EC_KEY_set_method(ec, k11->ec_key_method); |
|
EC_KEY_set_ex_data(ec, 0, k11); |
|
|
|
return (0); |
|
} |
|
|
/* remove trailing spaces */ |
/* remove trailing spaces */ |
static void |
static void |
rmspace(u_char *buf, size_t len) |
rmspace(u_char *buf, size_t len) |
|
|
* if pin == NULL we delay login until key use |
* if pin == NULL we delay login until key use |
*/ |
*/ |
static int |
static int |
pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin) |
pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin, |
|
CK_ULONG user) |
{ |
{ |
CK_RV rv; |
CK_RV rv; |
CK_FUNCTION_LIST *f; |
CK_FUNCTION_LIST *f; |
CK_SESSION_HANDLE session; |
CK_SESSION_HANDLE session; |
int login_required; |
int login_required, ret; |
|
|
f = p->function_list; |
f = p->function_list; |
login_required = p->slotinfo[slotidx].token.flags & CKF_LOGIN_REQUIRED; |
login_required = p->slotinfo[slotidx].token.flags & CKF_LOGIN_REQUIRED; |
if (pin && login_required && !strlen(pin)) { |
if (pin && login_required && !strlen(pin)) { |
error("pin required"); |
error("pin required"); |
return (-1); |
return (-SSH_PKCS11_ERR_PIN_REQUIRED); |
} |
} |
if ((rv = f->C_OpenSession(p->slotlist[slotidx], CKF_RW_SESSION| |
if ((rv = f->C_OpenSession(p->slotlist[slotidx], CKF_RW_SESSION| |
CKF_SERIAL_SESSION, NULL, NULL, &session)) |
CKF_SERIAL_SESSION, NULL, NULL, &session)) |
|
|
return (-1); |
return (-1); |
} |
} |
if (login_required && pin) { |
if (login_required && pin) { |
rv = f->C_Login(session, CKU_USER, |
rv = f->C_Login(session, user, |
(u_char *)pin, strlen(pin)); |
(u_char *)pin, strlen(pin)); |
if (rv != CKR_OK && rv != CKR_USER_ALREADY_LOGGED_IN) { |
if (rv != CKR_OK && rv != CKR_USER_ALREADY_LOGGED_IN) { |
error("C_Login failed: %lu", rv); |
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) |
if ((rv = f->C_CloseSession(session)) != CKR_OK) |
error("C_CloseSession failed: %lu", rv); |
error("C_CloseSession failed: %lu", rv); |
return (-1); |
return (ret); |
} |
} |
p->slotinfo[slotidx].logged_in = 1; |
p->slotinfo[slotidx].logged_in = 1; |
} |
} |
|
|
return (0); |
return (0); |
} |
} |
|
|
/* |
|
* 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_filter(struct pkcs11_provider *, CK_ULONG, |
|
CK_ATTRIBUTE [], CK_ATTRIBUTE [3], struct sshkey ***, int *) |
|
__attribute__((__bounded__(__minbytes__,4, 3 * sizeof(CK_ATTRIBUTE)))); |
|
|
|
static int |
static int |
pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx, |
|
struct sshkey ***keysp, int *nkeys) |
|
{ |
|
CK_OBJECT_CLASS pubkey_class = CKO_PUBLIC_KEY; |
|
CK_OBJECT_CLASS cert_class = CKO_CERTIFICATE; |
|
CK_ATTRIBUTE pubkey_filter[] = { |
|
{ CKA_CLASS, &pubkey_class, sizeof(pubkey_class) } |
|
}; |
|
CK_ATTRIBUTE cert_filter[] = { |
|
{ CKA_CLASS, &cert_class, sizeof(cert_class) } |
|
}; |
|
CK_ATTRIBUTE pubkey_attribs[] = { |
|
{ CKA_ID, NULL, 0 }, |
|
{ CKA_MODULUS, NULL, 0 }, |
|
{ CKA_PUBLIC_EXPONENT, NULL, 0 } |
|
}; |
|
CK_ATTRIBUTE cert_attribs[] = { |
|
{ CKA_ID, NULL, 0 }, |
|
{ CKA_SUBJECT, NULL, 0 }, |
|
{ CKA_VALUE, NULL, 0 } |
|
}; |
|
|
|
if (pkcs11_fetch_keys_filter(p, slotidx, pubkey_filter, pubkey_attribs, |
|
keysp, nkeys) < 0 || |
|
pkcs11_fetch_keys_filter(p, slotidx, cert_filter, cert_attribs, |
|
keysp, nkeys) < 0) |
|
return (-1); |
|
return (0); |
|
} |
|
|
|
static int |
|
pkcs11_key_included(struct sshkey ***keysp, int *nkeys, struct sshkey *key) |
pkcs11_key_included(struct sshkey ***keysp, int *nkeys, struct sshkey *key) |
{ |
{ |
int i; |
int i; |
|
|
return (0); |
return (0); |
} |
} |
|
|
|
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; |
|
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 = (const unsigned char *)key_attr[1].pValue; |
|
if (o2i_ECPublicKey(&ec, &attrp, key_attr[1].ulValueLen) == NULL) { |
|
/* try to skip DER header (octet string type and length byte) */ |
|
attrp = (const unsigned char *)key_attr[1].pValue + 2; |
|
if (o2i_ECPublicKey(&ec, &attrp, key_attr[1].ulValueLen - 2) |
|
== 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); |
|
|
|
return (key); |
|
} |
|
|
|
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; |
|
int nid; |
|
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 */ |
|
} else if (EVP_PKEY_base_id(evp) == EVP_PKEY_EC) { |
|
/* XXX XXX fix accessor */ |
|
if (evp->pkey.ec == NULL) { |
|
error("invalid x509; no ec key"); |
|
goto fail; |
|
} |
|
if ((ec = EC_KEY_dup(evp->pkey.ec)) == 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 */ |
|
} 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 |
static int |
have_rsa_key(const RSA *rsa) |
have_rsa_key(const RSA *rsa) |
{ |
{ |
|
|
RSA_get0_key(rsa, &rsa_n, &rsa_e, NULL); |
RSA_get0_key(rsa, &rsa_n, &rsa_e, NULL); |
return rsa_n != NULL && 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 |
static int |
pkcs11_fetch_keys_filter(struct pkcs11_provider *p, CK_ULONG slotidx, |
pkcs11_fetch_certs(struct pkcs11_provider *p, CK_ULONG slotidx, |
CK_ATTRIBUTE filter[], CK_ATTRIBUTE attribs[3], |
|
struct sshkey ***keysp, int *nkeys) |
struct sshkey ***keysp, int *nkeys) |
{ |
{ |
struct sshkey *key; |
struct sshkey *key = NULL; |
RSA *rsa; |
CK_OBJECT_CLASS key_class; |
X509 *x509; |
CK_ATTRIBUTE key_attr[1]; |
EVP_PKEY *evp; |
CK_SESSION_HANDLE session; |
int i; |
CK_FUNCTION_LIST *f = NULL; |
const u_char *cp; |
CK_RV rv; |
CK_RV rv; |
CK_OBJECT_HANDLE obj; |
CK_OBJECT_HANDLE obj; |
CK_ULONG n = 0; |
CK_ULONG nfound; |
int ret = -1; |
CK_SESSION_HANDLE session; |
|
CK_FUNCTION_LIST *f; |
|
|
|
f = p->function_list; |
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; |
session = p->slotinfo[slotidx].session; |
/* setup a filter the looks for public keys */ |
f = p->function_list; |
if ((rv = f->C_FindObjectsInit(session, filter, 1)) != CKR_OK) { |
|
|
rv = f->C_FindObjectsInit(session, key_attr, 1); |
|
if (rv != CKR_OK) { |
error("C_FindObjectsInit failed: %lu", rv); |
error("C_FindObjectsInit failed: %lu", rv); |
return (-1); |
goto fail; |
} |
} |
|
|
while (1) { |
while (1) { |
/* XXX 3 attributes in attribs[] */ |
CK_CERTIFICATE_TYPE ck_cert_type; |
for (i = 0; i < 3; i++) { |
|
attribs[i].pValue = NULL; |
rv = f->C_FindObjects(session, &obj, 1, &n); |
attribs[i].ulValueLen = 0; |
if (rv != CKR_OK) { |
|
error("C_FindObjects failed: %lu", rv); |
|
goto fail; |
} |
} |
if ((rv = f->C_FindObjects(session, &obj, 1, &nfound)) != CKR_OK |
if (n == 0) |
|| nfound == 0) |
|
break; |
break; |
/* found a key, so figure out size of the attributes */ |
|
if ((rv = f->C_GetAttributeValue(session, obj, attribs, 3)) |
memset(&ck_cert_type, 0, sizeof(ck_cert_type)); |
!= CKR_OK) { |
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); |
error("C_GetAttributeValue failed: %lu", rv); |
continue; |
goto fail; |
} |
} |
/* |
|
* Allow CKA_ID (always first attribute) to be empty, but |
switch (ck_cert_type) { |
* ensure that none of the others are zero length. |
case CKC_X_509: |
* XXX assumes CKA_ID is always first. |
key = pkcs11_fetch_x509_pubkey(p, slotidx, &obj); |
*/ |
break; |
if (attribs[1].ulValueLen == 0 || |
default: |
attribs[2].ulValueLen == 0) { |
/* XXX print key type? */ |
|
error("skipping unsupported certificate type"); |
|
} |
|
|
|
if (key == NULL) { |
|
error("failed to fetch key"); |
continue; |
continue; |
} |
} |
/* allocate buffers for attributes */ |
|
for (i = 0; i < 3; i++) { |
if (pkcs11_key_included(keysp, nkeys, key)) { |
if (attribs[i].ulValueLen > 0) { |
sshkey_free(key); |
attribs[i].pValue = xmalloc( |
} else { |
attribs[i].ulValueLen); |
/* 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; |
* retrieve ID, modulus and public exponent of RSA key, |
fail: |
* or ID, subject and value for certificates. |
rv = f->C_FindObjectsFinal(session); |
*/ |
if (rv != CKR_OK) { |
rsa = NULL; |
error("C_FindObjectsFinal failed: %lu", rv); |
if ((rv = f->C_GetAttributeValue(session, obj, attribs, 3)) |
ret = -1; |
!= CKR_OK) { |
} |
|
|
|
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); |
error("C_GetAttributeValue failed: %lu", rv); |
} else if (attribs[1].type == CKA_MODULUS ) { |
goto fail; |
if ((rsa = RSA_new()) == NULL) { |
} |
error("RSA_new failed"); |
|
} else { |
|
BIGNUM *rsa_n, *rsa_e; |
|
|
|
rsa_n = BN_bin2bn(attribs[1].pValue, |
switch (ck_key_type) { |
attribs[1].ulValueLen, NULL); |
case CKK_RSA: |
rsa_e = BN_bin2bn(attribs[2].pValue, |
key = pkcs11_fetch_rsa_pubkey(p, slotidx, &obj); |
attribs[2].ulValueLen, NULL); |
break; |
if (rsa_n != NULL && rsa_e != NULL) { |
case CKK_ECDSA: |
if (!RSA_set0_key(rsa, |
key = pkcs11_fetch_ecdsa_pubkey(p, slotidx, &obj); |
rsa_n, rsa_e, NULL)) |
break; |
fatal("%s: set key", __func__); |
default: |
rsa_n = rsa_e = NULL; /* transferred */ |
/* XXX print key type? */ |
} |
error("skipping unsupported key type"); |
BN_free(rsa_n); |
|
BN_free(rsa_e); |
|
} |
|
} else { |
|
cp = attribs[2].pValue; |
|
if ((x509 = X509_new()) == NULL) { |
|
error("X509_new failed"); |
|
} else if (d2i_X509(&x509, &cp, attribs[2].ulValueLen) |
|
== NULL) { |
|
error("d2i_X509 failed"); |
|
} else if ((evp = X509_get_pubkey(x509)) == NULL || |
|
EVP_PKEY_base_id(evp) != EVP_PKEY_RSA || |
|
EVP_PKEY_get0_RSA(evp) == NULL) { |
|
debug("X509_get_pubkey failed or no rsa"); |
|
} else if ((rsa = RSAPublicKey_dup( |
|
EVP_PKEY_get0_RSA(evp))) == NULL) { |
|
error("RSAPublicKey_dup"); |
|
} |
|
X509_free(x509); |
|
} |
} |
if (rsa && have_rsa_key(rsa) && |
|
pkcs11_rsa_wrap(p, slotidx, &attribs[0], rsa) == 0) { |
if (key == NULL) { |
if ((key = sshkey_new(KEY_UNSPEC)) == NULL) |
error("failed to fetch key"); |
fatal("sshkey_new failed"); |
continue; |
key->rsa = rsa; |
|
key->type = KEY_RSA; |
|
key->flags |= SSHKEY_FLAG_EXT; |
|
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); |
|
} |
|
} else if (rsa) { |
|
RSA_free(rsa); |
|
} |
} |
for (i = 0; i < 3; i++) |
|
free(attribs[i].pValue); |
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); |
|
} |
} |
} |
if ((rv = f->C_FindObjectsFinal(session)) != CKR_OK) |
|
|
ret = 0; |
|
fail: |
|
rv = f->C_FindObjectsFinal(session); |
|
if (rv != CKR_OK) { |
error("C_FindObjectsFinal failed: %lu", rv); |
error("C_FindObjectsFinal failed: %lu", rv); |
return (0); |
ret = -1; |
|
} |
|
|
|
return (ret); |
} |
} |
|
|
#ifdef HAVE_DLOPEN |
#ifdef WITH_PKCS11_KEYGEN |
/* register a new provider, fails if provider already exists */ |
#define FILL_ATTR(attr, idx, typ, val, len) \ |
int |
{ (attr[idx]).type=(typ); (attr[idx]).pValue=(val); (attr[idx]).ulValueLen=len; idx++; } |
pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp) |
|
|
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 */ |
|
}; |
|
|
|
*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 nkeys, need_finalize = 0; |
|
int ret = -1; |
struct pkcs11_provider *p = NULL; |
struct pkcs11_provider *p = NULL; |
void *handle = NULL; |
void *handle = NULL; |
CK_RV (*getfunctionlist)(CK_FUNCTION_LIST **); |
CK_RV (*getfunctionlist)(CK_FUNCTION_LIST **); |
|
|
CK_TOKEN_INFO *token; |
CK_TOKEN_INFO *token; |
CK_ULONG i; |
CK_ULONG i; |
|
|
*keyp = NULL; |
if (providerp == NULL) |
|
goto fail; |
|
*providerp = NULL; |
|
|
|
if (keyp != NULL) |
|
*keyp = NULL; |
|
|
if (pkcs11_provider_lookup(provider_id) != NULL) { |
if (pkcs11_provider_lookup(provider_id) != NULL) { |
debug("%s: provider already registered: %s", |
debug("%s: provider already registered: %s", |
__func__, provider_id); |
__func__, provider_id); |
goto fail; |
goto fail; |
} |
} |
/* open shared pkcs11-libarary */ |
/* open shared pkcs11-library */ |
if ((handle = dlopen(provider_id, RTLD_NOW)) == NULL) { |
if ((handle = dlopen(provider_id, RTLD_NOW)) == NULL) { |
error("dlopen %s failed: %s", provider_id, dlerror()); |
error("dlopen %s failed: %s", provider_id, dlerror()); |
goto fail; |
goto fail; |
|
|
goto fail; |
goto fail; |
} |
} |
if (p->nslots == 0) { |
if (p->nslots == 0) { |
debug("%s: provider %s returned no slots", __func__, |
error("%s: provider %s returned no slots", __func__, |
provider_id); |
provider_id); |
|
ret = -SSH_PKCS11_ERR_NO_SLOTS; |
goto fail; |
goto fail; |
} |
} |
p->slotlist = xcalloc(p->nslots, sizeof(CK_SLOT_ID)); |
p->slotlist = xcalloc(p->nslots, sizeof(CK_SLOT_ID)); |
|
|
provider_id, (unsigned long)i, |
provider_id, (unsigned long)i, |
token->label, token->manufacturerID, token->model, |
token->label, token->manufacturerID, token->model, |
token->serialNumber, token->flags); |
token->serialNumber, token->flags); |
/* open session, login with pin and retrieve public keys */ |
/* |
if (pkcs11_open_session(p, i, pin) == 0) |
* 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_keys(p, i, keyp, &nkeys); |
|
pkcs11_fetch_certs(p, i, keyp, &nkeys); |
|
} |
} |
} |
if (nkeys > 0) { |
|
TAILQ_INSERT_TAIL(&pkcs11_providers, p, next); |
/* now owned by caller */ |
p->refcount++; /* add to provider list */ |
*providerp = p; |
return (nkeys); |
|
} |
TAILQ_INSERT_TAIL(&pkcs11_providers, p, next); |
debug("%s: provider %s returned no keys", __func__, provider_id); |
p->refcount++; /* add to provider list */ |
/* don't add the provider, since it does not have any keys */ |
|
|
return (nkeys); |
fail: |
fail: |
if (need_finalize && (rv = f->C_Finalize(NULL)) != CKR_OK) |
if (need_finalize && (rv = f->C_Finalize(NULL)) != CKR_OK) |
error("C_Finalize for provider %s failed: %lu", |
error("C_Finalize for provider %s failed: %lu", |
provider_id, rv); |
provider_id, rv); |
if (p) { |
if (p) { |
|
free(p->name); |
free(p->slotlist); |
free(p->slotlist); |
free(p->slotinfo); |
free(p->slotinfo); |
free(p); |
free(p); |
} |
} |
if (handle) |
if (handle) |
dlclose(handle); |
dlclose(handle); |
return (-1); |
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 |
#else |
int |
int |
pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp) |
pkcs11_add_provider(char *provider_id, char *pin, struct sshkey ***keyp) |
|
|
error("dlopen() not supported"); |
error("dlopen() not supported"); |
return (-1); |
return (-1); |
} |
} |
#endif |
#endif /* HAVE_DLOPEN */ |