diff options
-rw-r--r-- | macros/neon.m4 | 11 | ||||
-rw-r--r-- | neon.mak | 3 | ||||
-rw-r--r-- | src/Makefile.in | 35 | ||||
-rw-r--r-- | src/ne_gnutls.c | 48 | ||||
-rw-r--r-- | src/ne_pkcs11.c | 466 | ||||
-rw-r--r-- | src/ne_pkcs11.h | 80 | ||||
-rw-r--r-- | src/ne_private.h | 4 | ||||
-rw-r--r-- | src/ne_privssl.h | 3 | ||||
-rw-r--r-- | src/ne_session.c | 7 |
9 files changed, 634 insertions, 23 deletions
diff --git a/macros/neon.m4 b/macros/neon.m4 index 6b476e6..9b3abbd 100644 --- a/macros/neon.m4 +++ b/macros/neon.m4 @@ -967,6 +967,17 @@ gnutls) if test ${ac_cv_func_gnutls_x509_dn_get_rdn_ava}X${ac_cv_header_iconv_h} = yesXyes; then AC_CHECK_FUNCS(iconv) fi + + if test x${ac_cv_func_gnutls_sign_callback_set} = xyes; then + # PKCS#11... ho! + NE_PKG_CONFIG(NE_PK11, pakchois, + [AC_MSG_NOTICE(using pakchois for PKCS11 support) + AC_DEFINE(HAVE_PAKCHOIS, 1, [Define if pakchois library supported]) + CPPFLAGS="$CPPFLAGS ${NE_PK11_CFLAGS}" + NEON_LIBS="${NEON_LIBS} ${NE_PK11_LIBS}"], + [AC_MSG_NOTICE(pakchois library not found; no PKCS11 support)]) + fi + ;; *) # Default to off; only create crypto-enabled binaries if requested. NE_DISABLE_SUPPORT(SSL, [SSL support is not enabled]) @@ -116,6 +116,7 @@ LIB32_OBJS= \ "$(INTDIR)\ne_dates.obj" \ "$(INTDIR)\ne_i18n.obj" \ "$(INTDIR)\ne_md5.obj" \ + "$(INTDIR)\ne_pkcs11.obj" \ "$(INTDIR)\ne_redirect.obj" \ "$(INTDIR)\ne_request.obj" \ "$(INTDIR)\ne_session.obj" \ @@ -174,6 +175,7 @@ CLEAN: $(ZLIB_CLEAN) -@erase "$(INTDIR)\ne_session.obj" -@erase "$(INTDIR)\ne_openssl.obj" -@erase "$(INTDIR)\ne_stubssl.obj" + -@erase "$(INTDIR)\ne_pkcs11.obj" -@erase "$(INTDIR)\ne_socket.obj" -@erase "$(INTDIR)\ne_sspi.obj" -@erase "$(INTDIR)\ne_string.obj" @@ -220,6 +222,7 @@ CLEAN: $(ZLIB_CLEAN) "$(INTDIR)\ne_session.obj": .\src\ne_session.c "$(INTDIR)\ne_openssl.obj": .\src\ne_openssl.c "$(INTDIR)\ne_stubssl.obj": .\src\ne_stubssl.c +"$(INTDIR)\ne_pkcs11.obj": .\src\ne_pkcs11.c "$(INTDIR)\ne_socket.obj": .\src\ne_socket.c "$(INTDIR)\ne_sspi.obj": .\src\ne_sspi.c "$(INTDIR)\ne_string.obj": .\src\ne_string.c diff --git a/src/Makefile.in b/src/Makefile.in index 4f8af46..1645e48 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -41,7 +41,7 @@ NEON_BASEOBJS = ne_request.@NEON_OBJEXT@ ne_session.@NEON_OBJEXT@ \ ne_md5.@NEON_OBJEXT@ ne_utils.@NEON_OBJEXT@ \ ne_socket.@NEON_OBJEXT@ ne_auth.@NEON_OBJEXT@ \ ne_redirect.@NEON_OBJEXT@ ne_compress.@NEON_OBJEXT@ \ - ne_i18n.@NEON_OBJEXT@ + ne_i18n.@NEON_OBJEXT@ ne_pkcs11.@NEON_OBJEXT@ NEON_DAVOBJS = $(NEON_BASEOBJS) \ ne_207.@NEON_OBJEXT@ ne_xml.@NEON_OBJEXT@ \ @@ -96,7 +96,7 @@ check-incl: update-deps: for f in `echo $(OBJECTS) | sed 's/\\.@NEON_OBJEXT@/.c/g'`; do \ $(CC) $(CPPFLAGS) -MM -c $$f; \ - done | sed 's, \.\./, $$(top_builddir)/,g;s/\.o: /.@NEON''_OBJEXT@: /' > new-deps + done | sed 's, \.\./, $$(top_builddir)/,g;s, /[^ ]*.h,,g;/^ .$$/d;s/\.o: /.@NEON''_OBJEXT@: /' > new-deps sed '/[-]--CUT---/q' Makefile.in > Makefile.new cat Makefile.new new-deps > Makefile.in rm new-deps Makefile.new @@ -104,10 +104,10 @@ update-deps: #### ---CUT--- DO NOT REMOVE THIS LINE. Generated dependencies follow. #### ne_request.@NEON_OBJEXT@: ne_request.c $(top_builddir)/config.h ne_internal.h ne_defs.h ne_alloc.h \ ne_request.h ne_utils.h ne_string.h ne_session.h ne_ssl.h ne_uri.h \ - ne_socket.h ne_private.h + ne_socket.h ne_private.h ne_pkcs11.h ne_session.@NEON_OBJEXT@: ne_session.c $(top_builddir)/config.h ne_session.h ne_ssl.h ne_defs.h \ ne_uri.h ne_socket.h ne_alloc.h ne_utils.h ne_internal.h ne_string.h \ - ne_dates.h ne_private.h ne_request.h + ne_dates.h ne_private.h ne_request.h ne_pkcs11.h ne_basic.@NEON_OBJEXT@: ne_basic.c $(top_builddir)/config.h ne_request.h ne_utils.h ne_defs.h \ ne_string.h ne_alloc.h ne_session.h ne_ssl.h ne_uri.h ne_socket.h \ ne_basic.h ne_207.h ne_xml.h ne_locks.h ne_dates.h ne_internal.h @@ -117,10 +117,13 @@ ne_dates.@NEON_OBJEXT@: ne_dates.c $(top_builddir)/config.h ne_alloc.h ne_defs.h ne_string.h ne_alloc.@NEON_OBJEXT@: ne_alloc.c $(top_builddir)/config.h ne_alloc.h ne_defs.h ne_md5.@NEON_OBJEXT@: ne_md5.c $(top_builddir)/config.h ne_md5.h ne_defs.h ne_string.h ne_alloc.h -ne_utils.@NEON_OBJEXT@: ne_utils.c $(top_builddir)/config.h ne_utils.h ne_defs.h ne_string.h \ - ne_alloc.h ne_dates.h -ne_socket.@NEON_OBJEXT@: ne_socket.c $(top_builddir)/config.h ne_privssl.h ne_ssl.h ne_defs.h \ - ne_socket.h ne_internal.h ne_utils.h ne_string.h ne_alloc.h ne_sspi.h +ne_utils.@NEON_OBJEXT@: ne_utils.c $(top_builddir)/config.h \ + ne_utils.h ne_defs.h \ + ne_string.h ne_alloc.h ne_dates.h +ne_socket.@NEON_OBJEXT@: ne_socket.c $(top_builddir)/config.h \ + ne_privssl.h ne_ssl.h \ + ne_defs.h ne_socket.h ne_internal.h ne_utils.h ne_string.h ne_alloc.h \ + ne_sspi.h ne_auth.@NEON_OBJEXT@: ne_auth.c $(top_builddir)/config.h ne_md5.h ne_defs.h ne_dates.h \ ne_request.h ne_utils.h ne_string.h ne_alloc.h ne_session.h ne_ssl.h \ ne_uri.h ne_socket.h ne_auth.h ne_internal.h @@ -131,6 +134,12 @@ ne_compress.@NEON_OBJEXT@: ne_compress.c $(top_builddir)/config.h ne_request.h n ne_defs.h ne_string.h ne_alloc.h ne_session.h ne_ssl.h ne_uri.h \ ne_socket.h ne_compress.h ne_internal.h ne_i18n.@NEON_OBJEXT@: ne_i18n.c $(top_builddir)/config.h ne_i18n.h ne_defs.h +ne_pkcs11.@NEON_OBJEXT@: ne_pkcs11.c $(top_builddir)/config.h ne_pkcs11.h ne_defs.h ne_session.h \ + ne_ssl.h ne_uri.h ne_socket.h \ + ne_internal.h \ + ne_alloc.h ne_private.h ne_request.h ne_utils.h ne_string.h \ + ne_privssl.h \ + ne_207.@NEON_OBJEXT@: ne_207.c $(top_builddir)/config.h ne_alloc.h ne_defs.h ne_utils.h ne_xml.h \ ne_207.h ne_request.h ne_string.h ne_session.h ne_ssl.h ne_uri.h \ ne_socket.h ne_basic.h ne_internal.h @@ -149,9 +158,7 @@ ne_xmlreq.@NEON_OBJEXT@: ne_xmlreq.c $(top_builddir)/config.h ne_internal.h ne_d ne_acl.@NEON_OBJEXT@: ne_acl.c $(top_builddir)/config.h ne_request.h ne_utils.h ne_defs.h \ ne_string.h ne_alloc.h ne_session.h ne_ssl.h ne_uri.h ne_socket.h \ ne_locks.h ne_acl.h ne_xml.h -ne_openssl.@NEON_OBJEXT@: ne_openssl.c $(top_builddir)/config.h ne_ssl.h ne_defs.h ne_string.h \ - ne_alloc.h ne_session.h ne_uri.h ne_socket.h ne_internal.h ne_private.h \ - ne_request.h ne_utils.h ne_privssl.h -ne_gnutls.@NEON_OBJEXT@: ne_gnutls.c $(top_builddir)/config.h ne_ssl.h ne_defs.h ne_string.h \ - ne_alloc.h ne_session.h ne_uri.h ne_socket.h ne_internal.h ne_private.h \ - ne_request.h ne_utils.h ne_privssl.h +ne_gnutls.@NEON_OBJEXT@: ne_gnutls.c $(top_builddir)/config.h \ + ne_ssl.h ne_defs.h \ + ne_string.h ne_alloc.h ne_session.h ne_uri.h ne_socket.h ne_internal.h \ + ne_private.h ne_request.h ne_utils.h ne_pkcs11.h ne_privssl.h diff --git a/src/ne_gnutls.c b/src/ne_gnutls.c index 0a1fe33..1dfbe52 100644 --- a/src/ne_gnutls.c +++ b/src/ne_gnutls.c @@ -1,6 +1,6 @@ /* neon SSL/TLS support using GNU TLS - Copyright (C) 2002-2007, Joe Orton <joe@manyfish.co.uk> + Copyright (C) 2002-2008, Joe Orton <joe@manyfish.co.uk> Copyright (C) 2004, Aleix Conchillo Flaque <aleix@member.fsf.org> This library is free software; you can redistribute it and/or @@ -69,6 +69,7 @@ struct ne_ssl_certificate_s { struct ne_ssl_client_cert_s { gnutls_pkcs12 p12; int decrypted; /* non-zero if successfully decrypted. */ + int keyless; ne_ssl_certificate cert; gnutls_x509_privkey pkey; char *friendly_name; @@ -502,13 +503,18 @@ static ne_ssl_client_cert *dup_client_cert(const ne_ssl_client_cert *cc) ne_ssl_client_cert *newcc = ne_calloc(sizeof *newcc); newcc->decrypted = 1; - - ret = gnutls_x509_privkey_init(&newcc->pkey); - if (ret != 0) goto dup_error; - - ret = gnutls_x509_privkey_cpy(newcc->pkey, cc->pkey); - if (ret != 0) goto dup_error; + if (cc->keyless) { + newcc->keyless = 1; + } + else { + ret = gnutls_x509_privkey_init(&newcc->pkey); + if (ret != 0) goto dup_error; + + ret = gnutls_x509_privkey_cpy(newcc->pkey, cc->pkey); + if (ret != 0) goto dup_error; + } + newcc->cert.subject = x509_crt_copy(cc->cert.subject); if (!newcc->cert.subject) goto dup_error; @@ -533,7 +539,7 @@ static int provide_client_cert(gnutls_session session, ne_session *sess = gnutls_session_get_ptr(session); if (!sess) { - return -1; + return GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; } if (!sess->client_cert && sess->ssl_provide_fn) { @@ -558,10 +564,11 @@ static int provide_client_cert(gnutls_session session, /* tell GNU TLS not to deallocate the certs. */ st->deinit_all = 0; } else { - return -1; + return GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE; } } else { NE_DEBUG(NE_DBG_SSL, "No client certificate supplied.\n"); + return GNUTLS_E_NO_CERTIFICATE_FOUND; } return 0; @@ -938,6 +945,29 @@ ne_ssl_client_cert *ne_ssl_clicert_read(const char *filename) } } +ne_ssl_client_cert *ne__ssl_clicert_exkey_import(const unsigned char *der, + size_t der_len) +{ + ne_ssl_client_cert *cc; + gnutls_x509_crt x5; + gnutls_datum datum; + + datum.data = (unsigned char *)der; + datum.size = der_len; + + if (gnutls_x509_crt_init(&x5) + || gnutls_x509_crt_import(x5, &datum, GNUTLS_X509_FMT_DER)) { + NE_DEBUG(NE_DBG_SSL, "ssl: crt_import failed.\n"); + return NULL; + } + + cc = ne_calloc(sizeof *cc); + cc->keyless = 1; + populate_cert(&cc->cert, x5); + + return cc; +} + int ne_ssl_clicert_encrypted(const ne_ssl_client_cert *cc) { return !cc->decrypted; diff --git a/src/ne_pkcs11.c b/src/ne_pkcs11.c new file mode 100644 index 0000000..6cf508e --- /dev/null +++ b/src/ne_pkcs11.c @@ -0,0 +1,466 @@ +/* + neon PKCS#11 support + Copyright (C) 2008, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA +*/ + +#include "config.h" + +#include "ne_pkcs11.h" + +#ifdef HAVE_PAKCHOIS +#include <string.h> + +#include <pakchois.h> + +#include "ne_internal.h" +#include "ne_alloc.h" +#include "ne_private.h" +#include "ne_privssl.h" + +struct pk11_context { + pakchois_module_t *module; + pakchois_session_t *session; + ck_object_handle_t privkey; +}; + +static void pk11_destroy(void *userdata) +{ + struct pk11_context *pk11 = userdata; + + if (pk11->session) { + pakchois_close_session(pk11->session); + } + pakchois_module_destroy(pk11->module); + ne_free(pk11); +} + +static int pk11_find_x509(pakchois_session_t *pks, ne_session *sess, + unsigned char *certid, unsigned long *cid_len) +{ + struct ck_attribute a[3]; + ck_object_class_t class; + ck_certificate_type_t type; + ck_rv_t rv; + ck_object_handle_t obj; + unsigned long count; + int found = 0; + + /* Find objects with cert class and X.509 cert type. */ + class = CKO_CERTIFICATE; + type = CKC_X_509; + + a[0].type = CKA_CLASS; + a[0].value = &class; + a[0].value_len = sizeof class; + a[1].type = CKA_CERTIFICATE_TYPE; + a[1].value = &type; + a[1].value_len = sizeof type; + + rv = pakchois_find_objects_init(pks, a, 2); + if (rv != CKR_OK) { + NE_DEBUG(NE_DBG_SSL, "pk11: FindObjectsInit failed.\n"); + return 0; + } + + while (pakchois_find_objects(pks, &obj, 1, &count) == CKR_OK + && count == 1) { + unsigned char value[8192], subject[8192]; + + a[0].type = CKA_VALUE; + a[0].value = value; + a[0].value_len = sizeof value; + a[1].type = CKA_ID; + a[1].value = certid; + a[1].value_len = *cid_len; + a[2].type = CKA_SUBJECT; + a[2].value = subject; + a[2].value_len = sizeof subject; + + if (pakchois_get_attribute_value(pks, obj, a, 3) == CKR_OK) { + ne_ssl_client_cert *cc; + + cc = ne__ssl_clicert_exkey_import(value, a[0].value_len); + if (cc) { + NE_DEBUG(NE_DBG_SSL, "pk11: Imported X.509 cert.\n"); + ne_ssl_set_clicert(sess, cc); + found = 1; + *cid_len = a[1].value_len; + break; + } + } + else { + NE_DEBUG(NE_DBG_SSL, "pk11: Skipped cert, missing attrs.\n"); + } + } + + pakchois_find_objects_final(pks); + return found; +} + +static int pk11_find_pkey(struct pk11_context *ctx, pakchois_session_t *pks, + unsigned char *certid, unsigned long cid_len) +{ + struct ck_attribute a[3]; + ck_object_class_t class; + ck_key_type_t type; + ck_rv_t rv; + ck_object_handle_t obj; + unsigned long count; + int found = 0; + + class = CKO_PRIVATE_KEY; + type = CKK_RSA; /* FIXME: check from the cert whether DSA or RSA */ + + /* Find an object with private key class and a certificate ID + * which matches the certificate. */ + /* FIXME: also match the cert subject. */ + a[0].type = CKA_CLASS; + a[0].value = &class; + a[0].value_len = sizeof class; + a[1].type = CKA_KEY_TYPE; + a[1].value = &type; + a[1].value_len = sizeof type; + a[2].type = CKA_ID; + a[2].value = certid; + a[2].value_len = cid_len; + + rv = pakchois_find_objects_init(pks, a, 3); + if (rv != CKR_OK) { + NE_DEBUG(NE_DBG_SSL, "pk11: FindObjectsInit failed.\n"); + /* TODO: error propagation */ + return 0; + } + + rv = pakchois_find_objects(pks, &obj, 1, &count); + if (rv == CKR_OK && count == 1) { + NE_DEBUG(NE_DBG_SSL, "pk11: Found private key.\n"); + found = 1; + ctx->privkey = obj; + } + + pakchois_find_objects_final(pks); + + return found; +} + +static int find_client_cert(ne_session *sess, struct pk11_context *ctx, + pakchois_session_t *pks) +{ + unsigned char certid[8192]; + unsigned long cid_len = sizeof certid; + + /* TODO: match cert subject too. */ + return pk11_find_x509(pks, sess, certid, &cid_len) + && pk11_find_pkey(ctx, pks, certid, cid_len); +} + +/* Callback invoked by GnuTLS to provide the signature. The signature + * operation is handled here by the PKCS#11 provider. */ +static int pk11_sign_callback(gnutls_session_t session, + void *userdata, + gnutls_certificate_type_t cert_type, + const gnutls_datum_t *cert, + const gnutls_datum_t *hash, + gnutls_datum_t *signature) +{ + struct pk11_context *ctx = userdata; + ck_rv_t rv; + struct ck_mechanism mech; + unsigned long siglen; + + if (!ctx->session || ctx->privkey == CK_INVALID_HANDLE) { + NE_DEBUG(NE_DBG_SSL, "p11: Boo, can't sign :(\n"); + return GNUTLS_E_NO_CERTIFICATE_FOUND; + } + + /* FIXME: from the object determine whether this should be + * CKM_DSA, or CKM_RSA_PKCS, or something unknown (&fail). */ + mech.mechanism = CKM_RSA_PKCS; + mech.parameter = NULL; + mech.parameter_len = 0; + + /* Initialize signing operation; using the private key discovered + * earlier. */ + rv = pakchois_sign_init(ctx->session, &mech, ctx->privkey); + if (rv != CKR_OK) { + NE_DEBUG(NE_DBG_SSL, "p11: SignInit failed: %lx.\n", rv); + return GNUTLS_E_PK_SIGN_FAILED; + } + + /* Work out how long the signature must be: */ + rv = pakchois_sign(ctx->session, hash->data, hash->size, NULL, &siglen); + if (rv != CKR_OK) { + NE_DEBUG(NE_DBG_SSL, "p11: Sign failed.\n"); + return GNUTLS_E_PK_SIGN_FAILED; + } + + signature->data = gnutls_malloc(siglen); + signature->size = siglen; + + rv = pakchois_sign(ctx->session, hash->data, hash->size, + signature->data, &siglen); + if (rv != CKR_OK) { + NE_DEBUG(NE_DBG_SSL, "p11: Sign failed.\n"); + return GNUTLS_E_PK_SIGN_FAILED; + } + + NE_DEBUG(NE_DBG_SSL, "p11: signed.\n"); + + return 0; +} + +static void terminate_string(unsigned char *str, size_t len) +{ + unsigned char *ptr = str + len - 1; + + while ((*ptr == ' ' || *ptr == '\t' || *ptr == '\0') && ptr >= str) + ptr--; + + if (ptr == str - 1) + str[0] = '\0'; + else if (ptr == str + len - 1) + str[len-1] = '\0'; + else + ptr[1] = '\0'; +} + +static int pk11_login(pakchois_module_t *module, ck_slot_id_t slot_id, + ne_session *sess, pakchois_session_t *pks, + struct ck_slot_info *sinfo) +{ + struct ck_token_info tinfo; + int attempt = 0; + ck_rv_t rv; + + if (pakchois_get_token_info(module, slot_id, &tinfo) != CKR_OK) { + NE_DEBUG(NE_DBG_SSL, "pk11: GetTokenInfo failed\n"); + /* TODO: propagate error. */ + return -1; + } + + if ((tinfo.flags & CKF_LOGIN_REQUIRED) == 0) { + NE_DEBUG(NE_DBG_SSL, "pk11: No login required.\n"); + return 0; + } + + /* For a token with a "protected" (out-of-band) authentication + * path, calling login with a NULL username is all that is + * required. */ + if (tinfo.flags & CKF_PROTECTED_AUTHENTICATION_PATH) { + if (pakchois_login(pks, CKU_USER, NULL, 0) == CKR_OK) { + return 0; + } + else { + NE_DEBUG(NE_DBG_SSL, "pk11: Protected login failed.\n"); + /* TODO: error propagation. */ + return -1; + } + } + + /* Otherwise, PIN entry is necessary for login, so fail if there's + * no callback. */ + if (!sess->ssl_pk11pin_fn) { + NE_DEBUG(NE_DBG_SSL, "pk11: No pin callback but login required.\n"); + /* TODO: propagate error. */ + return -1; + } + + terminate_string(sinfo->slot_description, sizeof sinfo->slot_description); + + do { + char pin[NE_SSL_P11PINLEN]; + unsigned int flags = 0; + + /* If login has been attempted once already, check the token + * status again, the flags might change. */ + if (attempt++) { + if (pakchois_get_token_info(module, slot_id, &tinfo) != CKR_OK) { + NE_DEBUG(NE_DBG_SSL, "pk11: GetTokenInfo failed\n"); + /* TODO: propagate error. */ + return -1; + } + } + + if (tinfo.flags & CKF_USER_PIN_COUNT_LOW) + flags |= NE_SSL_P11PIN_COUNT_LOW; + if (tinfo.flags & CKF_USER_PIN_FINAL_TRY) + flags |= NE_SSL_P11PIN_FINAL_TRY; + + terminate_string(tinfo.label, sizeof tinfo.label); + + if (sess->ssl_pk11pin_fn(sess->ssl_pk11pin_ud, attempt, + (const char *)sinfo->slot_description, + (const char *)tinfo.label, + flags, pin)) { + return -1; + } + + rv = pakchois_login(pks, CKU_USER, (unsigned char *)pin, strlen(pin)); + + /* Try to scrub the pin off the stack. Clever compilers will + * probably optimize this away, oh well. */ + memset(pin, 0, sizeof pin); + } while (rv == CKR_PIN_INCORRECT); + + NE_DEBUG(NE_DBG_SSL, "pk11: Login result = %lu\n", rv); + + return rv == CKR_OK ? 0 : -1; +} + +static void pk11_provide(void *userdata, ne_session *sess, + const ne_ssl_dname *const *dnames, + int dncount) +{ + struct pk11_context *ctx = userdata; + ck_slot_id_t *slots; + unsigned long scount, n; + + if (pakchois_get_slot_list(ctx->module, 1, NULL, &scount) != CKR_OK + || scount == 0) { + NE_DEBUG(NE_DBG_SSL, "pk11: No slots.\n"); + /* TODO: propagate error. */ + return; + } + + slots = ne_malloc(scount * sizeof *slots); + if (pakchois_get_slot_list(ctx->module, 1, slots, &scount) != CKR_OK) { + ne_free(slots); + NE_DEBUG(NE_DBG_SSL, "pk11: Really, no slots?\n"); + /* TODO: propagate error. */ + return; + } + + NE_DEBUG(NE_DBG_SSL, "pk11: Found %ld slots.\n", scount); + + for (n = 0; n < scount; n++) { + pakchois_session_t *pks; + ck_rv_t rv; + struct ck_slot_info sinfo; + + if (pakchois_get_slot_info(ctx->module, slots[n], &sinfo) != CKR_OK) { + NE_DEBUG(NE_DBG_SSL, "pk11: GetSlotInfo failed\n"); + continue; + } + + if ((sinfo.flags & CKF_TOKEN_PRESENT) == 0) { + NE_DEBUG(NE_DBG_SSL, "pk11: slot empty, ignoring\n"); + continue; + } + + rv = pakchois_open_session(ctx->module, slots[n], + CKF_SERIAL_SESSION, + NULL, NULL, &pks); + if (rv != CKR_OK) { + NE_DEBUG(NE_DBG_SSL, "pk11: could not open slot, %ld (%ld: %ld)\n", + rv, n, slots[n]); + continue; + } + + if (pk11_login(ctx->module, slots[n], sess, pks, &sinfo) == 0) { + if (find_client_cert(sess, ctx, pks)) { + NE_DEBUG(NE_DBG_SSL, "pk11: Setup complete.\n"); + ctx->session = pks; + + return; + } + } + + pakchois_close_session(pks); + } + + ne_free(slots); +} + +static int pk11_setup(ne_session *sess, pakchois_module_t *module) +{ + struct pk11_context *ctx; + + ctx = ne_malloc(sizeof *ctx); + ctx->module = module; + ctx->session = NULL; + ctx->privkey = CK_INVALID_HANDLE; + + ne_hook_destroy_session(sess, pk11_destroy, ctx); + + ne_ssl_provide_clicert(sess, pk11_provide, ctx); + + /* FIXME: this is bad, because it means only one PKCS#11 provider + * can be used at a time ("last through the door wins"), but needs + * to be done early currently so that ne_sock_connect_ssl installs + * the callback. */ + + sess->ssl_context->sign_func = pk11_sign_callback; + sess->ssl_context->sign_data = ctx; + + return NE_OK; +} + +int ne_ssl_provide_pkcs11_clicert(ne_session *sess, const char *provider) +{ + pakchois_module_t *pm; + ck_rv_t rv; + + rv = pakchois_module_load(&pm, provider); + if (rv != CKR_OK) { + ne_set_error(sess, _("Could not load PKCS#11 module: %ld"), rv); + return NE_LOOKUP; + } + + return pk11_setup(sess, pm); +} + +int ne_ssl_provide_nsspk11_clicert(ne_session *sess, + const char *provider, + const char *directory, + const char *cert_prefix, + const char *key_prefix, + const char *secmod_db) +{ + pakchois_module_t *pm; + ck_rv_t rv; + + rv = pakchois_module_nssload(&pm, provider, + directory, cert_prefix, + key_prefix, secmod_db); + if (rv != CKR_OK) { + ne_set_error(sess, _("Could not load PKCS#11 module: %ld"), rv); + return NE_LOOKUP; + } + + return pk11_setup(sess, pm); +} + +#else /* !HAVE_PAKCHOIS */ + +int ne_ssl_provide_pkcs11_clicert(ne_session *sess, const char *provider) +{ + return NE_FAILED; +} + +int ne_ssl_provide_nsspk11_clicert(ne_session *sess, + const char *provider, + const char *directory, + const char *cert_prefix, + const char *key_prefix, + const char *secmod_db) +{ + return NE_FAILED; +} + +#endif /* HAVE_PAKCHOIS */ + diff --git a/src/ne_pkcs11.h b/src/ne_pkcs11.h new file mode 100644 index 0000000..e947f15 --- /dev/null +++ b/src/ne_pkcs11.h @@ -0,0 +1,80 @@ +/* + PKCS#11 support for neon + Copyright (C) 2008, Joe Orton <joe@manyfish.co.uk> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA + +*/ + +#ifndef NE_PKCS11_H +#define NE_PKCS11_H 1 + +#include "ne_defs.h" +#include "ne_session.h" + +NE_BEGIN_DECLS + +#define NE_SSL_P11PIN_COUNT_LOW (0x01) /* an incorrect PIN has been + * entered. */ +#define NE_SSL_P11PIN_FINAL_TRY (0x02) /* token will become locked if + * entered PIN is incorrect */ + +/* Callback for PKCS#11 PIN entry. The callback provides the PIN code + * to unlock the token with label 'token_label' in the slot described + * by 'slot_descr'. + * + * The PIN code, as a NUL-terminated ASCII string, should be copied + * into the 'pin' buffer (of fixed length NE_SSL_P11PINLEN), and + * return 0 to indicate success. Alternatively, the callback may + * return -1 to indicate failure (in which case, the pin buffer is + * ignored). When the PIN is needed for the first time, the + * + * The NE_SSL_P11PIN_COUNT_LOW and/or NE_SSL_P11PIN_FAST_TRY hints may + * be set in the 'flags' argument, if these hints are made available + * by the token. */ +typedef int (*ne_ssl_pkcs11_pin_fn)(void *userdata, int attempt, + const char *slot_descr, + const char *token_label, + unsigned int flags, + char *pin); +#define NE_SSL_P11PINLEN (256) + +void ne_ssl_set_pkcs11_pin(ne_session *sess, ne_ssl_pkcs11_pin_fn fn, + void *userdata); + +/* Use a PKCS#11 provider of given name to supply a client certificate + * if requested. Returns NE_OK on success, NE_LOOKUP if the provider + * could not be loaded/initialized (in which case, the session error + * string is set), and NE_FAILED if PKCS#11 is not supported. */ +int ne_ssl_provide_pkcs11_clicert(ne_session *sess, + const char *provider); + +/* Use a NSS softoken pseudo-PKCS#11 provider of given name + * (e.g. "softokn3") to supply a client certificate if requested, + * using database in given directory name; the other parameters may be + * NULL. Returns NE_OK on success, NE_LOOKUP if the provider could + * not be loaded/initialized (in which case, the session error string + * is set), and NE_FAILED if PKCS#11 is not supported. */ +int ne_ssl_provide_nsspk11_clicert(ne_session *sess, + const char *provider, + const char *directory, + const char *cert_prefix, + const char *key_prefix, + const char *secmod_db); + +NE_END_DECLS + +#endif /* NE_PKCS11_H */ diff --git a/src/ne_private.h b/src/ne_private.h index 69b4b35..810f6c4 100644 --- a/src/ne_private.h +++ b/src/ne_private.h @@ -28,6 +28,7 @@ #include "ne_request.h" #include "ne_socket.h" #include "ne_ssl.h" +#include "ne_pkcs11.h" struct host_info { char *hostname; @@ -107,6 +108,9 @@ struct ne_session_s { ne_ssl_provide_fn ssl_provide_fn; void *ssl_provide_ud; + ne_ssl_pkcs11_pin_fn ssl_pk11pin_fn; + void *ssl_pk11pin_ud; + ne_session_status_info status; /* Error string */ diff --git a/src/ne_privssl.h b/src/ne_privssl.h index 46c28cb..60192c0 100644 --- a/src/ne_privssl.h +++ b/src/ne_privssl.h @@ -79,6 +79,9 @@ struct ne_ssl_context_s { typedef gnutls_session ne_ssl_socket; +ne_ssl_client_cert *ne__ssl_clicert_exkey_import(const unsigned char *der, + size_t der_len); + #endif /* HAVE_GNUTLS */ ne_ssl_socket ne__sock_sslsock(ne_socket *sock); diff --git a/src/ne_session.c b/src/ne_session.c index b0bf31a..165916a 100644 --- a/src/ne_session.c +++ b/src/ne_session.c @@ -318,6 +318,13 @@ void ne_ssl_provide_clicert(ne_session *sess, sess->ssl_provide_ud = userdata; } +void ne_ssl_set_pkcs11_pin(ne_session *sess, + ne_ssl_pkcs11_pin_fn fn, void *userdata) +{ + sess->ssl_pk11pin_fn = fn; + sess->ssl_pk11pin_ud = userdata; +} + void ne_ssl_trust_cert(ne_session *sess, const ne_ssl_certificate *cert) { #ifdef NE_HAVE_SSL |