/* * 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 * */ #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_session_t session, 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) { /* fail if we receive unsolicited compressed certificate */ if (!(session->internals.hsk_flags & HSK_COMP_CRT_REQ_SENT)) return gnutls_assert_val( GNUTLS_E_UNEXPECTED_PACKET); 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(session, &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_session_t session, 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); if (!_gnutls_compress_certificate_is_method_enabled(session, comp_method)) return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER); 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; }