Initial commit

This commit is contained in:
Night Kaly 2024-04-30 17:17:17 +01:00
commit 297f55bc60
Signed by: night0721
GPG key ID: 957D67B8DB7A119B
9 changed files with 801 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
zsm
zsmc
*.o
*.tar.gz

52
Makefile Normal file
View file

@ -0,0 +1,52 @@
.POSIX:
.SUFFIXES:
CC = cc
VERSION = 1.0
SERVER = zsm
CLIENT = zsmc
MANPAGE = $(TARGET).1
PREFIX ?= /usr/local
BINDIR = $(PREFIX)/bin
MANDIR = $(PREFIX)/share/man/man1
# Flags
LDFLAGS = $(shell pkg-config --libs libsodium libnotify ncurses)
CFLAGS = -O3 -mtune=native -march=native -pipe -g -std=c99 -Wno-pointer-sign -Wpedantic -Wall -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600 $(shell pkg-config --cflags libsodium libnotify ncurses) -lpthread
SERVERSRC = src/server/*.c
CLIENTSRC = src/client/*.c
LIBSRC = lib/*.c
INCLUDE = -Iinclude/
$(SERVER): $(SERVERSRC) $(LIBSRC)
$(CC) $(SERVERSRC) $(LIBSRC) $(INCLUDE) -o $@ $(CFLAGS) $(LDFLAGS)
$(CLIENT): $(CLIENTSRC) $(LIBSRC)
$(CC) $(CLIENTSRC) $(LIBSRC) $(INCLUDE) -o $@ $(CFLAGS) $(LDFLAGS)
dist:
mkdir -p $(TARGET)-$(VERSION)
cp -R README.md $(MANPAGE) $(TARGET) $(TARGET)-$(VERSION)
tar -cf $(TARGET)-$(VERSION).tar $(TARGET)-$(VERSION)
gzip $(TARGET)-$(VERSION).tar
rm -rf $(TARGET)-$(VERSION)
install: $(TARGET)
mkdir -p $(DESTDIR)$(BINDIR)
mkdir -p $(DESTDIR)$(MANDIR)
cp -p $(TARGET) $(DESTDIR)$(BINDIR)/$(TARGET)
chmod 755 $(DESTDIR)$(BINDIR)/$(TARGET)
cp -p $(MANPAGE) $(DESTDIR)$(MANDIR)/$(MANPAGE)
chmod 644 $(DESTDIR)$(MANDIR)/$(MANPAGE)
uninstall:
$(RM) $(DESTDIR)$(BINDIR)/$(TARGET)
$(RM) $(DESTDIR)$(MANDIR)/$(MANPAGE)
clean:
$(RM) $(TARGET)
all: $(TARGET)
.PHONY: all dist install uninstall clean

22
README.md Normal file
View file

@ -0,0 +1,22 @@
# zsm
Zen Secure Messaging(zsm) is a secure messaging protocol specifically for Linux(Unix-based) systems.
## Dependencies
- sqlite
- libsodium
- libnotify
## Building
> You will need to run these with elevated privilages.
> You will need `*-dev` packages to build both server and client.
```sh
git clone https://github.com/night0721/zsm
make
make install
```
## License
This project is licensed under the GNU Public License v3.0. See [LICENSE](https://github.com/night0721/zsm/blob/master/LICENSE) for more information.

8
include/notification.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef NOTIFICATION_H
#define NOTIFICATION_H
#include <libnotify/notify.h>
void send_notification(const char *content);
#endif

69
include/packet.h Normal file
View file

@ -0,0 +1,69 @@
#ifndef PACKET_H
#define PACKET_H
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sodium.h>
#define DEBUG 1
#define DOMAIN "127.0.0.1"
#define PORT 20247
#define MAX_CONNECTION 5
#define MAX_MESSAGE_LENGTH 8192
#define ERROR_LENGTH 26
#define ZSM_TYP_KEY 0x1
#define ZSM_TYP_SEND_MESSAGE 0x2
#define ZSM_TYP_UPDATE_MESSAGE 0x3
#define ZSM_TYP_DELETE_MESSAGE 0x4
#define ZSM_TYP_PRESENCE 0x5
#define ZSM_TYP_TYPING 0x6
#define ZSM_TYP_ERROR 0x7 /* Error message */
#define ZSM_TYP_B 0x8
#define ZSM_STA_SUCCESS 0x1
#define ZSM_STA_INVALID_TYPE 0x2
#define ZSM_STA_INVALID_LENGTH 0x3
#define ZSM_STA_TOO_LONG 0x4
#define ZSM_STA_READING_SOCKET 0x5
#define ZSM_STA_WRITING_SOCKET 0x6
#define ZSM_STA_UNKNOWN_USER 0x7
#define ZSM_STA_MEMORY_ALLOCATION 0x8
#define ZSM_STA_WRONG_KEY_LENGTH 0x9
#define PUBLIC_KEY_SIZE crypto_kx_PUBLICKEYBYTES
#define PRIVATE_KEY_SIZE crypto_kx_SECRETKEYBYTES
#define SHARED_KEY_SIZE crypto_kx_SESSIONKEYBYTES
#define NONCE_SIZE crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
#define ADDITIONAL_SIZE crypto_aead_xchacha20poly1305_ietf_ABYTES
typedef struct message {
uint8_t option;
uint8_t type;
unsigned long long length;
unsigned char *data;
} message;
/* Utilities functions */
void error(int fatal, const char *fmt, ...);
void *memalloc(size_t size);
void *estrdup(void *str);
unsigned char *get_public_key(int sockfd);
int send_public_key(int sockfd, unsigned char *pk);
void print_packet(message *msg);
int recv_packet(message *msg, int fd);
message *create_error_packet(int code);
message *create_packet(uint8_t option, uint8_t type, uint32_t length, char *data);
int send_packet(message *msg, int fd);
void free_packet(message *msg);
#endif

