From 33fbd8d8221be3814efbf18d73df2de6c17b8c2f Mon Sep 17 00:00:00 2001 From: David von Oheimb Date: Sat, 21 May 2016 19:04:50 +0100 Subject: [PATCH] improved cert verify callbacks: added CRL strictness and CRL retrieval; new stengthened callback also for server side --- example/client/estclient.c | 74 ++++++++++-- example/server/apps.h | 3 +- example/server/estExampleCA.cnf | 3 +- example/server/estserver.c | 67 ++++++++++- example/util/crl.c | 193 ++++++++++++++++++++++++++++++++ example/util/crl.h | 14 +++ src/est/est.c | 73 +++++++++++- src/est/est.h | 4 +- src/est/est_client.c | 95 ++++++---------- src/est/est_client_http.c | 2 + src/est/est_locl.h | 8 ++ src/est/est_ossl_util.c | 75 ++++--------- src/est/est_ossl_util.h | 2 +- src/est/est_server.c | 4 +- src/est/est_server_http.c | 21 +++- 15 files changed, 499 insertions(+), 139 deletions(-) create mode 100644 example/util/crl.c create mode 100644 example/util/crl.h diff --git a/example/client/estclient.c b/example/client/estclient.c index c51fdd0..944dbb8 100644 --- a/example/client/estclient.c +++ b/example/client/estclient.c @@ -26,12 +26,18 @@ #include #include "../util/utils.h" +#include "../util/crl.c" // TODO include properly into Makefile #define EST_UT_MAX_CMD_LEN 255 #define MAX_SERVER_LEN 255 #define MAX_FILENAME_LEN 255 #define MAX_CN 64 +/* + * The CRL retrieval code in crl.c and client_manual_cert_verify() need this BIO to send errors to + */ +BIO *bio_err = NULL; + /* * Global variables to hold command line options */ @@ -44,6 +50,10 @@ static char est_server[MAX_SERVER_LEN]; static char est_auth_token[MAX_AUTH_TOKEN_LEN+1]; static int est_port; static int verbose = 0; +static int enable_crl = 0; +static int require_crl = 0; +static int use_cdp = 0; +static char default_cdp[MAX_FILENAME_LEN] = ""; static int srp = 0; static int token_auth_mode = 0; static int pem_out = 0; @@ -106,6 +116,9 @@ static void show_usage_and_exit (void) " -y Use existing CSR in the given file\n" " -s Enrollment server IP address\n" " -p TCP port number for enrollment server\n" + " -l[+] Enable CRL checks; +: require strict CRL checking\n" + " -L Use CDPs, which may be specified in certificates, for CRL checking\n" + " --cdp Default CDP; URLs may start with 'http://' or 'file:'\n" " -o Directory where pkcs7 certs will be written\n" " -w Timeout in seconds to wait for server response (default=10)\n" //EST_SSL_READ_TIMEOUT_DEF " -f Runs EST Client in FIPS MODE = ON\n" @@ -235,12 +248,10 @@ EST_HTTP_AUTH_CRED_RC auth_credentials_token_cb (EST_HTTP_AUTH_HDR *auth_credent static int client_manual_cert_verify (X509 *cur_cert, int openssl_cert_error) { - if (openssl_cert_error == X509_V_ERR_UNABLE_TO_GET_CRL) { + if (openssl_cert_error == X509_V_ERR_UNABLE_TO_GET_CRL && !require_crl) { // this part could now be handled by the library alone return 1; // accepted } - BIO *bio_err; - bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); int approve = 0; /* @@ -259,8 +270,6 @@ static int client_manual_cert_verify (X509 *cur_cert, int openssl_cert_error) */ X509_signature_print(bio_err, cur_cert->sig_alg, cur_cert->signature); - BIO_free(bio_err); - return approve; } @@ -634,6 +643,11 @@ static void retry_enroll_delay (int retry_delay, time_t retry_time) } } +static STACK_OF(X509_CRL) *lookup_crls_cb(X509_STORE_CTX *ctx, X509_NAME *nm) +{ + return load_crls_from_cdps(ctx, nm, default_cdp); +} + static void do_operation () { @@ -655,6 +669,25 @@ static void do_operation () exit(1); } + if (enable_crl) { + rv = est_enable_crl(ectx); + if (rv != EST_ERR_NONE) { + printf("\nUnable to enable CRL checking. Aborting!!!\n"); + exit(1); + } + } + rv = est_set_crl_strictness_lookup(ectx, require_crl, use_cdp ? lookup_crls_cb : NULL); + if (rv != EST_ERR_NONE) { + printf("\nUnable to set CRL checking. Aborting!!!\n"); + exit(1); + } + + if (rv != EST_ERR_NONE) { + printf("\nUnable to enable CRL checking. Aborting!!!\n"); + printf("EST error code %d (%s)\n", rv, EST_ERR_NUM_TO_STR(rv)); + exit(1); + } + rv = est_client_set_read_timeout(ectx, read_timeout); if (rv != EST_ERR_NONE) { printf("\nUnable to configure read timeout from server. Aborting!!!\n"); @@ -852,6 +885,7 @@ int main (int argc, char **argv) BIO *certin; static struct option long_options[] = { { "trustanchor", 1, 0, 0 }, + { "cdp", 1, 0, 0 }, { "srp", 0, 0, 0 }, { "srp-user", 1, 0, 0 }, { "srp-password", 1, 0, 0 }, @@ -864,6 +898,12 @@ int main (int argc, char **argv) int trustanchor = 1; /* default to require a trust anchor */ char *trustanchor_file = NULL; + bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); + if (!bio_err) { + printf("\nBIO not working\n"); + exit(1); + } + est_http_uid[0] = 0x0; est_http_pwd[0] = 0x0; @@ -878,7 +918,7 @@ int main (int argc, char **argv) memset(client_cert_file, 0, 1); memset(out_dir, 0, 1); - while ((c = getopt_long(argc, argv, "?zfvagerx:y:k:s:p:o:c:w:u:h:", long_options, &option_index)) != -1) { + while ((c = getopt_long(argc, argv, "?zfvagerx:y:k:s:p:o:c:w:u:h:l::L", long_options, &option_index)) != -1) { switch (c) { case 0: #if 0 @@ -895,6 +935,10 @@ int main (int argc, char **argv) trustanchor_file = optarg; } } + if (!strcmp(long_options[option_index].name, "cdp")) { + use_cdp = 1; + strncpy(default_cdp, optarg, MAX_FILENAME_LEN); + } if (!strncmp(long_options[option_index].name, "srp", strlen("srp"))) { srp = 1; } @@ -918,6 +962,20 @@ int main (int argc, char **argv) case 'v': verbose = 1; break; + case 'l': + enable_crl = 1; + if (optarg) { + if (!strcmp(optarg, "+")) { + require_crl = 1; + } else { + fprintf(stderr, "Unrecognized -l option parameter '%s'. Aborting!!!\n", optarg); + exit(1); + } + } + break; + case 'L': + use_cdp = 1; + break; case 'z': force_pop = 1; break; @@ -1172,13 +1230,11 @@ int main (int argc, char **argv) est_apps_shutdown(); #if DEBUG_OSSL_LEAKS - BIO *bio_err; - bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); CRYPTO_mem_leaks(bio_err); - BIO_free(bio_err); #endif printf("\n"); + BIO_free(bio_err); return 0; } diff --git a/example/server/apps.h b/example/server/apps.h index 523da6e..a2d5ce1 100644 --- a/example/server/apps.h +++ b/example/server/apps.h @@ -130,8 +130,6 @@ long app_RAND_load_files(char *file); /* `file' is a list of files to read, * (see e_os.h). The string is * destroyed! */ -CONF *config=NULL; - extern CONF *config; extern char *default_config_file; extern BIO *bio_err; @@ -235,6 +233,7 @@ typedef struct ca_db_st #define FORMAT_ASN1RSA 10 /* DER RSAPubicKey format */ #define FORMAT_MSBLOB 11 /* MS Key blob format */ #define FORMAT_PVK 12 /* MS PVK file format */ +#define FORMAT_HTTP 13 /* Download using HTTP */ #define EXT_COPY_NONE 0 #define EXT_COPY_ADD 1 diff --git a/example/server/estExampleCA.cnf b/example/server/estExampleCA.cnf index 758a3c5..8f87d2e 100644 --- a/example/server/estExampleCA.cnf +++ b/example/server/estExampleCA.cnf @@ -50,7 +50,7 @@ serialNumber = optional # requires this to avoid interpreting an end user certificate as a CA. basicConstraints=CA:FALSE keyUsage=digitalSignature -crlDistributionPoints=URI:http://example.com/crl.pem +crlDistributionPoints=URI:file:../server/estCA/crl.pem # Add the id-kp-cmcRA usage. This isn't defined in OpenSSL, so we # Need to use the OID value #extendedKeyUsage=critical,serverAuth,clientAuth,1.3.6.1.5.5.7.3.28 @@ -67,6 +67,7 @@ x509_extensions = v3_ca [ v3_ca ] basicConstraints = CA:TRUE subjectKeyIdentifier=hash +crlDistributionPoints=URI:file:../server/estCA/crl.pem [ req ] default_bits = 2048 diff --git a/example/server/estserver.c b/example/server/estserver.c index 4d1d322..700768f 100644 --- a/example/server/estserver.c +++ b/example/server/estserver.c @@ -28,21 +28,26 @@ #include #include "ossl_srv.h" #include "../util/utils.h" +#include "../util/crl.c" // TODO include properly into Makefile #include "../util/simple_server.h" #define MAX_FILENAME_LEN 255 /* - * The OpenSSL CA needs this BIO to send errors too + * The CRL retrieval code in crl.c and the OpenSSL CA need this BIO to send errors to */ BIO *bio_err = NULL; +CONF *config=NULL; /* * These are the command line options with defaults provided below */ static int verbose = 0; static int write_csr = 0; -static int crl = 0; +static int enable_crl = 0; +static int require_crl = 0; +static int use_cdp = 0; +static char default_cdp[MAX_FILENAME_LEN] = ""; static int pop = 0; static int v6 = 0; static int srp = 0; @@ -143,7 +148,9 @@ static void show_usage_and_exit (void) " -c PEM file to use for server cert\n" " -k PEM file to use for server key\n" " -r HTTP realm to present to clients\n" - " -l Enable CRL checks\n" + " -l[+] Enable CRL checks; +: require strict CRL checking\n" + " -L Use CDPs, which may be specified in certificates, for CRL checking\n" + " --cdp Default CDP; URLs may start with 'http://' or 'file:'\n" " -t Enable check for binding client PoP to the TLS UID\n" #ifndef DISABLE_TSEARCH " -m Simulate manual CA enrollment\n" @@ -640,6 +647,31 @@ void cleanup (void) } +static int server_strong_cert_verify(EST_CTX *ectx, X509_STORE_CTX *store_ctx, int ok) // just a demo +{ + if (store_ctx->untrusted) { + printf("Subjects of currently untrusted certs:\n"); + for (int i = 0; iuntrusted); i++) + printf("%s\n", sk_X509_value(store_ctx->untrusted, i)->name); + } else { + printf("No currently untrusted certs\n"); + } + + printf("Current CRL score: 0x%04x\n", store_ctx->current_crl_score); + if (store_ctx->current_crl && store_ctx->current_crl->crl) { + printf("Issuer of currently used CRL: %s\n", X509_NAME_oneline(store_ctx->current_crl->crl->issuer, NULL, 0)); + } else { + printf("No currently used CRL\n"); + } + + return ok; +} + +static STACK_OF(X509_CRL) *lookup_crls_cb(X509_STORE_CTX *ctx, X509_NAME *nm) +{ + return load_crls_from_cdps(ctx, nm, default_cdp); +} + /* * This is the main entry point into the example EST server. * This routine parses the command line options, reads in the @@ -662,6 +694,7 @@ int main (int argc, char **argv) char vfile[255]; int option_index = 0; static struct option long_options[] = { + {"cdp", 1, 0, 0}, {"srp", 1, NULL, 0}, {"enforce-csr", 0, NULL, 0}, {"token", 1, 0, 0}, @@ -674,7 +707,7 @@ int main (int argc, char **argv) show_usage_and_exit(); } - while ((c = getopt_long(argc, argv, "?fhbwnovr:c:k:m:p:d:lt6", long_options, &option_index)) != -1) { + while ((c = getopt_long(argc, argv, "?fhbwnovr:c:k:m:p:d:l::Lt6", long_options, &option_index)) != -1) { switch (c) { case 0: #if 0 @@ -684,6 +717,10 @@ int main (int argc, char **argv) } printf ("\n"); #endif + if (!strcmp(long_options[option_index].name, "cdp")) { + use_cdp = 1; + strncpy(default_cdp, optarg, MAX_FILENAME_LEN); + } if (!strncmp(long_options[option_index].name,"srp", strlen("srp"))) { srp = 1; strncpy(vfile, optarg, 255); @@ -722,7 +759,18 @@ int main (int argc, char **argv) verbose = 1; break; case 'l': - crl = 1; + enable_crl = 1; + if (optarg) { + if (!strcmp(optarg, "+")) { + require_crl = 1; + } else { + fprintf(stderr, "Unrecognized -l option parameter '%s'. Aborting!!!\n", optarg); + exit(1); + } + } + break; + case 'L': + use_cdp = 1; break; case 't': pop = 1; @@ -868,6 +916,7 @@ int main (int argc, char **argv) exit(1); } est_set_ex_data(ectx, &test_app_data); + est_set_strong_cert_verify_cb(ectx, server_strong_cert_verify); if (enforce_csr) { est_server_enforce_csrattr(ectx); @@ -880,9 +929,15 @@ int main (int argc, char **argv) if (verbose) printf("\nRetry period being set to: %d \n", retry_period); est_server_set_retry_period(ectx, retry_period); - if (crl) { + if (enable_crl) { est_enable_crl(ectx); } + rv = est_set_crl_strictness_lookup(ectx, require_crl, use_cdp ? lookup_crls_cb : NULL); + if (rv != EST_ERR_NONE) { + printf("\nUnable to set CRL checking. Aborting!!!\n"); + exit(1); + } + if (!pop) { if (verbose) printf("\nDisabling PoP check"); est_server_disable_pop(ectx); diff --git a/example/util/crl.c b/example/util/crl.c new file mode 100644 index 0000000..d0a4cc8 --- /dev/null +++ b/example/util/crl.c @@ -0,0 +1,193 @@ +/*------------------------------------------------------------------ + * crl.c - CDP/CRL download related code, taken from openssl/apps/apps.c and slightly extended + *------------------------------------------------------------------ + */ + +#include +#include +#include +#include "../server/apps.h" + +int load_cert_crl_http(const char *url, BIO *err, + X509 **pcert, X509_CRL **pcrl) +{ + char *host = NULL, *port = NULL, *path = NULL; + BIO *bio = NULL; + OCSP_REQ_CTX *rctx = NULL; + int use_ssl, rv = 0; + if (!OCSP_parse_url(url, &host, &port, &path, &use_ssl)) + goto err; + if (use_ssl) { + if (err) + BIO_puts(err, "https not supported\n"); + goto err; + } + bio = BIO_new_connect(host); + if (!bio || !BIO_set_conn_port(bio, port)) + goto err; + rctx = OCSP_REQ_CTX_new(bio, 1024); + if (!rctx) + goto err; + if (!OCSP_REQ_CTX_http(rctx, "GET", path)) + goto err; + if (!OCSP_REQ_CTX_add1_header(rctx, "Host", host)) + goto err; + if (pcert) { + do { + rv = X509_http_nbio(rctx, pcert); + } + while (rv == -1); + } else { + do { + rv = X509_CRL_http_nbio(rctx, pcrl); + } while (rv == -1); + } + + err: + if (host) + OPENSSL_free(host); + if (path) + OPENSSL_free(path); + if (port) + OPENSSL_free(port); + if (bio) + BIO_free_all(bio); + if (rctx) + OCSP_REQ_CTX_free(rctx); + if (rv != 1) { + if (bio && err) + BIO_printf(bio_err, "Error loading %s from '%s'\n", + pcert ? "certificate" : "CRL", url); + ERR_print_errors(bio_err); + } + return rv; +} + +X509_CRL *load_crl(const char *infile, int format) +{ + X509_CRL *x = NULL; + BIO *in = NULL; + + BIO_printf(bio_err, "\nDEBUG: Loading CRL from '%s' ", infile); + if (strncmp(infile, "http://", 7)) + format = FORMAT_PEM; // unless http, try loading PEM file + + if (format == FORMAT_HTTP) { + load_cert_crl_http(infile, bio_err, NULL, &x); + return x; + } + + in = BIO_new(BIO_s_file()); + if (in == NULL) { + ERR_print_errors(bio_err); + goto end; + } + + if (infile == NULL) + goto end; // BIO_set_fp(in, stdin, BIO_NOCLOSE); + else { + if (!strncmp(infile, "file:", 5)) + infile += 5; + if (BIO_read_filename(in, infile) <= 0) { + perror(infile); + goto end; + } + } + if (format == FORMAT_ASN1) + x = d2i_X509_CRL_bio(in, NULL); + else if (format == FORMAT_PEM) + x = PEM_read_bio_X509_CRL(in, NULL, NULL, NULL); + else { + BIO_printf(bio_err, "bad input format specified for input crl\n"); + goto end; + } + if (x == NULL) { + BIO_printf(bio_err, "unable to load CRL\n"); + ERR_print_errors(bio_err); + goto end; + } + + end: + ERR_clear_error(); // needed to prevent EST_ERR_SYSCALL, e.g., when infile does not exist + BIO_free(in); + return (x); +} + +/* Get first http URL from a DIST_POINT structure */ + +static const char *get_dp_url(DIST_POINT *dp) +{ + GENERAL_NAMES *gens; + GENERAL_NAME *gen; + int i, gtype; + ASN1_STRING *uri; + if (!dp->distpoint || dp->distpoint->type != 0) + return NULL; + gens = dp->distpoint->name.fullname; + for (i = 0; i < sk_GENERAL_NAME_num(gens); i++) { + gen = sk_GENERAL_NAME_value(gens, i); + uri = GENERAL_NAME_get0_value(gen, >ype); + if (gtype == GEN_URI && ASN1_STRING_length(uri) > 6) { + char *uptr = (char *)ASN1_STRING_data(uri); + if (!strncmp(uptr, "http://", 7) || !strncmp(uptr, "file:", 5)) + return uptr; + } + } + return NULL; +} + +/* + * Look through a CRLDP structure and attempt to find an http URL to + * downloads a CRL from. + */ + +static X509_CRL *load_crl_crldp(STACK_OF(DIST_POINT) *crldp, const char *default_cdp) +{ + int i; + X509_CRL *crl = NULL; + const char *urlptr = NULL; + for (i = 0; i < sk_DIST_POINT_num(crldp); i++) { + DIST_POINT *dp = sk_DIST_POINT_value(crldp, i); + urlptr = get_dp_url(dp); + if (urlptr && (crl = load_crl(urlptr, FORMAT_HTTP))) + break; + } + if (!crl && default_cdp && default_cdp[0]) + crl = load_crl(default_cdp, FORMAT_HTTP); + return crl; +} + +/* + * Example of downloading CRLs from CRLDP: not usable for real world as it + * always downloads, doesn't support non-blocking I/O and doesn't cache + * anything. + */ + +STACK_OF(X509_CRL) *load_crls_from_cdps(X509_STORE_CTX *ctx, X509_NAME *nm, const char *default_cdp) +{ + X509 *x; + STACK_OF(X509_CRL) *crls = NULL; + X509_CRL *crl; + STACK_OF(DIST_POINT) *crldp; + x = X509_STORE_CTX_get_current_cert(ctx); + crldp = X509_get_ext_d2i(x, NID_crl_distribution_points, NULL, NULL); + crl = load_crl_crldp(crldp, default_cdp); + sk_DIST_POINT_pop_free(crldp, DIST_POINT_free); + if (!crl) + return NULL; + crls = sk_X509_CRL_new_null(); + sk_X509_CRL_push(crls, crl); + /* Try to download delta CRL */ + crldp = X509_get_ext_d2i(x, NID_freshest_crl, NULL, NULL); + crl = load_crl_crldp(crldp, default_cdp); + sk_DIST_POINT_pop_free(crldp, DIST_POINT_free); + if (crl) + sk_X509_CRL_push(crls, crl); + return crls; +} + +STACK_OF(X509_CRL) *crls_http_cb(X509_STORE_CTX *ctx, X509_NAME *nm) +{ + return load_crls_from_cdps(ctx, nm, NULL/* default_cdp */); +} + diff --git a/example/util/crl.h b/example/util/crl.h new file mode 100644 index 0000000..eaf1bd9 --- /dev/null +++ b/example/util/crl.h @@ -0,0 +1,14 @@ +/*------------------------------------------------------------------ + * crl.h - CDP/CRL download related code, taken from openssl/apps/apps.c and slightly extended + *------------------------------------------------------------------ + */ + +#ifndef HEADER_CRL_H +#define HEADER_CRL_H + +#include + +STACK_OF(X509_CRL) *load_crls_from_cdps(X509_STORE_CTX *ctx, X509_NAME *nm, const char *default_cdp); +STACK_OF(X509_CRL) *crls_http_cb(X509_STORE_CTX *ctx, X509_NAME *nm); + +#endif diff --git a/src/est/est.c b/src/est/est.c index 4626afd..e50683c 100644 --- a/src/est/est.c +++ b/src/est/est.c @@ -50,6 +50,7 @@ static void est_logger_stderr (char *format, va_list l) funlockfile(stderr); } +#ifndef DISABLE_BACKTRACE static void est_log_msg (char *format, ...) { va_list arguments; @@ -66,6 +67,7 @@ static void est_log_msg (char *format, ...) } va_end(arguments); } +#endif /* * Global function to be called to log something @@ -587,7 +589,8 @@ EST_ERROR est_load_trusted_certs (EST_CTX *ctx, unsigned char *certs, int certs_ EST_LOG_ERR("Unable to allocate combined cert store"); return (EST_ERR_LOAD_TRUST_CERTS); } - X509_STORE_set_verify_cb(ctx->trusted_certs_store, ossl_verify_cb); + X509_STORE_set_verify_cb(ctx->trusted_certs_store, est_cert_verify_cb); + X509_STORE_set_lookup_crls_cb(ctx->trusted_certs_store, ctx->lookup_crls_cb); rv = ossl_init_cert_store(ctx->trusted_certs_store, certs, certs_len); if (rv != EST_ERR_NONE) { EST_LOG_ERR("Unable to populate combined cert store"); @@ -963,6 +966,74 @@ EST_ERROR est_enable_crl (EST_CTX *ctx) return (EST_ERR_NONE); } +/*! @brief est_set_crl_strictness_lookup() is used by an application + to set the strictness of CRL checking for TLS peer certificates + and optionally set an OpenSSL callback function for retrieving CRLs, + e.g., from CDP entries in certificates during the TLS handshake. + This may update CRL entries set by est_load_trusted_certs(), which is + called also from est_client_init(), est_server_init(), and est_proxy_init(). + + @param ctx Pointer to the EST context + @param require Whether the presence of CRLs is strictly required + @param lookup_crls_cb NULL or callback for retrieving CRLs + + @return EST_ERROR. + + NOTE: This should be done soon after calling est_client_init(), est_server_init(), + or est_proxy_init() and cannot affect cacerts loading in these functions. + */ +EST_ERROR est_set_crl_strictness_lookup (EST_CTX *ctx, int require, + STACK_OF(X509_CRL) *(*lookup_crls_cb)(X509_STORE_CTX *ctx, X509_NAME *nm)) +// TODO ideally, this should become part of est_client_init(), est_server_init(), and est_server_init() +{ + if (!ctx) { + EST_LOG_ERR("Null context"); + return (EST_ERR_NO_CTX); + } + + ctx->require_crl = require; + ctx->lookup_crls_cb = lookup_crls_cb; + + X509_STORE *trusted_certs_store = ctx->trusted_certs_store; + if (!trusted_certs_store) // check if it has been forwarded by est_client_init[_ssl_ctx]() + trusted_certs_store = SSL_CTX_get_cert_store(ctx->ssl_ctx); + if (!trusted_certs_store) { + EST_LOG_ERR("Null trusted cert store"); + return (EST_ERR_NO_CTX); + } + + X509_STORE_set_lookup_crls_cb(trusted_certs_store, lookup_crls_cb); + return (EST_ERR_NONE); +} + +/*! @brief est_set_strong_cert_verify_cb() can be used by an application + to set a certificate verification callback function that is stronger + than the one optionally given as parameter of est_client_init(): + It will be called for any current status (ok = 0, 1, or 2) of certificate + verification by OpenSSL. It is given access to the EST context, the full + store context, and the current outcome of the verification decision. + + @param ctx Pointer to the EST context + @param strong_cert_verify_cb NULL or callback for managing the cert verify + outcome, see also http://linux.die.net/man/3/x509_store_ctx_set_verify_cb + + @return EST_ERROR. + + NOTE: This should be done soon after calling est_client_init(), est_server_init(), + or est_proxy_init() and cannot affect cacerts loading in these functions. +*/ +EST_ERROR est_set_strong_cert_verify_cb (EST_CTX *ctx, int (*strong_cert_verify_cb)(EST_CTX *, X509_STORE_CTX *, int ok)) +// TODO ideally, this should become part of est_client_init(), est_server_init(), and est_server_init() +{ + if (!ctx) { + EST_LOG_ERR("Null context"); + return (EST_ERR_NO_CTX); + } + + ctx->strong_cert_verify_cb = strong_cert_verify_cb; + return (EST_ERR_NONE); +} + /* * est_asn1_sanity_test - perform a sanity test on the CSR * attribute string. This function operates on an ASN.1 hex diff --git a/src/est/est.h b/src/est/est.h index 05a6095..51fb2db 100644 --- a/src/est/est.h +++ b/src/est/est.h @@ -335,6 +335,8 @@ typedef EST_HTTP_AUTH_CRED_RC (*auth_credentials_cb)(EST_HTTP_AUTH_HDR *auth_cre * Begin the public API prototypes */ EST_ERROR est_enable_crl(EST_CTX *ctx); +EST_ERROR est_set_crl_strictness_lookup(EST_CTX *ctx, int require, STACK_OF(X509_CRL) *(*lookup_crls_cb)(X509_STORE_CTX *ctx, X509_NAME *nm)); +EST_ERROR est_set_strong_cert_verify_cb(EST_CTX *ctx, int (*strong_cert_verify_cb)(EST_CTX *, X509_STORE_CTX *, int ok)); EST_ERROR est_init_logger(EST_LOG_LEVEL lvl, void (*loggerfunc)(char *, va_list)); int est_get_api_level(void); const char * est_get_version(void); @@ -381,7 +383,7 @@ EST_ERROR est_proxy_set_auth_cred_cb(EST_CTX *ctx, auth_credentials_cb); */ EST_CTX *est_client_init(unsigned char *ca_chain, int ca_chain_len, EST_CERT_FORMAT cert_format, - int (*cert_verify_cb)(X509 *, int)); + int (*cert_verify_cb)(X509 *, int openssl_cert_error)); EST_ERROR est_client_set_auth(EST_CTX *ctx, const char *uid, const char *pwd, X509 *client_cert, EVP_PKEY *private_key); EST_ERROR est_client_set_auth_cred_cb(EST_CTX *ctx, auth_credentials_cb); diff --git a/src/est/est_client.c b/src/est/est_client.c index b87b324..381e5ca 100644 --- a/src/est/est_client.c +++ b/src/est/est_client.c @@ -29,8 +29,6 @@ #include "est_ossl_util.h" #include -#define SSL_EXDATA_INDEX_INVALID -1 - int e_ctx_ssl_exdata_index = SSL_EXDATA_INDEX_INVALID; /***************************************************************************** @@ -211,39 +209,6 @@ static EST_ERROR est_generate_pkcs10 (EST_CTX *ctx, char *cn, char *cp, } -/* - * This function is a callback used by OpenSSL's verify_cert function. - * It's called at the end of a cert verification to allow an opportunity to - * gather more information regarding a failing cert verification, and to - * possibly change the result of the verification. - * - * This callback is similar to the ossl routine, but does not alter - * the verification result. - */ -static int est_client_cacert_verify_cb (int ok, X509_STORE_CTX *ctx) -{ - int cert_error = X509_STORE_CTX_get_error(ctx); - X509 *current_cert = X509_STORE_CTX_get_current_cert(ctx); - - EST_LOG_INFO("enter function: ok=%d cert_error=%d", ok, cert_error); - - if (!ok) { - if (current_cert) { - X509_NAME_print_ex_fp(stdout, - X509_get_subject_name(current_cert), - 0, XN_FLAG_ONELINE); - printf("\n"); - } - EST_LOG_INFO("%s error %d at %d depth lookup: %s\n", - X509_STORE_CTX_get0_parent_ctx(ctx) ? "[CRL path]" : "", - cert_error, - X509_STORE_CTX_get_error_depth(ctx), - X509_verify_cert_error_string(cert_error)); - } - return (ok); -} - - /* * This function will remove CRLs from a received cacert response buffer. * @@ -553,7 +518,8 @@ static EST_ERROR verify_cacert_resp (EST_CTX *ctx, unsigned char *cacerts, return (EST_ERR_MALLOC); } - X509_STORE_set_verify_cb(trusted_cacerts_store, est_client_cacert_verify_cb); + X509_STORE_set_verify_cb(trusted_cacerts_store, est_cert_verify_cb); + X509_STORE_set_lookup_crls_cb(trusted_cacerts_store, ctx->lookup_crls_cb); for (i=0; imanual_cert_verify_cb) { - EST_LOG_INFO("EST client application server cert verify function is registered\n"); + EST_LOG_INFO("Using simple application-level cert verify function"); approve = e_ctx->manual_cert_verify_cb(current_cert, cert_error); } else { - EST_LOG_INFO("NO EST client application server cert verify function registered\n"); + EST_LOG_INFO("No simple application-level cert verify function registered"); - if (cert_error == X509_V_ERR_UNABLE_TO_GET_CRL) { + if (cert_error == X509_V_ERR_UNABLE_TO_GET_CRL && !(e_ctx->require_crl)) { /* * We've enabled CRL checking in the TLS stack. If the * application hasn't loaded a CRL, then this verify error * can occur. The peer's cert is valid, but we can't * confirm if it was revoked. The app has not provided - * a way for us to notify on this, so our only option is + * a manual_cert_verify_cb for us to notify on this. + * Moreover, strict CRL checking is not required, so it is safe * to log a warning and proceed on. */ - EST_LOG_WARN("No CRL loaded, TLS peer will be allowed."); + EST_LOG_WARN("No CRL loaded and strict checking not required, TLS peer should be allowed."); approve = 1; } } - break; + // do not break; thus allow strong callback to override result /* The remainder of these will result in the ok state remaining unchanged * and a EST log warning message being logged. @@ -756,11 +727,13 @@ static int cert_verify_cb (int ok, X509_STORE_CTX *x_ctx) case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION: case X509_V_ERR_CERT_REVOKED: default: - EST_LOG_WARN("Certificate verify failed (reason = %d) (%s)", - cert_error, X509_verify_cert_error_string(cert_error)); break; } } + if (e_ctx->strong_cert_verify_cb) { + EST_LOG_INFO("Using strong application-level cert verify function"); + approve = e_ctx->strong_cert_verify_cb(e_ctx, x_ctx, approve); + } return (approve); } @@ -816,7 +789,7 @@ EST_ERROR est_client_init_ssl_ctx (EST_CTX *ctx) * Make sure we're verifying the server */ SSL_CTX_set_verify(s_ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, - cert_verify_cb); + est_cert_verify_cb); /* * leverage the cert store we already created from the @@ -860,7 +833,7 @@ EST_ERROR est_client_init_ssl_ctx (EST_CTX *ctx) /* * Save the reference to the SSL session * This will be used later when matching the EST_CTX to the SSL context - * in est_ssl_info_cb(). + * in est_cert_verify_cb(). */ ctx->ssl_ctx = s_ctx; @@ -2944,7 +2917,7 @@ EST_ERROR est_client_enroll (EST_CTX *ctx, char *cn, int *pkcs7_len, The provisioning response also includes the latest copy of the trusted CA certificates from the EST server. These should be persisted locally by the application for future use. The ca_cert_len argument will contain the - length of the certicates, which can then be retrieved by invoking + length of the certificates, which can then be retrieved by invoking est_client_copy_cacerts(). */ EST_ERROR est_client_provision_cert (EST_CTX *ctx, char *cn, diff --git a/src/est/est_client_http.c b/src/est/est_client_http.c index 1df4b85..cf3b419 100644 --- a/src/est/est_client_http.c +++ b/src/est/est_client_http.c @@ -46,7 +46,9 @@ #include "est_server_http.h" #include "est_ossl_util.h" +#ifndef INT_MAX #define INT_MAX ( 2147483647 ) +#endif #ifdef RETRY_AFTER_DELAY_TIME_SUPPORT diff --git a/src/est/est_locl.h b/src/est/est_locl.h index 340a248..ed68395 100644 --- a/src/est/est_locl.h +++ b/src/est/est_locl.h @@ -172,6 +172,8 @@ struct est_ctx { char realm[MAX_REALM+1]; SSL_CTX *ssl_ctx; int enable_crl; + int require_crl; + STACK_OF(X509_CRL) *(*lookup_crls_cb)(X509_STORE_CTX *ctx, X509_NAME *nm); char token_error[MAX_TOKEN_ERROR+1]; char token_error_desc[MAX_TOKEN_ERROR_DESC+1]; @@ -207,6 +209,7 @@ struct est_ctx { SSL_SESSION *sess; int read_timeout; int (*manual_cert_verify_cb)(X509 *cur_cert, int openssl_cert_error); + int (*strong_cert_verify_cb)(EST_CTX *, X509_STORE_CTX *, int ok); const EVP_MD *signing_digest; int retry_after_delay; time_t retry_after_date; @@ -312,6 +315,11 @@ void est_client_disconnect(EST_CTX *ctx, SSL **ssl); int est_client_set_cert_and_key(SSL_CTX *ctx, X509 *cert, EVP_PKEY *key); EST_ERROR est_client_set_uid_pw(EST_CTX *ctx, const char *uid, const char *pwd); +int est_cert_verify_cb (int ok, X509_STORE_CTX *x_ctx); +// used for referencing the EST_CTX in est_cert_verify_cb(), currently defined in est_client.c: +#define SSL_EXDATA_INDEX_INVALID -1 +extern int e_ctx_ssl_exdata_index; + /* From est_client_http.c */ EST_ERROR est_io_get_response (EST_CTX *ctx, SSL *ssl, EST_OPERATION op, unsigned char **buf, int *payload_len); diff --git a/src/est/est_ossl_util.c b/src/est/est_ossl_util.c index b7689f8..2d53e3c 100644 --- a/src/est/est_ossl_util.c +++ b/src/est/est_ossl_util.c @@ -86,61 +86,28 @@ /***************************************************************************************** * Authorization routines *****************************************************************************************/ -int ossl_verify_cb (int ok, X509_STORE_CTX *ctx) +/* + * This function is a callback used by OpenSSL's verify_cert function. + * It's called at the end of a cert verification to allow an opportunity to + * gather more information regarding a failing cert verification, and to + * possibly change the result of the verification. + * + * This callback is just for printing and does not alter the verification result. + */ +int ossl_print_cert_verify_cb (int ok, X509_STORE_CTX *ctx) { - int cert_error = X509_STORE_CTX_get_error(ctx); - X509 *current_cert = X509_STORE_CTX_get_current_cert(ctx); - - EST_LOG_INFO("enter function: ok=%d cert_error=%d", ok, cert_error); - - if (!ok) { - if (current_cert) { - X509_NAME_print_ex_fp(stdout, - X509_get_subject_name(current_cert), - 0, XN_FLAG_ONELINE); - printf("\n"); - } - EST_LOG_INFO("%serror %d at %d depth lookup:%s\n", - X509_STORE_CTX_get0_parent_ctx(ctx) ? "[CRL path]" : "", - cert_error, - X509_STORE_CTX_get_error_depth(ctx), - X509_verify_cert_error_string(cert_error)); - switch (cert_error) { - case X509_V_ERR_UNABLE_TO_GET_CRL: - /* - * We've enabled CRL checking in the TLS stack. If - * the application hasn't loaded a CRL, then this - * verify error can occur. The peer's cert is valid, - * but we can't confirm if it was revoked. We'll - * warn the application. - */ - EST_LOG_WARN("No CRL loaded, TLS peer will be allowed."); - ok = 1; - break; - case X509_V_ERR_NO_EXPLICIT_POLICY: - case X509_V_ERR_CERT_HAS_EXPIRED: - - /* since we are just checking the certificates, it is - * ok if they are self signed. But we should still warn - * the user. - */ - - case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: - /* Continue after extension errors too */ - case X509_V_ERR_INVALID_CA: - case X509_V_ERR_INVALID_NON_CA: - case X509_V_ERR_PATH_LENGTH_EXCEEDED: - case X509_V_ERR_INVALID_PURPOSE: - case X509_V_ERR_CRL_HAS_EXPIRED: - case X509_V_ERR_CRL_NOT_YET_VALID: - case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION: - case X509_V_ERR_CERT_REVOKED: - default: - EST_LOG_WARN("Certificate verify failed (reason=%d)", - cert_error); - break; - } - return ok; + if (ctx == NULL) { + EST_LOG_ERR("Invalid X509 context pointer"); + } else { + int cert_error = X509_STORE_CTX_get_error(ctx); + X509 *current_cert = X509_STORE_CTX_get_current_cert(ctx); + + EST_LOG_INFO("%s: depth=%d ok=%d error=%d (%s) for cert subject='%s', issuer='%s'", + X509_STORE_CTX_get0_parent_ctx(ctx) ? "CRL path validation" : "cert verification", + X509_STORE_CTX_get_error_depth(ctx), ok, + cert_error, X509_verify_cert_error_string(cert_error), + current_cert ? X509_NAME_oneline(X509_get_subject_name(current_cert), NULL, 0) : "(no cert)" , + current_cert ? X509_NAME_oneline(X509_get_issuer_name(current_cert), NULL, 0) : "(no cert)"); } return (ok); } diff --git a/src/est/est_ossl_util.h b/src/est/est_ossl_util.h index 7fcf53f..126804f 100644 --- a/src/est/est_ossl_util.h +++ b/src/est/est_ossl_util.h @@ -13,7 +13,7 @@ #include "est.h" -int ossl_verify_cb(int ok, X509_STORE_CTX *ctx); +int ossl_print_cert_verify_cb(int ok, X509_STORE_CTX *ctx); void ossl_dump_ssl_errors(void); EST_ERROR ossl_init_cert_store (X509_STORE *store, unsigned char *raw1, int size1); diff --git a/src/est/est_server.c b/src/est/est_server.c index 856026f..a32dcdf 100644 --- a/src/est/est_server.c +++ b/src/est/est_server.c @@ -275,8 +275,8 @@ EST_AUTH_STATE est_enroll_auth (EST_CTX *ctx, void *http_ctx, SSL *ssl, EST_LOG_INFO("TLS: client certificate is valid"); rv = EST_CERT_AUTH; } - else if (X509_V_ERR_UNABLE_TO_GET_CRL == v_result) { - EST_LOG_WARN("Peer cert is valid, but no CRL was loaded. Unable to determine if peer cert is revoked."); + else if (X509_V_ERR_UNABLE_TO_GET_CRL == v_result && !(ctx->require_crl)) { + EST_LOG_WARN("Peer cert is valid but no CRL was found. Although unable to determine if peer cert is revoked, passing because strict checking is not required."); rv = EST_CERT_AUTH; } else { EST_LOG_INFO("TLS: client certificate not verified (v_result=%d)", diff --git a/src/est/est_server_http.c b/src/est/est_server_http.c index 279a176..5ed3ea0 100644 --- a/src/est/est_server_http.c +++ b/src/est/est_server_http.c @@ -1296,14 +1296,24 @@ static int set_ssl_option (struct mg_context *ctx) cry(fc(ctx), "SSL_CTX_new (server) error: %s", ssl_error()); return 0; } + + /* + * Save the reference to the SSL session. + * This will be used also when matching the EST_CTX to the SSL context + * in est_cert_verify_cb(). + */ ctx->ssl_ctx = ssl_ctx; + + if (e_ctx_ssl_exdata_index == SSL_EXDATA_INDEX_INVALID) { + e_ctx_ssl_exdata_index = SSL_get_ex_new_index(0, "EST Context", NULL, NULL, NULL); + } ectx = ctx->est_ctx; conn = fc(ctx); conn->request_info.ev_data = ctx->ssl_ctx; - SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, est_cert_verify_cb); /* * Set the Session ID context to enable OpenSSL session @@ -1619,6 +1629,15 @@ EST_ERROR est_server_handle_request (EST_CTX *ctx, int fd) */ conn->ssl = SSL_new(conn->ctx->ssl_ctx); if (conn->ssl != NULL) { + if (e_ctx_ssl_exdata_index == SSL_EXDATA_INDEX_INVALID) { + EST_LOG_WARN("Invalid SSL ex_data index for EST context value"); + } else { + /* + * Need to set the EST ctx into the exdata of the SSL session context so + * that it can be retrieved on a per session basis. + */ + SSL_set_ex_data(conn->ssl, e_ctx_ssl_exdata_index, ctx); + } SSL_set_fd(conn->ssl, conn->client.sock); ssl_err = SSL_accept(conn->ssl); if (ssl_err <= 0) {