Commit 0f837758 authored by hark's avatar hark

use sodium / nacl / avrnacl instead of the other crypto thingies

parent 004b974e
......@@ -2,13 +2,13 @@
CC=colorgcc
CFLAGS="-I ../libs -DDEBUG"
all: test rx-test tx-test keygen
all: test
base-tx: base-tx.c
$(CC) -o base-tx base-tx.c $(LIBS) $(CFLAGS)
test: test.c ../libs/pagerlib/pagerlib.c ../libs/pagerlib/things.c ../libs/pagerlib/pagerlib.h ../libs/pagerlib/packets.h
$(CC) -std=c99 -g -o test test.c ../libs/micro-ecc/uECC.c ../libs/pagerlib/pagerlib.c ../libs/pagerlib/things.c -lmbedtls -lmbedx509 -lmbedcrypto $(LIBS) $(CFLAGS)
$(CC) -std=c99 -g -o test test.c ../libs/micro-ecc/uECC.c ../libs/pagerlib/pagerlib.c ../libs/pagerlib/things.c -lmbedtls -lmbedx509 -lsodium -lmbedcrypto $(LIBS) $(CFLAGS)
rx-test: rx-test.c ../libs/pagerlib/pagerlib.c ../libs/pagerlib/things.c
$(CC) -std=c99 -g -o rx-test rx-test.c ../libs/micro-ecc/uECC.c ../libs/pagerlib/pagerlib.c ../libs/pagerlib/things.c -lmbedtls -lmbedx509 -lmbedcrypto $(LIBS) $(CFLAGS)
......
......@@ -37,10 +37,12 @@ int main() {
/* create keypairs */
receiver = pl_create_keypair(rcv_ctx);
pl_load_key_in_list(rcv_ctx, receiver);
pl_load_key_in_list(sender_ctx, receiver);
rcv_ctx->kp = receiver;
sender = pl_create_keypair(sender_ctx);
pl_load_key_in_list(sender_ctx, sender);
pl_load_key_in_list(rcv_ctx, sender);
sender_ctx->kp = sender;
......@@ -64,16 +66,40 @@ int main() {
int f = 0;
while ( 1 ) {
while ( f < 10 ) {
f++;
// load the message to send
memcpy(sender_ctx->msg->msg, clear_message, MSG_SIZE);
//to who to send the message to (get from kp)
memcpy(&sender_ctx->receiver_compressed_point, &receiver->compressed_point, sizeof(sender_ctx->receiver_compressed_point));
memcpy(&sender_ctx->receiver_public_key, &receiver->public_key, sizeof(sender_ctx->receiver_public_key));
int r;
switch(pl_send_message(sender_ctx)) {
case 0:
printf("SEND: OK! \n");
break;
case -1:
printf("SEND: -1 \n");
break;
case -2:
printf("SEND: -2 \n");
break;
case -3:
printf("SEND: -3 \n");
break;
default:
printf("SEND: unknown error \n");
break;
pl_send_message(sender_ctx);
printf("crypted msg: %s",sender_ctx->msg->msg);
}
// printf("crypted msg: %s",sender_ctx->msg->msg);
/* copy the message to receiver */
struct pl_pagermessage * message = NULL;
......@@ -82,19 +108,35 @@ int main() {
rcv_ctx->msg = message;
message = NULL;
printf("\n encrypted message: \n %s \n END encrypted message \n", rcv_ctx->msg->msg );
/* receive the msg */
if ( pl_receive_message(rcv_ctx) == 1) {
printf("failed to receive this message ! (exit 1) \n");
} else {
printf("recv success! \n");
printf(" Message! \n to: %u from: %u \n the decrypted message: %s \n", rcv_ctx->msg->address, compressed_point_to_addr(rcv_ctx->msg->sender_compressed_point), rcv_ctx->msg->msg);
}
switch(pl_receive_message(sender_ctx)) {
case 0:
printf("RCV: OK! \n");
printf(" Message! \n to: %u from: %u \n the decrypted message: %s \n", rcv_ctx->msg->address, compressed_point_to_addr(rcv_ctx->msg->sender_public_key), rcv_ctx->msg->msg);
break;
case -1:
printf("RCV: -1 (box open error)\n");
break;
case -2:
printf("RCV: -2 \n");
break;
case -3:
printf("RCV: -3 (zerobytes not zero)\n");
break;
case -4:
printf("RCV: -4 (key not found) \n");
break;
default:
printf("RCV: unknown error \n");
break;
}
} // main
}
} // main
/*
DBG("\n sender.keypair");
DBM("sender", sizeof(struct pl_keypair), sender);
......
......@@ -7,53 +7,31 @@ extern "C"
{
#endif
#ifndef ARDUINO
#include <sodium.h>
#else
// include avr-nacl
#endif
/* curve ecc size and shared secret size
ECC_COMPRESSED_SIZE: size of the compressed publiv key, curvesize+1, for example, if the curve is secp256r1 compressed must be 33 bytes long.
curve can be:
uECC_secp160r1();
uECC_secp192r1();
uECC_secp224r1();
uECC_secp256k1();
*/
/*
#define CURVE uECC_secp192r1()
#define ECC_COMPRESSED_SIZE 25
#define SHARED_SECRET_SIZE 24
*/
#define CURVE uECC_secp256r1()
#define ECC_COMPRESSED_SIZE 33
#define SHARED_SECRET_SIZE 32
/* AES stuff
AES_KEYSIZE: keysize of aes key
IV_SIZE: always 16? FIXME
*/
//#define AES_KEYSIZE 192
#define AES_KEYSIZE 256
#define IV_SIZE 16
// the text message
#define MSG_SIZE 80
// the address (hash or part of pubkey?)
#define PLAIN_MSG_SIZE 80
#define CRYPTED_MSG_SIZE PLAIN_MSG_SIZE + crypto_box_ZEROBYTES
#define MSG_SIZE CRYPTED_MSG_SIZE
// the address (hash or part of pubkey?)
#define ADDRESS_SIZE 2
struct pl_keypair
{
// uint8_t public_key[64];
uint8_t private_key[32];
uint8_t compressed_point[ECC_COMPRESSED_SIZE];
char public_key[crypto_box_PUBLICKEYBYTES];
char secret_key[crypto_box_SECRETKEYBYTES];
};
//Will be filled in with the compressed public key. Must be at least
// (curve size + 1) bytes long; for example, if the curve is secp256r1,
// compressed must be 33 bytes long.
struct pl_pagermessage
{
uint32_t address;
uint8_t sender_compressed_point[ECC_COMPRESSED_SIZE];
unsigned char iv[IV_SIZE];
uint8_t address;
char sender_public_key[crypto_box_PUBLICKEYBYTES];
unsigned char nonce[crypto_box_NONCEBYTES];
char msg[MSG_SIZE];
};
......
#include <stdio.h>
#include <string.h>
#include "../micro-ecc/uECC.h"
#include "packets.h"
#include <stdlib.h>
#include "things.h"
#include "pagerlib.h"
#ifndef ARDUINO
//#include "nacl/crypto_box.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#else
//#include "avrnacl.h"
#endif
unsigned char fakeiv[] = "123456789abcdefghijklmnop";
/*
* int rng (uint8_t *dest, unsigned size)
......@@ -55,16 +55,15 @@ int rng (uint8_t *dest, unsigned size) {
}
/*
* uint32_t compressed_point_to_addr( uint8_t input[])
* takes an compressed point and turns it into an address
* should become a proper hash function one day
* FIXME: should become a proper hash function
*/
inline uint32_t compressed_point_to_addr( uint8_t input[])
inline uint8_t compressed_point_to_addr( uint8_t input[])
{
uint32_t hashval;
uint8_t hashval;
int i = 0;
// DBM("hash address input", ECC_COMPRESSED_SIZE, input);
while( hashval < UINT32_MAX && i < ECC_COMPRESSED_SIZE ) {
while( hashval < UINT32_MAX && i < sizeof(input) ) {
//printf("hashval %u i: %u\n ", hashval, i);
hashval = hashval << 8;
hashval += input[ i ];
......@@ -81,17 +80,11 @@ inline uint32_t compressed_point_to_addr( uint8_t input[])
inline struct pl_ctx * pl_init() {
struct pl_ctx * ctx;
ctx = (struct pl_ctx *) malloc(sizeof(struct pl_ctx));
ctx->curve = CURVE;
ctx->msg = (struct pl_pagermessage *) malloc(sizeof(struct pl_pagermessage));
memset(ctx->msg, 7, sizeof(struct pl_pagermessage));
// ctx->kp = (struct pl_keypair *) malloc(sizeof(struct pl_keypair));
// memset(ctx->kp, 9, sizeof(struct pl_keypair));
ctx->kp = NULL;
// ctx->receiver_compressed_point;
// ctx->keypairs = (struct list_kp *) malloc( sizeof(struct list_kp) );
ctx->keypairs = NULL;
ctx->inbox = NULL;
uECC_set_rng(rng);
return ctx;
}
......@@ -107,147 +100,108 @@ inline int pl_set_receiver(struct pl_ctx *ctx, struct pl_keypair *keypair)
*/
inline int pl_send_message(struct pl_ctx *ctx) {
memset(ctx->decompressed_point, 7, sizeof(ctx->decompressed_point));
memset(ctx->shared_secret, 7, sizeof(ctx->shared_secret));
memset(ctx->msg->msg, 23, MSG_SIZE);
#ifndef ARDUINO
/* create a random iv */
rng((char *)&ctx->msg->iv, sizeof(ctx->msg->iv));
#else
memcpy(&ctx->msg->iv, &fakeiv, sizeof(ctx->msg->iv));
#endif
// copy compressed point from keypair into pager message
memcpy(&ctx->msg->sender_compressed_point, &ctx->kp->compressed_point, sizeof(ctx->msg->sender_compressed_point));
// copy sender public key from keypair into pager message
memcpy(&ctx->msg->sender_public_key, &ctx->kp->public_key, sizeof(ctx->msg->sender_public_key));
// turn receiver public key into address
ctx->msg->address = compressed_point_to_addr(ctx->receiver_public_key);
//DBG("Sending message \n");
/* decompress key */
uECC_decompress(&ctx->receiver_compressed_point, &ctx->decompressed_point, ctx->curve);
// DBM("#receiver compressed point ",sizeof(ctx->receiver_compressed_point) , &ctx->receiver_compressed_point);
// DBM("#decompr point ",sizeof(ctx->decompressed_point) , &ctx->decompressed_point);
ctx->msg->address = compressed_point_to_addr(ctx->receiver_compressed_point);
// DBM("address: ", sizeof(ctx->msg->address), &ctx->msg->address);
//printf(">>>>>>>>>>>>>>>>>>>address: %u \n \n", ctx->msg->address );
/*calculate shared secret on sender*/
if (uECC_shared_secret((uint8_t *)&ctx->decompressed_point, (uint8_t *)&ctx->kp->private_key, (uint8_t *)&ctx->shared_secret, ctx->curve)) {
DBG("shared_secret() failed in send_message (1)\n");
ctx->err = 10;
return 1;
}
// DBM("msg->msg in pl_send_message before crypt", sizeof(ctx->msg->msg), &ctx->msg->msg);
// DBM("shared secret in pl_send_message", sizeof(ctx->shared_secret), &ctx->shared_secret);
#ifdef ARDUINO
// encrypt with NaCl
char temp_plain[PLAIN_MSG_SIZE];
char temp_encrypted[CRYPTED_MSG_SIZE];
int rc;
ctx->aes_ctx = aes192_cbc_enc_start(&ctx->shared_secret, ctx->msg->iv);
aes192_cbc_enc_continue(ctx->aes_ctx, ctx->msg->msg, MSG_SIZE);
aes192_cbc_enc_finish(ctx->aes_ctx);
memcpy(ctx->msg->msg, "not working??", MSG_SIZE);
#else
mbedtls_aes_init( &ctx->aes_ctx );
// if(MSG_SIZE+crypto_box_ZEROBYTES >= MAX_MSG_SIZE) {
// return -2;
// }
/* encrypt message with aes using shared secret as the key */
mbedtls_aes_setkey_enc( &ctx->aes_ctx, (char *)&ctx->shared_secret, AES_KEYSIZE );
memset(temp_plain, '\0', crypto_box_ZEROBYTES);
memcpy(temp_plain + crypto_box_ZEROBYTES, ctx->msg->msg, PLAIN_MSG_SIZE);
//char nonce[crypto_box_NONCEBYTES];
randombytes(ctx->msg->nonce, crypto_box_NONCEBYTES);
char cmsg[MSG_SIZE];
memcpy(&cmsg, &ctx->msg->msg, MSG_SIZE);
char civ[IV_SIZE];
memcpy(&civ, &ctx->msg->iv, IV_SIZE);
rc = crypto_box(temp_encrypted, temp_plain, crypto_box_ZEROBYTES + PLAIN_MSG_SIZE, ctx->msg->nonce, ctx->receiver_public_key, ctx->kp->secret_key);
// DBM("iv in pl_send_message before crypt", sizeof(ctx->msg->iv), &ctx->msg->iv);
mbedtls_aes_crypt_cbc( &ctx->aes_ctx, MBEDTLS_AES_ENCRYPT, MSG_SIZE, civ, (char *)&cmsg, (char *)&ctx->msg->msg );
mbedtls_aes_free (&ctx->aes_ctx);
// DBG("message to send: %s \n ", ctx->msg);
#endif
/*
char to_base64[sizeof(struct pl_keypair)];
base64_encode(&to_base64, to, sizeof(struct pl_keypair));
DBG(" \nto (keypair): len: %u base64: \n %129.129s \n", sizeof(struct pl_keypair) ,to_base64, to);
if( rc != 0 ) {
return -1;
}
char msg_base64[sizeof(struct pl_pagermessage)];
base64_encode(&msg_base64, msg, sizeof(struct pl_pagermessage));
DBG(" \n encmsg: len: %u base64: \n %177.177s \n", sizeof(struct pl_pagermessage) ,msg_base64, msg);
*/
// DBM("msg->msg in pl_send_message after crypt", sizeof(ctx->msg->msg), &ctx->msg->msg);
// DBM("iv in pl_send_message after crypt", sizeof(ctx->msg->iv), &ctx->msg->iv);
// memcpy(&ctx->msg->msg, "not working?????????????????????????????????", 80);
if( is_zero(temp_plain, crypto_box_BOXZEROBYTES) != 0 ) {
return -3;
}
memcpy(ctx->msg->msg, temp_encrypted, CRYPTED_MSG_SIZE);
printf("temp_encrypted: \n %s \n ", temp_encrypted);
// return crypto_box_ZEROBYTES + length - crypto_box_BOXZEROBYTES;
return 0;
}
int pl_receive_message(struct pl_ctx * ctx)
{
// memset(&ctx->shared_secret, 7, sizeof(ctx->shared_secret));
// memset(&ctx->clear_message, 7, sizeof(ctx->clear_message));
// DBM("msg->msg in pl_receive_message before crypt", sizeof(ctx->msg->msg), &ctx->msg->msg);
DBM("msg->msg in pl_receive_message before crypt", sizeof(ctx->msg->msg), &ctx->msg->msg);
/* check if message is for us */
#ifndef ARDUINO
struct list_kp * list;
int found = 0;
for(list = ctx->keypairs; list != NULL; list = list->next) {
// printf("\n address in msg: %u address in list: %u \n", ctx->msg->address, list->id );
printf("\n address in msg: %u address in list: %u \n", ctx->msg->address, list->id );
if (ctx->msg->address == list->id)
{
// set keypair to use
ctx->kp = list->kp;
found = 1;
break;
}
}
// exit when address not found
if (found == 0) return 1;
#endif
/* decompress the senders public key */
uECC_decompress(&ctx->msg->sender_compressed_point, &ctx->decompressed_point, ctx->curve);
//DBM("ctx->kp", sizeof(struct pl_pagermessage),&ctx->kp);
//DBM("ctx->kp->private_key", sizeof(ctx->kp->private_key),&ctx->kp->private_key);
/*calculate shared secret on receiver*/
if (!uECC_shared_secret(&ctx->decompressed_point, &ctx->kp->private_key, &ctx->shared_secret, ctx->curve)) {
DBG("shared_secret() failed (receive)\n");
ctx->err = 10;
}
// DBM("shared secret in pl_receive_message", sizeof(ctx->shared_secret), &ctx->shared_secret);
// exit (and trow away msg) when address not found
if (found == 0) {
ctx->msg = (struct pl_pagermessage *) malloc(sizeof(struct pl_pagermessage));
free(ctx->msg);
return -4;
}
#ifndef NOCRYPT
#ifdef ARDUINO
// memcpy(ctx->clear_message, ctx->msg->msg, MSG_SIZE);
////////////////////////////////////////////////
// actually decrypt the message with NaCl //
////////////////////////////////////////////////
aes_context aes_ctx;
aes_ctx = aes192_cbc_dec_start(ctx->shared_secret, ctx->msg->iv);
aes192_cbc_dec_continue(aes_ctx, ctx->msg->msg, MSG_SIZE);
aes192_cbc_dec_finish(aes_ctx);
char temp_encrypted[CRYPTED_MSG_SIZE];
char temp_plain[PLAIN_MSG_SIZE];
char plain[PLAIN_MSG_SIZE];
int rc;
#else
/* decrypt the message */
mbedtls_aes_init( &ctx->aes_ctx );
//DBM("iv in pl_receive_message", sizeof(ctx->msg->iv), &ctx->msg->iv);
// if(MSG_SIZE+crypto_box_BOXZEROBYTES >= MSG_SIZE) {
// return -2;
// }
mbedtls_aes_setkey_dec( &ctx->aes_ctx, (char *)&ctx->shared_secret, AES_KEYSIZE );
memset(temp_encrypted, '\0', crypto_box_BOXZEROBYTES);
memcpy(temp_encrypted, ctx->msg->msg, CRYPTED_MSG_SIZE);
mbedtls_aes_crypt_cbc( &ctx->aes_ctx, MBEDTLS_AES_DECRYPT, MSG_SIZE, ctx->msg->iv, (char *)&ctx->msg->msg, (char *)&ctx->clear_message );
mbedtls_aes_free ( &ctx->aes_ctx );
memcpy(ctx->msg->msg, ctx->clear_message, MSG_SIZE);
rc = crypto_box_open(temp_plain, temp_encrypted, CRYPTED_MSG_SIZE, ctx->msg->nonce, ctx->msg->sender_public_key, ctx->kp->secret_key);
if( rc != 0 ) {
return -1;
}
if( is_zero(temp_plain, crypto_box_ZEROBYTES) != 0 ) {
return -3;
}
memcpy(plain, temp_plain + crypto_box_ZEROBYTES, PLAIN_MSG_SIZE);
// for now store unencrypted, but should be stored encrypted no?
memcpy(ctx->msg->msg + crypto_box_ZEROBYTES, temp_plain + crypto_box_ZEROBYTES, PLAIN_MSG_SIZE);
printf("new message: \n %s \n ", plain);
// return crypto_box_BOXZEROBYTES + MSG_SIZE - crypto_box_ZEROBYTES;
#endif
#endif
/*
DBM("msg->msg in pl_receive_message after crypt", sizeof(ctx->msg->msg), &ctx->msg->msg);
DBM("iv in pl_receive_message after crypt", sizeof(ctx->msg->iv), &ctx->msg->iv);
*/
// message decrypted, now store it in the inbox
// message decrypted, now store it (encrypted??)in the inbox
#ifndef ARDUINO
pl_inbox_append(ctx, ctx->msg);
// make pointer to current message null to prevent overwriting the stored message
......@@ -261,26 +215,31 @@ return 0;
}
int is_zero( const char *data, int len )
{
int i;
int rc;
rc = 0;
for(i = 0; i < len; ++i) {
rc |= data[i];
}
return rc;
}
struct pl_keypair * pl_create_keypair(struct pl_ctx *ctx) {
struct pl_keypair *keypair;
// memset(keypair->public_key, 3, sizeof(keypair->public_key));
// memset(keypair->private_key, 4, sizeof(keypair->private_key));
// memset(keypair->compressed_point, 5, sizeof(keypair->compressed_point));
// arduino doesn't have RNG, so can't be used for generating keys
#ifdef ARDUINO
return NULL;
#else
keypair = (struct pl_keypair *) malloc(sizeof(struct pl_keypair));
crypto_box_keypair(keypair->public_key, keypair->secret_key);
/* Generate arbitrary EC point (public) on Curve */
if (!uECC_make_key(ctx->decompressed_point, keypair->private_key, ctx->curve)) {
DBG("uECC_make_key() failed\n");
ctx->err = 11;
}
uECC_compress(ctx->decompressed_point, keypair->compressed_point, ctx->curve);
// DBG("compress failed in create_keypair");
return keypair;
#endif
}
int
......@@ -428,7 +387,7 @@ int pl_inbox_display(struct pl_ctx *ctx) {
struct list_inbox * list;
list = ctx->inbox_curr;
#ifndef ARDUINO
printf("m %u | from: %u to: %u %s \n", list->id , compressed_point_to_addr(list->msg->sender_compressed_point), list->msg->address, list->msg->msg );
printf("m %u | from: %u to: %u %s \n", list->id , compressed_point_to_addr(list->msg->sender_public_key), list->msg->address, list->msg->msg );
#endif
return 0;
} else {
......@@ -447,7 +406,7 @@ int pl_inbox_display_all(struct pl_ctx *ctx) {
#endif
for(list = ctx->inbox; list != NULL; list = list->next) {
#ifndef ARDUINO
printf("m %u | from: %u to: %u %s \n", list->id , compressed_point_to_addr(list->msg->sender_compressed_point), list->msg->address, list->msg->msg );
printf("m %u | from: %u to: %u %s \n", list->id , compressed_point_to_addr(list->msg->sender_public_key), list->msg->address, list->msg->msg );
#endif
i++;
}
......@@ -461,7 +420,7 @@ int pl_load_key_in_list(struct pl_ctx *ctx, struct pl_keypair *key){
// make new list item
ni = (struct list_kp *) malloc( sizeof(struct list_kp) );
ni->next = NULL;
ni->id = compressed_point_to_addr(key->compressed_point);
ni->id = compressed_point_to_addr(key->public_key);
ni->kp = key;
......
......@@ -24,6 +24,7 @@ extern "C"
#include "mbedtls/aes.h"
#include <unistd.h>
#include <ctype.h>
#include <sodium.h>
#endif
#ifdef DEBUG
......@@ -82,11 +83,11 @@ typedef struct list_inbox {
struct pl_ctx
{
// the curve we are using (maybe this should be somewhere else?)
uECC_Curve curve;
// uECC_Curve curve;
// place to store the calculated shared secret
char shared_secret[SHARED_SECRET_SIZE];
// char shared_secret[SHARED_SECRET_SIZE];
// place to store the decompressed public key
char decompressed_point[64];
// char decompressed_point[64];
// contains the message that will be send or is received
char clear_message[MSG_SIZE];
// my (currently used ) keypair
......@@ -98,16 +99,18 @@ struct pl_ctx
int msgcount;
struct list_inbox *inbox;
struct list_inbox *inbox_curr;
uint8_t receiver_compressed_point[ECC_COMPRESSED_SIZE];
// uint8_t receiver_public_key[ECC_COMPRESSED_SIZE];
char receiver_public_key[crypto_box_PUBLICKEYBYTES];
uint8_t err;
#ifdef ARDUINO
aes_context aes_ctx;
//#ifdef ARDUINO
// aes_context aes_ctx;
//aes_context dec_aes;
#else
mbedtls_aes_context aes_ctx;
//#else
//mbedtls_aes_context aes_ctx;
//mbedtls_aes_context dec_aes;
#endif
//#endif
};
......@@ -131,7 +134,7 @@ int pl_print_keylist(struct pl_ctx *ctx);
int pl_load_key_in_list(struct pl_ctx *ctx, struct pl_keypair *key);
int pl_inbox_append(struct pl_ctx *ctx, struct pl_pagermessage *msg);
uint32_t compressed_point_to_addr( uint8_t input[]);
uint8_t compressed_point_to_addr( uint8_t input[]);
int pl_inbox_display(struct pl_ctx *ctx);
int pl_inbox_next(struct pl_ctx *ctx);
int pl_inbox_prev(struct pl_ctx *ctx);
......
......@@ -32,6 +32,21 @@ const char b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
inline unsigned char b64_lookup(char c);
*/
char* to_hex( char hex[], const char bin[], size_t length )
{
int i;
char *p0 = (char *)bin;
char *p1 = hex;
for( i = 0; i < length; i++ ) {
snprintf( p1, 3, "%02x", *p0 );
p0 += 1;
p1 += 2;
}
return hex;
}
#ifdef ARDUINO
void dump_buffer(unsigned int n, const unsigned char* buf)
{
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment