/*
* Copyright (C) 2012-2017 Free Software Foundation, Inc.
* Copyright (C) 2017 Red Hat, Inc.
*
* Author: Simon Josefsson, 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
*
*/
/*
Status Request (OCSP) TLS extension. See RFC 6066 section 8:
https://tools.ietf.org/html/rfc6066#section-8
*/
#include "gnutls_int.h"
#include "errors.h"
#include
#include
#include
#include
#include
#include
#ifdef ENABLE_OCSP
typedef struct {
/* server response */
gnutls_datum_t sresp;
unsigned int expect_cstatus;
} status_request_ext_st;
/*
From RFC 6066. Client sends:
struct {
CertificateStatusType status_type;
select (status_type) {
case ocsp: OCSPStatusRequest;
} request;
} CertificateStatusRequest;
enum { ocsp(1), (255) } CertificateStatusType;
struct {
ResponderID responder_id_list<0..2^16-1>;
Extensions request_extensions;
} OCSPStatusRequest;
opaque ResponderID<1..2^16-1>;
opaque Extensions<0..2^16-1>;
*/
static int
client_send(gnutls_session_t session,
gnutls_buffer_st * extdata, status_request_ext_st * priv)
{
const uint8_t data[5] = "\x01\x00\x00\x00\x00";
const int len = 5;
int ret;
/* We do not support setting either ResponderID or Extensions */
ret = _gnutls_buffer_append_data(extdata, data, len);
if (ret < 0)
return gnutls_assert_val(ret);
session->internals.hsk_flags |= HSK_OCSP_REQUESTED;
return len;
}
static int
server_recv(gnutls_session_t session,
const uint8_t * data, size_t data_size)
{
unsigned rid_bytes = 0;
/* minimum message is type (1) + responder_id_list (2) +
request_extension (2) = 5 */
if (data_size < 5)
return
gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
/* We ignore non-ocsp CertificateStatusType. The spec is unclear
what should be done. */
if (data[0] != 0x01) {
gnutls_assert();
_gnutls_handshake_log("EXT[%p]: unknown status_type %d\n",
session, data[0]);
return 0;
}
DECR_LEN(data_size, 1);
data++;
rid_bytes = _gnutls_read_uint16(data);
DECR_LEN(data_size, 2);
/*data += 2;*/
/* sanity check only, we don't use any of the data below */
if (data_size < rid_bytes)
return
gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER);
_gnutls_handshake_log("EXT[%p]: OCSP status was requested\n", session);
session->internals.hsk_flags |= HSK_OCSP_REQUESTED;
return 0;
}
static int
client_recv(gnutls_session_t session,
status_request_ext_st * priv,
const uint8_t * data, size_t size)
{
if (size != 0)
return
gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
else {
priv->expect_cstatus = 1;
return 0;
}
}
/*
* Servers return a certificate response along with their certificate
* by sending a "CertificateStatus" message immediately after the
* "Certificate" message (and before any "ServerKeyExchange" or
* "CertificateRequest" messages). If a server returns a
* "CertificateStatus" message, then the server MUST have included an
* extension of type "status_request" with empty "extension_data" in
* the extended server hello.
*
* According to the description above, as a server we could simply
* return GNUTLS_E_INT_RET_0 on this function. In that case we would
* only need to use the callbacks at the time we need to send the data,
* and skip the status response packet if no such data are there.
* However, that behavior would break gnutls 3.3.x which expects the status
* response to be always send if the extension is present.
*
* Instead we ensure that this extension is parsed after the CS/certificate
* are selected (with the _GNUTLS_EXT_TLS_POST_CS type), and we discover
* (or not) the response to send early.
*/
static int
server_send(gnutls_session_t session,
gnutls_buffer_st * extdata, status_request_ext_st * priv)
{
int ret;
gnutls_certificate_credentials_t cred;
gnutls_status_request_ocsp_func func;
void *func_ptr;
const version_entry_st *ver = get_version(session);
cred = (gnutls_certificate_credentials_t)
_gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE);
if (cred == NULL) /* no certificate authentication */
return 0;
/* no need to set sresp; responses are send during certificate sending and
* no response is required from server side. */
if (ver && ver->multi_ocsp)
return 0;
/* Under TLS1.2 we obtain the response at this point in order to respond
* appropriately (empty extension vs no response) */
if (session->internals.selected_ocsp_length > 0) {
if (session->internals.selected_ocsp[0].response.data) {
if (session->internals.selected_ocsp[0].exptime != 0 &&
(gnutls_time(0) >= session->internals.selected_ocsp[0].exptime)) {
gnutls_assert();
return 0;
}
ret = _gnutls_set_datum(&priv->sresp,
session->internals.selected_ocsp[0].response.data,
session->internals.selected_ocsp[0].response.size);
if (ret < 0)
return gnutls_assert_val(ret);
return GNUTLS_E_INT_RET_0;
} else {
return 0;
}
} else if (session->internals.selected_ocsp_func) {
func = session->internals.selected_ocsp_func;
func_ptr = session->internals.selected_ocsp_func_ptr;
if (func == NULL)
return 0;
ret = func(session, func_ptr, &priv->sresp);
if (ret == GNUTLS_E_NO_CERTIFICATE_STATUS)
return 0;
else if (ret < 0)
return gnutls_assert_val(ret);
} else {
return 0;
}
return GNUTLS_E_INT_RET_0;
}
static int
_gnutls_status_request_send_params(gnutls_session_t session,
gnutls_buffer_st * extdata)
{
gnutls_ext_priv_data_t epriv;
status_request_ext_st *priv;
int ret;
/* Do not bother sending the OCSP status request extension
* if we are not using certificate authentication */
if (_gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE) == NULL)
return 0;
if (session->security_parameters.entity == GNUTLS_CLIENT) {
ret = _gnutls_hello_ext_get_priv(session,
GNUTLS_EXTENSION_STATUS_REQUEST,
&epriv);
if (ret < 0 || epriv == NULL) /* it is ok not to have it */
return 0;
priv = epriv;
return client_send(session, extdata, priv);
} else {
epriv = priv = gnutls_calloc(1, sizeof(*priv));
if (priv == NULL)
return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
_gnutls_hello_ext_set_priv(session,
GNUTLS_EXTENSION_STATUS_REQUEST,
epriv);
return server_send(session, extdata, priv);
}
}
static int
_gnutls_status_request_recv_params(gnutls_session_t session,
const uint8_t * data, size_t size)
{
gnutls_ext_priv_data_t epriv;
status_request_ext_st *priv;
int ret;
if (session->security_parameters.entity == GNUTLS_CLIENT) {
ret = _gnutls_hello_ext_get_priv(session,
GNUTLS_EXTENSION_STATUS_REQUEST,
&epriv);
if (ret < 0 || epriv == NULL) /* it is ok not to have it */
return 0;
priv = epriv;
return client_recv(session, priv, data, size);
} else {
return server_recv(session, data, size);
}
}
/**
* gnutls_ocsp_status_request_enable_client:
* @session: is a #gnutls_session_t type.
* @responder_id: ignored, must be %NULL
* @responder_id_size: ignored, must be zero
* @extensions: ignored, must be %NULL
*
* This function is to be used by clients to request OCSP response
* from the server, using the "status_request" TLS extension. Only
* OCSP status type is supported.
*
* Previous versions of GnuTLS supported setting @responder_id and
* @extensions fields, but due to the difficult semantics of the
* parameter usage, and other issues, this support was removed
* since 3.6.0 and these parameters must be set to %NULL.
*
* Returns: On success, %GNUTLS_E_SUCCESS (0) is returned,
* otherwise a negative error code is returned.
*
* Since: 3.1.3
**/
int
gnutls_ocsp_status_request_enable_client(gnutls_session_t session,
gnutls_datum_t * responder_id,
size_t responder_id_size,
gnutls_datum_t * extensions)
{
status_request_ext_st *priv;
gnutls_ext_priv_data_t epriv;
if (session->security_parameters.entity == GNUTLS_SERVER)
return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
epriv = priv = gnutls_calloc(1, sizeof(*priv));
if (priv == NULL)
return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
_gnutls_hello_ext_set_priv(session,
GNUTLS_EXTENSION_STATUS_REQUEST,
epriv);
return 0;
}
static void _gnutls_status_request_deinit_data(gnutls_ext_priv_data_t epriv)
{
status_request_ext_st *priv = epriv;
if (priv == NULL)
return;
gnutls_free(priv->sresp.data);
gnutls_free(priv);
}
const hello_ext_entry_st ext_mod_status_request = {
.name = "OCSP Status Request",
.tls_id = STATUS_REQUEST_TLS_ID,
.gid = GNUTLS_EXTENSION_STATUS_REQUEST,
.validity = GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_DTLS | GNUTLS_EXT_FLAG_CLIENT_HELLO |
GNUTLS_EXT_FLAG_TLS12_SERVER_HELLO,
.client_parse_point = _GNUTLS_EXT_TLS_POST_CS,
.server_parse_point = _GNUTLS_EXT_TLS_POST_CS,
.recv_func = _gnutls_status_request_recv_params,
.send_func = _gnutls_status_request_send_params,
.deinit_func = _gnutls_status_request_deinit_data,
.cannot_be_overriden = 1
};
/* Functions to be called from handshake */
int
_gnutls_send_server_certificate_status(gnutls_session_t session, int again)
{
mbuffer_st *bufel = NULL;
uint8_t *data;
int data_size = 0;
int ret;
gnutls_ext_priv_data_t epriv;
status_request_ext_st *priv;
if (!(session->internals.hsk_flags & HSK_OCSP_REQUESTED))
return 0;
if (again == 0) {
ret =
_gnutls_hello_ext_get_priv(session,
GNUTLS_EXTENSION_STATUS_REQUEST,
&epriv);
if (ret < 0)
return 0;
priv = epriv;
if (!priv->sresp.size)
return 0;
data_size = priv->sresp.size + 4;
bufel =
_gnutls_handshake_alloc(session, data_size);
if (!bufel) {
_gnutls_free_datum(&priv->sresp);
return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
}
data = _mbuffer_get_udata_ptr(bufel);
data[0] = 0x01;
_gnutls_write_uint24(priv->sresp.size, &data[1]);
memcpy(&data[4], priv->sresp.data, priv->sresp.size);
_gnutls_free_datum(&priv->sresp);
}
return _gnutls_send_handshake(session, data_size ? bufel : NULL,
GNUTLS_HANDSHAKE_CERTIFICATE_STATUS);
}
int _gnutls_parse_ocsp_response(gnutls_session_t session, const uint8_t *data, ssize_t data_size, gnutls_datum_t *resp)
{
int ret;
ssize_t r_size;
resp->data = 0;
resp->size = 0;
/* minimum message is type (1) + response (3) + data */
if (data_size < 4)
return
gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
if (data[0] != 0x01) {
gnutls_assert();
_gnutls_handshake_log("EXT[%p]: unknown status_type %d\n",
session, data[0]);
return 0;
}
DECR_LENGTH_RET(data_size, 1,
GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
data++;
DECR_LENGTH_RET(data_size, 3,
GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
r_size = _gnutls_read_uint24(data);
data += 3;
DECR_LENGTH_RET(data_size, r_size,
GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
if (r_size < 1)
return
gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
ret = _gnutls_set_datum(resp, data, r_size);
if (ret < 0)
return gnutls_assert_val(ret);
return 0;
}
int _gnutls_recv_server_certificate_status(gnutls_session_t session)
{
uint8_t *data;
ssize_t data_size;
gnutls_buffer_st buf;
int ret;
gnutls_datum_t resp;
status_request_ext_st *priv = NULL;
gnutls_ext_priv_data_t epriv;
cert_auth_info_t info = _gnutls_get_auth_info(session, GNUTLS_CRD_CERTIFICATE);
if (info == NULL)
return 0;
ret =
_gnutls_hello_ext_get_priv(session,
GNUTLS_EXTENSION_STATUS_REQUEST,
&epriv);
if (ret < 0)
return 0;
priv = epriv;
if (!priv->expect_cstatus)
return 0;
ret = _gnutls_recv_handshake(session,
GNUTLS_HANDSHAKE_CERTIFICATE_STATUS,
1, &buf);
if (ret < 0)
return gnutls_assert_val_fatal(ret);
priv->expect_cstatus = 0;
data = buf.data;
data_size = buf.length;
if (data_size == 0) {
ret = 0;
goto error;
}
ret = _gnutls_parse_ocsp_response(session, data, data_size, &resp);
if (ret < 0) {
gnutls_assert();
goto error;
}
if (resp.data && resp.size > 0) {
for (unsigned int i = 0; i < info->nocsp; i++)
gnutls_free(info->raw_ocsp_list[i].data);
gnutls_free(info->raw_ocsp_list);
info->raw_ocsp_list = gnutls_malloc(sizeof(gnutls_datum_t));
if (info->raw_ocsp_list == NULL) {
ret = GNUTLS_E_MEMORY_ERROR;
goto error;
}
info->raw_ocsp_list[0].data = resp.data;
info->raw_ocsp_list[0].size = resp.size;
info->nocsp = 1;
}
ret = 0;
error:
_gnutls_buffer_clear(&buf);
return ret;
}
#endif