Ok, so this work was done by Marko Kreen, all as the result of a very long discussion in:
https://github.com/libressl-portable/openbsd/pull/47 In a nutshell, I threw down a glove that libtls could have functions to support OCSP, and make it where a client could write ocsp stuff, but I would resist making libtls be and http library that does that for you. I challenged him to add the necessary support functions so it was possible to write a client. He delivered, and I've cleaned a few things up in it. (after a long delay which I apologize for) Attached to this message is marko's test program, which uses libcurl - The diff is for our libtls, and I've been able to compile and use his test program with it: $ ./oc amazon.com libssl: LibreSSL 2.4.1 OCSP stapling: good req_status=0 cert_status=0 crl_reason=0 this update: Mon Jul 4 08:17:21 2016 next update: Mon Jul 11 08:17:21 2016 revocation: -- OCSP URL: http://ss.symcd.com OCSP responder: good req_status=0 cert_status=0 crl_reason=0 this update: Mon Jul 4 08:17:21 2016 next update: Mon Jul 11 08:17:21 2016 revocation: -- $ ./oc google.com libssl: LibreSSL 2.4.1 OCSP stapling: no-ocsp OCSP URL: http://clients1.google.com/ocsp OCSP responder: good req_status=0 cert_status=0 crl_reason=0 this update: Tue Jul 5 13:00:28 2016 next update: Tue Jul 12 13:00:28 2016 revocation: -- $ Discussion, OK's diff --git lib/libtls/Makefile lib/libtls/Makefile index ca2f00b..461bf44 100644 --- lib/libtls/Makefile +++ lib/libtls/Makefile @@ -19,6 +19,7 @@ SRCS= tls.c \ tls_peer.c \ tls_server.c \ tls_util.c \ + tls_ocsp.c \ tls_verify.c MAN= tls_init.3 diff --git lib/libtls/tls.c lib/libtls/tls.c index 76d00e5..b00bea8 100644 --- lib/libtls/tls.c +++ lib/libtls/tls.c @@ -393,6 +393,13 @@ tls_reset(struct tls *ctx) tls_free_conninfo(ctx->conninfo); free(ctx->conninfo); ctx->conninfo = NULL; + + tls_ocsp_info_free(ctx->ocsp_info); + ctx->ocsp_info = NULL; + ctx->ocsp_result = NULL; + + if (ctx->flags & TLS_OCSP_CLIENT) + tls_ocsp_client_free(ctx); } int diff --git lib/libtls/tls.h lib/libtls/tls.h index 75c46c1..da6cd69 100644 --- lib/libtls/tls.h +++ lib/libtls/tls.h @@ -40,6 +40,29 @@ extern "C" { #define TLS_WANT_POLLIN -2 #define TLS_WANT_POLLOUT -3 +#define TLS_NO_OCSP -4 + +#define TLS_OCSP_RESPONSE_SUCCESSFUL 0 +#define TLS_OCSP_RESPONSE_MALFORMED 1 +#define TLS_OCSP_RESPONSE_INTERNALERR 2 +#define TLS_OCSP_RESPONSE_TRYLATER 3 +#define TLS_OCSP_RESPONSE_SIGREQUIRED 5 +#define TLS_OCSP_RESPONSE_UNAUTHORIZED 6 + +#define TLS_OCSP_CERT_GOOD 0 +#define TLS_OCSP_CERT_REVOKED 1 +#define TLS_OCSP_CERT_UNKNOWN 2 + +#define TLS_CRL_REASON_UNPSECIFIED 0 +#define TLS_CRL_REASON_KEY_COMPROMISE 1 +#define TLS_CRL_REASON_CA_COMPROMISE 2 +#define TLS_CRL_REASON_AFFILIATION_CHANGED 3 +#define TLS_CRL_REASON_SUPERSEDED 4 +#define TLS_CRL_REASON_CESSATION_OF_OPERATION 5 +#define TLS_CRL_REASON_CERTIFICATE_HOLD 6 +#define TLS_CRL_REASON_REMOVE_FROM_CRL 8 +#define TLS_CRL_REASON_PRIVILEGE_WITH_DRAWN 9 +#define TLS_CRL_REASON_AA_COMPROMISE 10 struct tls; struct tls_config; @@ -70,6 +93,8 @@ int tls_config_set_keypair_file(struct tls_config *_config, const char *_cert_file, const char *_key_file); int tls_config_set_keypair_mem(struct tls_config *_config, const uint8_t *_cert, size_t _cert_len, const uint8_t *_key, size_t _key_len); +int tls_config_set_ocsp_stapling_file(struct tls_config *_config, const char *_blob_file); +int tls_config_set_ocsp_stapling_mem(struct tls_config *_config, const uint8_t *_blob, size_t _len); void tls_config_set_protocols(struct tls_config *_config, uint32_t _protocols); void tls_config_set_verify_depth(struct tls_config *_config, int _verify_depth); @@ -121,6 +146,18 @@ const char *tls_conn_cipher(struct tls *_ctx); uint8_t *tls_load_file(const char *_file, size_t *_len, char *_password); +int tls_get_ocsp_info(struct tls *ctx, int *response_status, int *cert_status, int *crl_reason, + time_t *this_update, time_t *next_update, time_t *revoction_time, + const char **result_text); + +int tls_ocsp_check_peer_request(struct tls **ocsp_ctx_p, struct tls *target, + char **ocsp_url, void **request_blob, size_t *request_size); + +int tls_ocsp_refresh_stapling_request(struct tls **ocsp_ctx_p, struct tls_config *config, + char **ocsp_url, void **request_blob, size_t *request_size); + +int tls_ocsp_process_response(struct tls *ctx, const void *response_blob, size_t size); + #ifdef __cplusplus } #endif diff --git lib/libtls/tls_client.c lib/libtls/tls_client.c index 3847f4c..86dd9a8 100644 --- lib/libtls/tls_client.c +++ lib/libtls/tls_client.c @@ -209,6 +209,11 @@ tls_connect_fds(struct tls *ctx, int fd_read, int fd_write, (tls_configure_ssl_verify(ctx, SSL_VERIFY_PEER) == -1)) goto err; + if (SSL_CTX_set_tlsext_status_cb(ctx->ssl_ctx, tls_ocsp_verify_callback) != 1) { + tls_set_errorx(ctx, "ssl OCSP verification setup failure"); + goto err; + } + if ((ctx->ssl_conn = SSL_new(ctx->ssl_ctx)) == NULL) { tls_set_errorx(ctx, "ssl connection failure"); goto err; @@ -222,6 +227,10 @@ tls_connect_fds(struct tls *ctx, int fd_read, int fd_write, tls_set_errorx(ctx, "ssl file descriptor failure"); goto err; } + if (SSL_set_tlsext_status_type(ctx->ssl_conn, TLSEXT_STATUSTYPE_ocsp) != 1) { + tls_set_errorx(ctx, "ssl OCSP extension setup failure"); + goto err; + } /* * RFC4366 (SNI): Literal IPv4 and IPv6 addresses are not diff --git lib/libtls/tls_config.c lib/libtls/tls_config.c index 8f73a5a..55655d6 100644 --- lib/libtls/tls_config.c +++ lib/libtls/tls_config.c @@ -371,6 +371,24 @@ tls_config_set_keypair_mem(struct tls_config *config, const uint8_t *cert, return (0); } +int +tls_config_set_ocsp_stapling_file(struct tls_config *config, const char *blob_file) +{ + if (blob_file != NULL) + tls_config_set_ocsp_stapling_mem(config, NULL, 0); + + return set_string(&config->ocsp_file, blob_file); +} + +int +tls_config_set_ocsp_stapling_mem(struct tls_config *config, const uint8_t *blob, size_t len) +{ + if (blob != NULL) + tls_config_set_ocsp_stapling_file(config, NULL); + + return set_mem(&config->ocsp_mem, &config->ocsp_len, blob, len); +} + void tls_config_set_protocols(struct tls_config *config, uint32_t protocols) { diff --git lib/libtls/tls_internal.h lib/libtls/tls_internal.h index 745fb40..9d53bc1 100644 --- lib/libtls/tls_internal.h +++ lib/libtls/tls_internal.h @@ -62,6 +62,9 @@ struct tls_config { int dheparams; int ecdhecurve; struct tls_keypair *keypair; + const char *ocsp_file; + char *ocsp_mem; + size_t ocsp_len; uint32_t protocols; int verify_cert; int verify_client; @@ -85,10 +88,14 @@ struct tls_conninfo { #define TLS_CLIENT (1 << 0) #define TLS_SERVER (1 << 1) #define TLS_SERVER_CONN (1 << 2) +#define TLS_OCSP_CLIENT (1 << 3) #define TLS_EOF_NO_CLOSE_NOTIFY (1 << 0) #define TLS_HANDSHAKE_COMPLETE (1 << 1) +struct tls_ocsp_query; +struct tls_ocsp_info; + struct tls { struct tls_config *config; struct tls_error error; @@ -103,6 +110,20 @@ struct tls { SSL_CTX *ssl_ctx; X509 *ssl_peer_cert; struct tls_conninfo *conninfo; + + const char *ocsp_result; + struct tls_ocsp_info *ocsp_info; + + struct tls_ocsp_query *ocsp_query; +}; + +struct tls_ocsp_info { + int response_status; + int cert_status; + int crl_reason; + time_t this_update; + time_t next_update; + time_t revocation_time; }; struct tls *tls_new(void); @@ -143,6 +164,11 @@ int tls_ssl_error(struct tls *ctx, SSL *ssl_conn, int ssl_ret, int tls_get_conninfo(struct tls *ctx); void tls_free_conninfo(struct tls_conninfo *conninfo); +int tls_ocsp_verify_callback(SSL *ssl, void *arg); +int tls_ocsp_stapling_callback(SSL *ssl, void *arg); +void tls_ocsp_client_free(struct tls *ctx); +void tls_ocsp_info_free(struct tls_ocsp_info *info); + int asn1_time_parse(const char *, size_t, struct tm *, int); #endif /* HEADER_TLS_INTERNAL_H */ diff --git lib/libtls/tls_ocsp.c lib/libtls/tls_ocsp.c new file mode 100644 index 0000000..8f8c1e3 --- /dev/null +++ lib/libtls/tls_ocsp.c @@ -0,0 +1,641 @@ +/* + * Copyright (c) 2015 Marko Kreen <mark...@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <openssl/err.h> +#include <openssl/ocsp.h> +#include <openssl/x509.h> + +#include <tls.h> +#include "tls_internal.h" + +#define MAXAGE_SEC (14*24*60*60) +#define JITTER_SEC (60) + +/* + * State for request. + */ + +struct tls_ocsp_query { + /* responder location */ + char *ocsp_url; + + /* request blob */ + uint8_t *request_data; + size_t request_size; + + /* network state */ + BIO *bio; + SSL_CTX *ssl_ctx; + OCSP_REQ_CTX *http_req; + + /* cert data, this struct does not own these */ + X509 *main_cert; + STACK_OF(X509) *extra_certs; + SSL_CTX *cert_ssl_ctx; +}; + +/* + * Extract OCSP response info. + */ + +static int +tls_asn1_parse_time(struct tls *ctx, ASN1_GENERALIZEDTIME *gt, time_t *dst_p) +{ + int res; + struct tm tm; + + if (!gt) { + *dst_p = 0; + return 0; + } + + res = asn1_time_parse(gt->data, gt->length, &tm, 0); + if (res == -1) + return -1; + + res = 0; + *dst_p = timegm(&tm); + if (*dst_p == (time_t)-1) + res = -1; + return res; +} + +static int +tls_ocsp_fill_info(struct tls *ctx, + int response_status, int cert_status, int crl_reason, + ASN1_GENERALIZEDTIME *revtime, + ASN1_GENERALIZEDTIME *thisupd, + ASN1_GENERALIZEDTIME *nextupd) +{ + struct tls_ocsp_info *info; + int res; + + info = calloc(1, sizeof (struct tls_ocsp_info)); + if (!info) { + tls_set_error(ctx, "calloc"); + return -1; + } + info->response_status = response_status; + info->cert_status = cert_status; + info->crl_reason = crl_reason; + + res = tls_asn1_parse_time(ctx, revtime, &info->revocation_time); + if (res == 0) + res = tls_asn1_parse_time(ctx, thisupd, &info->this_update); + if (res == 0) + res = tls_asn1_parse_time(ctx, nextupd, &info->next_update); + + if (res == 0) { + ctx->ocsp_info = info; + } else { + tls_ocsp_info_free(info); + } + return res; +} + +static void +tls_ocsp_fill_result(struct tls *ctx, int res) +{ + struct tls_ocsp_info *info = ctx->ocsp_info; + if (res < 0) { + ctx->ocsp_result = "error"; + } else if (info->response_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + ctx->ocsp_result = OCSP_response_status_str(info->response_status); + } else if (info->cert_status != V_OCSP_CERTSTATUS_REVOKED) { + ctx->ocsp_result = OCSP_cert_status_str(info->cert_status); + } else { + ctx->ocsp_result = OCSP_crl_reason_str(info->crl_reason); + } +} + +void +tls_ocsp_info_free(struct tls_ocsp_info *info) +{ + free(info); +} + +int +tls_get_ocsp_info(struct tls *ctx, int *response_status, int *cert_status, + int *crl_reason, time_t *this_update, + time_t *next_update, time_t *revoction_time, + const char **result_text) +{ + static const struct tls_ocsp_info no_ocsp = { -1, -1, -1, 0, 0, 0 }; + const struct tls_ocsp_info *info = ctx->ocsp_info; + const char *ocsp_result = ctx->ocsp_result; + int ret = 0; + + if (!info) { + info = &no_ocsp; + ret = -1; + } + + if (!ocsp_result) { + ret = TLS_NO_OCSP; + ocsp_result = "no-ocsp"; + } + + if (response_status) + *response_status = info->response_status; + if (cert_status) + *cert_status = info->cert_status; + if (crl_reason) + *crl_reason = info->crl_reason; + if (this_update) + *this_update = info->this_update; + if (next_update) + *next_update = info->next_update; + if (revoction_time) + *revoction_time = info->revocation_time; + if (result_text) + *result_text = ocsp_result; + + return ret; +} + +/* + * Verify stapled response + */ + +static OCSP_CERTID * +tls_ocsp_get_certid(X509 *main_cert, STACK_OF(X509) *extra_certs, SSL_CTX *ssl_ctx) +{ + X509_NAME *issuer_name; + X509 *issuer; + X509_STORE_CTX storectx; + X509_OBJECT tmpobj; + OCSP_CERTID *cid = NULL; + X509_STORE *store; + int ok; + + issuer_name = X509_get_issuer_name(main_cert); + if (!issuer_name) + return NULL; + + if (extra_certs) { + issuer = X509_find_by_subject(extra_certs, issuer_name); + if (issuer) + return OCSP_cert_to_id(NULL, main_cert, issuer); + } + + store = SSL_CTX_get_cert_store(ssl_ctx); + if (!store) + return NULL; + ok = X509_STORE_CTX_init(&storectx, store, main_cert, extra_certs); + if (ok != 1) + return NULL; + ok = X509_STORE_get_by_subject(&storectx, X509_LU_X509, issuer_name, &tmpobj); + if (ok == 1) { + cid = OCSP_cert_to_id(NULL, main_cert, tmpobj.data.x509); + X509_free(tmpobj.data.x509); + } + X509_STORE_CTX_cleanup(&storectx); + return cid; +} + +static int +tls_ocsp_verify_response(struct tls *ctx, X509 *main_cert, STACK_OF(X509) *extra_certs, + SSL_CTX *ssl_ctx, OCSP_RESPONSE *resp) +{ + OCSP_BASICRESP *br = NULL; + STACK_OF(X509) *ocsp_chain = NULL; + ASN1_GENERALIZEDTIME *revtime = NULL, *thisupd = NULL, *nextupd = NULL; + OCSP_CERTID *cid = NULL; + STACK_OF(X509) *combined = NULL; + int response_status=0, cert_status=0, crl_reason=0; + int ssl_res, ret = -1; + unsigned long flags; + + br = OCSP_response_get1_basic(resp); + if (!br) { + tls_set_errorx(ctx, "ocsp error: cannot load"); + goto error; + } + + /* + * Skip validation of 'extra_certs' as this should be done + * already as part of main handshake. + */ + flags = OCSP_TRUSTOTHER; + ocsp_chain = extra_certs; + + /* now verify */ + ssl_res = OCSP_basic_verify(br, ocsp_chain, SSL_CTX_get_cert_store(ssl_ctx), flags); + + if (ssl_res != 1) { + tls_set_error(ctx, "ocsp verify failed"); + goto error; + } + + /* signature OK, look inside */ + response_status = OCSP_response_status(resp); + if (response_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + tls_set_errorx(ctx, "ocsp verify failed: unsuccessful response - %s", + OCSP_response_status_str(response_status)); + goto error; + } + + cid = tls_ocsp_get_certid(main_cert, extra_certs, ssl_ctx); + if (!cid) { + tls_set_errorx(ctx, "ocsp verify failed: no issuer cert"); + goto error; + } + + ssl_res = OCSP_resp_find_status(br, cid, &cert_status, &crl_reason, &revtime, &thisupd, &nextupd); + if (ssl_res != 1) { + tls_set_errorx(ctx, "ocsp verify failed: no result for cert"); + goto error; + } + + ssl_res = OCSP_check_validity(thisupd, nextupd, JITTER_SEC, MAXAGE_SEC); + if (ssl_res != 1) { + tls_set_errorx(ctx, "ocsp verify failed: bad age"); + goto error; + } + + ssl_res = tls_ocsp_fill_info(ctx, response_status, cert_status, crl_reason, revtime, thisupd, nextupd); + if (ssl_res != 0) + goto error; + + /* finally can look at status */ + if (cert_status != V_OCSP_CERTSTATUS_GOOD && cert_status != V_OCSP_CERTSTATUS_UNKNOWN) { + tls_set_errorx(ctx, "ocsp verify failed: revoked cert - %s", + OCSP_crl_reason_str(crl_reason)); + goto error; + } + + ret = 0; + +error: + sk_X509_free(combined); + OCSP_CERTID_free(cid); + OCSP_BASICRESP_free(br); + return ret; +} + +/* + * Same callback on client-side has different error proto: + * 1=OK, 0=bad, -1=internal error + */ + +int +tls_ocsp_verify_callback(SSL *ssl, void *arg) +{ + OCSP_RESPONSE *resp = NULL; + STACK_OF(X509) *extra_certs = NULL; + X509 *peer = NULL; + const unsigned char *raw = NULL; + int size, res = -1; + struct tls *ctx; + + ctx = SSL_get_app_data(ssl); + if (!ctx) + return -1; + + size = SSL_get_tlsext_status_ocsp_resp(ssl, &raw); + if (size <= 0) + return 1; + + peer = SSL_get_peer_certificate(ssl); + if (!peer) { + tls_set_errorx(ctx, "ocsp verify failed: no peer cert"); + goto error; + } + + resp = d2i_OCSP_RESPONSE(NULL, &raw, size); + if (!resp) { + tls_set_errorx(ctx, "ocsp verify failed: parse failed"); + goto error; + } + + extra_certs = SSL_get_peer_cert_chain(ssl); + res = tls_ocsp_verify_response(ctx, peer, extra_certs, ctx->ssl_ctx, resp); +error: + tls_ocsp_fill_result(ctx, res); + OCSP_RESPONSE_free(resp); + X509_free(peer); + return (res == 0) ? 1 : 0; +} + +/* + * Staple OCSP response to server handshake. + */ + +int +tls_ocsp_stapling_callback(SSL *ssl, void *arg) +{ + struct tls *ctx; + char *mem, *fmem = NULL; + uint8_t *xmem; + size_t len; + int ret = SSL_TLSEXT_ERR_ALERT_FATAL; + + ctx = SSL_get_app_data(ssl); + if (!ctx) + return SSL_TLSEXT_ERR_NOACK; + + if (ctx->config->ocsp_file) { + fmem = mem = (char*)tls_load_file(ctx->config->ocsp_file, &len, NULL); + if (!mem) + goto err; + } else { + mem = ctx->config->ocsp_mem; + len = ctx->config->ocsp_len; + if (!mem) + return SSL_TLSEXT_ERR_NOACK; + } + xmem = malloc(len); + if (xmem) { + memcpy(xmem, mem, len); + if (SSL_set_tlsext_status_ocsp_resp(ctx->ssl_conn, xmem, len) != 1) { + free(xmem); + goto err; + } + ret = SSL_TLSEXT_ERR_OK; + } +err: + free(fmem); + return ret; +} + +/* + * Query OCSP responder over HTTP(S). + */ + +void +tls_ocsp_client_free(struct tls *ctx) +{ + struct tls_ocsp_query *q; + if (!ctx) + return; + q = ctx->ocsp_query; + if (q) { + if (q->http_req) + OCSP_REQ_CTX_free(q->http_req); + BIO_free_all(q->bio); + SSL_CTX_free(q->ssl_ctx); + + free(q->ocsp_url); + free(q->request_data); + free(q); + + ctx->ocsp_query = NULL; + } +} + +static struct tls * +tls_ocsp_client_new(void) +{ + struct tls *ctx; + + ctx = tls_new(); + if (!ctx) + return NULL; + ctx->flags = TLS_OCSP_CLIENT; + + ctx->ocsp_query = calloc(1, sizeof (struct tls_ocsp_query)); + if (!ctx->ocsp_query) { + tls_free(ctx); + return NULL; + } + return ctx; +} + +static int +tls_build_ocsp_request(struct tls *ctx) +{ + struct tls_ocsp_query *q; + int ok, ret = -1; + OCSP_REQUEST *req = NULL; + OCSP_CERTID *cid = NULL; + OCSP_ONEREQ *onereq = NULL; + BIO *mem = NULL; + void *data; + + q = ctx->ocsp_query; + + cid = tls_ocsp_get_certid(q->main_cert, q->extra_certs, q->cert_ssl_ctx); + if (!cid) { + tls_set_errorx(ctx, "Cannot create cert-id"); + goto failed; + } + + req = OCSP_REQUEST_new(); + if (!req) { + tls_set_error(ctx, "Cannot create request"); + goto failed; + } + + onereq = OCSP_request_add0_id(req, cid); + if (!onereq) { + tls_set_error(ctx, "Cannot add cert-id to request"); + goto failed; + } + cid = NULL; + + /* + * Now render it. + */ + + mem = BIO_new(BIO_s_mem()); + if (!mem) { + tls_set_errorx(ctx, "BIO_new"); + goto failed; + } + + ok = i2d_OCSP_REQUEST_bio(mem, req); + if (!ok) { + tls_set_error(ctx, "i2d_OCSP_RESPONSE_bio"); + goto failed; + } + q->request_size = BIO_get_mem_data(mem, &data); + q->request_data = malloc(q->request_size); + if (!q->request_data) { + tls_set_error(ctx, "Failed to allocate request data"); + goto failed; + } + memcpy(q->request_data, data, q->request_size); + + req = NULL; + ret = 0; +failed: + OCSP_CERTID_free(cid); + OCSP_REQUEST_free(req); + BIO_free(mem); + return ret; +} + +static int +tls_ocsp_setup(struct tls **ocsp_ctx_p, struct tls_config *config, struct tls *target) +{ + struct tls *ctx; + struct tls_ocsp_query *q; + int ret; + STACK_OF(OPENSSL_STRING) *ocsp_urls; + + ctx = tls_ocsp_client_new(); + if (!ctx) + return -1; + + *ocsp_ctx_p = ctx; + q = ctx->ocsp_query; + + if (config) { + /* create ctx->ssl_ctx */ + ctx->flags = TLS_SERVER; + ret = tls_configure(ctx, config); + ctx->flags = TLS_OCSP_CLIENT; + if (ret != 0) + return ret; + + q->main_cert = SSL_get_certificate(ctx->ssl_conn); + q->cert_ssl_ctx = ctx->ssl_ctx; + SSL_CTX_get_extra_chain_certs(ctx->ssl_ctx, &q->extra_certs); + } else { + /* steal state from target struct */ + q->main_cert = SSL_get_peer_certificate(target->ssl_conn); + q->extra_certs = SSL_get_peer_cert_chain(target->ssl_conn); + q->cert_ssl_ctx = target->ssl_ctx; + X509_free(q->main_cert); /* unref */ + } + + if (!q->main_cert) { + tls_set_errorx(ctx, "No cert"); + return -1; + } + + ocsp_urls = X509_get1_ocsp(q->main_cert); + if (!ocsp_urls) + return TLS_NO_OCSP; + q->ocsp_url = strdup(sk_OPENSSL_STRING_value(ocsp_urls, 0)); + if (!q->ocsp_url) { + tls_set_errorx(ctx, "Cannot copy URL"); + goto failed; + } + + ret = tls_build_ocsp_request(ctx); + if (ret != 0) + goto failed; + + *ocsp_ctx_p = ctx; + +failed: + X509_email_free(ocsp_urls); + return ret; +} + +static int +tls_ocsp_process_response_parsed(struct tls *ctx, struct tls_config *config, OCSP_RESPONSE *resp) +{ + struct tls_ocsp_query *q = ctx->ocsp_query; + BIO *mem = NULL; + size_t len; + unsigned char *data; + int ret = -1, ok, res; + + res = tls_ocsp_verify_response(ctx, q->main_cert, q->extra_certs, q->cert_ssl_ctx, resp); + if (res < 0) + goto failed; + + /* Update blob in config */ + if (config) { + mem = BIO_new(BIO_s_mem()); + if (!mem) { + tls_set_error(ctx, "BIO_new"); + goto failed; + } + ok = i2d_OCSP_RESPONSE_bio(mem, resp); + if (!ok) { + tls_set_error(ctx, "i2d_OCSP_RESPONSE_bio"); + goto failed; + } + len = BIO_get_mem_data(mem, &data); + res = tls_config_set_ocsp_stapling_mem(config, data, len); + if (res < 0) + goto failed; + } + ret = 0; +failed: + BIO_free(mem); + tls_ocsp_fill_result(ctx, ret); + return ret; +} + +static int +tls_ocsp_create_request(struct tls **ocsp_ctx_p, + struct tls_config *config, struct tls *target, + char **ocsp_url, + void **request_blob, size_t *request_size) +{ + int res; + struct tls_ocsp_query *q; + + res = tls_ocsp_setup(ocsp_ctx_p, config, target); + if (res != 0) + return res; + q = (*ocsp_ctx_p)->ocsp_query; + + *ocsp_url = q->ocsp_url; + *request_blob = q->request_data; + *request_size = q->request_size; + + return 0; +} + +/* + * Public API for request blobs. + */ + +int +tls_ocsp_check_peer_request(struct tls **ocsp_ctx_p, struct tls *target, + char **ocsp_url, void **request_blob, size_t *request_size) +{ + return tls_ocsp_create_request(ocsp_ctx_p, NULL, target, + ocsp_url, request_blob, request_size); +} + +int +tls_ocsp_refresh_stapling_request(struct tls **ocsp_ctx_p, + struct tls_config *config, + char **ocsp_url, void **request_blob, size_t *request_size) +{ + return tls_ocsp_create_request(ocsp_ctx_p, config, NULL, + ocsp_url, request_blob, request_size); +} + +int +tls_ocsp_process_response(struct tls *ctx, const void *response_blob, size_t size) +{ + int ret; + OCSP_RESPONSE *resp; + const unsigned char *raw = response_blob; + + resp = d2i_OCSP_RESPONSE(NULL, &raw, size); + if (!resp) { + ctx->ocsp_result = "parse-failed"; + tls_set_error(ctx, "parse failed"); + return -1; + } + ret = tls_ocsp_process_response_parsed(ctx, ctx->config, resp); + OCSP_RESPONSE_free(resp); + return ret; +} + diff --git lib/libtls/tls_server.c lib/libtls/tls_server.c index 1d94c99..2d383d2 100644 --- lib/libtls/tls_server.c +++ lib/libtls/tls_server.c @@ -93,6 +93,11 @@ tls_configure_server(struct tls *ctx) SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + if (SSL_CTX_set_tlsext_status_cb(ctx->ssl_ctx, tls_ocsp_stapling_callback) != 1) { + tls_set_errorx(ctx, "ssl OCSP stapling setup failure"); + goto err; + } + /* * Set session ID context to a random value. We don't support * persistent caching of sessions so it is OK to set a temporary
ocsp-connect.tgz
Description: application/tar-gz
diff --git lib/libtls/Makefile lib/libtls/Makefile index ca2f00b..461bf44 100644 --- lib/libtls/Makefile +++ lib/libtls/Makefile @@ -19,6 +19,7 @@ SRCS= tls.c \ tls_peer.c \ tls_server.c \ tls_util.c \ + tls_ocsp.c \ tls_verify.c MAN= tls_init.3 diff --git lib/libtls/tls.c lib/libtls/tls.c index 76d00e5..b00bea8 100644 --- lib/libtls/tls.c +++ lib/libtls/tls.c @@ -393,6 +393,13 @@ tls_reset(struct tls *ctx) tls_free_conninfo(ctx->conninfo); free(ctx->conninfo); ctx->conninfo = NULL; + + tls_ocsp_info_free(ctx->ocsp_info); + ctx->ocsp_info = NULL; + ctx->ocsp_result = NULL; + + if (ctx->flags & TLS_OCSP_CLIENT) + tls_ocsp_client_free(ctx); } int diff --git lib/libtls/tls.h lib/libtls/tls.h index 75c46c1..da6cd69 100644 --- lib/libtls/tls.h +++ lib/libtls/tls.h @@ -40,6 +40,29 @@ extern "C" { #define TLS_WANT_POLLIN -2 #define TLS_WANT_POLLOUT -3 +#define TLS_NO_OCSP -4 + +#define TLS_OCSP_RESPONSE_SUCCESSFUL 0 +#define TLS_OCSP_RESPONSE_MALFORMED 1 +#define TLS_OCSP_RESPONSE_INTERNALERR 2 +#define TLS_OCSP_RESPONSE_TRYLATER 3 +#define TLS_OCSP_RESPONSE_SIGREQUIRED 5 +#define TLS_OCSP_RESPONSE_UNAUTHORIZED 6 + +#define TLS_OCSP_CERT_GOOD 0 +#define TLS_OCSP_CERT_REVOKED 1 +#define TLS_OCSP_CERT_UNKNOWN 2 + +#define TLS_CRL_REASON_UNPSECIFIED 0 +#define TLS_CRL_REASON_KEY_COMPROMISE 1 +#define TLS_CRL_REASON_CA_COMPROMISE 2 +#define TLS_CRL_REASON_AFFILIATION_CHANGED 3 +#define TLS_CRL_REASON_SUPERSEDED 4 +#define TLS_CRL_REASON_CESSATION_OF_OPERATION 5 +#define TLS_CRL_REASON_CERTIFICATE_HOLD 6 +#define TLS_CRL_REASON_REMOVE_FROM_CRL 8 +#define TLS_CRL_REASON_PRIVILEGE_WITH_DRAWN 9 +#define TLS_CRL_REASON_AA_COMPROMISE 10 struct tls; struct tls_config; @@ -70,6 +93,8 @@ int tls_config_set_keypair_file(struct tls_config *_config, const char *_cert_file, const char *_key_file); int tls_config_set_keypair_mem(struct tls_config *_config, const uint8_t *_cert, size_t _cert_len, const uint8_t *_key, size_t _key_len); +int tls_config_set_ocsp_stapling_file(struct tls_config *_config, const char *_blob_file); +int tls_config_set_ocsp_stapling_mem(struct tls_config *_config, const uint8_t *_blob, size_t _len); void tls_config_set_protocols(struct tls_config *_config, uint32_t _protocols); void tls_config_set_verify_depth(struct tls_config *_config, int _verify_depth); @@ -121,6 +146,18 @@ const char *tls_conn_cipher(struct tls *_ctx); uint8_t *tls_load_file(const char *_file, size_t *_len, char *_password); +int tls_get_ocsp_info(struct tls *ctx, int *response_status, int *cert_status, int *crl_reason, + time_t *this_update, time_t *next_update, time_t *revoction_time, + const char **result_text); + +int tls_ocsp_check_peer_request(struct tls **ocsp_ctx_p, struct tls *target, + char **ocsp_url, void **request_blob, size_t *request_size); + +int tls_ocsp_refresh_stapling_request(struct tls **ocsp_ctx_p, struct tls_config *config, + char **ocsp_url, void **request_blob, size_t *request_size); + +int tls_ocsp_process_response(struct tls *ctx, const void *response_blob, size_t size); + #ifdef __cplusplus } #endif diff --git lib/libtls/tls_client.c lib/libtls/tls_client.c index 3847f4c..86dd9a8 100644 --- lib/libtls/tls_client.c +++ lib/libtls/tls_client.c @@ -209,6 +209,11 @@ tls_connect_fds(struct tls *ctx, int fd_read, int fd_write, (tls_configure_ssl_verify(ctx, SSL_VERIFY_PEER) == -1)) goto err; + if (SSL_CTX_set_tlsext_status_cb(ctx->ssl_ctx, tls_ocsp_verify_callback) != 1) { + tls_set_errorx(ctx, "ssl OCSP verification setup failure"); + goto err; + } + if ((ctx->ssl_conn = SSL_new(ctx->ssl_ctx)) == NULL) { tls_set_errorx(ctx, "ssl connection failure"); goto err; @@ -222,6 +227,10 @@ tls_connect_fds(struct tls *ctx, int fd_read, int fd_write, tls_set_errorx(ctx, "ssl file descriptor failure"); goto err; } + if (SSL_set_tlsext_status_type(ctx->ssl_conn, TLSEXT_STATUSTYPE_ocsp) != 1) { + tls_set_errorx(ctx, "ssl OCSP extension setup failure"); + goto err; + } /* * RFC4366 (SNI): Literal IPv4 and IPv6 addresses are not diff --git lib/libtls/tls_config.c lib/libtls/tls_config.c index 8f73a5a..55655d6 100644 --- lib/libtls/tls_config.c +++ lib/libtls/tls_config.c @@ -371,6 +371,24 @@ tls_config_set_keypair_mem(struct tls_config *config, const uint8_t *cert, return (0); } +int +tls_config_set_ocsp_stapling_file(struct tls_config *config, const char *blob_file) +{ + if (blob_file != NULL) + tls_config_set_ocsp_stapling_mem(config, NULL, 0); + + return set_string(&config->ocsp_file, blob_file); +} + +int +tls_config_set_ocsp_stapling_mem(struct tls_config *config, const uint8_t *blob, size_t len) +{ + if (blob != NULL) + tls_config_set_ocsp_stapling_file(config, NULL); + + return set_mem(&config->ocsp_mem, &config->ocsp_len, blob, len); +} + void tls_config_set_protocols(struct tls_config *config, uint32_t protocols) { diff --git lib/libtls/tls_internal.h lib/libtls/tls_internal.h index 745fb40..9d53bc1 100644 --- lib/libtls/tls_internal.h +++ lib/libtls/tls_internal.h @@ -62,6 +62,9 @@ struct tls_config { int dheparams; int ecdhecurve; struct tls_keypair *keypair; + const char *ocsp_file; + char *ocsp_mem; + size_t ocsp_len; uint32_t protocols; int verify_cert; int verify_client; @@ -85,10 +88,14 @@ struct tls_conninfo { #define TLS_CLIENT (1 << 0) #define TLS_SERVER (1 << 1) #define TLS_SERVER_CONN (1 << 2) +#define TLS_OCSP_CLIENT (1 << 3) #define TLS_EOF_NO_CLOSE_NOTIFY (1 << 0) #define TLS_HANDSHAKE_COMPLETE (1 << 1) +struct tls_ocsp_query; +struct tls_ocsp_info; + struct tls { struct tls_config *config; struct tls_error error; @@ -103,6 +110,20 @@ struct tls { SSL_CTX *ssl_ctx; X509 *ssl_peer_cert; struct tls_conninfo *conninfo; + + const char *ocsp_result; + struct tls_ocsp_info *ocsp_info; + + struct tls_ocsp_query *ocsp_query; +}; + +struct tls_ocsp_info { + int response_status; + int cert_status; + int crl_reason; + time_t this_update; + time_t next_update; + time_t revocation_time; }; struct tls *tls_new(void); @@ -143,6 +164,11 @@ int tls_ssl_error(struct tls *ctx, SSL *ssl_conn, int ssl_ret, int tls_get_conninfo(struct tls *ctx); void tls_free_conninfo(struct tls_conninfo *conninfo); +int tls_ocsp_verify_callback(SSL *ssl, void *arg); +int tls_ocsp_stapling_callback(SSL *ssl, void *arg); +void tls_ocsp_client_free(struct tls *ctx); +void tls_ocsp_info_free(struct tls_ocsp_info *info); + int asn1_time_parse(const char *, size_t, struct tm *, int); #endif /* HEADER_TLS_INTERNAL_H */ diff --git lib/libtls/tls_ocsp.c lib/libtls/tls_ocsp.c new file mode 100644 index 0000000..8f8c1e3 --- /dev/null +++ lib/libtls/tls_ocsp.c @@ -0,0 +1,641 @@ +/* + * Copyright (c) 2015 Marko Kreen <mark...@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <openssl/err.h> +#include <openssl/ocsp.h> +#include <openssl/x509.h> + +#include <tls.h> +#include "tls_internal.h" + +#define MAXAGE_SEC (14*24*60*60) +#define JITTER_SEC (60) + +/* + * State for request. + */ + +struct tls_ocsp_query { + /* responder location */ + char *ocsp_url; + + /* request blob */ + uint8_t *request_data; + size_t request_size; + + /* network state */ + BIO *bio; + SSL_CTX *ssl_ctx; + OCSP_REQ_CTX *http_req; + + /* cert data, this struct does not own these */ + X509 *main_cert; + STACK_OF(X509) *extra_certs; + SSL_CTX *cert_ssl_ctx; +}; + +/* + * Extract OCSP response info. + */ + +static int +tls_asn1_parse_time(struct tls *ctx, ASN1_GENERALIZEDTIME *gt, time_t *dst_p) +{ + int res; + struct tm tm; + + if (!gt) { + *dst_p = 0; + return 0; + } + + res = asn1_time_parse(gt->data, gt->length, &tm, 0); + if (res == -1) + return -1; + + res = 0; + *dst_p = timegm(&tm); + if (*dst_p == (time_t)-1) + res = -1; + return res; +} + +static int +tls_ocsp_fill_info(struct tls *ctx, + int response_status, int cert_status, int crl_reason, + ASN1_GENERALIZEDTIME *revtime, + ASN1_GENERALIZEDTIME *thisupd, + ASN1_GENERALIZEDTIME *nextupd) +{ + struct tls_ocsp_info *info; + int res; + + info = calloc(1, sizeof (struct tls_ocsp_info)); + if (!info) { + tls_set_error(ctx, "calloc"); + return -1; + } + info->response_status = response_status; + info->cert_status = cert_status; + info->crl_reason = crl_reason; + + res = tls_asn1_parse_time(ctx, revtime, &info->revocation_time); + if (res == 0) + res = tls_asn1_parse_time(ctx, thisupd, &info->this_update); + if (res == 0) + res = tls_asn1_parse_time(ctx, nextupd, &info->next_update); + + if (res == 0) { + ctx->ocsp_info = info; + } else { + tls_ocsp_info_free(info); + } + return res; +} + +static void +tls_ocsp_fill_result(struct tls *ctx, int res) +{ + struct tls_ocsp_info *info = ctx->ocsp_info; + if (res < 0) { + ctx->ocsp_result = "error"; + } else if (info->response_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + ctx->ocsp_result = OCSP_response_status_str(info->response_status); + } else if (info->cert_status != V_OCSP_CERTSTATUS_REVOKED) { + ctx->ocsp_result = OCSP_cert_status_str(info->cert_status); + } else { + ctx->ocsp_result = OCSP_crl_reason_str(info->crl_reason); + } +} + +void +tls_ocsp_info_free(struct tls_ocsp_info *info) +{ + free(info); +} + +int +tls_get_ocsp_info(struct tls *ctx, int *response_status, int *cert_status, + int *crl_reason, time_t *this_update, + time_t *next_update, time_t *revoction_time, + const char **result_text) +{ + static const struct tls_ocsp_info no_ocsp = { -1, -1, -1, 0, 0, 0 }; + const struct tls_ocsp_info *info = ctx->ocsp_info; + const char *ocsp_result = ctx->ocsp_result; + int ret = 0; + + if (!info) { + info = &no_ocsp; + ret = -1; + } + + if (!ocsp_result) { + ret = TLS_NO_OCSP; + ocsp_result = "no-ocsp"; + } + + if (response_status) + *response_status = info->response_status; + if (cert_status) + *cert_status = info->cert_status; + if (crl_reason) + *crl_reason = info->crl_reason; + if (this_update) + *this_update = info->this_update; + if (next_update) + *next_update = info->next_update; + if (revoction_time) + *revoction_time = info->revocation_time; + if (result_text) + *result_text = ocsp_result; + + return ret; +} + +/* + * Verify stapled response + */ + +static OCSP_CERTID * +tls_ocsp_get_certid(X509 *main_cert, STACK_OF(X509) *extra_certs, SSL_CTX *ssl_ctx) +{ + X509_NAME *issuer_name; + X509 *issuer; + X509_STORE_CTX storectx; + X509_OBJECT tmpobj; + OCSP_CERTID *cid = NULL; + X509_STORE *store; + int ok; + + issuer_name = X509_get_issuer_name(main_cert); + if (!issuer_name) + return NULL; + + if (extra_certs) { + issuer = X509_find_by_subject(extra_certs, issuer_name); + if (issuer) + return OCSP_cert_to_id(NULL, main_cert, issuer); + } + + store = SSL_CTX_get_cert_store(ssl_ctx); + if (!store) + return NULL; + ok = X509_STORE_CTX_init(&storectx, store, main_cert, extra_certs); + if (ok != 1) + return NULL; + ok = X509_STORE_get_by_subject(&storectx, X509_LU_X509, issuer_name, &tmpobj); + if (ok == 1) { + cid = OCSP_cert_to_id(NULL, main_cert, tmpobj.data.x509); + X509_free(tmpobj.data.x509); + } + X509_STORE_CTX_cleanup(&storectx); + return cid; +} + +static int +tls_ocsp_verify_response(struct tls *ctx, X509 *main_cert, STACK_OF(X509) *extra_certs, + SSL_CTX *ssl_ctx, OCSP_RESPONSE *resp) +{ + OCSP_BASICRESP *br = NULL; + STACK_OF(X509) *ocsp_chain = NULL; + ASN1_GENERALIZEDTIME *revtime = NULL, *thisupd = NULL, *nextupd = NULL; + OCSP_CERTID *cid = NULL; + STACK_OF(X509) *combined = NULL; + int response_status=0, cert_status=0, crl_reason=0; + int ssl_res, ret = -1; + unsigned long flags; + + br = OCSP_response_get1_basic(resp); + if (!br) { + tls_set_errorx(ctx, "ocsp error: cannot load"); + goto error; + } + + /* + * Skip validation of 'extra_certs' as this should be done + * already as part of main handshake. + */ + flags = OCSP_TRUSTOTHER; + ocsp_chain = extra_certs; + + /* now verify */ + ssl_res = OCSP_basic_verify(br, ocsp_chain, SSL_CTX_get_cert_store(ssl_ctx), flags); + + if (ssl_res != 1) { + tls_set_error(ctx, "ocsp verify failed"); + goto error; + } + + /* signature OK, look inside */ + response_status = OCSP_response_status(resp); + if (response_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + tls_set_errorx(ctx, "ocsp verify failed: unsuccessful response - %s", + OCSP_response_status_str(response_status)); + goto error; + } + + cid = tls_ocsp_get_certid(main_cert, extra_certs, ssl_ctx); + if (!cid) { + tls_set_errorx(ctx, "ocsp verify failed: no issuer cert"); + goto error; + } + + ssl_res = OCSP_resp_find_status(br, cid, &cert_status, &crl_reason, &revtime, &thisupd, &nextupd); + if (ssl_res != 1) { + tls_set_errorx(ctx, "ocsp verify failed: no result for cert"); + goto error; + } + + ssl_res = OCSP_check_validity(thisupd, nextupd, JITTER_SEC, MAXAGE_SEC); + if (ssl_res != 1) { + tls_set_errorx(ctx, "ocsp verify failed: bad age"); + goto error; + } + + ssl_res = tls_ocsp_fill_info(ctx, response_status, cert_status, crl_reason, revtime, thisupd, nextupd); + if (ssl_res != 0) + goto error; + + /* finally can look at status */ + if (cert_status != V_OCSP_CERTSTATUS_GOOD && cert_status != V_OCSP_CERTSTATUS_UNKNOWN) { + tls_set_errorx(ctx, "ocsp verify failed: revoked cert - %s", + OCSP_crl_reason_str(crl_reason)); + goto error; + } + + ret = 0; + +error: + sk_X509_free(combined); + OCSP_CERTID_free(cid); + OCSP_BASICRESP_free(br); + return ret; +} + +/* + * Same callback on client-side has different error proto: + * 1=OK, 0=bad, -1=internal error + */ + +int +tls_ocsp_verify_callback(SSL *ssl, void *arg) +{ + OCSP_RESPONSE *resp = NULL; + STACK_OF(X509) *extra_certs = NULL; + X509 *peer = NULL; + const unsigned char *raw = NULL; + int size, res = -1; + struct tls *ctx; + + ctx = SSL_get_app_data(ssl); + if (!ctx) + return -1; + + size = SSL_get_tlsext_status_ocsp_resp(ssl, &raw); + if (size <= 0) + return 1; + + peer = SSL_get_peer_certificate(ssl); + if (!peer) { + tls_set_errorx(ctx, "ocsp verify failed: no peer cert"); + goto error; + } + + resp = d2i_OCSP_RESPONSE(NULL, &raw, size); + if (!resp) { + tls_set_errorx(ctx, "ocsp verify failed: parse failed"); + goto error; + } + + extra_certs = SSL_get_peer_cert_chain(ssl); + res = tls_ocsp_verify_response(ctx, peer, extra_certs, ctx->ssl_ctx, resp); +error: + tls_ocsp_fill_result(ctx, res); + OCSP_RESPONSE_free(resp); + X509_free(peer); + return (res == 0) ? 1 : 0; +} + +/* + * Staple OCSP response to server handshake. + */ + +int +tls_ocsp_stapling_callback(SSL *ssl, void *arg) +{ + struct tls *ctx; + char *mem, *fmem = NULL; + uint8_t *xmem; + size_t len; + int ret = SSL_TLSEXT_ERR_ALERT_FATAL; + + ctx = SSL_get_app_data(ssl); + if (!ctx) + return SSL_TLSEXT_ERR_NOACK; + + if (ctx->config->ocsp_file) { + fmem = mem = (char*)tls_load_file(ctx->config->ocsp_file, &len, NULL); + if (!mem) + goto err; + } else { + mem = ctx->config->ocsp_mem; + len = ctx->config->ocsp_len; + if (!mem) + return SSL_TLSEXT_ERR_NOACK; + } + xmem = malloc(len); + if (xmem) { + memcpy(xmem, mem, len); + if (SSL_set_tlsext_status_ocsp_resp(ctx->ssl_conn, xmem, len) != 1) { + free(xmem); + goto err; + } + ret = SSL_TLSEXT_ERR_OK; + } +err: + free(fmem); + return ret; +} + +/* + * Query OCSP responder over HTTP(S). + */ + +void +tls_ocsp_client_free(struct tls *ctx) +{ + struct tls_ocsp_query *q; + if (!ctx) + return; + q = ctx->ocsp_query; + if (q) { + if (q->http_req) + OCSP_REQ_CTX_free(q->http_req); + BIO_free_all(q->bio); + SSL_CTX_free(q->ssl_ctx); + + free(q->ocsp_url); + free(q->request_data); + free(q); + + ctx->ocsp_query = NULL; + } +} + +static struct tls * +tls_ocsp_client_new(void) +{ + struct tls *ctx; + + ctx = tls_new(); + if (!ctx) + return NULL; + ctx->flags = TLS_OCSP_CLIENT; + + ctx->ocsp_query = calloc(1, sizeof (struct tls_ocsp_query)); + if (!ctx->ocsp_query) { + tls_free(ctx); + return NULL; + } + return ctx; +} + +static int +tls_build_ocsp_request(struct tls *ctx) +{ + struct tls_ocsp_query *q; + int ok, ret = -1; + OCSP_REQUEST *req = NULL; + OCSP_CERTID *cid = NULL; + OCSP_ONEREQ *onereq = NULL; + BIO *mem = NULL; + void *data; + + q = ctx->ocsp_query; + + cid = tls_ocsp_get_certid(q->main_cert, q->extra_certs, q->cert_ssl_ctx); + if (!cid) { + tls_set_errorx(ctx, "Cannot create cert-id"); + goto failed; + } + + req = OCSP_REQUEST_new(); + if (!req) { + tls_set_error(ctx, "Cannot create request"); + goto failed; + } + + onereq = OCSP_request_add0_id(req, cid); + if (!onereq) { + tls_set_error(ctx, "Cannot add cert-id to request"); + goto failed; + } + cid = NULL; + + /* + * Now render it. + */ + + mem = BIO_new(BIO_s_mem()); + if (!mem) { + tls_set_errorx(ctx, "BIO_new"); + goto failed; + } + + ok = i2d_OCSP_REQUEST_bio(mem, req); + if (!ok) { + tls_set_error(ctx, "i2d_OCSP_RESPONSE_bio"); + goto failed; + } + q->request_size = BIO_get_mem_data(mem, &data); + q->request_data = malloc(q->request_size); + if (!q->request_data) { + tls_set_error(ctx, "Failed to allocate request data"); + goto failed; + } + memcpy(q->request_data, data, q->request_size); + + req = NULL; + ret = 0; +failed: + OCSP_CERTID_free(cid); + OCSP_REQUEST_free(req); + BIO_free(mem); + return ret; +} + +static int +tls_ocsp_setup(struct tls **ocsp_ctx_p, struct tls_config *config, struct tls *target) +{ + struct tls *ctx; + struct tls_ocsp_query *q; + int ret; + STACK_OF(OPENSSL_STRING) *ocsp_urls; + + ctx = tls_ocsp_client_new(); + if (!ctx) + return -1; + + *ocsp_ctx_p = ctx; + q = ctx->ocsp_query; + + if (config) { + /* create ctx->ssl_ctx */ + ctx->flags = TLS_SERVER; + ret = tls_configure(ctx, config); + ctx->flags = TLS_OCSP_CLIENT; + if (ret != 0) + return ret; + + q->main_cert = SSL_get_certificate(ctx->ssl_conn); + q->cert_ssl_ctx = ctx->ssl_ctx; + SSL_CTX_get_extra_chain_certs(ctx->ssl_ctx, &q->extra_certs); + } else { + /* steal state from target struct */ + q->main_cert = SSL_get_peer_certificate(target->ssl_conn); + q->extra_certs = SSL_get_peer_cert_chain(target->ssl_conn); + q->cert_ssl_ctx = target->ssl_ctx; + X509_free(q->main_cert); /* unref */ + } + + if (!q->main_cert) { + tls_set_errorx(ctx, "No cert"); + return -1; + } + + ocsp_urls = X509_get1_ocsp(q->main_cert); + if (!ocsp_urls) + return TLS_NO_OCSP; + q->ocsp_url = strdup(sk_OPENSSL_STRING_value(ocsp_urls, 0)); + if (!q->ocsp_url) { + tls_set_errorx(ctx, "Cannot copy URL"); + goto failed; + } + + ret = tls_build_ocsp_request(ctx); + if (ret != 0) + goto failed; + + *ocsp_ctx_p = ctx; + +failed: + X509_email_free(ocsp_urls); + return ret; +} + +static int +tls_ocsp_process_response_parsed(struct tls *ctx, struct tls_config *config, OCSP_RESPONSE *resp) +{ + struct tls_ocsp_query *q = ctx->ocsp_query; + BIO *mem = NULL; + size_t len; + unsigned char *data; + int ret = -1, ok, res; + + res = tls_ocsp_verify_response(ctx, q->main_cert, q->extra_certs, q->cert_ssl_ctx, resp); + if (res < 0) + goto failed; + + /* Update blob in config */ + if (config) { + mem = BIO_new(BIO_s_mem()); + if (!mem) { + tls_set_error(ctx, "BIO_new"); + goto failed; + } + ok = i2d_OCSP_RESPONSE_bio(mem, resp); + if (!ok) { + tls_set_error(ctx, "i2d_OCSP_RESPONSE_bio"); + goto failed; + } + len = BIO_get_mem_data(mem, &data); + res = tls_config_set_ocsp_stapling_mem(config, data, len); + if (res < 0) + goto failed; + } + ret = 0; +failed: + BIO_free(mem); + tls_ocsp_fill_result(ctx, ret); + return ret; +} + +static int +tls_ocsp_create_request(struct tls **ocsp_ctx_p, + struct tls_config *config, struct tls *target, + char **ocsp_url, + void **request_blob, size_t *request_size) +{ + int res; + struct tls_ocsp_query *q; + + res = tls_ocsp_setup(ocsp_ctx_p, config, target); + if (res != 0) + return res; + q = (*ocsp_ctx_p)->ocsp_query; + + *ocsp_url = q->ocsp_url; + *request_blob = q->request_data; + *request_size = q->request_size; + + return 0; +} + +/* + * Public API for request blobs. + */ + +int +tls_ocsp_check_peer_request(struct tls **ocsp_ctx_p, struct tls *target, + char **ocsp_url, void **request_blob, size_t *request_size) +{ + return tls_ocsp_create_request(ocsp_ctx_p, NULL, target, + ocsp_url, request_blob, request_size); +} + +int +tls_ocsp_refresh_stapling_request(struct tls **ocsp_ctx_p, + struct tls_config *config, + char **ocsp_url, void **request_blob, size_t *request_size) +{ + return tls_ocsp_create_request(ocsp_ctx_p, config, NULL, + ocsp_url, request_blob, request_size); +} + +int +tls_ocsp_process_response(struct tls *ctx, const void *response_blob, size_t size) +{ + int ret; + OCSP_RESPONSE *resp; + const unsigned char *raw = response_blob; + + resp = d2i_OCSP_RESPONSE(NULL, &raw, size); + if (!resp) { + ctx->ocsp_result = "parse-failed"; + tls_set_error(ctx, "parse failed"); + return -1; + } + ret = tls_ocsp_process_response_parsed(ctx, ctx->config, resp); + OCSP_RESPONSE_free(resp); + return ret; +} + diff --git lib/libtls/tls_server.c lib/libtls/tls_server.c index 1d94c99..2d383d2 100644 --- lib/libtls/tls_server.c +++ lib/libtls/tls_server.c @@ -93,6 +93,11 @@ tls_configure_server(struct tls *ctx) SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + if (SSL_CTX_set_tlsext_status_cb(ctx->ssl_ctx, tls_ocsp_stapling_callback) != 1) { + tls_set_errorx(ctx, "ssl OCSP stapling setup failure"); + goto err; + } + /* * Set session ID context to a random value. We don't support * persistent caching of sessions so it is OK to set a temporary