/*
* Copyright (C) 2011-2012 Free Software Foundation, Inc.
* Copyright (C) 2016 Dmitry Eremin-Solenikov
*
* 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/>
*
*/
#include "gnutls_int.h"
#include "auth.h"
#include "errors.h"
#include "vko.h"
#include <state.h>
#include <datum.h>
#include <ext/signature.h>
#include <ext/supported_groups.h>
#include <auth/cert.h>
#include <pk.h>
#include <abstract_int.h>
#if defined(ENABLE_GOST)
static int gen_vko_gost_client_kx(gnutls_session_t, gnutls_buffer_st *);
static int proc_vko_gost_client_kx(gnutls_session_t session,
uint8_t * data, size_t _data_size);
/* VKO GOST Key Exchange:
* see draft-smyshlyaev-tls12-gost-suites-06, Section 4.2.4
*
* Client generates ephemeral key pair, uses server's public key (from
* certificate), ephemeral private key and additional nonce (UKM) to generate
* (VKO) shared point/shared secret. This secret is used to encrypt (key wrap)
* random PMS. Then encrypted PMS and client's ephemeral public key are wrappen
* in ASN.1 structure and sent in KX message.
*
* Server uses decodes ASN.1 structure and uses it's own private key and
* client's ephemeral public key to unwrap PMS.
*
* Note, this KX is not PFS one, despite using ephemeral key pairs on client
* side.
*/
const mod_auth_st vko_gost_auth_struct = {
"VKO_GOST",
_gnutls_gen_cert_server_crt,
_gnutls_gen_cert_client_crt,
NULL,
gen_vko_gost_client_kx,
_gnutls_gen_cert_client_crt_vrfy,
_gnutls_gen_cert_server_cert_req,
_gnutls_proc_crt,
_gnutls_proc_crt,
NULL,
proc_vko_gost_client_kx,
_gnutls_proc_cert_client_crt_vrfy,
_gnutls_proc_cert_cert_req
};
#define VKO_GOST_UKM_LEN 8
static int
calc_ukm(gnutls_session_t session, uint8_t *ukm)
{
gnutls_digest_algorithm_t digalg = GNUTLS_DIG_STREEBOG_256;
gnutls_hash_hd_t dig;
int ret;
ret = gnutls_hash_init(&dig, digalg);
if (ret < 0)
return gnutls_assert_val(ret);
gnutls_hash(dig, session->security_parameters.client_random,
sizeof(session->security_parameters.client_random));
gnutls_hash(dig, session->security_parameters.server_random,
sizeof(session->security_parameters.server_random));
gnutls_hash_deinit(dig, ukm);
return gnutls_hash_get_len(digalg);
}
static int print_priv_key(gnutls_pk_params_st *params)
{
int ret;
uint8_t priv_buf[512/8];
char buf[512 / 4 + 1];
size_t bytes = sizeof(priv_buf);
/* Check if _gnutls_hard_log will print anything */
if (likely(_gnutls_log_level < 9))
return GNUTLS_E_SUCCESS;
ret = _gnutls_mpi_print(params->params[GOST_K],
priv_buf, &bytes);
if (ret < 0)
return gnutls_assert_val(ret);
_gnutls_hard_log("INT: VKO PRIVATE KEY[%zd]: %s\n",
bytes, _gnutls_bin2hex(priv_buf,
bytes,
buf, sizeof(buf),
NULL));
return 0;
}
static int
vko_prepare_client_keys(gnutls_session_t session,
gnutls_pk_params_st *pub,
gnutls_pk_params_st *priv)
{
int ret;
gnutls_ecc_curve_t curve;
const gnutls_group_entry_st *group;
cert_auth_info_t info;
gnutls_pcert_st peer_cert;
info = _gnutls_get_auth_info(session, GNUTLS_CRD_CERTIFICATE);
if (info == NULL || info->ncerts == 0)
return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
ret = _gnutls_get_auth_info_pcert(&peer_cert,
session->security_parameters.
server_ctype, info);
if (ret < 0)
return gnutls_assert_val(ret);
/* Copy public key contents and free the rest */
memcpy(pub, &peer_cert.pubkey->params, sizeof(gnutls_pk_params_st));
gnutls_free(peer_cert.pubkey);
peer_cert.pubkey = NULL;
gnutls_pcert_deinit(&peer_cert);
curve = pub->curve;
group = _gnutls_id_to_group(_gnutls_ecc_curve_get_group(curve));
if (group == NULL) {
_gnutls_debug_log("received unknown curve %d\n", curve);
return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER);
} else {
_gnutls_debug_log("received curve %s\n", group->name);
}
ret = _gnutls_session_supports_group(session, group->id);
if (ret < 0)
return gnutls_assert_val(ret);
if (pub->algo == GNUTLS_PK_GOST_12_512) {
gnutls_sign_algorithm_set_server(session, GNUTLS_SIGN_GOST_512);
} else {
gnutls_sign_algorithm_set_server(session, GNUTLS_SIGN_GOST_256);
}
_gnutls_session_group_set(session, group);
ret = _gnutls_pk_generate_keys(pub->algo,
curve,
priv, 1);
if (ret < 0)
return gnutls_assert_val(ret);
priv->gost_params = pub->gost_params;
print_priv_key(priv);
session->key.key.size = 32; /* GOST key size */
session->key.key.data = gnutls_malloc(session->key.key.size);
if (session->key.key.data == NULL) {
gnutls_assert();
session->key.key.size = 0;
return GNUTLS_E_MEMORY_ERROR;
}
/* Generate random */
ret = gnutls_rnd(GNUTLS_RND_RANDOM, session->key.key.data,
session->key.key.size);
if (ret < 0) {
gnutls_assert();
gnutls_free(session->key.key.data);
session->key.key.size = 0;
return ret;
}
return 0;
}
/* KX message is:
TLSGostKeyTransportBlob ::= SEQUENCE {
keyBlob GostR3410-KeyTransport,
proxyKeyBlobs SEQUENCE OF TLSProxyKeyTransportBlob OPTIONAL
}
draft-smyshlyaev-tls12-gost-suites does not define proxyKeyBlobs, but old
CSPs still send additional information after keyBlob.
We only need keyBlob and we completely ignore the rest of the structure.
_gnutls_gost_keytrans_decrypt will decrypt GostR3410-KeyTransport
*/
static int
proc_vko_gost_client_kx(gnutls_session_t session,
uint8_t * data, size_t _data_size)
{
int ret, i = 0;
ssize_t data_size = _data_size;
gnutls_privkey_t privkey = session->internals.selected_key;
uint8_t ukm_data[MAX_HASH_SIZE];
gnutls_datum_t ukm = {ukm_data, VKO_GOST_UKM_LEN};
gnutls_datum_t cek;
int len;
if (!privkey || privkey->type != GNUTLS_PRIVKEY_X509)
return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
/* Skip TLSGostKeyTransportBlob tag and length */
DECR_LEN(data_size, 1);
if (data[0] != (ASN1_TAG_SEQUENCE | ASN1_CLASS_STRUCTURED))
return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER);
i += 1;
ret = asn1_get_length_der(&data[i], data_size, &len);
if (ret < 0)
return gnutls_assert_val(GNUTLS_E_ASN1_DER_ERROR);
DECR_LEN(data_size, len);
i += len;
/* Check that nothing is left after TLSGostKeyTransportBlob */
DECR_LEN_FINAL(data_size, ret);
/* Point data to GostR3410-KeyTransport */
data_size = ret;
data += i;
/* Now do the tricky part: determine length of GostR3410-KeyTransport */
DECR_LEN(data_size, 1); /* tag */
ret = asn1_get_length_der(&data[1], data_size, &len);
DECR_LEN_FINAL(data_size, len + ret);
cek.data = data;
cek.size = ret + len + 1;
ret = calc_ukm(session, ukm_data);
if (ret < 0)
return gnutls_assert_val(ret);
ret = _gnutls_gost_keytrans_decrypt(&privkey->key.x509->params,
&cek, &ukm,
&session->key.key);
if (ret < 0)
return gnutls_assert_val(ret);
return 0;
}
static int
gen_vko_gost_client_kx(gnutls_session_t session,
gnutls_buffer_st * data)
{
int ret;
gnutls_datum_t out = {};
uint8_t ukm_data[MAX_HASH_SIZE];
gnutls_datum_t ukm = {ukm_data, VKO_GOST_UKM_LEN};
gnutls_pk_params_st pub;
gnutls_pk_params_st priv;
uint8_t tl[1 + ASN1_MAX_LENGTH_SIZE];
int len;
ret = calc_ukm(session, ukm_data);
if (ret < 0)
return gnutls_assert_val(ret);
gnutls_pk_params_init(&pub);
gnutls_pk_params_init(&priv);
ret = vko_prepare_client_keys(session, &pub, &priv);
if (ret < 0)
return gnutls_assert_val(ret);
ret = _gnutls_gost_keytrans_encrypt(&pub,
&priv,
&session->key.key,
&ukm, &out);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
tl[0] = ASN1_TAG_SEQUENCE | ASN1_CLASS_STRUCTURED;
asn1_length_der(out.size, tl + 1, &len);
ret = gnutls_buffer_append_data(data, tl, len + 1);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
ret = gnutls_buffer_append_data(data, out.data, out.size);
if (ret < 0) {
gnutls_assert();
goto cleanup;
}
ret = data->length;
cleanup:
/* no longer needed */
gnutls_pk_params_release(&priv);
gnutls_pk_params_release(&pub);
_gnutls_free_datum(&out);
return ret;
}
#endif