8
lib/notification.c Normal file
View file

@ -0,0 +1,8 @@
#include "notification.h"
void send_notification(const char *content)
{
NotifyNotification *noti = notify_notification_new("Client", content, "dialog-information");
notify_notification_show(noti, NULL);
g_object_unref(G_OBJECT(noti));
}

273
lib/packet.c Normal file
View file

@ -0,0 +1,273 @@
#include "packet.h"
/*
* msg is the error message to print to stderr
* will include error message from function if errno isn't 0
* end program is fatal is 1
*/
void error(int fatal, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
/* to preserve errno */
int errsv = errno;
/* Determine the length of the formatted error message */
va_list args_copy;
va_copy(args_copy, args);
size_t error_len = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
/* 7 for [zsm], space and null */
char errorstr[error_len + 1];
vsnprintf(errorstr, error_len + 1, fmt, args);
fprintf(stderr, "[zsm] ");
if (errsv != 0) {
perror(errorstr);
errno = 0;
} else {
fprintf(stderr, "%s\n", errorstr);
}
va_end(args);
if (fatal) exit(1);
}
void *memalloc(size_t size)
{
void *ptr = malloc(size);
if (!ptr) {
error(0, "Error allocating memory");
return NULL;
}
return ptr;
}
void *estrdup(void *str)
{
void *modstr = strdup(str);
if (modstr == NULL) {
error(0, "Error allocating memory");
return NULL;
}
return modstr;
}
uint8_t *get_public_key(int sockfd)
{
message keyex_msg;
if (recv_packet(&keyex_msg, sockfd) != ZSM_STA_SUCCESS) {
/* We can't do anything if key exchange already failed */
close(sockfd);
return NULL;
} else {
int status = 0;
/* Check to see if the content is actually a key */
if (keyex_msg.type != ZSM_TYP_KEY) {
status = ZSM_STA_INVALID_TYPE;
}
if (keyex_msg.length != PUBLIC_KEY_SIZE) {
status = ZSM_STA_WRONG_KEY_LENGTH;
}
if (status != 0) {
free(keyex_msg.data);
message *error_msg = create_error_packet(status);
send_packet(error_msg, sockfd);
free_packet(error_msg);
close(sockfd);
return NULL;
}
}
/* Obtain public key from packet */
uint8_t *pk = memalloc(PUBLIC_KEY_SIZE * sizeof(char));
memcpy(pk, keyex_msg.data, PUBLIC_KEY_SIZE);
if (pk == NULL) {
free(keyex_msg.data);
/* Fatal, we couldn't complete key exchange */
close(sockfd);
return NULL;
}
free(keyex_msg.data);
return pk;
}
int send_public_key(int sockfd, uint8_t *pk)
{
/* send_packet requires heap allocated buffer */
uint8_t *pk_dup = memalloc(PUBLIC_KEY_SIZE * sizeof(char));
memcpy(pk_dup, pk, PUBLIC_KEY_SIZE);
if (pk_dup == NULL) {
close(sockfd);
return -1;
}
/* Sending our public key to client */
/* option???? */
message *keyex = create_packet(1, ZSM_TYP_KEY, PUBLIC_KEY_SIZE, pk_dup);
send_packet(keyex, sockfd);
free_packet(keyex);
return 0;
}
void print_packet(message *msg)
{
printf("Option: %d\n", msg->option);
printf("Type: %d\n", msg->type);
printf("Length: %lld\n", msg->length);
printf("Data: %s\n\n", msg->data);
}
/*
* Requires manually free message data
*/
int recv_packet(message *msg, int fd)
{
int status = ZSM_STA_SUCCESS;
/* Read the message components */
if (recv(fd, &msg->option, sizeof(msg->option), 0) < 0 ||
recv(fd, &msg->type, sizeof(msg->type), 0) < 0 ||
recv(fd, &msg->length, sizeof(msg->length), 0) < 0) {
status = ZSM_STA_READING_SOCKET;
error(0, "Error reading from socket");
}
#if DEBUG == 1
printf("==========PACKET RECEIVED==========\n");
#endif
#if DEBUG == 1
printf("Option: %d\n", msg->option);
#endif
if (msg->type > 0xFF || msg->type < 0x0) {
status = ZSM_STA_INVALID_TYPE;
error(0, "Invalid message type");
goto failure;
}
#if DEBUG == 1
printf("Type: %d\n", msg->type);
#endif
/* Convert message length from network byte order to host byte order */
if (msg->length > MAX_MESSAGE_LENGTH) {
status = ZSM_STA_TOO_LONG;
error(0, "Message too long: %lld", msg->length);
goto failure;
}
#if DEBUG == 1
printf("Length: %lld\n", msg->length);
#endif
// Allocate memory for message data
msg->data = memalloc((msg->length + 1) * sizeof(char));
if (msg->data == NULL) {
status = ZSM_STA_MEMORY_ALLOCATION;
goto failure;
}
/* Read message data from the socket */
size_t bytes_read = 0;
if ((bytes_read = recv(fd, msg->data, msg->length, 0)) < 0) {
status = ZSM_STA_READING_SOCKET;
error(0, "Error reading from socket");
free(msg->data);
goto failure;
}
if (bytes_read != msg->length) {
status = ZSM_STA_INVALID_LENGTH;
error(0, "Invalid message length: bytes_read=%ld != msg->length=%lld", bytes_read, msg->length);
free(msg->data);
goto failure;
}
msg->data[msg->length] = '\0';
#if DEBUG == 1
printf("Data: %s\n\n", msg->data);
#endif
return status;
failure:;
message *error_msg = create_error_packet(status);
if (send_packet(error_msg, fd) != ZSM_STA_SUCCESS) {
/* Resend it? */
error(0, "Failed to send error packet to peer. Error status => %d", status);
}
free_packet(error_msg);
return status;
}
message *create_error_packet(int code)
{
char *err = memalloc(ERROR_LENGTH * sizeof(char));
switch (code) {
case ZSM_STA_INVALID_TYPE:
strcpy(err, "Invalid message type ");
break;
case ZSM_STA_INVALID_LENGTH:
strcpy(err, "Invalid message length ");
break;
case ZSM_STA_TOO_LONG:
strcpy(err, "Message too long ");
break;
case ZSM_STA_READING_SOCKET:
strcpy(err, "Error reading from socket");
break;
case ZSM_STA_WRITING_SOCKET:
strcpy(err, "Error writing to socket ");
break;
case ZSM_STA_UNKNOWN_USER:
strcpy(err, "Unknwon user ");
break;
case ZSM_STA_WRONG_KEY_LENGTH:
strcpy(err, "Wrong public key length ");
break;
}
return create_packet(1, ZSM_TYP_ERROR, ERROR_LENGTH, err);
}
/*
* Requires heap allocated msg data
*/
message *create_packet(uint8_t option, uint8_t type, uint32_t length, char *data)
{
message *msg = memalloc(sizeof(message));
msg->option = option;
msg->type = type;
msg->length = length;
msg->data = data;
return msg;
}
/*
* Requires heap allocated msg data
*/
int send_packet(message *msg, int fd)
{
int status = ZSM_STA_SUCCESS;
uint32_t length = msg->length;
// Send the message back to the client
if (send(fd, &msg->option, sizeof(msg->option), 0) <= 0 ||
send(fd, &msg->type, sizeof(msg->type), 0) <= 0 ||
send(fd, &msg->length, sizeof(msg->length), 0) <= 0 ||
send(fd, msg->data, length, 0) <= 0) {
status = ZSM_STA_WRITING_SOCKET;
error(0, "Error writing to socket");
//free(msg->data);
close(fd); // Close the socket and continue accepting connections
}
#if DEBUG == 1
printf("==========PACKET SENT==========\n");
print_packet(msg);
#endif
return status;
}
void free_packet(message *msg)
{
if (msg->type != 0x10) {
/* temp solution, dont use stack allocated msg to send to client */
free(msg->data);
}
free(msg);
}

