summaryrefslogtreecommitdiffstats
path: root/lib/tls13/certificate.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/tls13/certificate.c662
1 files changed, 662 insertions, 0 deletions
diff --git a/lib/tls13/certificate.c b/lib/tls13/certificate.c
new file mode 100644
index 0000000..9792629
--- /dev/null
+++ b/lib/tls13/certificate.c
@@ -0,0 +1,662 @@
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * Author: Nikos Mavrogiannopoulos
+ *
+ * This file is part of GnuTLS.
+ *
+ * The GnuTLS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>
+ *
+ */
+
+#include "gnutls_int.h"
+#include "compress.h"
+#include "errors.h"
+#include "extv.h"
+#include "handshake.h"
+#include "tls13/certificate.h"
+#include "auth/cert.h"
+#include "mbuffers.h"
+#include "ext/compress_certificate.h"
+#include "ext/status_request.h"
+
+static int parse_cert_extension(void *ctx, unsigned tls_id, const uint8_t *data, unsigned data_size);
+static int parse_cert_list(gnutls_session_t session, uint8_t * data, size_t data_size);
+static int compress_certificate(gnutls_buffer_st * buf, unsigned cert_pos_mark,
+ gnutls_compression_method_t comp_method);
+static int decompress_certificate(gnutls_buffer_st * buf);
+
+int _gnutls13_recv_certificate(gnutls_session_t session)
+{
+ int ret, err, decompress_cert = 0;
+ gnutls_buffer_st buf;
+ unsigned optional = 0;
+
+ if (!session->internals.initial_negotiation_completed &&
+ session->internals.hsk_flags & HSK_PSK_SELECTED)
+ return 0;
+
+ if (session->security_parameters.entity == GNUTLS_SERVER) {
+ /* if we didn't request a certificate, there will not be any */
+ if (session->internals.send_cert_req == 0)
+ return 0;
+
+ if (session->internals.send_cert_req != GNUTLS_CERT_REQUIRE)
+ optional = 1;
+ }
+
+ ret = _gnutls_recv_handshake(session, GNUTLS_HANDSHAKE_CERTIFICATE_PKT, 0, &buf);
+ if (ret == GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET) {
+ /* check if we received compressed certificate */
+ err = _gnutls_recv_handshake(session, GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT, 0, &buf);
+ if (err >= 0) {
+ decompress_cert = 1;
+ ret = err;
+ }
+ }
+ if (ret < 0) {
+ if (ret == GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET && session->internals.send_cert_req)
+ return gnutls_assert_val(GNUTLS_E_NO_CERTIFICATE_FOUND);
+
+ return gnutls_assert_val(ret);
+ }
+
+ if (buf.length == 0) {
+ gnutls_assert();
+ ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER;
+ goto cleanup;
+ }
+
+ if (decompress_cert) {
+ ret = decompress_certificate(&buf);
+ if (ret < 0) {
+ gnutls_assert();
+ gnutls_alert_send(session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
+ goto cleanup;
+ }
+ }
+
+ if (session->internals.initial_negotiation_completed &&
+ session->internals.post_handshake_cr_context.size > 0) {
+ gnutls_datum_t context;
+
+ /* verify whether the context matches */
+ ret = _gnutls_buffer_pop_datum_prefix8(&buf, &context);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+
+ if (context.size != session->internals.post_handshake_cr_context.size ||
+ memcmp(context.data, session->internals.post_handshake_cr_context.data,
+ context.size) != 0) {
+ ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER;
+ gnutls_assert();
+ goto cleanup;
+ }
+ } else {
+ if (buf.data[0] != 0) {
+ /* The context field must be empty during handshake */
+ gnutls_assert();
+ ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER;
+ goto cleanup;
+ }
+
+ /* buf.length is positive */
+ buf.data++;
+ buf.length--;
+ }
+
+ _gnutls_handshake_log("HSK[%p]: parsing certificate message\n", session);
+
+ ret = parse_cert_list(session, buf.data, buf.length);
+ if (ret < 0) {
+ if (ret == GNUTLS_E_NO_CERTIFICATE_FOUND) {
+ if (optional)
+ ret = 0;
+ else if (session->security_parameters.entity ==
+ GNUTLS_SERVER)
+ ret = GNUTLS_E_CERTIFICATE_REQUIRED;
+ }
+ gnutls_assert();
+ goto cleanup;
+ }
+
+ session->internals.hsk_flags |= HSK_CRT_VRFY_EXPECTED;
+
+ ret = 0;
+cleanup:
+
+ _gnutls_buffer_clear(&buf);
+ return ret;
+}
+
+struct ocsp_req_ctx_st {
+ gnutls_pcert_st *pcert;
+ unsigned cert_index;
+ gnutls_session_t session;
+ gnutls_certificate_credentials_t cred;
+};
+
+static
+int append_status_request(void *_ctx, gnutls_buffer_st *buf)
+{
+ struct ocsp_req_ctx_st *ctx = _ctx;
+ gnutls_session_t session = ctx->session;
+ int ret;
+ gnutls_datum_t resp;
+ unsigned free_resp = 0;
+
+ assert(session->internals.selected_ocsp_func != NULL ||
+ session->internals.selected_ocsp_length != 0);
+
+ /* The global ocsp callback function can only be used to return
+ * a single certificate request */
+ if (session->internals.selected_ocsp_length == 1 && ctx->cert_index != 0)
+ return 0;
+
+ if (session->internals.selected_ocsp_length > 0) {
+ if (ctx->cert_index < session->internals.selected_ocsp_length) {
+ if ((session->internals.selected_ocsp[ctx->cert_index].exptime != 0 &&
+ gnutls_time(0) >= session->internals.selected_ocsp[ctx->cert_index].exptime) ||
+ session->internals.selected_ocsp[ctx->cert_index].response.data == NULL) {
+ return 0;
+ }
+
+ resp.data = session->internals.selected_ocsp[ctx->cert_index].response.data;
+ resp.size = session->internals.selected_ocsp[ctx->cert_index].response.size;
+ ret = 0;
+ } else {
+ return 0;
+ }
+ } else if (session->internals.selected_ocsp_func) {
+ if (ctx->cert_index == 0) {
+ ret = session->internals.selected_ocsp_func(session, session->internals.selected_ocsp_func_ptr, &resp);
+ free_resp = 1;
+ } else {
+ return 0;
+ }
+ } else
+ return 0;
+
+ if (ret == GNUTLS_E_NO_CERTIFICATE_STATUS || resp.data == 0) {
+ return 0;
+ } else if (ret < 0) {
+ return gnutls_assert_val(ret);
+ }
+
+ ret = _gnutls_buffer_append_data(buf, "\x01", 1);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+
+ ret = _gnutls_buffer_append_data_prefix(buf, 24, resp.data, resp.size);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+
+ ret = 0;
+ cleanup:
+ if (free_resp)
+ gnutls_free(resp.data);
+ return ret;
+}
+
+int _gnutls13_send_certificate(gnutls_session_t session, unsigned again)
+{
+ int ret, compress_cert;
+ gnutls_pcert_st *apr_cert_list = NULL;
+ gnutls_privkey_t apr_pkey = NULL;
+ int apr_cert_list_length = 0;
+ mbuffer_st *bufel = NULL;
+ gnutls_buffer_st buf;
+ unsigned pos_mark, ext_pos_mark, cert_pos_mark;
+ unsigned i;
+ struct ocsp_req_ctx_st ctx;
+ gnutls_certificate_credentials_t cred;
+ gnutls_compression_method_t comp_method;
+ gnutls_handshake_description_t h_type;
+
+ comp_method = gnutls_compress_certificate_get_selected_method(session);
+ compress_cert = comp_method != GNUTLS_COMP_UNKNOWN;
+ h_type = compress_cert ? GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT
+ : GNUTLS_HANDSHAKE_CERTIFICATE_PKT;
+
+ if (again == 0) {
+ if (!session->internals.initial_negotiation_completed &&
+ session->internals.hsk_flags & HSK_PSK_SELECTED)
+ return 0;
+
+ if (session->security_parameters.entity == GNUTLS_SERVER &&
+ session->internals.resumed)
+ return 0;
+
+ cred = (gnutls_certificate_credentials_t)
+ _gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE);
+ if (cred == NULL) {
+ gnutls_assert();
+ return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
+ }
+
+ if (session->security_parameters.entity == GNUTLS_CLIENT &&
+ !(session->internals.hsk_flags & HSK_CRT_ASKED)) {
+ return 0;
+ }
+
+ ret = _gnutls_get_selected_cert(session, &apr_cert_list,
+ &apr_cert_list_length, &apr_pkey);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ ret = _gnutls_buffer_init_handshake_mbuffer(&buf);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ cert_pos_mark = buf.length;
+
+ if (session->security_parameters.entity == GNUTLS_CLIENT) {
+ ret = _gnutls_buffer_append_data_prefix(&buf, 8,
+ session->internals.post_handshake_cr_context.data,
+ session->internals.post_handshake_cr_context.size);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+
+ } else {
+ ret = _gnutls_buffer_append_prefix(&buf, 8, 0);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+ }
+
+ /* mark total size */
+ pos_mark = buf.length;
+ ret = _gnutls_buffer_append_prefix(&buf, 24, 0);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+
+ for (i=0;i<(unsigned)apr_cert_list_length;i++) {
+ ret = _gnutls_buffer_append_data_prefix(&buf, 24,
+ apr_cert_list[i].cert.data,
+ apr_cert_list[i].cert.size);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+
+#ifdef ENABLE_OCSP
+ if ((session->internals.selected_ocsp_length > 0 ||
+ session->internals.selected_ocsp_func) &&
+ (((session->internals.hsk_flags & HSK_OCSP_REQUESTED) && IS_SERVER(session)) ||
+ ((session->internals.hsk_flags & HSK_CLIENT_OCSP_REQUESTED) && !IS_SERVER(session)))) {
+ /* append status response if available */
+ ret = _gnutls_extv_append_init(&buf);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+ ext_pos_mark = ret;
+
+ ctx.pcert = &apr_cert_list[i];
+ ctx.cert_index = i;
+ ctx.session = session;
+ ctx.cred = cred;
+ ret = _gnutls_extv_append(&buf, STATUS_REQUEST_TLS_ID,
+ &ctx, append_status_request);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+
+ ret = _gnutls_extv_append_final(&buf, ext_pos_mark, 0);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+ } else
+#endif
+ {
+ ret = _gnutls_buffer_append_prefix(&buf, 16, 0);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+ }
+ }
+
+ _gnutls_write_uint24(buf.length-pos_mark-3, &buf.data[pos_mark]);
+
+ if (compress_cert) {
+ ret = compress_certificate(&buf, cert_pos_mark, comp_method);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+ }
+
+ bufel = _gnutls_buffer_to_mbuffer(&buf);
+ }
+
+ return _gnutls_send_handshake(session, bufel, h_type);
+
+ cleanup:
+ _gnutls_buffer_clear(&buf);
+ return ret;
+}
+
+typedef struct crt_cert_ctx_st {
+ gnutls_session_t session;
+ gnutls_datum_t *ocsp;
+ unsigned idx;
+} crt_cert_ctx_st;
+
+static int parse_cert_extension(void *_ctx, unsigned tls_id, const uint8_t *data, unsigned data_size)
+{
+ crt_cert_ctx_st *ctx = _ctx;
+ gnutls_session_t session = ctx->session;
+ int ret;
+
+ if (tls_id == STATUS_REQUEST_TLS_ID) {
+#ifdef ENABLE_OCSP
+ if (!_gnutls_hello_ext_is_present(session, ext_mod_status_request.gid)) {
+ gnutls_assert();
+ goto unexpected;
+ }
+
+ _gnutls_handshake_log("Found OCSP response on cert %d\n", ctx->idx);
+
+ ret = _gnutls_parse_ocsp_response(session, data, data_size, ctx->ocsp);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+#endif
+ } else {
+ goto unexpected;
+ }
+
+ return 0;
+
+ unexpected:
+ _gnutls_debug_log("received unexpected certificate extension (%d)\n", (int)tls_id);
+ return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION);
+}
+
+static int
+parse_cert_list(gnutls_session_t session, uint8_t * data, size_t data_size)
+{
+ int ret;
+ size_t len;
+ uint8_t *p = data;
+ cert_auth_info_t info;
+ gnutls_certificate_credentials_t cred;
+ size_t size;
+ int i;
+ unsigned npeer_certs, npeer_ocsp, j;
+ crt_cert_ctx_st ctx;
+ gnutls_datum_t *peer_certs = NULL;
+ gnutls_datum_t *peer_ocsp = NULL;
+ unsigned nentries = 0;
+
+ cred = (gnutls_certificate_credentials_t)
+ _gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE);
+ if (cred == NULL) {
+ gnutls_assert();
+ return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
+ }
+
+ if ((ret =
+ _gnutls_auth_info_init(session, GNUTLS_CRD_CERTIFICATE,
+ sizeof(cert_auth_info_st), 1)) < 0) {
+ gnutls_assert();
+ return ret;
+ }
+
+ if (data == NULL || data_size == 0) {
+ /* no certificate was sent */
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+ }
+
+ info = _gnutls_get_auth_info(session, GNUTLS_CRD_CERTIFICATE);
+ if (info == NULL)
+ return gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS);
+
+ DECR_LEN(data_size, 3);
+ size = _gnutls_read_uint24(p);
+ p += 3;
+
+ if (size != data_size)
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+
+ if (size == 0)
+ return gnutls_assert_val(GNUTLS_E_NO_CERTIFICATE_FOUND);
+
+ i = data_size;
+
+ while (i > 0) {
+ DECR_LEN(data_size, 3);
+ len = _gnutls_read_uint24(p);
+ if (len == 0)
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+
+ DECR_LEN(data_size, len);
+ p += len + 3;
+ i -= len + 3;
+
+ DECR_LEN(data_size, 2);
+ len = _gnutls_read_uint16(p);
+ DECR_LEN(data_size, len);
+
+ i -= len + 2;
+ p += len + 2;
+
+ nentries++;
+ }
+
+ if (data_size != 0)
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+
+ /* this is unnecessary - keeping to avoid a regression due to a re-org
+ * of the loop above */
+ if (nentries == 0)
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+
+ npeer_ocsp = 0;
+ npeer_certs = 0;
+
+ /* Ok we now allocate the memory to hold the
+ * certificate list
+ */
+ peer_certs = gnutls_calloc(nentries, sizeof(gnutls_datum_t));
+ if (peer_certs == NULL)
+ return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+
+ peer_ocsp = gnutls_calloc(nentries, sizeof(gnutls_datum_t));
+ if (peer_ocsp == NULL) {
+ ret = gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+ goto cleanup;
+ }
+
+ p = data+3;
+
+ /* Now we start parsing the list (again).
+ * We don't use DECR_LEN since the list has
+ * been parsed before.
+ */
+
+ ctx.session = session;
+
+ for (j = 0; j < nentries; j++) {
+ len = _gnutls_read_uint24(p);
+ p += 3;
+
+ ret = _gnutls_set_datum(&peer_certs[j], p, len);
+ if (ret < 0) {
+ gnutls_assert();
+ ret = GNUTLS_E_CERTIFICATE_ERROR;
+ goto cleanup;
+ }
+ npeer_certs++;
+
+ p += len;
+
+ len = _gnutls_read_uint16(p);
+
+ ctx.ocsp = &peer_ocsp[j];
+ ctx.idx = j;
+
+ ret = _gnutls_extv_parse(&ctx, parse_cert_extension, p, len+2);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+
+ p += len+2;
+ npeer_ocsp++;
+ }
+
+ /* The OCSP entries match the certificate entries, although
+ * the contents of each OCSP entry may be NULL.
+ */
+ for(j=0;j<info->ncerts;j++)
+ gnutls_free(info->raw_certificate_list[j].data);
+ gnutls_free(info->raw_certificate_list);
+
+ for(j=0;j<info->nocsp;j++)
+ gnutls_free(info->raw_ocsp_list[j].data);
+ gnutls_free(info->raw_ocsp_list);
+
+
+ info->raw_certificate_list = peer_certs;
+ info->ncerts = npeer_certs;
+
+ info->raw_ocsp_list = peer_ocsp;
+ info->nocsp = npeer_ocsp;
+
+ return 0;
+
+ cleanup:
+ for(j=0;j<npeer_certs;j++)
+ gnutls_free(peer_certs[j].data);
+
+ for(j=0;j<npeer_ocsp;j++)
+ gnutls_free(peer_ocsp[j].data);
+ gnutls_free(peer_certs);
+ gnutls_free(peer_ocsp);
+ return ret;
+
+}
+
+static int
+compress_certificate(gnutls_buffer_st * buf, unsigned cert_pos_mark,
+ gnutls_compression_method_t comp_method)
+{
+ int ret, method_num;
+ size_t comp_bound;
+ gnutls_datum_t plain, comp = { NULL, 0 };
+
+ method_num = _gnutls_compress_certificate_method2num(comp_method);
+ if (method_num == GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER)
+ return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER);
+
+ plain.data = buf->data + cert_pos_mark;
+ plain.size = buf->length - cert_pos_mark;
+
+ comp_bound = _gnutls_compress_bound(comp_method, plain.size);
+ if (comp_bound == 0)
+ return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
+ comp.data = gnutls_malloc(comp_bound);
+ if (comp.data == NULL)
+ return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+ ret = _gnutls_compress(comp_method, comp.data, comp_bound, plain.data, plain.size);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+ comp.size = ret;
+
+ buf->length = cert_pos_mark;
+ ret = _gnutls_buffer_append_prefix(buf, 16, method_num);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+ ret = _gnutls_buffer_append_prefix(buf, 24, plain.size);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+ ret = _gnutls_buffer_append_data_prefix(buf, 24, comp.data, comp.size);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+
+cleanup:
+ gnutls_free(comp.data);
+ return ret;
+}
+
+static int
+decompress_certificate(gnutls_buffer_st * buf)
+{
+ int ret;
+ size_t method_num, plain_exp_len;
+ gnutls_datum_t comp, plain = { NULL, 0 };
+ gnutls_compression_method_t comp_method;
+
+ ret = _gnutls_buffer_pop_prefix16(buf, &method_num, 0);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+ comp_method = _gnutls_compress_certificate_num2method(method_num);
+
+ ret = _gnutls_buffer_pop_prefix24(buf, &plain_exp_len, 0);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ ret = _gnutls_buffer_pop_datum_prefix24(buf, &comp);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ plain.data = gnutls_malloc(plain_exp_len);
+ if (plain.data == NULL)
+ return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
+ ret = _gnutls_decompress(comp_method, plain.data, plain_exp_len, comp.data, comp.size);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+ plain.size = ret;
+
+ if (plain.size != plain_exp_len) {
+ gnutls_assert();
+ ret = GNUTLS_E_DECOMPRESSION_FAILED;
+ goto cleanup;
+ }
+
+ _gnutls_buffer_clear(buf);
+ ret = _gnutls_buffer_append_data(buf, plain.data, plain.size);
+ if (ret < 0) {
+ gnutls_assert();
+ goto cleanup;
+ }
+
+cleanup:
+ gnutls_free(plain.data);
+ return ret;
+}