1/* $NetBSD: ssh-pkcs11.c,v 1.4 2011/07/25 03:03:11 christos Exp $ */ 2/* $OpenBSD: ssh-pkcs11.c,v 1.6 2010/06/08 21:32:19 markus Exp $ */ 3/* 4 * Copyright (c) 2010 Markus Friedl. All rights reserved. 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18#include "includes.h" 19__RCSID("$NetBSD: ssh-pkcs11.c,v 1.4 2011/07/25 03:03:11 christos Exp $"); 20 21#include <sys/types.h> 22#include <sys/queue.h> 23#include <sys/time.h> 24#include <stdarg.h> 25#include <stdio.h> 26 27#include <string.h> 28#include <dlfcn.h> 29 30#define CRYPTOKI_COMPAT 31#include "pkcs11.h" 32 33#include "log.h" 34#include "misc.h" 35#include "key.h" 36#include "ssh-pkcs11.h" 37#include "xmalloc.h" 38 39struct pkcs11_slotinfo { 40 CK_TOKEN_INFO token; 41 CK_SESSION_HANDLE session; 42 int logged_in; 43}; 44 45struct pkcs11_provider { 46 char *name; 47 void *handle; 48 CK_FUNCTION_LIST *function_list; 49 CK_INFO info; 50 CK_ULONG nslots; 51 CK_SLOT_ID *slotlist; 52 struct pkcs11_slotinfo *slotinfo; 53 int valid; 54 int refcount; 55 TAILQ_ENTRY(pkcs11_provider) next; 56}; 57 58TAILQ_HEAD(, pkcs11_provider) pkcs11_providers; 59 60struct pkcs11_key { 61 struct pkcs11_provider *provider; 62 CK_ULONG slotidx; 63 int (*orig_finish)(RSA *rsa); 64 RSA_METHOD rsa_method; 65 char *keyid; 66 int keyid_len; 67}; 68 69int pkcs11_interactive = 0; 70 71int 72pkcs11_init(int interactive) 73{ 74 pkcs11_interactive = interactive; 75 TAILQ_INIT(&pkcs11_providers); 76 return (0); 77} 78 79/* 80 * finalize a provider shared libarary, it's no longer usable. 81 * however, there might still be keys referencing this provider, 82 * so the actuall freeing of memory is handled by pkcs11_provider_unref(). 83 * this is called when a provider gets unregistered. 84 */ 85static void 86pkcs11_provider_finalize(struct pkcs11_provider *p) 87{ 88 CK_RV rv; 89 CK_ULONG i; 90 91 debug("pkcs11_provider_finalize: %p refcount %d valid %d", 92 p, p->refcount, p->valid); 93 if (!p->valid) 94 return; 95 for (i = 0; i < p->nslots; i++) { 96 if (p->slotinfo[i].session && 97 (rv = p->function_list->C_CloseSession( 98 p->slotinfo[i].session)) != CKR_OK) 99 error("C_CloseSession failed: %lu", rv); 100 } 101 if ((rv = p->function_list->C_Finalize(NULL)) != CKR_OK) 102 error("C_Finalize failed: %lu", rv); 103 p->valid = 0; 104 p->function_list = NULL; 105#ifdef HAVE_DLOPEN 106 dlclose(p->handle); 107#endif 108} 109 110/* 111 * remove a reference to the provider. 112 * called when a key gets destroyed or when the provider is unregistered. 113 */ 114static void 115pkcs11_provider_unref(struct pkcs11_provider *p) 116{ 117 debug("pkcs11_provider_unref: %p refcount %d", p, p->refcount); 118 if (--p->refcount <= 0) { 119 if (p->valid) 120 error("pkcs11_provider_unref: %p still valid", p); 121 xfree(p->slotlist); 122 xfree(p->slotinfo); 123 xfree(p); 124 } 125} 126 127/* unregister all providers, keys might still point to the providers */ 128void 129pkcs11_terminate(void) 130{ 131 struct pkcs11_provider *p; 132 133 while ((p = TAILQ_FIRST(&pkcs11_providers)) != NULL) { 134 TAILQ_REMOVE(&pkcs11_providers, p, next); 135 pkcs11_provider_finalize(p); 136 pkcs11_provider_unref(p); 137 } 138} 139 140/* lookup provider by name */ 141static struct pkcs11_provider * 142pkcs11_provider_lookup(char *provider_id) 143{ 144 struct pkcs11_provider *p; 145 146 TAILQ_FOREACH(p, &pkcs11_providers, next) { 147 debug("check %p %s", p, p->name); 148 if (!strcmp(provider_id, p->name)) 149 return (p); 150 } 151 return (NULL); 152} 153 154/* unregister provider by name */ 155int 156pkcs11_del_provider(char *provider_id) 157{ 158 struct pkcs11_provider *p; 159 160 if ((p = pkcs11_provider_lookup(provider_id)) != NULL) { 161 TAILQ_REMOVE(&pkcs11_providers, p, next); 162 pkcs11_provider_finalize(p); 163 pkcs11_provider_unref(p); 164 return (0); 165 } 166 return (-1); 167} 168 169#ifdef HAVE_DLOPEN 170/* openssl callback for freeing an RSA key */ 171static int 172pkcs11_rsa_finish(RSA *rsa) 173{ 174 struct pkcs11_key *k11; 175 int rv = -1; 176 177 if ((k11 = RSA_get_app_data(rsa)) != NULL) { 178 if (k11->orig_finish) 179 rv = k11->orig_finish(rsa); 180 if (k11->provider) 181 pkcs11_provider_unref(k11->provider); 182 if (k11->keyid) 183 xfree(k11->keyid); 184 xfree(k11); 185 } 186 return (rv); 187} 188 189/* find a single 'obj' for given attributes */ 190static int 191pkcs11_find(struct pkcs11_provider *p, CK_ULONG slotidx, CK_ATTRIBUTE *attr, 192 CK_ULONG nattr, CK_OBJECT_HANDLE *obj) 193{ 194 CK_FUNCTION_LIST *f; 195 CK_SESSION_HANDLE session; 196 CK_ULONG nfound = 0; 197 CK_RV rv; 198 int ret = -1; 199 200 f = p->function_list; 201 session = p->slotinfo[slotidx].session; 202 if ((rv = f->C_FindObjectsInit(session, attr, nattr)) != CKR_OK) { 203 error("C_FindObjectsInit failed (nattr %lu): %lu", nattr, rv); 204 return (-1); 205 } 206 if ((rv = f->C_FindObjects(session, obj, 1, &nfound)) != CKR_OK || 207 nfound != 1) { 208 debug("C_FindObjects failed (nfound %lu nattr %lu): %lu", 209 nfound, nattr, rv); 210 } else 211 ret = 0; 212 if ((rv = f->C_FindObjectsFinal(session)) != CKR_OK) 213 error("C_FindObjectsFinal failed: %lu", rv); 214 return (ret); 215} 216 217/* openssl callback doing the actual signing operation */ 218static int 219pkcs11_rsa_private_encrypt(int flen, const u_char *from, u_char *to, RSA *rsa, 220 int padding) 221{ 222 struct pkcs11_key *k11; 223 struct pkcs11_slotinfo *si; 224 CK_FUNCTION_LIST *f; 225 CK_OBJECT_HANDLE obj; 226 CK_ULONG tlen = 0; 227 CK_RV rv; 228 CK_OBJECT_CLASS private_key_class = CKO_PRIVATE_KEY; 229 CK_BBOOL true = CK_TRUE; 230 CK_MECHANISM mech = { 231 CKM_RSA_PKCS, NULL_PTR, 0 232 }; 233 CK_ATTRIBUTE key_filter[] = { 234 {CKA_CLASS, &private_key_class, sizeof(private_key_class) }, 235 {CKA_ID, NULL, 0}, 236 {CKA_SIGN, &true, sizeof(true) } 237 }; 238 char *pin, prompt[1024]; 239 int rval = -1; 240 241 if ((k11 = RSA_get_app_data(rsa)) == NULL) { 242 error("RSA_get_app_data failed for rsa %p", rsa); 243 return (-1); 244 } 245 if (!k11->provider || !k11->provider->valid) { 246 error("no pkcs11 (valid) provider for rsa %p", rsa); 247 return (-1); 248 } 249 f = k11->provider->function_list; 250 si = &k11->provider->slotinfo[k11->slotidx]; 251 if ((si->token.flags & CKF_LOGIN_REQUIRED) && !si->logged_in) { 252 if (!pkcs11_interactive) { 253 error("need pin"); 254 return (-1); 255 } 256 snprintf(prompt, sizeof(prompt), "Enter PIN for '%s': ", 257 si->token.label); 258 pin = read_passphrase(prompt, RP_ALLOW_EOF); 259 if (pin == NULL) 260 return (-1); /* bail out */ 261 if ((rv = f->C_Login(si->session, CKU_USER, pin, strlen(pin))) 262 != CKR_OK) { 263 xfree(pin); 264 error("C_Login failed: %lu", rv); 265 return (-1); 266 } 267 xfree(pin); 268 si->logged_in = 1; 269 } 270 key_filter[1].pValue = k11->keyid; 271 key_filter[1].ulValueLen = k11->keyid_len; 272 /* try to find object w/CKA_SIGN first, retry w/o */ 273 if (pkcs11_find(k11->provider, k11->slotidx, key_filter, 3, &obj) < 0 && 274 pkcs11_find(k11->provider, k11->slotidx, key_filter, 2, &obj) < 0) { 275 error("cannot find private key"); 276 } else if ((rv = f->C_SignInit(si->session, &mech, obj)) != CKR_OK) { 277 error("C_SignInit failed: %lu", rv); 278 } else { 279 /* XXX handle CKR_BUFFER_TOO_SMALL */ 280 tlen = RSA_size(rsa); 281 rv = f->C_Sign(si->session, __UNCONST(from), flen, to, &tlen); 282 if (rv == CKR_OK) 283 rval = tlen; 284 else 285 error("C_Sign failed: %lu", rv); 286 } 287 return (rval); 288} 289 290static int 291pkcs11_rsa_private_decrypt(int flen, const u_char *from, u_char *to, RSA *rsa, 292 int padding) 293{ 294 return (-1); 295} 296 297/* redirect private key operations for rsa key to pkcs11 token */ 298static int 299pkcs11_rsa_wrap(struct pkcs11_provider *provider, CK_ULONG slotidx, 300 CK_ATTRIBUTE *keyid_attrib, RSA *rsa) 301{ 302 struct pkcs11_key *k11; 303 const RSA_METHOD *def = RSA_get_default_method(); 304 305 k11 = xcalloc(1, sizeof(*k11)); 306 k11->provider = provider; 307 provider->refcount++; /* provider referenced by RSA key */ 308 k11->slotidx = slotidx; 309 /* identify key object on smartcard */ 310 k11->keyid_len = keyid_attrib->ulValueLen; 311 k11->keyid = xmalloc(k11->keyid_len); 312 memcpy(k11->keyid, keyid_attrib->pValue, k11->keyid_len); 313 k11->orig_finish = def->finish; 314 memcpy(&k11->rsa_method, def, sizeof(k11->rsa_method)); 315 k11->rsa_method.name = "pkcs11"; 316 k11->rsa_method.rsa_priv_enc = pkcs11_rsa_private_encrypt; 317 k11->rsa_method.rsa_priv_dec = pkcs11_rsa_private_decrypt; 318 k11->rsa_method.finish = pkcs11_rsa_finish; 319 RSA_set_method(rsa, &k11->rsa_method); 320 RSA_set_app_data(rsa, k11); 321 return (0); 322} 323 324/* remove trailing spaces */ 325static void 326rmspace(char *buf, size_t len) 327{ 328 size_t i; 329 330 if (!len) 331 return; 332 for (i = len - 1; i > 0; i--) 333 if (i == len - 1 || buf[i] == ' ') 334 buf[i] = '\0'; 335 else 336 break; 337} 338 339/* 340 * open a pkcs11 session and login if required. 341 * if pin == NULL we delay login until key use 342 */ 343static int 344pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin) 345{ 346 CK_RV rv; 347 CK_FUNCTION_LIST *f; 348 CK_SESSION_HANDLE session; 349 int login_required; 350 351 f = p->function_list; 352 login_required = p->slotinfo[slotidx].token.flags & CKF_LOGIN_REQUIRED; 353 if (pin && login_required && !strlen(pin)) { 354 error("pin required"); 355 return (-1); 356 } 357 if ((rv = f->C_OpenSession(p->slotlist[slotidx], CKF_RW_SESSION| 358 CKF_SERIAL_SESSION, NULL, NULL, &session)) 359 != CKR_OK) { 360 error("C_OpenSession failed: %lu", rv); 361 return (-1); 362 } 363 if (login_required && pin) { 364 if ((rv = f->C_Login(session, CKU_USER, pin, strlen(pin))) 365 != CKR_OK) { 366 error("C_Login failed: %lu", rv); 367 if ((rv = f->C_CloseSession(session)) != CKR_OK) 368 error("C_CloseSession failed: %lu", rv); 369 return (-1); 370 } 371 p->slotinfo[slotidx].logged_in = 1; 372 } 373 p->slotinfo[slotidx].session = session; 374 return (0); 375} 376 377/* 378 * lookup public keys for token in slot identified by slotidx, 379 * add 'wrapped' public keys to the 'keysp' array and increment nkeys. 380 * keysp points to an (possibly empty) array with *nkeys keys. 381 */ 382static int 383pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx, Key ***keysp, 384 int *nkeys) 385{ 386 Key *key; 387 RSA *rsa; 388 int i; 389 CK_RV rv; 390 CK_OBJECT_HANDLE obj; 391 CK_ULONG nfound; 392 CK_SESSION_HANDLE session; 393 CK_FUNCTION_LIST *f; 394 CK_OBJECT_CLASS pubkey_class = CKO_PUBLIC_KEY; 395 CK_ATTRIBUTE pubkey_filter[] = { 396 { CKA_CLASS, &pubkey_class, sizeof(pubkey_class) } 397 }; 398 CK_ATTRIBUTE attribs[] = { 399 { CKA_ID, NULL, 0 }, 400 { CKA_MODULUS, NULL, 0 }, 401 { CKA_PUBLIC_EXPONENT, NULL, 0 } 402 }; 403 404 f = p->function_list; 405 session = p->slotinfo[slotidx].session; 406 /* setup a filter the looks for public keys */ 407 if ((rv = f->C_FindObjectsInit(session, pubkey_filter, 1)) != CKR_OK) { 408 error("C_FindObjectsInit failed: %lu", rv); 409 return (-1); 410 } 411 while (1) { 412 /* XXX 3 attributes in attribs[] */ 413 for (i = 0; i < 3; i++) { 414 attribs[i].pValue = NULL; 415 attribs[i].ulValueLen = 0; 416 } 417 if ((rv = f->C_FindObjects(session, &obj, 1, &nfound)) != CKR_OK 418 || nfound == 0) 419 break; 420 /* found a key, so figure out size of the attributes */ 421 if ((rv = f->C_GetAttributeValue(session, obj, attribs, 3)) 422 != CKR_OK) { 423 error("C_GetAttributeValue failed: %lu", rv); 424 continue; 425 } 426 /* check that none of the attributes are zero length */ 427 if (attribs[0].ulValueLen == 0 || 428 attribs[1].ulValueLen == 0 || 429 attribs[2].ulValueLen == 0) { 430 continue; 431 } 432 /* allocate buffers for attributes */ 433 for (i = 0; i < 3; i++) 434 attribs[i].pValue = xmalloc(attribs[i].ulValueLen); 435 /* retrieve ID, modulus and public exponent of RSA key */ 436 if ((rv = f->C_GetAttributeValue(session, obj, attribs, 3)) 437 != CKR_OK) { 438 error("C_GetAttributeValue failed: %lu", rv); 439 } else if ((rsa = RSA_new()) == NULL) { 440 error("RSA_new failed"); 441 } else { 442 rsa->n = BN_bin2bn(attribs[1].pValue, 443 attribs[1].ulValueLen, NULL); 444 rsa->e = BN_bin2bn(attribs[2].pValue, 445 attribs[2].ulValueLen, NULL); 446 if (rsa->n && rsa->e && 447 pkcs11_rsa_wrap(p, slotidx, &attribs[0], rsa) == 0) { 448 key = key_new(KEY_UNSPEC); 449 key->rsa = rsa; 450 key->type = KEY_RSA; 451 key->flags |= KEY_FLAG_EXT; 452 /* expand key array and add key */ 453 *keysp = xrealloc(*keysp, *nkeys + 1, 454 sizeof(Key *)); 455 (*keysp)[*nkeys] = key; 456 *nkeys = *nkeys + 1; 457 debug("have %d keys", *nkeys); 458 } else { 459 RSA_free(rsa); 460 } 461 } 462 for (i = 0; i < 3; i++) 463 xfree(attribs[i].pValue); 464 } 465 if ((rv = f->C_FindObjectsFinal(session)) != CKR_OK) 466 error("C_FindObjectsFinal failed: %lu", rv); 467 return (0); 468} 469 470/* register a new provider, fails if provider already exists */ 471int 472pkcs11_add_provider(char *provider_id, char *pin, Key ***keyp) 473{ 474 int nkeys, need_finalize = 0; 475 struct pkcs11_provider *p = NULL; 476 void *handle = NULL; 477 CK_RV (*getfunctionlist)(CK_FUNCTION_LIST **); 478 CK_RV rv; 479 CK_FUNCTION_LIST *f = NULL; 480 CK_TOKEN_INFO *token; 481 CK_ULONG i; 482 483 *keyp = NULL; 484 if (pkcs11_provider_lookup(provider_id) != NULL) { 485 error("provider already registered: %s", provider_id); 486 goto fail; 487 } 488 /* open shared pkcs11-libarary */ 489 if ((handle = dlopen(provider_id, RTLD_NOW)) == NULL) { 490 error("dlopen %s failed: %s", provider_id, dlerror()); 491 goto fail; 492 } 493 if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL) { 494 error("dlsym(C_GetFunctionList) failed: %s", dlerror()); 495 goto fail; 496 } 497 p = xcalloc(1, sizeof(*p)); 498 p->name = xstrdup(provider_id); 499 p->handle = handle; 500 /* setup the pkcs11 callbacks */ 501 if ((rv = (*getfunctionlist)(&f)) != CKR_OK) { 502 error("C_GetFunctionList failed: %lu", rv); 503 goto fail; 504 } 505 p->function_list = f; 506 if ((rv = f->C_Initialize(NULL)) != CKR_OK) { 507 error("C_Initialize failed: %lu", rv); 508 goto fail; 509 } 510 need_finalize = 1; 511 if ((rv = f->C_GetInfo(&p->info)) != CKR_OK) { 512 error("C_GetInfo failed: %lu", rv); 513 goto fail; 514 } 515 rmspace(p->info.manufacturerID, sizeof(p->info.manufacturerID)); 516 rmspace(p->info.libraryDescription, sizeof(p->info.libraryDescription)); 517 debug("manufacturerID <%s> cryptokiVersion %d.%d" 518 " libraryDescription <%s> libraryVersion %d.%d", 519 p->info.manufacturerID, 520 p->info.cryptokiVersion.major, 521 p->info.cryptokiVersion.minor, 522 p->info.libraryDescription, 523 p->info.libraryVersion.major, 524 p->info.libraryVersion.minor); 525 if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &p->nslots)) != CKR_OK) { 526 error("C_GetSlotList failed: %lu", rv); 527 goto fail; 528 } 529 if (p->nslots == 0) { 530 error("no slots"); 531 goto fail; 532 } 533 p->slotlist = xcalloc(p->nslots, sizeof(CK_SLOT_ID)); 534 if ((rv = f->C_GetSlotList(CK_TRUE, p->slotlist, &p->nslots)) 535 != CKR_OK) { 536 error("C_GetSlotList failed: %lu", rv); 537 goto fail; 538 } 539 p->slotinfo = xcalloc(p->nslots, sizeof(struct pkcs11_slotinfo)); 540 p->valid = 1; 541 nkeys = 0; 542 for (i = 0; i < p->nslots; i++) { 543 token = &p->slotinfo[i].token; 544 if ((rv = f->C_GetTokenInfo(p->slotlist[i], token)) 545 != CKR_OK) { 546 error("C_GetTokenInfo failed: %lu", rv); 547 continue; 548 } 549 rmspace(token->label, sizeof(token->label)); 550 rmspace(token->manufacturerID, sizeof(token->manufacturerID)); 551 rmspace(token->model, sizeof(token->model)); 552 rmspace(token->serialNumber, sizeof(token->serialNumber)); 553 debug("label <%s> manufacturerID <%s> model <%s> serial <%s>" 554 " flags 0x%lx", 555 token->label, token->manufacturerID, token->model, 556 token->serialNumber, token->flags); 557 /* open session, login with pin and retrieve public keys */ 558 if (pkcs11_open_session(p, i, pin) == 0) 559 pkcs11_fetch_keys(p, i, keyp, &nkeys); 560 } 561 if (nkeys > 0) { 562 TAILQ_INSERT_TAIL(&pkcs11_providers, p, next); 563 p->refcount++; /* add to provider list */ 564 return (nkeys); 565 } 566 error("no keys"); 567 /* don't add the provider, since it does not have any keys */ 568fail: 569 if (need_finalize && (rv = f->C_Finalize(NULL)) != CKR_OK) 570 error("C_Finalize failed: %lu", rv); 571 if (p) { 572 if (p->slotlist) 573 xfree(p->slotlist); 574 if (p->slotinfo) 575 xfree(p->slotinfo); 576 xfree(p); 577 } 578 if (handle) 579 dlclose(handle); 580 return (-1); 581} 582#else 583int 584pkcs11_add_provider(char *provider_id, char *pin, Key ***keyp) 585{ 586 error("dlopen() not supported"); 587 return (-1); 588} 589#endif 590