summaryrefslogtreecommitdiff
path: root/lib/ext/client_cert_type.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ext/client_cert_type.c')
-rw-r--r--lib/ext/client_cert_type.c371
1 files changed, 371 insertions, 0 deletions
diff --git a/lib/ext/client_cert_type.c b/lib/ext/client_cert_type.c
new file mode 100644
index 0000000000..8bce721ace
--- /dev/null
+++ b/lib/ext/client_cert_type.c
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2016 - 2018 ARPA2 project
+ *
+ * Author: Tom Vrancken (dev@tomvrancken.nl)
+ *
+ * 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 <http://www.gnu.org/licenses/>
+ *
+ * This file is part of the client_certificate_type extension as
+ * defined in RFC7250 (https://tools.ietf.org/html/rfc7250).
+ *
+ * The client_certificate_type extension in the client hello indicates
+ * the certificate types the client is able to provide to the server,
+ * when requested using a certificate_request message.
+ */
+
+#include "gnutls_int.h"
+#include <gnutls/gnutls.h>
+#include "ext/cert_types.h"
+#include "ext/client_cert_type.h"
+#include "hello_ext.h"
+#include "hello_ext_lib.h"
+#include "errors.h"
+#include <state.h>
+#include <datum.h>
+
+
+static int _gnutls_client_cert_type_recv_params(gnutls_session_t session,
+ const uint8_t* data,
+ size_t data_size);
+static int _gnutls_client_cert_type_send_params(gnutls_session_t session,
+ gnutls_buffer_st* data);
+
+
+const hello_ext_entry_st ext_mod_client_cert_type = {
+ .name = "Client Certificate Type",
+ .tls_id = 19,
+ .gid = GNUTLS_EXTENSION_CLIENT_CERT_TYPE,
+ .parse_type = GNUTLS_EXT_TLS,
+ .validity = GNUTLS_EXT_FLAG_TLS |
+ GNUTLS_EXT_FLAG_DTLS |
+ GNUTLS_EXT_FLAG_CLIENT_HELLO |
+ GNUTLS_EXT_FLAG_TLS12_SERVER_HELLO |
+ GNUTLS_EXT_FLAG_EE,
+ .recv_func = _gnutls_client_cert_type_recv_params,
+ .send_func = _gnutls_client_cert_type_send_params,
+ .pack_func = _gnutls_hello_ext_default_pack,
+ .unpack_func = _gnutls_hello_ext_default_unpack,
+ .deinit_func = _gnutls_hello_ext_default_deinit,
+ .cannot_be_overriden = 1
+};
+
+
+static int _gnutls_client_cert_type_recv_params(gnutls_session_t session,
+ const uint8_t* data,
+ size_t data_size)
+{
+ int ret;
+ gnutls_datum_t cert_types; // Holds the received cert types
+ gnutls_datum_t sent_cert_types; // Holds the previously sent cert types
+ gnutls_certificate_type_t cert_type;
+
+ uint8_t i, found = 0;
+ ssize_t len = data_size;
+ const uint8_t* pdata = data;
+
+ /* Only activate this extension if cert type negotiation is enabled
+ * and we have cert credentials set */
+ if (!_gnutls_has_negotiate_ctypes(session) ||
+ _gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE) == NULL)
+ return 0;
+
+ if (!IS_SERVER(session)) { // client mode
+
+ /* Compare packet length with expected packet length. For the
+ * client this is a single byte. */
+ if (len != 1) {
+ return
+ gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+ }
+
+ /* The server picked one of the offered cert types iff he supports
+ * at least one of them and decided to do a client certificate
+ * request. If both parties play by the rules then we may only
+ * receive a cert type that we offered, i.e. one that we support.
+ * Because the world isn't as beautiful as it may seem, we're going
+ * to check it nevertheless. */
+ cert_type = _gnutls_IANA2cert_type(pdata[0]);
+
+ // Check validity of cert type
+ if (cert_type == GNUTLS_CRT_UNKNOWN) {
+ return gnutls_assert_val(GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE);
+ }
+
+ /* Get the cert types that we sent to the server (they were stored
+ * in IANA representation.
+ */
+ ret = _gnutls_hello_ext_get_datum(session,
+ GNUTLS_EXTENSION_CLIENT_CERT_TYPE,
+ &sent_cert_types);
+ if (ret < 0) {
+ /* This should not happen and indicate a memory corruption!
+ * Assertion are always on in production code so execution
+ * will halt here. */
+ assert(false);
+ }
+
+ // Check whether what we got back is actually offered by us
+ for (i = 0; i < sent_cert_types.size; i++) {
+ if (_gnutls_IANA2cert_type(sent_cert_types.data[i]) == cert_type)
+ found = 1;
+ }
+
+ if (found) {
+ // Everything OK, now set the client certificate type
+ _gnutls_session_client_cert_type_set(session, cert_type);
+ ret = GNUTLS_E_SUCCESS;
+ } else {
+ // No valid cert type found
+ ret = GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE;
+ }
+
+ return ret;
+
+ } else { // server mode
+ // Compare packet length with expected packet length.
+ DECR_LEN(len, 1);
+ if (data[0] != len) {
+ return
+ gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+ }
+ pdata += 1;
+
+ // Assign the contents of our data buffer to a gnutls_datum_t
+ cert_types.data = (uint8_t*)pdata; // Need casting to get rid of 'discards const qualifier' warning
+ cert_types.size = len;
+
+ // Store the client certificate types in our session
+ _gnutls_hello_ext_set_datum(session,
+ GNUTLS_EXTENSION_CLIENT_CERT_TYPE,
+ &cert_types);
+
+ /* We receive a list of supported certificate types that the client
+ * is able to provide when requested via a client certificate
+ * request. This list is sorted by order of preference. We now check
+ * in this order of preference whether we support any of these
+ * certificate types.
+ */
+ for (i = 0; i < cert_types.size; i++) {
+ // Convert to internal representation
+ cert_type = _gnutls_IANA2cert_type(cert_types.data[i]);
+
+ // If we have an invalid cert id then continue to the next
+ if (cert_type == GNUTLS_CRT_UNKNOWN)
+ continue;
+
+ // Check for support of this cert type
+ if (_gnutls_session_cert_type_supported
+ (session, cert_type, false, GNUTLS_CTYPE_CLIENT) == 0) {
+ found = 1;
+ break;
+ }
+ }
+
+ // We found a matching ctype, we pick this one
+ if (found) {
+ _gnutls_session_client_cert_type_set(session, cert_type);
+ ret = GNUTLS_E_SUCCESS;
+ } else {
+ /* If no supported certificate type can be found we terminate
+ * with a fatal alert of type "unsupported_certificate"
+ * (according to specification rfc7250).
+ */
+ _gnutls_handshake_log
+ ("EXT[%p]: No supported client certificate type was found. "
+ "Aborting connection.\n", session);
+ ret = GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE;
+ }
+
+ return ret;
+ }
+}
+
+static int _gnutls_client_cert_type_send_params(gnutls_session_t session,
+ gnutls_buffer_st* data)
+{
+ int ret;
+ uint8_t cert_type; // Holds an IANA cert type ID
+ uint8_t i = 0, num_cert_types = 0;
+ priority_st* cert_priors;
+ gnutls_datum_t tmp_cert_types; // For type conversion
+ uint8_t cert_types[GNUTLS_CRT_MAX]; // The list with supported cert types
+ const version_entry_st* vers = get_version(session);
+
+ /* Only activate this extension if cert type negotiation is enabled
+ * and we have cert credentials set */
+ if (!_gnutls_has_negotiate_ctypes(session) ||
+ _gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE) == NULL)
+ return 0;
+
+ if (!IS_SERVER(session)) { // Client mode
+ // For brevity
+ cert_priors =
+ &session->internals.priorities->client_ctype;
+
+ /* Retrieve client certificate type priorities if any. If no
+ * priorities are set then the default client certificate type
+ * initialization values apply. This default is currently set to
+ * x.509 in which case we don't enable this extension.
+ */
+ if (cert_priors->algorithms > 0) { // Priorities are explicitly set
+ /* If the certificate priority is explicitly set to only
+ * X.509 (default) then, according to spec we don't send
+ * this extension. We check this here to avoid further work in
+ * this routine. We also check it below after pruning supported
+ * types.
+ */
+ if (cert_priors->algorithms == 1 &&
+ cert_priors->priority[0] == DEFAULT_CERT_TYPE) {
+ _gnutls_handshake_log
+ ("EXT[%p]: Client certificate type was set to default cert type (%s). "
+ "We therefore do not send this extension.\n",
+ session,
+ gnutls_certificate_type_get_name(DEFAULT_CERT_TYPE));
+
+ // Explicitly set but default ctype, so don't send anything
+ return 0;
+ }
+
+ /* We are only allowed to send certificate types that we support,
+ * i.e. have credentials for. Therefore we check this here and
+ * prune our original list.
+ */
+ for (i = 0; i < cert_priors->algorithms; i++) {
+ if (_gnutls_session_cert_type_supported
+ (session, cert_priors->priority[i],
+ true, GNUTLS_CTYPE_CLIENT) == 0) {
+ /* Check whether we are allowed to store another cert type
+ * in our buffer. In other words, prevent a possible buffer
+ * overflow. This situation can occur when a user sets
+ * duplicate cert types in the priority strings. */
+ if (num_cert_types >= GNUTLS_CRT_MAX)
+ return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER);
+
+ // Convert to IANA representation
+ cert_type = _gnutls_cert_type2IANA(cert_priors->priority[i]);
+ // Add this cert type to our list with supported types
+ cert_types[num_cert_types] = cert_type;
+ num_cert_types++;
+
+ _gnutls_handshake_log
+ ("EXT[%p]: Client certificate type %s (%d) was queued.\n",
+ session,
+ gnutls_certificate_type_get_name(cert_priors->priority[i]),
+ cert_type);
+ }
+ }
+
+ /* Check whether there are any supported certificate types left
+ * after the previous pruning step. If not, we do not send this
+ * extension. Also, if the only supported type is the default type
+ * we do not send this extension (according to RFC7250).
+ */
+ if (num_cert_types == 0) {
+ _gnutls_handshake_log
+ ("EXT[%p]: Client certificate types were set but none of them is supported. "
+ "You might want to check your credentials or your priorities. "
+ "We do not send this extension.\n",
+ session);
+
+ return 0;
+ } else if (num_cert_types == 1 &&
+ _gnutls_IANA2cert_type(cert_types[0]) == DEFAULT_CERT_TYPE) {
+ _gnutls_handshake_log
+ ("EXT[%p]: The only supported client certificate type is (%s) which is the default. "
+ "We therefore do not send this extension.\n",
+ session,
+ gnutls_certificate_type_get_name(DEFAULT_CERT_TYPE));
+
+ return 0;
+ }
+
+ /* We have data to send and store a copy internally. We convert
+ * our list with supported cert types to a datum_t in order to
+ * be able to make the ..._set_datum call.
+ */
+ tmp_cert_types.data = cert_types;
+ tmp_cert_types.size = num_cert_types;
+
+ _gnutls_hello_ext_set_datum(session,
+ GNUTLS_EXTENSION_CLIENT_CERT_TYPE,
+ &tmp_cert_types);
+
+ /* Serialize the certificate types into a sequence of octets
+ * uint8: length of sequence of cert types (1 octet)
+ * uint8: cert types (0 <= #octets <= 255)
+ */
+ ret = _gnutls_buffer_append_data_prefix(data, 8,
+ cert_types,
+ num_cert_types);
+
+ // Check for errors
+ if (ret < 0) {
+ return gnutls_assert_val(ret);
+ } else {
+ // Number of bytes we are sending
+ return num_cert_types + 1;
+ }
+ }
+ } else { // Server mode
+ /* TLS 1.2:
+ * Check whether we are going to send a certificate request,
+ * otherwise omit the response. This is conform spec.
+ * (RFC7250, 4.2 case 3.).
+ *
+ * TLS 1.3:
+ * TLS 1.3 supports post-handshake authentication for the client.
+ * It means that a server can ask for a client certificate anytime
+ * after the handshake. In order for this to work we must always
+ * complete the certificate type negotiation and therefore respond
+ * with a cert type message.
+ */
+ if (session->internals.send_cert_req != 0 ||
+ vers->tls13_sem) {
+ /* Retrieve negotiated client certificate type and send it to
+ * the client.
+ * The scenario where we want to send a certificate request but
+ * do not have a matching certificate does not occur because we
+ * already terminate the connection at reception of this extension
+ * when we cannot find a matching client certificate. This is conform
+ * spec (RFC7250, 4.2 case 2.).
+ */
+ cert_type =
+ _gnutls_cert_type2IANA(session->
+ security_parameters.client_ctype);
+
+ ret = gnutls_buffer_append_data(data, &cert_type, 1);
+
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ return 1; // sent one byte
+ }
+ }
+
+ // In all other cases don't enable this extension
+ return 0;
+}
+
+
+/** Extension interface **/
+
+/* The interface is defined in state.c:
+ * Public:
+ * - gnutls_certificate_type_get2
+ *
+ * Private:
+ * - _gnutls_session_client_cert_type_set
+ */