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