1 /* $OpenBSD: ssh-pkcs11.c,v 1.6 2010/06/08 21:32:19 markus Exp $ */
3 * Copyright (c) 2010 Markus Friedl. All rights reserved.
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 #include <sys/types.h>
23 #ifdef HAVE_SYS_TIME_H
24 # include <sys/time.h>
32 #include "openbsd-compat/sys-queue.h"
34 #define CRYPTOKI_COMPAT
40 #include "ssh-pkcs11.h"
43 struct pkcs11_slotinfo
{
45 CK_SESSION_HANDLE session
;
49 struct pkcs11_provider
{
52 CK_FUNCTION_LIST
*function_list
;
56 struct pkcs11_slotinfo
*slotinfo
;
59 TAILQ_ENTRY(pkcs11_provider
) next
;
62 TAILQ_HEAD(, pkcs11_provider
) pkcs11_providers
;
65 struct pkcs11_provider
*provider
;
67 int (*orig_finish
)(RSA
*rsa
);
68 RSA_METHOD rsa_method
;
73 int pkcs11_interactive
= 0;
76 pkcs11_init(int interactive
)
78 pkcs11_interactive
= interactive
;
79 TAILQ_INIT(&pkcs11_providers
);
84 * finalize a provider shared libarary, it's no longer usable.
85 * however, there might still be keys referencing this provider,
86 * so the actuall freeing of memory is handled by pkcs11_provider_unref().
87 * this is called when a provider gets unregistered.
90 pkcs11_provider_finalize(struct pkcs11_provider
*p
)
95 debug("pkcs11_provider_finalize: %p refcount %d valid %d",
96 p
, p
->refcount
, p
->valid
);
99 for (i
= 0; i
< p
->nslots
; i
++) {
100 if (p
->slotinfo
[i
].session
&&
101 (rv
= p
->function_list
->C_CloseSession(
102 p
->slotinfo
[i
].session
)) != CKR_OK
)
103 error("C_CloseSession failed: %lu", rv
);
105 if ((rv
= p
->function_list
->C_Finalize(NULL
)) != CKR_OK
)
106 error("C_Finalize failed: %lu", rv
);
108 p
->function_list
= NULL
;
113 * remove a reference to the provider.
114 * called when a key gets destroyed or when the provider is unregistered.
117 pkcs11_provider_unref(struct pkcs11_provider
*p
)
119 debug("pkcs11_provider_unref: %p refcount %d", p
, p
->refcount
);
120 if (--p
->refcount
<= 0) {
122 error("pkcs11_provider_unref: %p still valid", p
);
129 /* unregister all providers, keys might still point to the providers */
131 pkcs11_terminate(void)
133 struct pkcs11_provider
*p
;
135 while ((p
= TAILQ_FIRST(&pkcs11_providers
)) != NULL
) {
136 TAILQ_REMOVE(&pkcs11_providers
, p
, next
);
137 pkcs11_provider_finalize(p
);
138 pkcs11_provider_unref(p
);
142 /* lookup provider by name */
143 static struct pkcs11_provider
*
144 pkcs11_provider_lookup(char *provider_id
)
146 struct pkcs11_provider
*p
;
148 TAILQ_FOREACH(p
, &pkcs11_providers
, next
) {
149 debug("check %p %s", p
, p
->name
);
150 if (!strcmp(provider_id
, p
->name
))
156 /* unregister provider by name */
158 pkcs11_del_provider(char *provider_id
)
160 struct pkcs11_provider
*p
;
162 if ((p
= pkcs11_provider_lookup(provider_id
)) != NULL
) {
163 TAILQ_REMOVE(&pkcs11_providers
, p
, next
);
164 pkcs11_provider_finalize(p
);
165 pkcs11_provider_unref(p
);
171 /* openssl callback for freeing an RSA key */
173 pkcs11_rsa_finish(RSA
*rsa
)
175 struct pkcs11_key
*k11
;
178 if ((k11
= RSA_get_app_data(rsa
)) != NULL
) {
179 if (k11
->orig_finish
)
180 rv
= k11
->orig_finish(rsa
);
182 pkcs11_provider_unref(k11
->provider
);
190 /* find a single 'obj' for given attributes */
192 pkcs11_find(struct pkcs11_provider
*p
, CK_ULONG slotidx
, CK_ATTRIBUTE
*attr
,
193 CK_ULONG nattr
, CK_OBJECT_HANDLE
*obj
)
196 CK_SESSION_HANDLE session
;
201 f
= p
->function_list
;
202 session
= p
->slotinfo
[slotidx
].session
;
203 if ((rv
= f
->C_FindObjectsInit(session
, attr
, nattr
)) != CKR_OK
) {
204 error("C_FindObjectsInit failed (nattr %lu): %lu", nattr
, rv
);
207 if ((rv
= f
->C_FindObjects(session
, obj
, 1, &nfound
)) != CKR_OK
||
209 debug("C_FindObjects failed (nfound %lu nattr %lu): %lu",
213 if ((rv
= f
->C_FindObjectsFinal(session
)) != CKR_OK
)
214 error("C_FindObjectsFinal failed: %lu", rv
);
218 /* openssl callback doing the actual signing operation */
220 pkcs11_rsa_private_encrypt(int flen
, const u_char
*from
, u_char
*to
, RSA
*rsa
,
223 struct pkcs11_key
*k11
;
224 struct pkcs11_slotinfo
*si
;
226 CK_OBJECT_HANDLE obj
;
229 CK_OBJECT_CLASS private_key_class
= CKO_PRIVATE_KEY
;
230 CK_BBOOL true_val
= CK_TRUE
;
231 CK_MECHANISM mech
= {
232 CKM_RSA_PKCS
, NULL_PTR
, 0
234 CK_ATTRIBUTE key_filter
[] = {
235 {CKA_CLASS
, NULL
, sizeof(private_key_class
) },
237 {CKA_SIGN
, NULL
, sizeof(true_val
) }
239 char *pin
, prompt
[1024];
242 /* some compilers complain about non-constant initializer so we
243 use NULL in CK_ATTRIBUTE above and set the values here */
244 key_filter
[0].pValue
= &private_key_class
;
245 key_filter
[2].pValue
= &true_val
;
247 if ((k11
= RSA_get_app_data(rsa
)) == NULL
) {
248 error("RSA_get_app_data failed for rsa %p", rsa
);
251 if (!k11
->provider
|| !k11
->provider
->valid
) {
252 error("no pkcs11 (valid) provider for rsa %p", rsa
);
255 f
= k11
->provider
->function_list
;
256 si
= &k11
->provider
->slotinfo
[k11
->slotidx
];
257 if ((si
->token
.flags
& CKF_LOGIN_REQUIRED
) && !si
->logged_in
) {
258 if (!pkcs11_interactive
) {
262 snprintf(prompt
, sizeof(prompt
), "Enter PIN for '%s': ",
264 pin
= read_passphrase(prompt
, RP_ALLOW_EOF
);
266 return (-1); /* bail out */
267 if ((rv
= f
->C_Login(si
->session
, CKU_USER
, pin
, strlen(pin
)))
270 error("C_Login failed: %lu", rv
);
276 key_filter
[1].pValue
= k11
->keyid
;
277 key_filter
[1].ulValueLen
= k11
->keyid_len
;
278 /* try to find object w/CKA_SIGN first, retry w/o */
279 if (pkcs11_find(k11
->provider
, k11
->slotidx
, key_filter
, 3, &obj
) < 0 &&
280 pkcs11_find(k11
->provider
, k11
->slotidx
, key_filter
, 2, &obj
) < 0) {
281 error("cannot find private key");
282 } else if ((rv
= f
->C_SignInit(si
->session
, &mech
, obj
)) != CKR_OK
) {
283 error("C_SignInit failed: %lu", rv
);
285 /* XXX handle CKR_BUFFER_TOO_SMALL */
286 tlen
= RSA_size(rsa
);
287 rv
= f
->C_Sign(si
->session
, (CK_BYTE
*)from
, flen
, to
, &tlen
);
291 error("C_Sign failed: %lu", rv
);
297 pkcs11_rsa_private_decrypt(int flen
, const u_char
*from
, u_char
*to
, RSA
*rsa
,
303 /* redirect private key operations for rsa key to pkcs11 token */
305 pkcs11_rsa_wrap(struct pkcs11_provider
*provider
, CK_ULONG slotidx
,
306 CK_ATTRIBUTE
*keyid_attrib
, RSA
*rsa
)
308 struct pkcs11_key
*k11
;
309 const RSA_METHOD
*def
= RSA_get_default_method();
311 k11
= xcalloc(1, sizeof(*k11
));
312 k11
->provider
= provider
;
313 provider
->refcount
++; /* provider referenced by RSA key */
314 k11
->slotidx
= slotidx
;
315 /* identify key object on smartcard */
316 k11
->keyid_len
= keyid_attrib
->ulValueLen
;
317 k11
->keyid
= xmalloc(k11
->keyid_len
);
318 memcpy(k11
->keyid
, keyid_attrib
->pValue
, k11
->keyid_len
);
319 k11
->orig_finish
= def
->finish
;
320 memcpy(&k11
->rsa_method
, def
, sizeof(k11
->rsa_method
));
321 k11
->rsa_method
.name
= "pkcs11";
322 k11
->rsa_method
.rsa_priv_enc
= pkcs11_rsa_private_encrypt
;
323 k11
->rsa_method
.rsa_priv_dec
= pkcs11_rsa_private_decrypt
;
324 k11
->rsa_method
.finish
= pkcs11_rsa_finish
;
325 RSA_set_method(rsa
, &k11
->rsa_method
);
326 RSA_set_app_data(rsa
, k11
);
330 /* remove trailing spaces */
332 rmspace(char *buf
, size_t len
)
338 for (i
= len
- 1; i
> 0; i
--)
339 if (i
== len
- 1 || buf
[i
] == ' ')
346 * open a pkcs11 session and login if required.
347 * if pin == NULL we delay login until key use
350 pkcs11_open_session(struct pkcs11_provider
*p
, CK_ULONG slotidx
, char *pin
)
354 CK_SESSION_HANDLE session
;
357 f
= p
->function_list
;
358 login_required
= p
->slotinfo
[slotidx
].token
.flags
& CKF_LOGIN_REQUIRED
;
359 if (pin
&& login_required
&& !strlen(pin
)) {
360 error("pin required");
363 if ((rv
= f
->C_OpenSession(p
->slotlist
[slotidx
], CKF_RW_SESSION
|
364 CKF_SERIAL_SESSION
, NULL
, NULL
, &session
))
366 error("C_OpenSession failed: %lu", rv
);
369 if (login_required
&& pin
) {
370 if ((rv
= f
->C_Login(session
, CKU_USER
, pin
, strlen(pin
)))
372 error("C_Login failed: %lu", rv
);
373 if ((rv
= f
->C_CloseSession(session
)) != CKR_OK
)
374 error("C_CloseSession failed: %lu", rv
);
377 p
->slotinfo
[slotidx
].logged_in
= 1;
379 p
->slotinfo
[slotidx
].session
= session
;
384 * lookup public keys for token in slot identified by slotidx,
385 * add 'wrapped' public keys to the 'keysp' array and increment nkeys.
386 * keysp points to an (possibly empty) array with *nkeys keys.
389 pkcs11_fetch_keys(struct pkcs11_provider
*p
, CK_ULONG slotidx
, Key
***keysp
,
396 CK_OBJECT_HANDLE obj
;
398 CK_SESSION_HANDLE session
;
400 CK_OBJECT_CLASS pubkey_class
= CKO_PUBLIC_KEY
;
401 CK_ATTRIBUTE pubkey_filter
[] = {
402 { CKA_CLASS
, NULL
, sizeof(pubkey_class
) }
404 CK_ATTRIBUTE attribs
[] = {
406 { CKA_MODULUS
, NULL
, 0 },
407 { CKA_PUBLIC_EXPONENT
, NULL
, 0 }
410 /* some compilers complain about non-constant initializer so we
411 use NULL in CK_ATTRIBUTE above and set the value here */
412 pubkey_filter
[0].pValue
= &pubkey_class
;
414 f
= p
->function_list
;
415 session
= p
->slotinfo
[slotidx
].session
;
416 /* setup a filter the looks for public keys */
417 if ((rv
= f
->C_FindObjectsInit(session
, pubkey_filter
, 1)) != CKR_OK
) {
418 error("C_FindObjectsInit failed: %lu", rv
);
422 /* XXX 3 attributes in attribs[] */
423 for (i
= 0; i
< 3; i
++) {
424 attribs
[i
].pValue
= NULL
;
425 attribs
[i
].ulValueLen
= 0;
427 if ((rv
= f
->C_FindObjects(session
, &obj
, 1, &nfound
)) != CKR_OK
430 /* found a key, so figure out size of the attributes */
431 if ((rv
= f
->C_GetAttributeValue(session
, obj
, attribs
, 3))
433 error("C_GetAttributeValue failed: %lu", rv
);
436 /* check that none of the attributes are zero length */
437 if (attribs
[0].ulValueLen
== 0 ||
438 attribs
[1].ulValueLen
== 0 ||
439 attribs
[2].ulValueLen
== 0) {
442 /* allocate buffers for attributes */
443 for (i
= 0; i
< 3; i
++)
444 attribs
[i
].pValue
= xmalloc(attribs
[i
].ulValueLen
);
445 /* retrieve ID, modulus and public exponent of RSA key */
446 if ((rv
= f
->C_GetAttributeValue(session
, obj
, attribs
, 3))
448 error("C_GetAttributeValue failed: %lu", rv
);
449 } else if ((rsa
= RSA_new()) == NULL
) {
450 error("RSA_new failed");
452 rsa
->n
= BN_bin2bn(attribs
[1].pValue
,
453 attribs
[1].ulValueLen
, NULL
);
454 rsa
->e
= BN_bin2bn(attribs
[2].pValue
,
455 attribs
[2].ulValueLen
, NULL
);
456 if (rsa
->n
&& rsa
->e
&&
457 pkcs11_rsa_wrap(p
, slotidx
, &attribs
[0], rsa
) == 0) {
458 key
= key_new(KEY_UNSPEC
);
461 key
->flags
|= KEY_FLAG_EXT
;
462 /* expand key array and add key */
463 *keysp
= xrealloc(*keysp
, *nkeys
+ 1,
465 (*keysp
)[*nkeys
] = key
;
467 debug("have %d keys", *nkeys
);
472 for (i
= 0; i
< 3; i
++)
473 xfree(attribs
[i
].pValue
);
475 if ((rv
= f
->C_FindObjectsFinal(session
)) != CKR_OK
)
476 error("C_FindObjectsFinal failed: %lu", rv
);
480 /* register a new provider, fails if provider already exists */
482 pkcs11_add_provider(char *provider_id
, char *pin
, Key
***keyp
)
484 int nkeys
, need_finalize
= 0;
485 struct pkcs11_provider
*p
= NULL
;
487 CK_RV (*getfunctionlist
)(CK_FUNCTION_LIST
**);
489 CK_FUNCTION_LIST
*f
= NULL
;
490 CK_TOKEN_INFO
*token
;
494 if (pkcs11_provider_lookup(provider_id
) != NULL
) {
495 error("provider already registered: %s", provider_id
);
498 /* open shared pkcs11-libarary */
499 if ((handle
= dlopen(provider_id
, RTLD_NOW
)) == NULL
) {
500 error("dlopen %s failed: %s", provider_id
, dlerror());
503 if ((getfunctionlist
= dlsym(handle
, "C_GetFunctionList")) == NULL
) {
504 error("dlsym(C_GetFunctionList) failed: %s", dlerror());
507 p
= xcalloc(1, sizeof(*p
));
508 p
->name
= xstrdup(provider_id
);
510 /* setup the pkcs11 callbacks */
511 if ((rv
= (*getfunctionlist
)(&f
)) != CKR_OK
) {
512 error("C_GetFunctionList failed: %lu", rv
);
515 p
->function_list
= f
;
516 if ((rv
= f
->C_Initialize(NULL
)) != CKR_OK
) {
517 error("C_Initialize failed: %lu", rv
);
521 if ((rv
= f
->C_GetInfo(&p
->info
)) != CKR_OK
) {
522 error("C_GetInfo failed: %lu", rv
);
525 rmspace(p
->info
.manufacturerID
, sizeof(p
->info
.manufacturerID
));
526 rmspace(p
->info
.libraryDescription
, sizeof(p
->info
.libraryDescription
));
527 debug("manufacturerID <%s> cryptokiVersion %d.%d"
528 " libraryDescription <%s> libraryVersion %d.%d",
529 p
->info
.manufacturerID
,
530 p
->info
.cryptokiVersion
.major
,
531 p
->info
.cryptokiVersion
.minor
,
532 p
->info
.libraryDescription
,
533 p
->info
.libraryVersion
.major
,
534 p
->info
.libraryVersion
.minor
);
535 if ((rv
= f
->C_GetSlotList(CK_TRUE
, NULL
, &p
->nslots
)) != CKR_OK
) {
536 error("C_GetSlotList failed: %lu", rv
);
539 if (p
->nslots
== 0) {
543 p
->slotlist
= xcalloc(p
->nslots
, sizeof(CK_SLOT_ID
));
544 if ((rv
= f
->C_GetSlotList(CK_TRUE
, p
->slotlist
, &p
->nslots
))
546 error("C_GetSlotList failed: %lu", rv
);
549 p
->slotinfo
= xcalloc(p
->nslots
, sizeof(struct pkcs11_slotinfo
));
552 for (i
= 0; i
< p
->nslots
; i
++) {
553 token
= &p
->slotinfo
[i
].token
;
554 if ((rv
= f
->C_GetTokenInfo(p
->slotlist
[i
], token
))
556 error("C_GetTokenInfo failed: %lu", rv
);
559 rmspace(token
->label
, sizeof(token
->label
));
560 rmspace(token
->manufacturerID
, sizeof(token
->manufacturerID
));
561 rmspace(token
->model
, sizeof(token
->model
));
562 rmspace(token
->serialNumber
, sizeof(token
->serialNumber
));
563 debug("label <%s> manufacturerID <%s> model <%s> serial <%s>"
565 token
->label
, token
->manufacturerID
, token
->model
,
566 token
->serialNumber
, token
->flags
);
567 /* open session, login with pin and retrieve public keys */
568 if (pkcs11_open_session(p
, i
, pin
) == 0)
569 pkcs11_fetch_keys(p
, i
, keyp
, &nkeys
);
572 TAILQ_INSERT_TAIL(&pkcs11_providers
, p
, next
);
573 p
->refcount
++; /* add to provider list */
577 /* don't add the provider, since it does not have any keys */
579 if (need_finalize
&& (rv
= f
->C_Finalize(NULL
)) != CKR_OK
)
580 error("C_Finalize failed: %lu", rv
);
593 #endif /* ENABLE_PKCS11 */