1 /* $NetBSD: ssh-pkcs11.c,v 1.5 2013/11/08 19:18:25 christos Exp $ */ 2 /* $OpenBSD: ssh-pkcs11.c,v 1.8 2013/07/12 00:20:00 djm 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.5 2013/11/08 19:18:25 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 39 struct pkcs11_slotinfo { 40 CK_TOKEN_INFO token; 41 CK_SESSION_HANDLE session; 42 int logged_in; 43 }; 44 45 struct 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 58 TAILQ_HEAD(, pkcs11_provider) pkcs11_providers; 59 60 struct 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 69 int pkcs11_interactive = 0; 70 71 int 72 pkcs11_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 */ 85 static void 86 pkcs11_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 */ 114 static void 115 pkcs11_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 free(p->slotlist); 122 free(p->slotinfo); 123 free(p); 124 } 125 } 126 127 /* unregister all providers, keys might still point to the providers */ 128 void 129 pkcs11_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 */ 141 static struct pkcs11_provider * 142 pkcs11_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 */ 155 int 156 pkcs11_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 */ 171 static int 172 pkcs11_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 free(k11->keyid); 183 free(k11); 184 } 185 return (rv); 186 } 187 188 /* find a single 'obj' for given attributes */ 189 static int 190 pkcs11_find(struct pkcs11_provider *p, CK_ULONG slotidx, CK_ATTRIBUTE *attr, 191 CK_ULONG nattr, CK_OBJECT_HANDLE *obj) 192 { 193 CK_FUNCTION_LIST *f; 194 CK_SESSION_HANDLE session; 195 CK_ULONG nfound = 0; 196 CK_RV rv; 197 int ret = -1; 198 199 f = p->function_list; 200 session = p->slotinfo[slotidx].session; 201 if ((rv = f->C_FindObjectsInit(session, attr, nattr)) != CKR_OK) { 202 error("C_FindObjectsInit failed (nattr %lu): %lu", nattr, rv); 203 return (-1); 204 } 205 if ((rv = f->C_FindObjects(session, obj, 1, &nfound)) != CKR_OK || 206 nfound != 1) { 207 debug("C_FindObjects failed (nfound %lu nattr %lu): %lu", 208 nfound, nattr, rv); 209 } else 210 ret = 0; 211 if ((rv = f->C_FindObjectsFinal(session)) != CKR_OK) 212 error("C_FindObjectsFinal failed: %lu", rv); 213 return (ret); 214 } 215 216 /* openssl callback doing the actual signing operation */ 217 static int 218 pkcs11_rsa_private_encrypt(int flen, const u_char *from, u_char *to, RSA *rsa, 219 int padding) 220 { 221 struct pkcs11_key *k11; 222 struct pkcs11_slotinfo *si; 223 CK_FUNCTION_LIST *f; 224 CK_OBJECT_HANDLE obj; 225 CK_ULONG tlen = 0; 226 CK_RV rv; 227 CK_OBJECT_CLASS private_key_class = CKO_PRIVATE_KEY; 228 CK_BBOOL true = CK_TRUE; 229 CK_MECHANISM mech = { 230 CKM_RSA_PKCS, NULL_PTR, 0 231 }; 232 CK_ATTRIBUTE key_filter[] = { 233 {CKA_CLASS, &private_key_class, sizeof(private_key_class) }, 234 {CKA_ID, NULL, 0}, 235 {CKA_SIGN, &true, sizeof(true) } 236 }; 237 char *pin, prompt[1024]; 238 int rval = -1; 239 240 if ((k11 = RSA_get_app_data(rsa)) == NULL) { 241 error("RSA_get_app_data failed for rsa %p", rsa); 242 return (-1); 243 } 244 if (!k11->provider || !k11->provider->valid) { 245 error("no pkcs11 (valid) provider for rsa %p", rsa); 246 return (-1); 247 } 248 f = k11->provider->function_list; 249 si = &k11->provider->slotinfo[k11->slotidx]; 250 if ((si->token.flags & CKF_LOGIN_REQUIRED) && !si->logged_in) { 251 if (!pkcs11_interactive) { 252 error("need pin"); 253 return (-1); 254 } 255 snprintf(prompt, sizeof(prompt), "Enter PIN for '%s': ", 256 si->token.label); 257 pin = read_passphrase(prompt, RP_ALLOW_EOF); 258 if (pin == NULL) 259 return (-1); /* bail out */ 260 if ((rv = f->C_Login(si->session, CKU_USER, 261 (u_char *)pin, strlen(pin))) != CKR_OK) { 262 free(pin); 263 error("C_Login failed: %lu", rv); 264 return (-1); 265 } 266 free(pin); 267 si->logged_in = 1; 268 } 269 key_filter[1].pValue = k11->keyid; 270 key_filter[1].ulValueLen = k11->keyid_len; 271 /* try to find object w/CKA_SIGN first, retry w/o */ 272 if (pkcs11_find(k11->provider, k11->slotidx, key_filter, 3, &obj) < 0 && 273 pkcs11_find(k11->provider, k11->slotidx, key_filter, 2, &obj) < 0) { 274 error("cannot find private key"); 275 } else if ((rv = f->C_SignInit(si->session, &mech, obj)) != CKR_OK) { 276 error("C_SignInit failed: %lu", rv); 277 } else { 278 /* XXX handle CKR_BUFFER_TOO_SMALL */ 279 tlen = RSA_size(rsa); 280 rv = f->C_Sign(si->session, __UNCONST(from), flen, to, &tlen); 281 if (rv == CKR_OK) 282 rval = tlen; 283 else 284 error("C_Sign failed: %lu", rv); 285 } 286 return (rval); 287 } 288 289 static int 290 pkcs11_rsa_private_decrypt(int flen, const u_char *from, u_char *to, RSA *rsa, 291 int padding) 292 { 293 return (-1); 294 } 295 296 /* redirect private key operations for rsa key to pkcs11 token */ 297 static int 298 pkcs11_rsa_wrap(struct pkcs11_provider *provider, CK_ULONG slotidx, 299 CK_ATTRIBUTE *keyid_attrib, RSA *rsa) 300 { 301 struct pkcs11_key *k11; 302 const RSA_METHOD *def = RSA_get_default_method(); 303 304 k11 = xcalloc(1, sizeof(*k11)); 305 k11->provider = provider; 306 provider->refcount++; /* provider referenced by RSA key */ 307 k11->slotidx = slotidx; 308 /* identify key object on smartcard */ 309 k11->keyid_len = keyid_attrib->ulValueLen; 310 k11->keyid = xmalloc(k11->keyid_len); 311 memcpy(k11->keyid, keyid_attrib->pValue, k11->keyid_len); 312 k11->orig_finish = def->finish; 313 memcpy(&k11->rsa_method, def, sizeof(k11->rsa_method)); 314 k11->rsa_method.name = "pkcs11"; 315 k11->rsa_method.rsa_priv_enc = pkcs11_rsa_private_encrypt; 316 k11->rsa_method.rsa_priv_dec = pkcs11_rsa_private_decrypt; 317 k11->rsa_method.finish = pkcs11_rsa_finish; 318 RSA_set_method(rsa, &k11->rsa_method); 319 RSA_set_app_data(rsa, k11); 320 return (0); 321 } 322 323 /* remove trailing spaces */ 324 static void 325 rmspace(u_char *buf, size_t len) 326 { 327 size_t i; 328 329 if (!len) 330 return; 331 for (i = len - 1; i > 0; i--) 332 if (i == len - 1 || buf[i] == ' ') 333 buf[i] = '\0'; 334 else 335 break; 336 } 337 338 /* 339 * open a pkcs11 session and login if required. 340 * if pin == NULL we delay login until key use 341 */ 342 static int 343 pkcs11_open_session(struct pkcs11_provider *p, CK_ULONG slotidx, char *pin) 344 { 345 CK_RV rv; 346 CK_FUNCTION_LIST *f; 347 CK_SESSION_HANDLE session; 348 int login_required; 349 350 f = p->function_list; 351 login_required = p->slotinfo[slotidx].token.flags & CKF_LOGIN_REQUIRED; 352 if (pin && login_required && !strlen(pin)) { 353 error("pin required"); 354 return (-1); 355 } 356 if ((rv = f->C_OpenSession(p->slotlist[slotidx], CKF_RW_SESSION| 357 CKF_SERIAL_SESSION, NULL, NULL, &session)) 358 != CKR_OK) { 359 error("C_OpenSession failed: %lu", rv); 360 return (-1); 361 } 362 if (login_required && pin) { 363 if ((rv = f->C_Login(session, CKU_USER, 364 (u_char *)pin, strlen(pin))) != CKR_OK) { 365 error("C_Login failed: %lu", rv); 366 if ((rv = f->C_CloseSession(session)) != CKR_OK) 367 error("C_CloseSession failed: %lu", rv); 368 return (-1); 369 } 370 p->slotinfo[slotidx].logged_in = 1; 371 } 372 p->slotinfo[slotidx].session = session; 373 return (0); 374 } 375 376 /* 377 * lookup public keys for token in slot identified by slotidx, 378 * add 'wrapped' public keys to the 'keysp' array and increment nkeys. 379 * keysp points to an (possibly empty) array with *nkeys keys. 380 */ 381 static int 382 pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx, Key ***keysp, 383 int *nkeys) 384 { 385 Key *key; 386 RSA *rsa; 387 int i; 388 CK_RV rv; 389 CK_OBJECT_HANDLE obj; 390 CK_ULONG nfound; 391 CK_SESSION_HANDLE session; 392 CK_FUNCTION_LIST *f; 393 CK_OBJECT_CLASS pubkey_class = CKO_PUBLIC_KEY; 394 CK_ATTRIBUTE pubkey_filter[] = { 395 { CKA_CLASS, &pubkey_class, sizeof(pubkey_class) } 396 }; 397 CK_ATTRIBUTE attribs[] = { 398 { CKA_ID, NULL, 0 }, 399 { CKA_MODULUS, NULL, 0 }, 400 { CKA_PUBLIC_EXPONENT, NULL, 0 } 401 }; 402 403 f = p->function_list; 404 session = p->slotinfo[slotidx].session; 405 /* setup a filter the looks for public keys */ 406 if ((rv = f->C_FindObjectsInit(session, pubkey_filter, 1)) != CKR_OK) { 407 error("C_FindObjectsInit failed: %lu", rv); 408 return (-1); 409 } 410 while (1) { 411 /* XXX 3 attributes in attribs[] */ 412 for (i = 0; i < 3; i++) { 413 attribs[i].pValue = NULL; 414 attribs[i].ulValueLen = 0; 415 } 416 if ((rv = f->C_FindObjects(session, &obj, 1, &nfound)) != CKR_OK 417 || nfound == 0) 418 break; 419 /* found a key, so figure out size of the attributes */ 420 if ((rv = f->C_GetAttributeValue(session, obj, attribs, 3)) 421 != CKR_OK) { 422 error("C_GetAttributeValue failed: %lu", rv); 423 continue; 424 } 425 /* check that none of the attributes are zero length */ 426 if (attribs[0].ulValueLen == 0 || 427 attribs[1].ulValueLen == 0 || 428 attribs[2].ulValueLen == 0) { 429 continue; 430 } 431 /* allocate buffers for attributes */ 432 for (i = 0; i < 3; i++) 433 attribs[i].pValue = xmalloc(attribs[i].ulValueLen); 434 /* retrieve ID, modulus and public exponent of RSA key */ 435 if ((rv = f->C_GetAttributeValue(session, obj, attribs, 3)) 436 != CKR_OK) { 437 error("C_GetAttributeValue failed: %lu", rv); 438 } else if ((rsa = RSA_new()) == NULL) { 439 error("RSA_new failed"); 440 } else { 441 rsa->n = BN_bin2bn(attribs[1].pValue, 442 attribs[1].ulValueLen, NULL); 443 rsa->e = BN_bin2bn(attribs[2].pValue, 444 attribs[2].ulValueLen, NULL); 445 if (rsa->n && rsa->e && 446 pkcs11_rsa_wrap(p, slotidx, &attribs[0], rsa) == 0) { 447 key = key_new(KEY_UNSPEC); 448 key->rsa = rsa; 449 key->type = KEY_RSA; 450 key->flags |= KEY_FLAG_EXT; 451 /* expand key array and add key */ 452 *keysp = xrealloc(*keysp, *nkeys + 1, 453 sizeof(Key *)); 454 (*keysp)[*nkeys] = key; 455 *nkeys = *nkeys + 1; 456 debug("have %d keys", *nkeys); 457 } else { 458 RSA_free(rsa); 459 } 460 } 461 for (i = 0; i < 3; i++) 462 free(attribs[i].pValue); 463 } 464 if ((rv = f->C_FindObjectsFinal(session)) != CKR_OK) 465 error("C_FindObjectsFinal failed: %lu", rv); 466 return (0); 467 } 468 469 /* register a new provider, fails if provider already exists */ 470 int 471 pkcs11_add_provider(char *provider_id, char *pin, Key ***keyp) 472 { 473 int nkeys, need_finalize = 0; 474 struct pkcs11_provider *p = NULL; 475 void *handle = NULL; 476 CK_RV (*getfunctionlist)(CK_FUNCTION_LIST **); 477 CK_RV rv; 478 CK_FUNCTION_LIST *f = NULL; 479 CK_TOKEN_INFO *token; 480 CK_ULONG i; 481 482 *keyp = NULL; 483 if (pkcs11_provider_lookup(provider_id) != NULL) { 484 error("provider already registered: %s", provider_id); 485 goto fail; 486 } 487 /* open shared pkcs11-libarary */ 488 if ((handle = dlopen(provider_id, RTLD_NOW)) == NULL) { 489 error("dlopen %s failed: %s", provider_id, dlerror()); 490 goto fail; 491 } 492 if ((getfunctionlist = dlsym(handle, "C_GetFunctionList")) == NULL) { 493 error("dlsym(C_GetFunctionList) failed: %s", dlerror()); 494 goto fail; 495 } 496 p = xcalloc(1, sizeof(*p)); 497 p->name = xstrdup(provider_id); 498 p->handle = handle; 499 /* setup the pkcs11 callbacks */ 500 if ((rv = (*getfunctionlist)(&f)) != CKR_OK) { 501 error("C_GetFunctionList failed: %lu", rv); 502 goto fail; 503 } 504 p->function_list = f; 505 if ((rv = f->C_Initialize(NULL)) != CKR_OK) { 506 error("C_Initialize failed: %lu", rv); 507 goto fail; 508 } 509 need_finalize = 1; 510 if ((rv = f->C_GetInfo(&p->info)) != CKR_OK) { 511 error("C_GetInfo failed: %lu", rv); 512 goto fail; 513 } 514 rmspace(p->info.manufacturerID, sizeof(p->info.manufacturerID)); 515 rmspace(p->info.libraryDescription, sizeof(p->info.libraryDescription)); 516 debug("manufacturerID <%s> cryptokiVersion %d.%d" 517 " libraryDescription <%s> libraryVersion %d.%d", 518 p->info.manufacturerID, 519 p->info.cryptokiVersion.major, 520 p->info.cryptokiVersion.minor, 521 p->info.libraryDescription, 522 p->info.libraryVersion.major, 523 p->info.libraryVersion.minor); 524 if ((rv = f->C_GetSlotList(CK_TRUE, NULL, &p->nslots)) != CKR_OK) { 525 error("C_GetSlotList failed: %lu", rv); 526 goto fail; 527 } 528 if (p->nslots == 0) { 529 error("no slots"); 530 goto fail; 531 } 532 p->slotlist = xcalloc(p->nslots, sizeof(CK_SLOT_ID)); 533 if ((rv = f->C_GetSlotList(CK_TRUE, p->slotlist, &p->nslots)) 534 != CKR_OK) { 535 error("C_GetSlotList failed: %lu", rv); 536 goto fail; 537 } 538 p->slotinfo = xcalloc(p->nslots, sizeof(struct pkcs11_slotinfo)); 539 p->valid = 1; 540 nkeys = 0; 541 for (i = 0; i < p->nslots; i++) { 542 token = &p->slotinfo[i].token; 543 if ((rv = f->C_GetTokenInfo(p->slotlist[i], token)) 544 != CKR_OK) { 545 error("C_GetTokenInfo failed: %lu", rv); 546 continue; 547 } 548 rmspace(token->label, sizeof(token->label)); 549 rmspace(token->manufacturerID, sizeof(token->manufacturerID)); 550 rmspace(token->model, sizeof(token->model)); 551 rmspace(token->serialNumber, sizeof(token->serialNumber)); 552 debug("label <%s> manufacturerID <%s> model <%s> serial <%s>" 553 " flags 0x%lx", 554 token->label, token->manufacturerID, token->model, 555 token->serialNumber, token->flags); 556 /* open session, login with pin and retrieve public keys */ 557 if (pkcs11_open_session(p, i, pin) == 0) 558 pkcs11_fetch_keys(p, i, keyp, &nkeys); 559 } 560 if (nkeys > 0) { 561 TAILQ_INSERT_TAIL(&pkcs11_providers, p, next); 562 p->refcount++; /* add to provider list */ 563 return (nkeys); 564 } 565 error("no keys"); 566 /* don't add the provider, since it does not have any keys */ 567 fail: 568 if (need_finalize && (rv = f->C_Finalize(NULL)) != CKR_OK) 569 error("C_Finalize failed: %lu", rv); 570 if (p) { 571 free(p->slotlist); 572 free(p->slotinfo); 573 free(p); 574 } 575 if (handle) 576 dlclose(handle); 577 return (-1); 578 } 579 #else 580 int 581 pkcs11_add_provider(char *provider_id, char *pin, Key ***keyp) 582 { 583 error("dlopen() not supported"); 584 return (-1); 585 } 586 #endif 587