147
src/client/client.c Normal file
View file

@ -0,0 +1,147 @@
#include "packet.h"
#include <pthread.h>
uint8_t shared_key[SHARED_KEY_SIZE];
int sockfd;
/*
* Connect to socket server
*/
int socket_init()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
error(1, "Error on opening socket");
}
struct hostent *server = gethostbyname(DOMAIN);
if (server == NULL) {
error(1, "No such host %s", DOMAIN);
}
struct sockaddr_in sv_addr;
memset(&sv_addr, 0, sizeof(sv_addr));
sv_addr.sin_family = AF_INET;
sv_addr.sin_port = htons(PORT);
memcpy(&sv_addr.sin_addr.s_addr, server->h_addr, server->h_length);
/* free(server); */
if (connect(sockfd, (struct sockaddr *) &sv_addr, sizeof(sv_addr)) < 0) {
error(1, "Error on connect");
close(sockfd);
return 0;
}
printf("Connected to server at %s\n", DOMAIN);
return sockfd;
}
/*
* Performs key exchange with server
*/
int key_exchange(int sockfd)
{
/* Generate the client's key pair */
uint8_t cl_pk[PUBLIC_KEY_SIZE], cl_sk[PRIVATE_KEY_SIZE];
crypto_kx_keypair(cl_pk, cl_sk);
/* Send our public key */
if (send_public_key(sockfd, cl_pk) < 0) {
return -1;
}
/* Get public key from server */
uint8_t *pk;
if ((pk = get_public_key(sockfd)) == NULL) {
return -1;
}
/* Compute a shared key using the server's public key and our secret key */
if (crypto_kx_client_session_keys(NULL, shared_key, cl_pk, cl_sk, pk) != 0) {
error(1, "Server public key is not acceptable");
free(pk);
close(sockfd);
return -1;
}
free(pk);
return 0;
}
void *sender()
{
while (1) {
printf("Enter message to send to server: ");
fflush(stdout);
char line[1024];
line[0] = '\0';
size_t length = strlen(line);
while (length <= 1) {
fgets(line, sizeof(line), stdin);
length = strlen(line);
}
length -= 1;
line[length] = '\0';
uint8_t nonce[NONCE_SIZE];
uint8_t encrypted[length + ADDITIONAL_SIZE];
unsigned long long encrypted_len;
randombytes_buf(nonce, sizeof(nonce));
crypto_aead_xchacha20poly1305_ietf_encrypt(encrypted, &encrypted_len,
line, length,
NULL, 0, NULL, nonce, shared_key);
size_t payload_t = NONCE_SIZE + encrypted_len;
uint8_t encryptedwithnonce[payload_t];
memcpy(encryptedwithnonce, nonce, NONCE_SIZE);
memcpy(encryptedwithnonce + NONCE_SIZE, encrypted, encrypted_len);
message *msg = create_packet(1, 0x10, payload_t, encryptedwithnonce);
if (send_packet(msg, sockfd) != ZSM_STA_SUCCESS) {
close(sockfd);
}
free_packet(msg);
}
close(sockfd);
}
void *receiver()
{
while (1) {
message servermsg;
if (recv_packet(&servermsg, sockfd) != ZSM_STA_SUCCESS) {
close(sockfd);
return 0;
}
free(servermsg.data);
}
return NULL;
}
int main()
{
if (sodium_init() < 0) {
error(1, "Error initializing libsodium");
}
sockfd = socket_init();
if (key_exchange(sockfd) < 0) {
/* Fatal */
error(1, "Error performing key exchange with server");
}
pthread_t recv_worker, send_worker;
if (pthread_create(&recv_worker, NULL, sender, NULL) != 0) {
fprintf(stderr, "Error creating incoming thread\n");
return 1;
}
if (pthread_create(&send_worker, NULL, receiver, NULL) != 0) {
fprintf(stderr, "Error creating outgoing thread\n");
return 1;
}
// Join threads
pthread_join(recv_worker, NULL);
pthread_join(send_worker, NULL);
return 0;
}

