2
0
Fork 0
lighttpd2/src/modules/gnutls_ocsp.c

316 lines
9.1 KiB
C

#include "gnutls_ocsp.h"
#include <gnutls/ocsp.h>
#include <gnutls/crypto.h>
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_decode_alloc("OCSP RESPONSE", &file, &decoded);
if (GNUTLS_E_SUCCESS > r) {
ERROR(srv, "gnutls_pem_base64_decode_alloc 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_decode_alloc("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_decode_alloc 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;
}
gboolean li_gnutls_ocsp_search_datum(liServer *srv, liGnuTLSOCSP *ocsp, gnutls_datum_t const* file) {
int r;
gnutls_datum_t decoded = { NULL, 0 };
gboolean result = FALSE;
r = gnutls_pem_base64_decode_alloc("OCSP RESPONSE", file, &decoded);
if (GNUTLS_E_SUCCESS <= r) {
result = add_response(srv, ocsp, &decoded);
if (!result) {
ERROR(srv, "%s", "Failed loading OCSP response from PEM block");
goto error;
}
} else if (GNUTLS_E_BASE64_UNEXPECTED_HEADER_ERROR == r) {
/* ignore GNUTLS_E_BASE64_UNEXPECTED_HEADER_ERROR */
} else {
ERROR(srv, "gnutls_pem_base64_decode_alloc failed to decode OCSP RESPONSE from PEM block (%s): %s",
gnutls_strerror_name(r), gnutls_strerror(r));
/* continue anyway */
}
result = TRUE;
error:
gnutls_free(decoded.data);
return result;
}