summaryrefslogtreecommitdiffstats
path: root/lib/ext/signature.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/ext/signature.c587
1 files changed, 587 insertions, 0 deletions
diff --git a/lib/ext/signature.c b/lib/ext/signature.c
new file mode 100644
index 0000000..bb350f5
--- /dev/null
+++ b/lib/ext/signature.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2002-2016 Free Software Foundation, Inc.
+ * Copyright (C) 2015-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/>
+ *
+ */
+
+/* This file contains the code for the Signature Algorithms TLS extension.
+ * This extension is currently gnutls specific.
+ */
+
+#include "gnutls_int.h"
+#include "errors.h"
+#include "num.h"
+#include <gnutls/gnutls.h>
+#include <ext/signature.h>
+#include <state.h>
+#include <num.h>
+#include <algorithms.h>
+#include <abstract_int.h>
+
+/*
+ * Some (all SChannel) clients fail to send proper SigAlgs due to Micro$oft crazyness.
+ * Patch the extension for them.
+ */
+#ifdef ENABLE_GOST
+#define GOST_SIG_FIXUP_SCHANNEL
+#endif
+
+static int _gnutls_signature_algorithm_recv_params(gnutls_session_t
+ session,
+ const uint8_t * data,
+ size_t data_size);
+static int _gnutls_signature_algorithm_send_params(gnutls_session_t
+ session,
+ gnutls_buffer_st * extdata);
+static void signature_algorithms_deinit_data(gnutls_ext_priv_data_t priv);
+static int signature_algorithms_pack(gnutls_ext_priv_data_t epriv,
+ gnutls_buffer_st * ps);
+static int signature_algorithms_unpack(gnutls_buffer_st * ps,
+ gnutls_ext_priv_data_t * _priv);
+
+const hello_ext_entry_st ext_mod_sig = {
+ .name = "Signature Algorithms",
+ .tls_id = 13,
+ .gid = GNUTLS_EXTENSION_SIGNATURE_ALGORITHMS,
+ .validity = GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_DTLS | GNUTLS_EXT_FLAG_CLIENT_HELLO,
+ .client_parse_point = GNUTLS_EXT_TLS,
+ .server_parse_point = GNUTLS_EXT_TLS,
+ .recv_func = _gnutls_signature_algorithm_recv_params,
+ .send_func = _gnutls_signature_algorithm_send_params,
+ .pack_func = signature_algorithms_pack,
+ .unpack_func = signature_algorithms_unpack,
+ .deinit_func = signature_algorithms_deinit_data,
+ .cannot_be_overriden = 1
+};
+
+typedef struct {
+ /* TLS 1.2 signature algorithms */
+ gnutls_sign_algorithm_t sign_algorithms[MAX_ALGOS];
+ uint16_t sign_algorithms_size;
+} sig_ext_st;
+
+/* generates a SignatureAndHashAlgorithm structure with length as prefix
+ * by using the setup priorities.
+ */
+int
+_gnutls_sign_algorithm_write_params(gnutls_session_t session,
+ gnutls_buffer_st * extdata)
+{
+ uint8_t *p;
+ unsigned int len, i;
+ const sign_algorithm_st *aid, *prev = NULL;
+ uint8_t buffer[MAX_ALGOS*2];
+
+ p = buffer;
+ len = 0;
+
+ /* This generates a list of TLS signature algorithms. It has
+ * limited duplicate detection, and does not add twice the same
+ * AID */
+
+ for (i=0;i<session->internals.priorities->sigalg.size;i++) {
+ aid = &session->internals.priorities->sigalg.entry[i]->aid;
+
+ if (HAVE_UNKNOWN_SIGAID(aid))
+ continue;
+
+ if (prev && prev->id[0] == aid->id[0] && prev->id[1] == aid->id[1])
+ continue;
+
+ /* Ignore non-GOST sign types for CertReq */
+ if (session->security_parameters.cs &&
+ _gnutls_kx_is_vko_gost(session->security_parameters.cs->kx_algorithm) &&
+ !_sign_is_gost(session->internals.priorities->sigalg.entry[i]))
+ continue;
+
+ _gnutls_handshake_log
+ ("EXT[%p]: sent signature algo (%d.%d) %s\n", session,
+ (int)aid->id[0], (int)aid->id[1],
+ session->internals.priorities->sigalg.entry[i]->name);
+
+ len += 2;
+ if (unlikely(len >= sizeof(buffer))) {
+ len -= 2;
+ break;
+ }
+
+ *p = aid->id[0];
+ p++;
+ *p = aid->id[1];
+ p++;
+ prev = aid;
+ }
+
+ return _gnutls_buffer_append_data_prefix(extdata, 16, buffer, len);
+}
+
+
+/* Parses the Signature Algorithm structure and stores data into
+ * session->security_parameters.extensions.
+ */
+int
+_gnutls_sign_algorithm_parse_data(gnutls_session_t session,
+ const uint8_t * data, size_t data_size)
+{
+ unsigned int sig, i;
+ sig_ext_st *priv;
+ gnutls_ext_priv_data_t epriv;
+ const version_entry_st *ver = get_version(session);
+
+ if (data_size == 0 || data_size % 2 != 0)
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+
+ if (ver == NULL) { /* assume TLS 1.2 semantics */
+ ver = version_to_entry(GNUTLS_TLS1_2);
+ if (unlikely(ver == NULL)) {
+ return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
+ }
+ }
+
+ priv = gnutls_calloc(1, sizeof(*priv));
+ if (priv == NULL) {
+ gnutls_assert();
+ return GNUTLS_E_MEMORY_ERROR;
+ }
+
+ for (i = 0; i < data_size; i += 2) {
+ uint8_t id[2];
+
+ id[0] = data[i];
+ id[1] = data[i + 1];
+
+ sig = _gnutls_tls_aid_to_sign(id[0], id[1], ver);
+
+ _gnutls_handshake_log
+ ("EXT[%p]: rcvd signature algo (%d.%d) %s\n", session,
+ (int)id[0], (int)id[1],
+ gnutls_sign_get_name(sig));
+
+ if (sig != GNUTLS_SIGN_UNKNOWN) {
+ if (priv->sign_algorithms_size == MAX_ALGOS)
+ break;
+ priv->sign_algorithms[priv->
+ sign_algorithms_size++] = sig;
+ }
+ }
+
+ epriv = priv;
+ _gnutls_hello_ext_set_priv(session,
+ GNUTLS_EXTENSION_SIGNATURE_ALGORITHMS,
+ epriv);
+
+ return 0;
+}
+
+/*
+ * In case of a server: if a SIGNATURE_ALGORITHMS extension type is
+ * received then it stores into the session security parameters the
+ * new value.
+ *
+ * In case of a client: If a signature_algorithms have been specified
+ * then it is an error;
+ */
+
+static int
+_gnutls_signature_algorithm_recv_params(gnutls_session_t session,
+ const uint8_t * data,
+ size_t data_size)
+{
+ int ret;
+
+ if (session->security_parameters.entity == GNUTLS_CLIENT) {
+ /* nothing for now */
+ gnutls_assert();
+ /* Although TLS 1.2 mandates that we must not accept reply
+ * to this message, there are good reasons to just ignore it. Check
+ * https://www.ietf.org/mail-archive/web/tls/current/msg03880.html
+ */
+ /* return GNUTLS_E_UNEXPECTED_PACKET; */
+ } else {
+ /* SERVER SIDE
+ */
+ if (data_size >= 2) {
+ uint16_t len;
+
+ DECR_LEN(data_size, 2);
+ len = _gnutls_read_uint16(data);
+ DECR_LEN(data_size, len);
+
+ if (data_size > 0)
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+
+ ret =
+ _gnutls_sign_algorithm_parse_data(session,
+ data + 2,
+ len);
+ if (ret < 0) {
+ gnutls_assert();
+ return ret;
+ }
+ } else {
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
+ }
+ }
+
+ return 0;
+}
+
+/* returns data_size or a negative number on failure
+ */
+static int
+_gnutls_signature_algorithm_send_params(gnutls_session_t session,
+ gnutls_buffer_st * extdata)
+{
+ int ret;
+ size_t init_length = extdata->length;
+ const version_entry_st *ver = get_version(session);
+
+ if (unlikely(ver == NULL))
+ return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
+
+ /* this function sends the client extension data */
+ if (session->security_parameters.entity == GNUTLS_CLIENT
+ && _gnutls_version_has_selectable_sighash(ver)) {
+ if (session->internals.priorities->sigalg.size > 0) {
+ ret =
+ _gnutls_sign_algorithm_write_params(session, extdata);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ return extdata->length - init_length;
+ }
+ }
+
+ /* if we are here it means we don't send the extension */
+ return 0;
+}
+
+#ifdef GOST_SIG_FIXUP_SCHANNEL
+static bool
+is_gost_sig_present(sig_ext_st *priv)
+{
+ unsigned i;
+ const gnutls_sign_entry_st *se;
+
+ for (i = 0; i < priv->sign_algorithms_size; i++) {
+ se = _gnutls_sign_to_entry(priv->sign_algorithms[i]);
+ if (se != NULL && _sign_is_gost(se))
+ return true;
+ }
+
+ return false;
+}
+#endif
+
+/* Returns a requested by the peer signature algorithm that
+ * matches the given certificate's public key algorithm.
+ *
+ * When the @client_cert flag is not set, then this function will
+ * also check whether the signature algorithm is allowed to be
+ * used in that session. Otherwise GNUTLS_SIGN_UNKNOWN is
+ * returned.
+ */
+gnutls_sign_algorithm_t
+_gnutls_session_get_sign_algo(gnutls_session_t session,
+ gnutls_pcert_st * cert,
+ gnutls_privkey_t privkey,
+ unsigned client_cert,
+ gnutls_kx_algorithm_t kx_algorithm)
+{
+ unsigned i;
+ int ret;
+ const version_entry_st *ver = get_version(session);
+ sig_ext_st *priv;
+ gnutls_ext_priv_data_t epriv;
+ unsigned int cert_algo;
+ const gnutls_sign_entry_st *se;
+
+ if (unlikely(ver == NULL))
+ return gnutls_assert_val(GNUTLS_SIGN_UNKNOWN);
+
+ cert_algo = gnutls_pubkey_get_pk_algorithm(cert->pubkey, NULL);
+
+ ret =
+ _gnutls_hello_ext_get_priv(session,
+ GNUTLS_EXTENSION_SIGNATURE_ALGORITHMS,
+ &epriv);
+ if (ret < 0)
+ priv = NULL;
+ else
+ priv = epriv;
+
+#ifdef GOST_SIG_FIXUP_SCHANNEL
+ /*
+ * Some (all SChannel) clients fail to send proper SigAlgs due to Micro$oft crazyness.
+ * If we are negotiating GOST KX (because we have received GOST
+ * ciphersuites) and if we have received no GOST SignatureAlgorithms,
+ * assume that the client could not send them and continue negotiation
+ * as if correct algorithm was sent.
+ */
+ if (_gnutls_kx_is_vko_gost(kx_algorithm) &&
+ (!priv ||
+ !is_gost_sig_present(priv) ||
+ !_gnutls_version_has_selectable_sighash(ver))) {
+ gnutls_digest_algorithm_t dig;
+
+ _gnutls_handshake_log("EXT[%p]: GOST KX, but no GOST SigAlgs received, patching up.", session);
+
+ if (cert_algo == GNUTLS_PK_GOST_01)
+ dig = GNUTLS_DIG_GOSTR_94;
+ else if (cert_algo == GNUTLS_PK_GOST_12_256)
+ dig = GNUTLS_DIG_STREEBOG_256;
+ else if (cert_algo == GNUTLS_PK_GOST_12_512)
+ dig = GNUTLS_DIG_STREEBOG_512;
+ else
+ dig = GNUTLS_DIG_SHA1;
+
+ ret = gnutls_pk_to_sign(cert_algo, dig);
+
+ if (!client_cert && _gnutls_session_sign_algo_enabled(session, ret) < 0)
+ goto fail;
+ return ret;
+ }
+#endif
+
+ if (!priv || !_gnutls_version_has_selectable_sighash(ver)) {
+ /* none set, allow SHA-1 only */
+ ret = gnutls_pk_to_sign(cert_algo, GNUTLS_DIG_SHA1);
+
+ if (!client_cert && _gnutls_session_sign_algo_enabled(session, ret) < 0)
+ goto fail;
+ return ret;
+ }
+
+
+
+ for (i = 0; i < priv->sign_algorithms_size; i++) {
+ se = _gnutls_sign_to_entry(priv->sign_algorithms[i]);
+ if (se == NULL)
+ continue;
+
+ _gnutls_handshake_log("checking cert compat with %s\n", se->name);
+
+ if (_gnutls_privkey_compatible_with_sig(privkey, priv->sign_algorithms[i]) == 0)
+ continue;
+
+ if (sign_supports_cert_pk_algorithm(se, cert_algo) != 0) {
+ if (_gnutls_pubkey_compatible_with_sig
+ (session, cert->pubkey, ver, se->id) < 0)
+ continue;
+
+ if (_gnutls_session_sign_algo_enabled
+ (session, se->id) < 0)
+ continue;
+
+ return se->id;
+ }
+ }
+
+ /* When having a legacy client certificate which can only be signed
+ * using algorithms we don't always enable by default (e.g., DSA-SHA1),
+ * continue and sign with it. */
+ if (client_cert) {
+ _gnutls_audit_log(session, "No shared signature schemes with peer for client certificate (%s). Is the certificate a legacy one?\n",
+ gnutls_pk_get_name(cert_algo));
+ }
+
+ fail:
+ return GNUTLS_SIGN_UNKNOWN;
+}
+
+/* Check if the given signature algorithm is supported.
+ * This means that it is enabled by the priority functions,
+ * and in case of a server a matching certificate exists.
+ */
+int
+_gnutls_session_sign_algo_enabled(gnutls_session_t session,
+ gnutls_sign_algorithm_t sig)
+{
+ unsigned i;
+ const version_entry_st *ver = get_version(session);
+
+ if (unlikely(ver == NULL))
+ return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
+
+ if (!_gnutls_version_has_selectable_sighash(ver)) {
+ return 0;
+ }
+
+ if (ver->tls13_sem) {
+ /* disallow RSA, DSA, and SHA1 */
+ const gnutls_sign_entry_st *se;
+
+ se = _gnutls_sign_to_entry(sig);
+ if (se == NULL || (se->flags & GNUTLS_SIGN_FLAG_TLS13_OK) == 0) {
+ gnutls_assert();
+ goto disallowed;
+ }
+ }
+
+ for (i = 0; i < session->internals.priorities->sigalg.size; i++) {
+ if (session->internals.priorities->sigalg.entry[i]->id == sig) {
+ return 0; /* ok */
+ }
+ }
+
+ disallowed:
+ _gnutls_handshake_log("Signature algorithm %s is not enabled\n", gnutls_sign_algorithm_get_name(sig));
+ return GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM;
+}
+
+static void signature_algorithms_deinit_data(gnutls_ext_priv_data_t priv)
+{
+ gnutls_free(priv);
+}
+
+static int
+signature_algorithms_pack(gnutls_ext_priv_data_t epriv,
+ gnutls_buffer_st * ps)
+{
+ sig_ext_st *priv = epriv;
+ int ret, i;
+
+ BUFFER_APPEND_NUM(ps, priv->sign_algorithms_size);
+ for (i = 0; i < priv->sign_algorithms_size; i++) {
+ BUFFER_APPEND_NUM(ps, priv->sign_algorithms[i]);
+ }
+ return 0;
+}
+
+static int
+signature_algorithms_unpack(gnutls_buffer_st * ps,
+ gnutls_ext_priv_data_t * _priv)
+{
+ sig_ext_st *priv;
+ int i, ret;
+ gnutls_ext_priv_data_t epriv;
+
+ priv = gnutls_calloc(1, sizeof(*priv));
+ if (priv == NULL) {
+ gnutls_assert();
+ return GNUTLS_E_MEMORY_ERROR;
+ }
+
+ BUFFER_POP_NUM(ps, priv->sign_algorithms_size);
+ for (i = 0; i < priv->sign_algorithms_size; i++) {
+ BUFFER_POP_NUM(ps, priv->sign_algorithms[i]);
+ }
+
+ epriv = priv;
+ *_priv = epriv;
+
+ return 0;
+
+ error:
+ gnutls_free(priv);
+ return ret;
+}
+
+
+
+/**
+ * gnutls_sign_algorithm_get_requested:
+ * @session: is a #gnutls_session_t type.
+ * @indx: is an index of the signature algorithm to return
+ * @algo: the returned certificate type will be stored there
+ *
+ * Returns the signature algorithm specified by index that was
+ * requested by the peer. If the specified index has no data available
+ * this function returns %GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE. If
+ * the negotiated TLS version does not support signature algorithms
+ * then %GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE will be returned even
+ * for the first index. The first index is 0.
+ *
+ * This function is useful in the certificate callback functions
+ * to assist in selecting the correct certificate.
+ *
+ * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise
+ * an error code is returned.
+ *
+ * Since: 2.10.0
+ **/
+int
+gnutls_sign_algorithm_get_requested(gnutls_session_t session,
+ size_t indx,
+ gnutls_sign_algorithm_t * algo)
+{
+ const version_entry_st *ver = get_version(session);
+ sig_ext_st *priv;
+ gnutls_ext_priv_data_t epriv;
+ int ret;
+
+ if (unlikely(ver == NULL))
+ return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
+
+ ret =
+ _gnutls_hello_ext_get_priv(session,
+ GNUTLS_EXTENSION_SIGNATURE_ALGORITHMS,
+ &epriv);
+ if (ret < 0) {
+ gnutls_assert();
+ return ret;
+ }
+ priv = epriv;
+
+ if (!_gnutls_version_has_selectable_sighash(ver)
+ || priv->sign_algorithms_size == 0) {
+ return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
+ }
+
+ if (indx < priv->sign_algorithms_size) {
+ *algo = priv->sign_algorithms[indx];
+ return 0;
+ } else
+ return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
+}
+
+/**
+ * gnutls_sign_algorithm_get:
+ * @session: is a #gnutls_session_t type.
+ *
+ * Returns the signature algorithm that is (or will be) used in this
+ * session by the server to sign data. This function should be
+ * used only with TLS 1.2 or later.
+ *
+ * Returns: The sign algorithm or %GNUTLS_SIGN_UNKNOWN.
+ *
+ * Since: 3.1.1
+ **/
+int gnutls_sign_algorithm_get(gnutls_session_t session)
+{
+ return session->security_parameters.server_sign_algo;
+}
+
+/**
+ * gnutls_sign_algorithm_get_client:
+ * @session: is a #gnutls_session_t type.
+ *
+ * Returns the signature algorithm that is (or will be) used in this
+ * session by the client to sign data. This function should be
+ * used only with TLS 1.2 or later.
+ *
+ * Returns: The sign algorithm or %GNUTLS_SIGN_UNKNOWN.
+ *
+ * Since: 3.1.11
+ **/
+int gnutls_sign_algorithm_get_client(gnutls_session_t session)
+{
+ return session->security_parameters.client_sign_algo;
+}