From: Petr P. <pet...@at...> - 2013-08-30 14:46:05
|
This feature allows to select approriate certificate and private key for SSL client authentication. An application provides list of certificate authorities known by the server and this code will return suitable certificate and private key. If more certificates are suitable, user will be asked to select one of them. --- src/engine_pkcs11.c | 256 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/engine_pkcs11.h | 4 + src/hw_pkcs11.c | 3 +- 3 files changed, 262 insertions(+), 1 deletion(-) diff --git a/src/engine_pkcs11.c b/src/engine_pkcs11.c index 1c1842d..992e43e 100644 --- a/src/engine_pkcs11.c +++ b/src/engine_pkcs11.c @@ -31,6 +31,7 @@ #include <openssl/crypto.h> #include <openssl/objects.h> #include <openssl/engine.h> +#include <openssl/x509v3.h> #include <libp11.h> #include "engine_pkcs11.h" @@ -43,6 +44,7 @@ /** The maximum length of an internally-allocated PIN */ #define MAX_PIN_LENGTH 32 +#define MAX_MESSAGE_LENGTH 256 static PKCS11_CTX *ctx; @@ -827,3 +829,257 @@ EVP_PKEY *pkcs11_load_private_key(ENGINE * e, const char *s_key_id, fail("PKCS11_get_private_key returned NULL\n"); return pk; } + +/* + * Return true if certificate issuer is listed in X509_NAME stack or if the + * stack is empty. Return false otherwise. + * + * @cert is a valid PKCS11 certificate + * @issuer_dns is a possibly empty stack of issuer names + */ +static int pkcs11_cert_issuer_matches(PKCS11_CERT *cert, STACK_OF(X509_NAME) *issuer_dns) +{ + int count; + int i; + + if (NULL == issuer_dns) + return 1; + count = sk_X509_NAME_num(issuer_dns); + if (count <= 0) + return 1; + for (i = 0; i < count; i++) { + if (!X509_NAME_cmp( + sk_X509_NAME_value(issuer_dns, i), + X509_get_issuer_name(cert->x509))) + return 1; + } + return 0; +} + + +/* + * Check if certificate can be used by an SSL client. + * + * @cert is a valid PKCS11 certificate. + * @return 1 if the certificate purpose allowes that. + * @return 0 if the certificate purpose prohibits that. + * @return -1 in case of an error. + */ +static int pkcs11_cert_is_for_ssl_client(PKCS11_CERT *cert) +{ + /* XXX: We have to work on a temporary copy because + * X509_check_purpose() modified the X509. */ + X509 *copy = X509_dup(cert->x509); + int suitable; + + if (NULL == copy) + return -1; + suitable = X509_check_purpose(copy, X509_PURPOSE_SSL_CLIENT, 0); + X509_free(copy); + + return suitable; +} + +/* + * Ask user to select a certificate from array of certificates if more + * certificates are listed. Otherwise selects the one certificate without + * asking. + * + * @certs is array of pointers to a PKCS11 certificate + * @cerrs_count is number of pointers in the array + * @ui_method is user interface to use to ask an user + * @callback_data are application data for the user interface + * @return selected certificate or NULL in case of an empty list or an error. + */ +static PKCS11_CERT *pkcs11_select_certificate(PKCS11_CERT **certs, + int certs_count, UI_METHOD *ui_method, void *callback_data) +{ + UI *ui; + char message[MAX_MESSAGE_LENGTH]; + int i; + + /* No certificate list */ + if (NULL == certs || 0 == certs_count) + return NULL; + + /* Exactly one certificate */ + if (1 == certs_count) + return certs[0]; + + /* More certificates, ask the user */ + ui = UI_new(); + if (ui == NULL) { + fail("UI_new failed\n"); + } + if (ui_method != NULL) + UI_set_method(ui, ui_method); + if (callback_data != NULL) + UI_add_user_data(ui, callback_data); + + UI_add_info_string(ui, "Available certificates:\n"); + for (i = 0; i < certs_count; i++) { + char *dn = NULL; + if (certs[i]->x509) + dn = X509_NAME_oneline(X509_get_subject_name + (certs[i]->x509), NULL, 0); + snprintf(message, MAX_MESSAGE_LENGTH - 1, "%2u. %s (%s)\n", + i + 1, certs[i]->label, dn); + message[MAX_MESSAGE_LENGTH-1] = '\0'; + UI_dup_info_string(ui, message); + if (dn) { + OPENSSL_free(dn); + } + } + + message[0] = '\0'; + if (!UI_add_input_string(ui, "Select certificate by number: ", + UI_INPUT_FLAG_ECHO, message, 0, MAX_MESSAGE_LENGTH-1)) { + UI_free(ui); + fail("UI_add_input_string failed\n"); + } + if (UI_process(ui)) { + UI_free(ui); + fail("UI_process failed\n"); + } + UI_free(ui); + + /* Parse the response */ + i = atoi(message); + if (i < 1 || i > certs_count) { + fail("Could not select a certificate because of wrong number\n"); + } + + return certs[i - 1]; +} + +/* + * This is ENGINE_SSL_CLIENT_CERT_PTR OpenSSL engine call-back used to select + * a certificate and a corresponding private key appropriate for SSL client. + * + * @engine is this engine context + * @ssl is current SSL connection in client authentication phase + * @ca_dn is stack of certificate authority distinguished names advertised by + * the SSL server. This list constrains certificate to return. + * @pcert is a memory to store pointer to selected X509 certificate. Only one + * certificate can be returned. + * @pkey us a memory to store pointer to selected private key + * @pother has unkown semantics. Not implemented. + * @ui_method is an user interface to select certificate by the user if there + * are more certificates available issued by one of @ca_dn. + * @return 1 in case of success, otherwise 0. + */ +int pkcs11_load_ssl_client_cert(ENGINE *e, SSL *ssl, + STACK_OF(X509_NAME) *ca_dn, X509 **pcert, EVP_PKEY **pkey, + STACK_OF(X509) **pother, UI_METHOD *ui_method, void *callback_data) +{ + PKCS11_SLOT *slot_list, *slot; + PKCS11_SLOT *found_slot = NULL; + PKCS11_TOKEN *tok; + PKCS11_CERT *certs, *selected_cert = NULL; + PKCS11_CERT **suitable_certs = NULL; + PKCS11_KEY *key; + X509 *x509; + + unsigned int slot_count, cert_count, n, suitable_certs_count; + + if (NULL != pcert) + *pcert = NULL; + if (NULL != pkey) + *pkey = NULL; + if (NULL != pother) + *pother = NULL; + + if (NULL == e) + return 0; + + /* Enumerate certificates */ + if (PKCS11_enumerate_slots(ctx, &slot_list, &slot_count) < 0) + fail0("failed to enumerate slots\n"); + if (!(slot = PKCS11_find_token(ctx, slot_list, slot_count))) { + PKCS11_release_all_slots(ctx, slot_list, slot_count); + fail0("didn't find any tokens\n"); + } + tok = slot->token; + if (tok == NULL) { + PKCS11_release_all_slots(ctx, slot_list, slot_count); + fail0("Found empty token\n"); + } + + if (PKCS11_enumerate_certs(tok, &certs, &cert_count)) { + PKCS11_release_all_slots(ctx, slot_list, slot_count); + fail0("unable to enumerate certificates\n"); + } + + /* Select suitable certificates */ + suitable_certs = malloc(cert_count * sizeof(*suitable_certs)); + if (NULL == suitable_certs) { + PKCS11_release_all_slots(ctx, slot_list, slot_count); + fail0("not enough memory to select certificates\n"); + } + + for (n = 0, suitable_certs_count = 0; n < cert_count; n++) { + PKCS11_CERT *k = certs + n; + if (pkcs11_cert_issuer_matches(k, ca_dn)) { + int suitable = pkcs11_cert_is_for_ssl_client(k); + /* ???: Exclude expired certificates */ + if (1 == suitable) { + /* Suitable certificate found */ + suitable_certs[suitable_certs_count++] = k; + } else if (0 != suitable) { + free(suitable_certs); + PKCS11_release_all_slots(ctx, slot_list, + slot_count); + fail0("Error while checking a certificate is " + "allowed for an SSL client\n"); + } + } + } + + /* Let user to select if more certificates are suitable */ + selected_cert = pkcs11_select_certificate(suitable_certs, suitable_certs_count, + ui_method, callback_data); + free(suitable_certs); + + /* Store selected certificate */ + if (NULL == selected_cert) { + PKCS11_release_all_slots(ctx, slot_list, slot_count); + fail0("No suitable certificate found\n"); + } + if (NULL != pcert) { + *pcert = X509_dup(selected_cert->x509); + if (NULL == *pcert) { + PKCS11_release_all_slots(ctx, slot_list, slot_count); + fail0("could not copy selected certificate\n"); + } + } + + if (NULL == pkey) { + /* No private key requested by an application */ + PKCS11_release_all_slots(ctx, slot_list, slot_count); + return (1); + } + + /* Find a private key corresponding to the certificate */ + if (!pkcs11_login(slot, tok, ui_method, callback_data)) { + PKCS11_release_all_slots(ctx, slot_list, slot_count); + if (NULL != *pcert) { + X509_free(*pcert); + *pcert = NULL; + } + fail0("could not log in to access private key corresponding to selected certificate\n"); + } + key = PKCS11_find_key(selected_cert); + if (NULL == key) { + PKCS11_release_all_slots(ctx, slot_list, slot_count); + if (NULL != *pcert) { + X509_free(*pcert); + *pcert = NULL; + } + fail0("could not find private key corresponding to selected certificate\n"); + } + /* ???: Duplicate EVP_PKEY as the X509 */ + *pkey = PKCS11_get_private_key(key); + + PKCS11_release_all_slots(ctx, slot_list, slot_count); + return (1); +} diff --git a/src/engine_pkcs11.h b/src/engine_pkcs11.h index 2159330..3bc55fb 100644 --- a/src/engine_pkcs11.h +++ b/src/engine_pkcs11.h @@ -55,4 +55,8 @@ EVP_PKEY *pkcs11_load_public_key(ENGINE * e, const char *s_key_id, EVP_PKEY *pkcs11_load_private_key(ENGINE * e, const char *s_key_id, UI_METHOD * ui_method, void *callback_data); +int pkcs11_load_ssl_client_cert(ENGINE *e, SSL *ssl, + STACK_OF(X509_NAME) *ca_dn, X509 **pcert, EVP_PKEY **pkey, + STACK_OF(X509) **pother, UI_METHOD *ui_method, void *callback_data); + #endif diff --git a/src/hw_pkcs11.c b/src/hw_pkcs11.c index 24806ff..f6e45d5 100644 --- a/src/hw_pkcs11.c +++ b/src/hw_pkcs11.c @@ -193,7 +193,8 @@ static int bind_helper(ENGINE * e) !ENGINE_set_BN_mod_exp(e, BN_mod_exp) || #endif !ENGINE_set_load_pubkey_function(e, pkcs11_load_public_key) || - !ENGINE_set_load_privkey_function(e, pkcs11_load_private_key)) { + !ENGINE_set_load_privkey_function(e, pkcs11_load_private_key) || + !ENGINE_set_load_ssl_client_cert_function(e, pkcs11_load_ssl_client_cert)) { return 0; } else { return 1; -- 1.8.1.5 |