316 lines
9.1 KiB
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;
|
|
}
|