diff options
Diffstat (limited to '')
-rw-r--r-- | lib/ext/signature.c | 587 |
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; +} |