libgcrypt: Implement chacha20-poly1305@openssh.com cipher using libgcrypt
Libgcrypt has supported ChaCha20 and Poly1305 since 1.7.0 version and provides fast assembler implementations. v3: - initialize pointers to NULL - use 'bool' for chacha20_poly1305_keysched.initialized - pass error codes from libgcrypt calls to variable - add SSH_LOG on error paths v2: - use braces for one-line blocks - use UNUSED_PARAM/UNUSED_VAR instead of cast to void - use calloc instead of malloc+memset Signed-off-by: Jussi Kivilinna <jussi.kivilinna@iki.fi> Reviewed-by: Jakub Jelen <jjelen@redhat.com> Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
Этот коммит содержится в:
родитель
af5de2d37e
Коммит
de4b8f88a2
@ -278,6 +278,9 @@ if (GCRYPT_FOUND)
|
|||||||
set(HAVE_GCRYPT_ECC 1)
|
set(HAVE_GCRYPT_ECC 1)
|
||||||
set(HAVE_ECC 1)
|
set(HAVE_ECC 1)
|
||||||
endif (GCRYPT_VERSION VERSION_GREATER "1.4.6")
|
endif (GCRYPT_VERSION VERSION_GREATER "1.4.6")
|
||||||
|
if (NOT GCRYPT_VERSION VERSION_LESS "1.7.0")
|
||||||
|
set(HAVE_GCRYPT_CHACHA_POLY 1)
|
||||||
|
endif (NOT GCRYPT_VERSION VERSION_LESS "1.7.0")
|
||||||
endif (GCRYPT_FOUND)
|
endif (GCRYPT_FOUND)
|
||||||
|
|
||||||
if (MBEDTLS_FOUND)
|
if (MBEDTLS_FOUND)
|
||||||
|
@ -103,6 +103,9 @@
|
|||||||
/* Define to 1 if you have OpenSSL with X25519 support */
|
/* Define to 1 if you have OpenSSL with X25519 support */
|
||||||
#cmakedefine HAVE_OPENSSL_X25519 1
|
#cmakedefine HAVE_OPENSSL_X25519 1
|
||||||
|
|
||||||
|
/* Define to 1 if you have gcrypt with ChaCha20/Poly1305 support */
|
||||||
|
#cmakedefine HAVE_GCRYPT_CHACHA_POLY 1
|
||||||
|
|
||||||
/*************************** FUNCTIONS ***************************/
|
/*************************** FUNCTIONS ***************************/
|
||||||
|
|
||||||
/* Define to 1 if you have the `EVP_aes128_ctr' function. */
|
/* Define to 1 if you have the `EVP_aes128_ctr' function. */
|
||||||
|
343
src/libgcrypt.c
343
src/libgcrypt.c
@ -25,6 +25,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "libssh/priv.h"
|
#include "libssh/priv.h"
|
||||||
#include "libssh/session.h"
|
#include "libssh/session.h"
|
||||||
@ -36,6 +37,34 @@
|
|||||||
#ifdef HAVE_LIBGCRYPT
|
#ifdef HAVE_LIBGCRYPT
|
||||||
#include <gcrypt.h>
|
#include <gcrypt.h>
|
||||||
|
|
||||||
|
#ifdef HAVE_GCRYPT_CHACHA_POLY
|
||||||
|
|
||||||
|
#define CHACHA20_BLOCKSIZE 64
|
||||||
|
#define CHACHA20_KEYLEN 32
|
||||||
|
|
||||||
|
#define POLY1305_TAGLEN 16
|
||||||
|
#define POLY1305_KEYLEN 32
|
||||||
|
|
||||||
|
struct chacha20_poly1305_keysched {
|
||||||
|
bool initialized;
|
||||||
|
/* cipher handle used for encrypting the packets */
|
||||||
|
gcry_cipher_hd_t main_hd;
|
||||||
|
/* cipher handle used for encrypting the length field */
|
||||||
|
gcry_cipher_hd_t header_hd;
|
||||||
|
/* mac handle used for authenticating the packets */
|
||||||
|
gcry_mac_hd_t mac_hd;
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct ssh_packet_header {
|
||||||
|
uint32_t length;
|
||||||
|
uint8_t payload[];
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
static const uint8_t zero_block[CHACHA20_BLOCKSIZE] = {0};
|
||||||
|
#endif /* HAVE_GCRYPT_CHACHA_POLY */
|
||||||
|
|
||||||
static int libgcrypt_initialized = 0;
|
static int libgcrypt_initialized = 0;
|
||||||
|
|
||||||
static int alloc_key(struct ssh_cipher_struct *cipher) {
|
static int alloc_key(struct ssh_cipher_struct *cipher) {
|
||||||
@ -558,6 +587,300 @@ static void des3_decrypt(struct ssh_cipher_struct *cipher, void *in,
|
|||||||
gcry_cipher_decrypt(cipher->key[0], out, len, in, len);
|
gcry_cipher_decrypt(cipher->key[0], out, len, in, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_GCRYPT_CHACHA_POLY
|
||||||
|
static void chacha20_cleanup(struct ssh_cipher_struct *cipher)
|
||||||
|
{
|
||||||
|
struct chacha20_poly1305_keysched *ctx = NULL;
|
||||||
|
|
||||||
|
if (cipher->chacha20_schedule == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = cipher->chacha20_schedule;
|
||||||
|
|
||||||
|
if (ctx->initialized) {
|
||||||
|
gcry_cipher_close(ctx->main_hd);
|
||||||
|
gcry_cipher_close(ctx->header_hd);
|
||||||
|
gcry_mac_close(ctx->mac_hd);
|
||||||
|
ctx->initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SAFE_FREE(cipher->chacha20_schedule);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher,
|
||||||
|
void *key,
|
||||||
|
UNUSED_PARAM(void *IV))
|
||||||
|
{
|
||||||
|
struct chacha20_poly1305_keysched *ctx = NULL;
|
||||||
|
uint8_t *u8key = key;
|
||||||
|
gpg_error_t err;
|
||||||
|
|
||||||
|
if (cipher->chacha20_schedule == NULL) {
|
||||||
|
ctx = calloc(1, sizeof(*ctx));
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
cipher->chacha20_schedule = ctx;
|
||||||
|
} else {
|
||||||
|
ctx = cipher->chacha20_schedule;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx->initialized) {
|
||||||
|
/* Open cipher/mac handles. */
|
||||||
|
err = gcry_cipher_open(&ctx->main_hd, GCRY_CIPHER_CHACHA20,
|
||||||
|
GCRY_CIPHER_MODE_STREAM, 0);
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_open failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
SAFE_FREE(cipher->chacha20_schedule);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
err = gcry_cipher_open(&ctx->header_hd, GCRY_CIPHER_CHACHA20,
|
||||||
|
GCRY_CIPHER_MODE_STREAM, 0);
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_open failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
gcry_cipher_close(ctx->main_hd);
|
||||||
|
SAFE_FREE(cipher->chacha20_schedule);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
err = gcry_mac_open(&ctx->mac_hd, GCRY_MAC_POLY1305, 0, NULL);
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_mac_open failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
gcry_cipher_close(ctx->main_hd);
|
||||||
|
gcry_cipher_close(ctx->header_hd);
|
||||||
|
SAFE_FREE(cipher->chacha20_schedule);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gcry_cipher_setkey(ctx->main_hd, u8key, CHACHA20_KEYLEN);
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setkey failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
chacha20_cleanup(cipher);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gcry_cipher_setkey(ctx->header_hd, u8key + CHACHA20_KEYLEN,
|
||||||
|
CHACHA20_KEYLEN);
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setkey failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
chacha20_cleanup(cipher);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher,
|
||||||
|
void *in,
|
||||||
|
void *out,
|
||||||
|
size_t len,
|
||||||
|
uint8_t *tag,
|
||||||
|
uint64_t seq)
|
||||||
|
{
|
||||||
|
struct ssh_packet_header *in_packet = in, *out_packet = out;
|
||||||
|
struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule;
|
||||||
|
uint8_t poly_key[CHACHA20_BLOCKSIZE];
|
||||||
|
size_t taglen = POLY1305_TAGLEN;
|
||||||
|
gpg_error_t err;
|
||||||
|
|
||||||
|
seq = htonll(seq);
|
||||||
|
|
||||||
|
/* step 1, prepare the poly1305 key */
|
||||||
|
err = gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq));
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
/* Output full ChaCha block so that counter increases by one for
|
||||||
|
* payload encryption step. */
|
||||||
|
err = gcry_cipher_encrypt(ctx->main_hd,
|
||||||
|
poly_key,
|
||||||
|
sizeof(poly_key),
|
||||||
|
zero_block,
|
||||||
|
sizeof(zero_block));
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
err = gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN);
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_mac_setkey failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* step 2, encrypt length field */
|
||||||
|
err = gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq));
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
err = gcry_cipher_encrypt(ctx->header_hd,
|
||||||
|
(uint8_t *)&out_packet->length,
|
||||||
|
sizeof(uint32_t),
|
||||||
|
(uint8_t *)&in_packet->length,
|
||||||
|
sizeof(uint32_t));
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* step 3, encrypt packet payload (main_hd counter == 1) */
|
||||||
|
err = gcry_cipher_encrypt(ctx->main_hd,
|
||||||
|
out_packet->payload,
|
||||||
|
len - sizeof(uint32_t),
|
||||||
|
in_packet->payload,
|
||||||
|
len - sizeof(uint32_t));
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* step 4, compute the MAC */
|
||||||
|
err = gcry_mac_write(ctx->mac_hd, (uint8_t *)out_packet, len);
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_mac_write failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
err = gcry_mac_read(ctx->mac_hd, tag, &taglen);
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_mac_read failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
explicit_bzero(poly_key, sizeof(poly_key));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int chacha20_poly1305_aead_decrypt_length(
|
||||||
|
struct ssh_cipher_struct *cipher,
|
||||||
|
void *in,
|
||||||
|
uint8_t *out,
|
||||||
|
size_t len,
|
||||||
|
uint64_t seq)
|
||||||
|
{
|
||||||
|
struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule;
|
||||||
|
gpg_error_t err;
|
||||||
|
|
||||||
|
if (len < sizeof(uint32_t)) {
|
||||||
|
return SSH_ERROR;
|
||||||
|
}
|
||||||
|
seq = htonll(seq);
|
||||||
|
|
||||||
|
err = gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq));
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
return SSH_ERROR;
|
||||||
|
}
|
||||||
|
err = gcry_cipher_decrypt(ctx->header_hd,
|
||||||
|
out,
|
||||||
|
sizeof(uint32_t),
|
||||||
|
in,
|
||||||
|
sizeof(uint32_t));
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_decrypt failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
return SSH_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SSH_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher,
|
||||||
|
void *complete_packet,
|
||||||
|
uint8_t *out,
|
||||||
|
size_t encrypted_size,
|
||||||
|
uint64_t seq)
|
||||||
|
{
|
||||||
|
struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule;
|
||||||
|
uint8_t *mac = (uint8_t *)complete_packet + sizeof(uint32_t) +
|
||||||
|
encrypted_size;
|
||||||
|
uint8_t poly_key[CHACHA20_BLOCKSIZE];
|
||||||
|
int ret = SSH_ERROR;
|
||||||
|
gpg_error_t err;
|
||||||
|
|
||||||
|
seq = htonll(seq);
|
||||||
|
|
||||||
|
/* step 1, prepare the poly1305 key */
|
||||||
|
err = gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq));
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
/* Output full ChaCha block so that counter increases by one for
|
||||||
|
* decryption step. */
|
||||||
|
err = gcry_cipher_encrypt(ctx->main_hd,
|
||||||
|
poly_key,
|
||||||
|
sizeof(poly_key),
|
||||||
|
zero_block,
|
||||||
|
sizeof(zero_block));
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
err = gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN);
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_mac_setkey failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* step 2, check MAC */
|
||||||
|
err = gcry_mac_write(ctx->mac_hd, (uint8_t *)complete_packet,
|
||||||
|
encrypted_size + sizeof(uint32_t));
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_mac_write failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
err = gcry_mac_verify(ctx->mac_hd, mac, POLY1305_TAGLEN);
|
||||||
|
if (gpg_err_code(err) == GPG_ERR_CHECKSUM) {
|
||||||
|
SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error");
|
||||||
|
goto out;
|
||||||
|
} else if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_mac_verify failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* step 3, decrypt packet payload (main_hd counter == 1) */
|
||||||
|
err = gcry_cipher_decrypt(ctx->main_hd,
|
||||||
|
out,
|
||||||
|
encrypted_size,
|
||||||
|
(uint8_t *)complete_packet + sizeof(uint32_t),
|
||||||
|
encrypted_size);
|
||||||
|
if (err != 0) {
|
||||||
|
SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_decrypt failed: %s",
|
||||||
|
gpg_strerror(err));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = SSH_OK;
|
||||||
|
|
||||||
|
out:
|
||||||
|
explicit_bzero(poly_key, sizeof(poly_key));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif /* HAVE_GCRYPT_CHACHA_POLY */
|
||||||
|
|
||||||
/* the table of supported ciphers */
|
/* the table of supported ciphers */
|
||||||
static struct ssh_cipher_struct ssh_ciphertab[] = {
|
static struct ssh_cipher_struct ssh_ciphertab[] = {
|
||||||
#ifdef WITH_BLOWFISH_CIPHER
|
#ifdef WITH_BLOWFISH_CIPHER
|
||||||
@ -679,7 +1002,23 @@ static struct ssh_cipher_struct ssh_ciphertab[] = {
|
|||||||
.decrypt = des3_decrypt
|
.decrypt = des3_decrypt
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
#ifdef HAVE_GCRYPT_CHACHA_POLY
|
||||||
|
.ciphertype = SSH_AEAD_CHACHA20_POLY1305,
|
||||||
|
.name = "chacha20-poly1305@openssh.com",
|
||||||
|
.blocksize = 8,
|
||||||
|
.lenfield_blocksize = 4,
|
||||||
|
.keylen = sizeof(struct chacha20_poly1305_keysched),
|
||||||
|
.keysize = 2 * CHACHA20_KEYLEN * 8,
|
||||||
|
.tag_size = POLY1305_TAGLEN,
|
||||||
|
.set_encrypt_key = chacha20_set_encrypt_key,
|
||||||
|
.set_decrypt_key = chacha20_set_encrypt_key,
|
||||||
|
.aead_encrypt = chacha20_poly1305_aead_encrypt,
|
||||||
|
.aead_decrypt_length = chacha20_poly1305_aead_decrypt_length,
|
||||||
|
.aead_decrypt = chacha20_poly1305_aead_decrypt,
|
||||||
|
.cleanup = chacha20_cleanup
|
||||||
|
#else
|
||||||
.name = "chacha20-poly1305@openssh.com"
|
.name = "chacha20-poly1305@openssh.com"
|
||||||
|
#endif
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = NULL,
|
.name = NULL,
|
||||||
@ -757,7 +1096,7 @@ fail:
|
|||||||
*/
|
*/
|
||||||
int ssh_crypto_init(void)
|
int ssh_crypto_init(void)
|
||||||
{
|
{
|
||||||
size_t i;
|
UNUSED_VAR(size_t i);
|
||||||
|
|
||||||
if (libgcrypt_initialized) {
|
if (libgcrypt_initialized) {
|
||||||
return SSH_OK;
|
return SSH_OK;
|
||||||
@ -776,6 +1115,7 @@ int ssh_crypto_init(void)
|
|||||||
/* Re-enable warning */
|
/* Re-enable warning */
|
||||||
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
|
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
|
||||||
|
|
||||||
|
#ifndef HAVE_GCRYPT_CHACHA_POLY
|
||||||
for (i = 0; ssh_ciphertab[i].name != NULL; i++) {
|
for (i = 0; ssh_ciphertab[i].name != NULL; i++) {
|
||||||
int cmp;
|
int cmp;
|
||||||
cmp = strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@openssh.com");
|
cmp = strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@openssh.com");
|
||||||
@ -786,6 +1126,7 @@ int ssh_crypto_init(void)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
libgcrypt_initialized = 1;
|
libgcrypt_initialized = 1;
|
||||||
|
|
||||||
|
Загрузка…
x
Ссылка в новой задаче
Block a user