#include "gnutls_ocsp.h" #include #include typedef struct ocsp_response_cert_entry { gnutls_digest_algorithm_t digest; gnutls_datum_t issuer_name_hash; gnutls_datum_t serial; } ocsp_response_cert_entry; typedef struct ocsp_response { gnutls_ocsp_resp_t resp; gnutls_datum_t resp_data; /* DER encoded */ GArray* certificates; } ocsp_response; struct liGnuTLSOCSP { GArray* responses; size_t max_serial_length; size_t max_hash_length; }; static void clear_entry(ocsp_response_cert_entry* entry) { gnutls_free(entry->issuer_name_hash.data); gnutls_free(entry->serial.data); memset(entry, 0, sizeof(*entry)); } static int get_entry(liServer *srv, ocsp_response_cert_entry* entry, gnutls_ocsp_resp_t resp, unsigned int ndx) { int r; memset(entry, 0, sizeof(*entry)); r = gnutls_ocsp_resp_get_single( resp, ndx, &entry->digest, &entry->issuer_name_hash, NULL, &entry->serial, NULL, NULL, NULL, NULL, NULL); if (GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE == r) return r; if (GNUTLS_E_SUCCESS != r) { ERROR(srv, "Couldn't retrieve OCSP response information for entry %u (%s): %s", ndx, gnutls_strerror_name(r), gnutls_strerror(r)); return r; } if (0 == entry->serial.size || GNUTLS_DIG_UNKNOWN == entry->digest || entry->issuer_name_hash.size != gnutls_hash_get_len(entry->digest)) { ERROR(srv, "Invalid OCSP response data in entry %u", ndx); return GNUTLS_E_OCSP_RESPONSE_ERROR; } return GNUTLS_E_SUCCESS; } static void clear_response(ocsp_response* response) { gnutls_free(response->resp_data.data); gnutls_ocsp_resp_deinit(response->resp); if (NULL != response->certificates) { guint i; for (i = 0; i < response->certificates->len; ++i) { ocsp_response_cert_entry* entry = &g_array_index(response->certificates, ocsp_response_cert_entry, i); clear_entry(entry); } g_array_free(response->certificates, TRUE); } memset(response, 0, sizeof(*response)); } /* pass ownership of der_data */ static gboolean add_response(liServer *srv, liGnuTLSOCSP *ocsp, gnutls_datum_t* der_data) { ocsp_response response; int r; guint i; memset(&response, 0, sizeof(response)); response.resp_data = *der_data; der_data->data = NULL; der_data->size = 0; if (GNUTLS_E_SUCCESS != (r = gnutls_ocsp_resp_init(&response.resp))) { ERROR(srv, "gnutls_ocsp_resp_init (%s): %s", gnutls_strerror_name(r), gnutls_strerror(r)); goto error; } if (GNUTLS_E_SUCCESS != (r = gnutls_ocsp_resp_import(response.resp, &response.resp_data))) { ERROR(srv, "gnutls_ocsp_resp_import (%s): %s", gnutls_strerror_name(r), gnutls_strerror(r)); goto error; } response.certificates = g_array_new(FALSE, TRUE, sizeof(ocsp_response_cert_entry)); for (i = 0; ; ++i) { ocsp_response_cert_entry entry; r = get_entry(srv, &entry, response.resp, i); if (GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE == r) break; /* got them all */ if (GNUTLS_E_SUCCESS != r) goto error; g_array_append_vals(response.certificates, &entry, 1); if (entry.serial.size > ocsp->max_serial_length) ocsp->max_serial_length = entry.serial.size; if (entry.issuer_name_hash.size > ocsp->max_hash_length) ocsp->max_hash_length = entry.issuer_name_hash.size; } g_array_append_vals(ocsp->responses, &response, 1); return TRUE; error: clear_response(&response); return FALSE; } static int ctx_ocsp_response(gnutls_session_t session, void* ptr, gnutls_datum_t* ocsp_resp) { liGnuTLSOCSP* ocsp = ptr; guint i; int r; gnutls_datum_t serial = { NULL, 0 }; gnutls_datum_t issuer_name = { NULL, 0 }; char* issuer_name_hash = NULL; if (0 == ocsp->responses->len) return GNUTLS_E_NO_CERTIFICATE_STATUS; serial.size = ocsp->max_serial_length; serial.data = gnutls_malloc(serial.size); issuer_name_hash = gnutls_malloc(ocsp->max_hash_length); { gnutls_datum_t const* crt_datum; gnutls_x509_crt_t crt = NULL; size_t serial_size = ocsp->max_serial_length; crt_datum = gnutls_certificate_get_ours(session); /* memory is NOT owned */ if (GNUTLS_E_SUCCESS != (r = gnutls_x509_crt_init(&crt))) goto cleanup; if (GNUTLS_E_SUCCESS != (r = gnutls_x509_crt_import(crt, crt_datum, GNUTLS_X509_FMT_DER))) { gnutls_x509_crt_deinit(crt); goto cleanup; } ; if (GNUTLS_E_SUCCESS != (r = gnutls_x509_crt_get_serial(crt, serial.data, &serial_size))) { gnutls_x509_crt_deinit(crt); goto cleanup; } serial.size = serial_size; if (GNUTLS_E_SUCCESS != (r = gnutls_x509_crt_get_raw_issuer_dn(crt, &issuer_name))) { gnutls_x509_crt_deinit(crt); goto cleanup; } gnutls_x509_crt_deinit(crt); } for (i = 0; i < ocsp->responses->len; ++i) { ocsp_response* response = &g_array_index(ocsp->responses, ocsp_response, i); guint j; for (j = 0; j < response->certificates->len; ++j) { ocsp_response_cert_entry* entry = &g_array_index(response->certificates, ocsp_response_cert_entry, i); if (serial.size != entry->serial.size || 0 != memcmp(serial.data, entry->serial.data, serial.size)) continue; if (GNUTLS_E_SUCCESS != (r = gnutls_hash_fast(entry->digest, issuer_name.data, issuer_name.size, issuer_name_hash))) goto cleanup; if (0 != memcmp(issuer_name_hash, entry->issuer_name_hash.data, entry->issuer_name_hash.size)) continue; ocsp_resp->size = response->resp_data.size; ocsp_resp->data = gnutls_malloc(ocsp_resp->size); memcpy(ocsp_resp->data, response->resp_data.data, ocsp_resp->size); r = GNUTLS_E_SUCCESS; goto cleanup; } } r = GNUTLS_E_NO_CERTIFICATE_STATUS; cleanup: gnutls_free(issuer_name_hash); gnutls_free(issuer_name.data); gnutls_free(serial.data); return r; } liGnuTLSOCSP* li_gnutls_ocsp_new(void) { liGnuTLSOCSP* ocsp = g_slice_new0(liGnuTLSOCSP); ocsp->responses = g_array_new(FALSE, TRUE, sizeof(ocsp_response)); ocsp->max_hash_length = ocsp->max_serial_length = 0; return ocsp; } void li_gnutls_ocsp_free(liGnuTLSOCSP *ocsp) { if (NULL != ocsp->responses) { guint i; for (i = 0; i < ocsp->responses->len; ++i) { ocsp_response* response = &g_array_index(ocsp->responses, ocsp_response, i); clear_response(response); } g_array_free(ocsp->responses, TRUE); } memset(ocsp, 0, sizeof(*ocsp)); g_slice_free(liGnuTLSOCSP, ocsp); } void li_gnutls_ocsp_use(liGnuTLSOCSP *ocsp, gnutls_certificate_credentials_t creds) { gnutls_certificate_set_ocsp_status_request_function(creds, ctx_ocsp_response, ocsp); } gboolean li_gnutls_ocsp_add(liServer *srv, liGnuTLSOCSP *ocsp, const char* filename) { int r; gnutls_datum_t file = { NULL, 0 }; gnutls_datum_t decoded = { NULL, 0 }; gnutls_datum_t* der_data; gboolean result = FALSE; if (GNUTLS_E_SUCCESS != (r = gnutls_load_file(filename, &file))) { ERROR(srv, "Failed to load OCSP file '%s' (%s): %s", filename, gnutls_strerror_name(r), gnutls_strerror(r)); goto error; } /* decode pem "-----BEGIN OCSP RESPONSE-----", otherwise expect DER */ if (file.size > 20 && 0 == memcmp(file.data, CONST_STR_LEN("-----BEGIN "))) { r = gnutls_pem_base64_decode2("OCSP RESPONSE", &file, &decoded); if (GNUTLS_E_SUCCESS != r) { ERROR(srv, "gnutls_pem_base64_decode2 failed to decode OCSP RESPONSE from '%s' (%s): %s", filename, gnutls_strerror_name(r), gnutls_strerror(r)); goto error; } der_data = &decoded; } else { der_data = &file; } result = add_response(srv, ocsp, der_data); if (!result) { ERROR(srv, "Failed loading OCSP response from '%s'", filename); } error: gnutls_free(file.data); gnutls_free(decoded.data); return result; } gboolean li_gnutls_ocsp_search(liServer *srv, liGnuTLSOCSP *ocsp, const char* filename) { int r; gnutls_datum_t file = { NULL, 0 }; gnutls_datum_t decoded = { NULL, 0 }; gboolean result = FALSE; if (GNUTLS_E_SUCCESS != (r = gnutls_load_file(filename, &file))) { ERROR(srv, "Failed to load OCSP file '%s' (%s): %s", filename, gnutls_strerror_name(r), gnutls_strerror(r)); goto error; } r = gnutls_pem_base64_decode2("OCSP RESPONSE", &file, &decoded); if (GNUTLS_E_SUCCESS == r) { result = add_response(srv, ocsp, &decoded); if (!result) { ERROR(srv, "Failed loading OCSP response from '%s'", filename); goto error; } } else if (GNUTLS_E_BASE64_UNEXPECTED_HEADER_ERROR == r) { /* ignore GNUTLS_E_BASE64_UNEXPECTED_HEADER_ERROR */ } else { ERROR(srv, "gnutls_pem_base64_decode2 failed to decode OCSP RESPONSE from '%s' (%s): %s", filename, gnutls_strerror_name(r), gnutls_strerror(r)); /* continue anyway */ } result = TRUE; error: gnutls_free(file.data); gnutls_free(decoded.data); return result; }