mirror of
https://github.com/irungentoo/toxcore.git
synced 2024-03-22 13:30:51 +08:00
Forward secrecy implemented into crypto.
This commit is contained in:
parent
928fc1e65a
commit
7458473dca
|
@ -18,9 +18,12 @@ uint8_t self_secret_key[crypto_box_SECRETKEYBYTES];
|
|||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t public_key[crypto_box_PUBLICKEYBYTES];
|
||||
uint8_t public_key[crypto_box_PUBLICKEYBYTES];//the real public key of the peer.
|
||||
uint8_t recv_nonce[crypto_box_NONCEBYTES];//nonce of recieved packets
|
||||
uint8_t sent_nonce[crypto_box_NONCEBYTES];//nonce of sent packets.
|
||||
uint8_t sessionpublic_key[crypto_box_PUBLICKEYBYTES];//our public key for this session.
|
||||
uint8_t sessionsecret_key[crypto_box_SECRETKEYBYTES];//our private key for this session.
|
||||
uint8_t peersessionpublic_key[crypto_box_PUBLICKEYBYTES];//The public key of the peer.
|
||||
uint8_t status;//0 if no connection, 1 we have sent a handshake, 2 if connexion is not confirmed yet
|
||||
//(we have recieved a hanshake but no empty data packet), 3 if the connection is established.
|
||||
//4 if the connection is timed out.
|
||||
|
@ -43,10 +46,11 @@ int outbound_friendrequests[MAX_FRIEND_REQUESTS];
|
|||
int incoming_connections[MAX_INCOMING];
|
||||
|
||||
//encrypts plain of length length to encrypted of length + 16 using the
|
||||
//public key(32 bytes) of the reciever and a 24 byte nonce
|
||||
//public key(32 bytes) of the reciever and the secret key of the sender and a 24 byte nonce
|
||||
//return -1 if there was a problem.
|
||||
//return length of encrypted data if everything was fine.
|
||||
int encrypt_data(uint8_t * public_key, uint8_t * nonce, uint8_t * plain, uint32_t length, uint8_t * encrypted)
|
||||
int encrypt_data(uint8_t * public_key, uint8_t * secret_key, uint8_t * nonce,
|
||||
uint8_t * plain, uint32_t length, uint8_t * encrypted)
|
||||
{
|
||||
if(length - crypto_box_BOXZEROBYTES + crypto_box_ZEROBYTES > MAX_DATA_SIZE || length == 0)
|
||||
{
|
||||
|
@ -59,7 +63,7 @@ int encrypt_data(uint8_t * public_key, uint8_t * nonce, uint8_t * plain, uint32_
|
|||
|
||||
memcpy(temp_plain + crypto_box_ZEROBYTES, plain, length);//pad the message with 32 0 bytes.
|
||||
|
||||
crypto_box(temp_encrypted, temp_plain, length + crypto_box_ZEROBYTES, nonce, public_key, self_secret_key);
|
||||
crypto_box(temp_encrypted, temp_plain, length + crypto_box_ZEROBYTES, nonce, public_key, secret_key);
|
||||
|
||||
//if encryption is successful the first crypto_box_BOXZEROBYTES of the message will be zero
|
||||
if(memcmp(temp_encrypted, zeroes, crypto_box_BOXZEROBYTES) != 0)
|
||||
|
@ -72,10 +76,11 @@ int encrypt_data(uint8_t * public_key, uint8_t * nonce, uint8_t * plain, uint32_
|
|||
}
|
||||
|
||||
//decrypts encrypted of length length to plain of length length - 16 using the
|
||||
//public key(32 bytes) of the sender and a 24 byte nonce
|
||||
//public key(32 bytes) of the sender, the secret key of the reciever and a 24 byte nonce
|
||||
//return -1 if there was a problem(decryption failed)
|
||||
//return length of plain data if everything was fine.
|
||||
int decrypt_data(uint8_t * public_key, uint8_t * nonce, uint8_t * encrypted, uint32_t length, uint8_t * plain)
|
||||
int decrypt_data(uint8_t * public_key, uint8_t * secret_key, uint8_t * nonce,
|
||||
uint8_t * encrypted, uint32_t length, uint8_t * plain)
|
||||
{
|
||||
if(length > MAX_DATA_SIZE || length <= crypto_box_BOXZEROBYTES)
|
||||
{
|
||||
|
@ -88,7 +93,7 @@ int decrypt_data(uint8_t * public_key, uint8_t * nonce, uint8_t * encrypted, uin
|
|||
memcpy(temp_encrypted + crypto_box_BOXZEROBYTES, encrypted, length);//pad the message with 16 0 bytes.
|
||||
|
||||
if(crypto_box_open(temp_plain, temp_encrypted, length + crypto_box_BOXZEROBYTES,
|
||||
nonce, public_key, self_secret_key) == -1)
|
||||
nonce, public_key, secret_key) == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
@ -146,7 +151,8 @@ int read_cryptpacket(int crypt_connection_id, uint8_t * data)
|
|||
{
|
||||
return -1;
|
||||
}
|
||||
int len = decrypt_data(crypto_connections[crypt_connection_id].public_key,
|
||||
int len = decrypt_data(crypto_connections[crypt_connection_id].peersessionpublic_key,
|
||||
crypto_connections[crypt_connection_id].sessionsecret_key,
|
||||
crypto_connections[crypt_connection_id].recv_nonce, temp_data + 1, length - 1, data);
|
||||
if(len != -1)
|
||||
{
|
||||
|
@ -170,7 +176,8 @@ int write_cryptpacket(int crypt_connection_id, uint8_t * data, uint32_t length)
|
|||
return 0;
|
||||
}
|
||||
uint8_t temp_data[MAX_DATA_SIZE];
|
||||
int len = encrypt_data(crypto_connections[crypt_connection_id].public_key,
|
||||
int len = encrypt_data(crypto_connections[crypt_connection_id].peersessionpublic_key,
|
||||
crypto_connections[crypt_connection_id].sessionsecret_key,
|
||||
crypto_connections[crypt_connection_id].sent_nonce, data, length, temp_data + 1);
|
||||
if(len == -1)
|
||||
{
|
||||
|
@ -210,7 +217,8 @@ int send_friendrequest(uint8_t * public_key, IP_Port ip_port, uint8_t * data, ui
|
|||
uint8_t temp_data[MAX_DATA_SIZE];
|
||||
uint8_t nonce[crypto_box_NONCEBYTES];
|
||||
random_nonce(nonce);
|
||||
int len = encrypt_data(public_key, nonce, data, length, 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + temp_data);
|
||||
int len = encrypt_data(public_key, self_secret_key, nonce, data, length,
|
||||
1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + temp_data);
|
||||
if(len == -1)
|
||||
{
|
||||
return -1;
|
||||
|
@ -266,14 +274,20 @@ int check_friendrequest(int friend_request)
|
|||
return 0;
|
||||
}
|
||||
|
||||
//Send a crypto handshake packet containing an encrypted secret nonce to peer with connection_id and public_key
|
||||
//Send a crypto handshake packet containing an encrypted secret nonce and session public key
|
||||
//to peer with connection_id and public_key
|
||||
//the packet is encrypted with a random nonce which is sent in plain text with the packet
|
||||
int send_cryptohandshake(int connection_id, uint8_t * public_key, uint8_t * secret_nonce)
|
||||
int send_cryptohandshake(int connection_id, uint8_t * public_key, uint8_t * secret_nonce, uint8_t * session_key)
|
||||
{
|
||||
uint8_t temp_data[MAX_DATA_SIZE];
|
||||
uint8_t temp[crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES];
|
||||
uint8_t nonce[crypto_box_NONCEBYTES];
|
||||
|
||||
random_nonce(nonce);
|
||||
int len = encrypt_data(public_key, nonce, secret_nonce, crypto_box_NONCEBYTES,
|
||||
memcpy(temp, secret_nonce, crypto_box_NONCEBYTES);
|
||||
memcpy(temp + crypto_box_NONCEBYTES, session_key, crypto_box_PUBLICKEYBYTES);
|
||||
|
||||
int len = encrypt_data(public_key, self_secret_key, nonce, temp, crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES,
|
||||
1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + temp_data);
|
||||
if(len == -1)
|
||||
{
|
||||
|
@ -285,13 +299,15 @@ int send_cryptohandshake(int connection_id, uint8_t * public_key, uint8_t * secr
|
|||
return write_packet(connection_id, temp_data, len + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES);
|
||||
}
|
||||
|
||||
//Extract secret nonce and public_key from a packet(data) with length length
|
||||
//Extract secret nonce, session public key and public_key from a packet(data) with length length
|
||||
//return 1 if successful
|
||||
//return 0 if failure
|
||||
int handle_cryptohandshake(uint8_t * public_key, uint8_t * secret_nonce, uint8_t * data, uint16_t length)
|
||||
int handle_cryptohandshake(uint8_t * public_key, uint8_t * secret_nonce,
|
||||
uint8_t * session_key, uint8_t * data, uint16_t length)
|
||||
{
|
||||
int pad = (- crypto_box_BOXZEROBYTES + crypto_box_ZEROBYTES);
|
||||
if(length != 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + crypto_box_NONCEBYTES + pad)
|
||||
if(length != 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES
|
||||
+ crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + pad)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
@ -299,13 +315,21 @@ int handle_cryptohandshake(uint8_t * public_key, uint8_t * secret_nonce, uint8_t
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
uint8_t temp[crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES];
|
||||
|
||||
memcpy(public_key, data + 1, crypto_box_PUBLICKEYBYTES);
|
||||
int len = decrypt_data(public_key, data + 1 + crypto_box_PUBLICKEYBYTES,
|
||||
data + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES, crypto_box_NONCEBYTES + pad, secret_nonce);
|
||||
if(len != crypto_box_NONCEBYTES)
|
||||
|
||||
int len = decrypt_data(public_key, self_secret_key, data + 1 + crypto_box_PUBLICKEYBYTES,
|
||||
data + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES,
|
||||
crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + pad, temp);
|
||||
|
||||
if(len != crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(secret_nonce, temp, crypto_box_NONCEBYTES);
|
||||
memcpy(session_key, temp + crypto_box_NONCEBYTES, crypto_box_PUBLICKEYBYTES);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -330,7 +354,7 @@ int handle_friendrequest(uint8_t * public_key, uint8_t * data)
|
|||
memcpy(public_key, temp_data + 1, crypto_box_PUBLICKEYBYTES);
|
||||
uint8_t nonce[crypto_box_NONCEBYTES];
|
||||
memcpy(nonce, temp_data + 1 + crypto_box_PUBLICKEYBYTES, crypto_box_NONCEBYTES);
|
||||
int len1 = decrypt_data(public_key, nonce, temp_data + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES,
|
||||
int len1 = decrypt_data(public_key, self_secret_key, nonce, temp_data + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES,
|
||||
len - (crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + 1), data);
|
||||
if(len1 != -1)
|
||||
{
|
||||
|
@ -371,7 +395,10 @@ int crypto_connect(uint8_t * public_key, IP_Port ip_port)
|
|||
crypto_connections[i].status = 1;
|
||||
random_nonce(crypto_connections[i].recv_nonce);
|
||||
memcpy(crypto_connections[i].public_key, public_key, crypto_box_PUBLICKEYBYTES);
|
||||
if(send_cryptohandshake(id, public_key, crypto_connections[i].recv_nonce) == 1)
|
||||
crypto_box_keypair(crypto_connections[i].sessionpublic_key, crypto_connections[i].sessionsecret_key);
|
||||
|
||||
if(send_cryptohandshake(id, public_key, crypto_connections[i].recv_nonce,
|
||||
crypto_connections[i].sessionpublic_key) == 1)
|
||||
{
|
||||
increment_nonce(crypto_connections[i].recv_nonce);
|
||||
return i;
|
||||
|
@ -385,10 +412,11 @@ int crypto_connect(uint8_t * public_key, IP_Port ip_port)
|
|||
//handle an incoming connection
|
||||
//return -1 if no crypto inbound connection
|
||||
//return incomming connection id (Lossless_UDP one) if there is an incomming crypto connection
|
||||
//Put the public key of the peer in public_key and the secret_nonce from the handshake into secret_nonce
|
||||
//Put the public key of the peer in public_key, the secret_nonce from the handshake into secret_nonce
|
||||
//and the session public key for the connection in session_key
|
||||
//to accept it see: accept_crypto_inbound(...)
|
||||
//to refuse it just call kill_connection(...) on the connection id
|
||||
int crypto_inbound(uint8_t * public_key, uint8_t * secret_nonce)
|
||||
int crypto_inbound(uint8_t * public_key, uint8_t * secret_nonce, uint8_t * session_key)
|
||||
{
|
||||
uint32_t i;
|
||||
for(i = 0; i < MAX_INCOMING; i++)
|
||||
|
@ -399,7 +427,7 @@ int crypto_inbound(uint8_t * public_key, uint8_t * secret_nonce)
|
|||
{
|
||||
uint8_t temp_data[MAX_DATA_SIZE];
|
||||
uint16_t len = read_packet(incoming_connections[i], temp_data);
|
||||
if(handle_cryptohandshake(public_key, secret_nonce, temp_data, len))
|
||||
if(handle_cryptohandshake(public_key, secret_nonce, session_key, temp_data, len))
|
||||
{
|
||||
int connection_id = incoming_connections[i];
|
||||
incoming_connections[i] = -1;//remove this connection from the incoming connection list.
|
||||
|
@ -433,7 +461,7 @@ int crypto_kill(int crypt_connection_id)
|
|||
//accept an incoming connection using the parameters provided by crypto_inbound
|
||||
//return -1 if not successful
|
||||
//returns the crypt_connection_id if successful
|
||||
int accept_crypto_inbound(int connection_id, uint8_t * public_key, uint8_t * secret_nonce)
|
||||
int accept_crypto_inbound(int connection_id, uint8_t * public_key, uint8_t * secret_nonce, uint8_t * session_key)
|
||||
{
|
||||
uint32_t i;
|
||||
if(connection_id == -1)
|
||||
|
@ -448,9 +476,14 @@ int accept_crypto_inbound(int connection_id, uint8_t * public_key, uint8_t * sec
|
|||
crypto_connections[i].status = 2;
|
||||
random_nonce(crypto_connections[i].recv_nonce);
|
||||
memcpy(crypto_connections[i].sent_nonce, secret_nonce, crypto_box_NONCEBYTES);
|
||||
memcpy(crypto_connections[i].peersessionpublic_key, session_key, crypto_box_PUBLICKEYBYTES);
|
||||
increment_nonce(crypto_connections[i].sent_nonce);
|
||||
memcpy(crypto_connections[i].public_key, public_key, crypto_box_PUBLICKEYBYTES);
|
||||
if(send_cryptohandshake(connection_id, public_key, crypto_connections[i].recv_nonce) == 1)
|
||||
|
||||
crypto_box_keypair(crypto_connections[i].sessionpublic_key, crypto_connections[i].sessionsecret_key);
|
||||
|
||||
if(send_cryptohandshake(connection_id, public_key, crypto_connections[i].recv_nonce,
|
||||
crypto_connections[i].sessionpublic_key) == 1)
|
||||
{
|
||||
increment_nonce(crypto_connections[i].recv_nonce);
|
||||
uint32_t zero = 0;
|
||||
|
@ -535,12 +568,14 @@ void recieve_crypto()
|
|||
uint8_t temp_data[MAX_DATA_SIZE];
|
||||
uint8_t secret_nonce[crypto_box_NONCEBYTES];
|
||||
uint8_t public_key[crypto_box_PUBLICKEYBYTES];
|
||||
uint8_t session_key[crypto_box_PUBLICKEYBYTES];
|
||||
uint16_t len = read_packet(crypto_connections[i].number, temp_data);
|
||||
if(handle_cryptohandshake(public_key, secret_nonce, temp_data, len))
|
||||
if(handle_cryptohandshake(public_key, secret_nonce, session_key, temp_data, len))
|
||||
{
|
||||
if(memcmp(public_key, crypto_connections[i].public_key, crypto_box_PUBLICKEYBYTES) == 0)
|
||||
{
|
||||
memcpy(crypto_connections[i].sent_nonce, secret_nonce, crypto_box_NONCEBYTES);
|
||||
memcpy(crypto_connections[i].peersessionpublic_key, session_key, crypto_box_PUBLICKEYBYTES);
|
||||
increment_nonce(crypto_connections[i].sent_nonce);
|
||||
uint32_t zero = 0;
|
||||
crypto_connections[i].status = 3;//connection status needs to be 3 for write_cryptpacket() to work
|
||||
|
@ -564,7 +599,8 @@ void recieve_crypto()
|
|||
uint8_t temp_data[MAX_DATA_SIZE];
|
||||
uint8_t data[MAX_DATA_SIZE];
|
||||
int length = read_packet(crypto_connections[i].number, temp_data);
|
||||
int len = decrypt_data(crypto_connections[i].public_key,
|
||||
int len = decrypt_data(crypto_connections[i].peersessionpublic_key,
|
||||
crypto_connections[i].sessionsecret_key,
|
||||
crypto_connections[i].recv_nonce, temp_data + 1, length - 1, data);
|
||||
uint32_t zero = 0;
|
||||
if(len == sizeof(uint32_t) && memcmp(((uint8_t *)&zero), data, sizeof(uint32_t)) == 0)
|
||||
|
|
|
@ -19,17 +19,19 @@ extern uint8_t self_public_key[crypto_box_PUBLICKEYBYTES];
|
|||
|
||||
|
||||
//encrypts plain of length length to encrypted of length + 16 using the
|
||||
//public key(32 bytes) of the reciever and a 24 byte nonce
|
||||
//public key(32 bytes) of the reciever and the secret key of the sender and a 24 byte nonce
|
||||
//return -1 if there was a problem.
|
||||
//return length of encrypted data if everything was fine.
|
||||
int encrypt_data(uint8_t * public_key, uint8_t * nonce, uint8_t * plain, uint32_t length, uint8_t * encrypted);
|
||||
int encrypt_data(uint8_t * public_key, uint8_t * secret_key, uint8_t * nonce,
|
||||
uint8_t * plain, uint32_t length, uint8_t * encrypted);
|
||||
|
||||
|
||||
//decrypts encrypted of length length to plain of length length - 16 using the
|
||||
//public key(32 bytes) of the sender and a 24 byte nonce
|
||||
//public key(32 bytes) of the sender, the secret key of the reciever and a 24 byte nonce
|
||||
//return -1 if there was a problem(decryption failed)
|
||||
//return length of plain data if everything was fine.
|
||||
int decrypt_data(uint8_t * public_key, uint8_t * nonce, uint8_t * encrypted, uint32_t length, uint8_t * plain);
|
||||
int decrypt_data(uint8_t * public_key, uint8_t * secret_key, uint8_t * nonce,
|
||||
uint8_t * encrypted, uint32_t length, uint8_t * plain);
|
||||
|
||||
|
||||
//return 0 if there is no received data in the buffer
|
||||
|
@ -76,16 +78,17 @@ int crypto_kill(int crypt_connection_id);
|
|||
//handle an incoming connection
|
||||
//return -1 if no crypto inbound connection
|
||||
//return incomming connection id (Lossless_UDP one) if there is an incomming crypto connection
|
||||
//Put the public key of the peer in public_key and the secret_nonce from the handshake into secret_nonce
|
||||
//Put the public key of the peer in public_key, the secret_nonce from the handshake into secret_nonce
|
||||
//and the session public key for the connection in session_key
|
||||
//to accept it see: accept_crypto_inbound(...)
|
||||
//to refuse it just call kill_connection(...) on the connection id
|
||||
int crypto_inbound(uint8_t * public_key, uint8_t * secret_nonce);
|
||||
int crypto_inbound(uint8_t * public_key, uint8_t * secret_nonce, uint8_t * session_key);
|
||||
|
||||
|
||||
//accept an incoming connection using the parameters provided by crypto_inbound
|
||||
//return -1 if not successful
|
||||
//returns the crypt_connection_id if successful
|
||||
int accept_crypto_inbound(int connection_id, uint8_t * public_key, uint8_t * secret_nonce);
|
||||
int accept_crypto_inbound(int connection_id, uint8_t * public_key, uint8_t * secret_nonce, uint8_t * session_key);
|
||||
|
||||
//return 0 if no connection, 1 we have sent a handshake, 2 if connexion is not confirmed yet
|
||||
//(we have recieved a hanshake but no empty data packet), 3 if the connection is established.
|
||||
|
|
|
@ -40,14 +40,18 @@ only crypto_box() is used for encryption and only crypto_box_open() for decrypti
|
|||
|
||||
Connecting to an already added friend:
|
||||
Alice and Bob are friends.
|
||||
As soon as they connect they each send themselves the following packet (the crypto handshake) (encrypted part encrypted with the public nonce in the packet
|
||||
As soon as they connect they each generate a new keypair which will only be used for the current connection (The session keys).
|
||||
They then send themselves the following packet (the crypto handshake) (encrypted part encrypted with the public nonce in the packet
|
||||
the public key of the receiver and private key of the sender)
|
||||
[char with a value of 02][Senders Public key (client_id) (32 bytes)][Random nonce (24 bytes)][Encrypted message containing a random 24 bytes base nonce]
|
||||
[char with a value of 02][Senders Public key (client_id) (32 bytes)][Random nonce (24 bytes)][Encrypted message containing a random 24 bytes base nonce and the session public key of the peer]
|
||||
|
||||
If the packet is decrypted successfully:
|
||||
Each start using the secret nonce provided by the other to encrypt data packets (adding to it + 1 for each packet.)
|
||||
Each start using the secret nonce, the public key provided by the other and their own session private key to encrypt data packets (adding to it + 1 for each packet.)
|
||||
Each node sends themselves an empty data packet (data packet with 4 encrypted zero bytes)
|
||||
Data packet:
|
||||
[char with a value of 03][Encrypted data]
|
||||
Each data packet received it is decrypted using the secret nonce sent to the other(with +1 added for the first packet +2 for the second, etc...)
|
||||
along with the private session key of the reciever.
|
||||
Every data packet sent is encrypted using the secret nonce we received (with +1 added for the first packet +2 for the second, etc...)
|
||||
the session public key of the reciever and the session private key of the sender.
|
||||
The encrypted connection is only deemed successful when a data packet is received and decrypted successfully.
|
||||
|
|
|
@ -167,8 +167,9 @@ int main(int argc, char *argv[])
|
|||
{
|
||||
uint8_t secret_nonce[crypto_box_NONCEBYTES];
|
||||
uint8_t public_key[crypto_box_PUBLICKEYBYTES];
|
||||
inconnection = crypto_inbound(public_key, secret_nonce);
|
||||
inconnection = accept_crypto_inbound(inconnection, acceptedfriend_public_key, secret_nonce);
|
||||
uint8_t session_key[crypto_box_PUBLICKEYBYTES];
|
||||
inconnection = crypto_inbound(public_key, secret_nonce, session_key);
|
||||
inconnection = accept_crypto_inbound(inconnection, acceptedfriend_public_key, secret_nonce, session_key);
|
||||
//inconnection = incoming_connection();
|
||||
if(inconnection != -1)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue
Block a user