218
src/server/server.c Normal file
View file

@ -0,0 +1,218 @@
#include "packet.h"
#include "notification.h"
#include <pthread.h>
socklen_t clilen;
struct sockaddr_in cli_address;
uint8_t shared_key[SHARED_KEY_SIZE];
int clientfd;
/*
* Initialise socket server
*/
int socket_init()
{
int serverfd = socket(AF_INET, SOCK_STREAM, 0);
if (serverfd < 0) {
error(1, "Error on opening socket");
}
/* Reuse addr(for debug) */
int optval = 1;
if (setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
error(1, "Error at setting SO_REUSEADDR");
}
struct sockaddr_in sv_addr;
memset(&sv_addr, 0, sizeof(sv_addr));
sv_addr.sin_family = AF_INET;
sv_addr.sin_addr.s_addr = INADDR_ANY;
sv_addr.sin_port = htons(PORT);
if (bind(serverfd, (struct sockaddr *) &sv_addr, sizeof(sv_addr)) < 0) {
error(1, "Error on bind");
}
if (listen(serverfd, MAX_CONNECTION) < 0) {
error(1, "Error on listen");
}
printf("Listening on port %d\n", PORT);
clilen = sizeof(cli_address);
return serverfd;
}
/*
* Performs key exchange with client
*/
int key_exchange(int clientfd)
{
/* Generate the server's key pair */
uint8_t sv_pk[PUBLIC_KEY_SIZE], sv_sk[PRIVATE_KEY_SIZE];
crypto_kx_keypair(sv_pk, sv_sk);
/* Get public key from client */
uint8_t *pk;
if ((pk = get_public_key(clientfd)) == NULL) {
return -1;
}
/* Send our public key */
if (send_public_key(clientfd, sv_pk) < 0) {
free(pk);
return -1;
}
/* Compute a shared key using the client's public key and our secret key. */
if (crypto_kx_server_session_keys(NULL, shared_key, sv_pk, sv_sk, pk) != 0) {
error(0, "Client public key is not acceptable");
free(pk);
close(clientfd);
return -1;
}
free(pk);
return 0;
}
void signal_handler(int signal)
{
switch (signal) {
case SIGPIPE:
error(0, "SIGPIPE received");
break;
case SIGABRT:
case SIGINT:
case SIGTERM:
notify_uninit();
error(1, "Shutdown signal received");
break;
}
}
void *receiver()
{
int serverfd = socket_init();
clientfd = accept(serverfd, (struct sockaddr *) &cli_address, &clilen);
if (clientfd < 0) {
error(0, "Error on accepting client");
/* Continue accpeting connections */
/* continue; */
}
if (key_exchange(clientfd) < 0) {
error(0, "Error performing key exchange with client");
/* continue; */
}
while (1) {
message msg;
memset(&msg, 0, sizeof(msg));
if (recv_packet(&msg, clientfd) != ZSM_STA_SUCCESS) {
close(clientfd);
break;
/* continue; */
}
size_t encrypted_len = msg.length - NONCE_SIZE;
size_t msg_len = encrypted_len - ADDITIONAL_SIZE;
uint8_t nonce[NONCE_SIZE];
uint8_t encrypted[encrypted_len];
uint8_t decrypted[msg_len + 1];
unsigned long long decrypted_len;
memcpy(nonce, msg.data, NONCE_SIZE);
memcpy(encrypted, msg.data + NONCE_SIZE, encrypted_len);
free(msg.data);
if (crypto_aead_xchacha20poly1305_ietf_decrypt(decrypted, &decrypted_len,
NULL,
encrypted, encrypted_len,
NULL, 0,
nonce, shared_key) != 0) {
error(0, "Cannot decrypt message");
} else {
/* Decrypted message */
decrypted[msg_len] = '\0';
printf("Decrypted: %s\n", decrypted);
send_notification(decrypted);
msg.data = malloc(14);
strcpy(msg.data, "Received data");
msg.length = 14;
send_packet(&msg, clientfd);
free(msg.data);
}
}
close(clientfd);
close(serverfd);
return NULL;
}
void *sender()
{
while (1) {
printf("Enter message to send to client: ");
fflush(stdout);
char line[1024];
line[0] = '\0';
size_t length = strlen(line);
while (length <= 1) {
fgets(line, sizeof(line), stdin);
length = strlen(line);
}
length -= 1;
line[length] = '\0';
uint8_t nonce[NONCE_SIZE];
uint8_t encrypted[length + ADDITIONAL_SIZE];
unsigned long long encrypted_len;
randombytes_buf(nonce, sizeof(nonce));
crypto_aead_xchacha20poly1305_ietf_encrypt(encrypted, &encrypted_len,
line, length,
NULL, 0, NULL, nonce, shared_key);
size_t payload_t = NONCE_SIZE + encrypted_len;
uint8_t encryptedwithnonce[payload_t];
memcpy(encryptedwithnonce, nonce, NONCE_SIZE);
memcpy(encryptedwithnonce + NONCE_SIZE, encrypted, encrypted_len);
message *msg = create_packet(1, 0x10, payload_t, encryptedwithnonce);
if (send_packet(msg, clientfd) != ZSM_STA_SUCCESS) {
close(clientfd);
}
free_packet(msg);
}
close(clientfd);
return NULL;
}
int main()
{
if (sodium_init() < 0) {
error(1, "Error initializing libsodium");
}
/* Init libnotify with app name */
if (notify_init("zsm") < 0) {
error(1, "Error initializing libnotify");
}
signal(SIGPIPE, signal_handler);
signal(SIGABRT, signal_handler);
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
pthread_t recv_worker, send_worker;
if (pthread_create(&recv_worker, NULL, sender, NULL) != 0) {
fprintf(stderr, "Error creating incoming thread\n");
return 1;
}
if (pthread_create(&send_worker, NULL, receiver, NULL) != 0) {
fprintf(stderr, "Error creating outgoing thread\n");
return 1;
}
// Join threads
pthread_join(recv_worker, NULL);
pthread_join(send_worker, NULL);
return 0;
}