/* * 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 "errors.h" #include "handshake.h" #include "auth/cert.h" #include "ext/signature.h" #include "algorithms.h" #include "tls13-sig.h" #include "mbuffers.h" #include "tls13/certificate_verify.h" #define SRV_CTX "TLS 1.3, server CertificateVerify" static const gnutls_datum_t srv_ctx = { (void *)SRV_CTX, sizeof(SRV_CTX) - 1 }; #define CLI_CTX "TLS 1.3, client CertificateVerify" static const gnutls_datum_t cli_ctx = { (void *)CLI_CTX, sizeof(CLI_CTX) - 1 }; int _gnutls13_recv_certificate_verify(gnutls_session_t session) { int ret; gnutls_buffer_st buf; const gnutls_sign_entry_st *se; gnutls_datum_t sig_data; gnutls_certificate_credentials_t cred; unsigned vflags; gnutls_pcert_st peer_cert; cert_auth_info_t info = _gnutls_get_auth_info(session, GNUTLS_CRD_CERTIFICATE); bool server = 0; gnutls_certificate_type_t cert_type; memset(&peer_cert, 0, sizeof(peer_cert)); /* this message is only expected if we have received * a certificate message */ if (!(session->internals.hsk_flags & HSK_CRT_VRFY_EXPECTED)) return 0; if (session->security_parameters.entity == GNUTLS_SERVER) server = 1; cred = (gnutls_certificate_credentials_t)_gnutls_get_cred( session, GNUTLS_CRD_CERTIFICATE); if (unlikely(cred == NULL)) return gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS); if (unlikely(info == NULL)) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); ret = _gnutls_recv_handshake( session, GNUTLS_HANDSHAKE_CERTIFICATE_VERIFY, 0, &buf); if (ret < 0) return gnutls_assert_val(ret); _gnutls_handshake_log("HSK[%p]: Parsing certificate verify\n", session); if (buf.length < 2) { gnutls_assert(); ret = GNUTLS_E_UNEXPECTED_PACKET_LENGTH; goto cleanup; } se = _gnutls_tls_aid_to_sign_entry(buf.data[0], buf.data[1], get_version(session)); if (se == NULL) { _gnutls_handshake_log("Found unsupported signature (%d.%d)\n", (int)buf.data[0], (int)buf.data[1]); ret = gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); goto cleanup; } if (server) gnutls_sign_algorithm_set_client(session, se->id); else gnutls_sign_algorithm_set_server(session, se->id); buf.data += 2; buf.length -= 2; /* we check during verification whether the algorithm is enabled */ ret = _gnutls_buffer_pop_datum_prefix16(&buf, &sig_data); if (ret < 0) { gnutls_assert(); goto cleanup; } if (sig_data.size == 0) { gnutls_assert(); ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; goto cleanup; } /* We verify the certificate of the peer. Therefore we need to * retrieve the negotiated certificate type for the peer. */ cert_type = get_certificate_type(session, GNUTLS_CTYPE_PEERS); /* Verify the signature */ ret = _gnutls_get_auth_info_pcert(&peer_cert, cert_type, info); if (ret < 0) { gnutls_assert(); goto cleanup; } vflags = cred->verify_flags | session->internals.additional_verify_flags; ret = _gnutls13_handshake_verify_data(session, vflags, &peer_cert, server ? (&cli_ctx) : (&srv_ctx), &sig_data, se); if (ret < 0) { gnutls_assert(); goto cleanup; } if (buf.length > 0) { gnutls_assert(); ret = GNUTLS_E_UNEXPECTED_PACKET_LENGTH; goto cleanup; } ret = 0; cleanup: gnutls_pcert_deinit(&peer_cert); _gnutls_buffer_clear(&buf); return ret; } int _gnutls13_send_certificate_verify(gnutls_session_t session, unsigned again) { int ret; gnutls_pcert_st *apr_cert_list; gnutls_privkey_t apr_pkey; int apr_cert_list_length; mbuffer_st *bufel = NULL; gnutls_buffer_st buf; gnutls_datum_t sig = { NULL, 0 }; gnutls_sign_algorithm_t algo; const gnutls_sign_entry_st *se; bool server = 0; 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; if (session->security_parameters.entity == GNUTLS_SERVER) server = 1; ret = _gnutls_get_selected_cert(session, &apr_cert_list, &apr_cert_list_length, &apr_pkey); if (ret < 0) return gnutls_assert_val(ret); if (apr_cert_list_length == 0) { if (server) { return gnutls_assert_val( GNUTLS_E_INSUFFICIENT_CREDENTIALS); } else { /* for client, this means either we * didn't get a cert request or we are * declining authentication; in either * case we don't send a cert verify */ return 0; } } if (server) { algo = _gnutls_session_get_sign_algo(session, &apr_cert_list[0], apr_pkey, 0, GNUTLS_KX_UNKNOWN); if (algo == GNUTLS_SIGN_UNKNOWN) return gnutls_assert_val( GNUTLS_E_INCOMPATIBLE_SIG_WITH_KEY); gnutls_sign_algorithm_set_server(session, algo); } else { /* for client, signature algorithm is already * determined from Certificate Request */ algo = gnutls_sign_algorithm_get_client(session); if (unlikely(algo == GNUTLS_SIGN_UNKNOWN)) return gnutls_assert_val( GNUTLS_E_INTERNAL_ERROR); } se = _gnutls_sign_to_entry(algo); ret = _gnutls13_handshake_sign_data( session, &apr_cert_list[0], apr_pkey, server ? (&srv_ctx) : (&cli_ctx), &sig, se); if (ret < 0) return gnutls_assert_val(ret); ret = _gnutls_buffer_init_handshake_mbuffer(&buf); if (ret < 0) { gnutls_assert(); goto cleanup; } ret = _gnutls_buffer_append_data(&buf, se->aid.id, 2); if (ret < 0) { gnutls_assert(); goto cleanup; } ret = _gnutls_buffer_append_data_prefix(&buf, 16, sig.data, sig.size); if (ret < 0) { gnutls_assert(); goto cleanup; } bufel = _gnutls_buffer_to_mbuffer(&buf); gnutls_free(sig.data); } return _gnutls_send_handshake(session, bufel, GNUTLS_HANDSHAKE_CERTIFICATE_VERIFY); cleanup: gnutls_free(sig.data); _gnutls_buffer_clear(&buf); return ret; }