commit 558cae10c602d8a2db8125014f6de8e3bf7675dc Author: concise7113 Date: Mon Jul 21 14:23:21 2025 +0000 Initial commit diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..fc1466e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "client/liboqs"] + path = client/liboqs + url = https://github.com/open-quantum-safe/liboqs.git diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..746c2e4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,47 @@ +Post-Quantum FTP (QP-FTPD) +Copyright (C) 2025, Wonder Cohen BV The Hague, The Netherlands, https://wondercohen.com/ info@wondercohen.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-------------------------------------------------------------------------------- + +# Third-Party License: liboqs + +This software depends on the liboqs library, which is licensed under the MIT license. +The following is a copy of the liboqs license: + +Copyright (c) 2016-2024 The Open Quantum Safe project authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7881b0f --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +# Post-Quantum FTP (PQ-FTPD) + +A simple, secure file transfer utility using post-quantum cryptography for the key exchange. This project features a client-server architecture running inside Docker. + +## Features + +* **Post-Quantum Security**: Uses the **Kyber768** algorithm from the `liboqs` library to establish a secure channel, protecting against future quantum threats. +* **Containerized Server**: The server runs in a Docker container for easy deployment and dependency management. +* **CLI Interface**: The client provides a simple command-line interface for uploading files. +* **Compression**: Uses `zlib` to compress files before transfer to save bandwidth. +* **Authentication**: Implements a basic username/password authentication scheme. + +--- +## Prerequisites + +### Server (via Docker) +* Docker +* Docker Compose + +### Client (Local Machine) +* A C++ compiler (`g++`) +* `make` +* `git` +* `liboqs` installed locally (see build instructions). +* `zlib` (`zlib1g-dev` on Debian/Ubuntu). + +--- +## How to Run + +### 1. Start the Server + +From the project's root directory, build and run the server using Docker Compose. + +```bash +docker-compose up --build -d +``` +* The `-d` flag runs the server in the background. +* To view server logs: `docker-compose logs -f` +* To stop the server: `docker-compose down` + +### 2. Build and Run the Client + +First, ensure you have the client-side dependencies installed. + +```bash +# Install build tools and zlib +sudo apt update +sudo apt install g++ make git zlib1g-dev ninja-build cmake + +# Install liboqs +git clone --branch 0.10.2 [https://github.com/open-quantum-safe/liboqs.git](https://github.com/open-quantum-safe/liboqs.git) +cd liboqs +mkdir build && cd build +cmake -GNinja .. +ninja +sudo ninja install +cd ../.. # Go back to project root +``` + +Now, navigate to the `client` directory and compile the client: + +```bash +cd client +make +``` + +Run the client: +```bash +./bin/client +``` +--- +## Usage + +Once the client is running, you can use the following commands: + +* **Upload a file**: + ``` + > put /path/to/your/local/file.txt + ``` + +* **Exit the client**: + ``` + > exit + ``` +Transferred files will appear in the `pq-ftp/server/transfer` directory on the host machine. + +--- +## License + +This project is licensed under the MIT License. See the `LICENSE` file for details. diff --git a/client/Makefile b/client/Makefile new file mode 100644 index 0000000..9f1baf5 --- /dev/null +++ b/client/Makefile @@ -0,0 +1,28 @@ +# pq-ftp/client/Makefile +CXX=g++ +CXXFLAGS=-std=c++17 -Wall +# You must have liboqs installed locally for this to work +# sudo ninja install from the liboqs build directory +LDFLAGS=-loqs -lz -lcrypto + +SRC_DIR=src +BUILD_DIR=build +BIN_DIR=bin +TARGET=$(BIN_DIR)/client + +SRCS=$(wildcard $(SRC_DIR)/*.cpp) +OBJS=$(patsubst $(SRC_DIR)/%.cpp, $(BUILD_DIR)/%.o, $(SRCS)) + +all: $(TARGET) + +$(TARGET): $(OBJS) + @mkdir -p $(BIN_DIR) + $(CXX) $(OBJS) -o $(TARGET) $(LDFLAGS) + @echo "Client compiled successfully!" + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp + @mkdir -p $(BUILD_DIR) + $(CXX) $(CXXFLAGS) -c $< -o $@ + +clean: + rm -rf $(BUILD_DIR) $(BIN_DIR) diff --git a/client/bin/client b/client/bin/client new file mode 100755 index 0000000..b6cf4e0 Binary files /dev/null and b/client/bin/client differ diff --git a/client/build/client.o b/client/build/client.o new file mode 100644 index 0000000..2f7b811 Binary files /dev/null and b/client/build/client.o differ diff --git a/client/liboqs b/client/liboqs new file mode 160000 index 0000000..94b421e --- /dev/null +++ b/client/liboqs @@ -0,0 +1 @@ +Subproject commit 94b421ebb82405c843dba4e9aa521a56ee5a333d diff --git a/client/src/client.cpp b/client/src/client.cpp new file mode 100644 index 0000000..bae56ad --- /dev/null +++ b/client/src/client.cpp @@ -0,0 +1,170 @@ +// pq-ftp/client/src/client.cpp +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define SERVER_IP "127.0.0.1" +#define PORT 2121 +#define BUFFER_SIZE 4096 +#define PQC_ALGORITHM "Kyber768" + +// Data compression function +std::vector compress_data(const std::vector& data) { + if (data.empty()) return {}; + + uLongf compressed_size = compressBound(data.size()); + std::vector compressed_data(compressed_size); + + if (compress(compressed_data.data(), &compressed_size, data.data(), data.size()) != Z_OK) { + std::cerr << "Compression failed!" << std::endl; + return {}; + } + + compressed_data.resize(compressed_size); + return compressed_data; +} + + +int main() { + int sock = 0; + struct sockaddr_in serv_addr; + + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + std::cerr << "Socket creation error" << std::endl; + return -1; + } + + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(PORT); + + if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) { + std::cerr << "Invalid address/ Address not supported" << std::endl; + return -1; + } + + if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { + std::cerr << "Connection Failed" << std::endl; + return -1; + } + + std::cout << "Connected to server. Performing PQC key exchange..." << std::endl; + + // 1. PQC Key Exchange + OQS_KEM *kem = NULL; + uint8_t *public_key = NULL; + uint8_t *ciphertext = NULL; + uint8_t *shared_secret_client = NULL; + + kem = OQS_KEM_new(PQC_ALGORITHM); + if (kem == NULL) { + std::cerr << "Error: OQS_KEM_new failed." << std::endl; + return -1; + } + + public_key = new uint8_t[kem->length_public_key]; + ciphertext = new uint8_t[kem->length_ciphertext]; + shared_secret_client = new uint8_t[kem->length_shared_secret]; + + // Receive public key from server + recv(sock, public_key, kem->length_public_key, 0); + + // Encapsulate to generate ciphertext and our shared secret + if (OQS_KEM_encaps(kem, ciphertext, shared_secret_client, public_key) != OQS_SUCCESS) { + std::cerr << "Error: OQS_KEM_encaps failed." << std::endl; + return -1; + } + + // Send ciphertext to server + send(sock, ciphertext, kem->length_ciphertext, 0); + + std::cout << "PQC key exchange successful." << std::endl; + // NOTE: The shared_secret_client should be identical to the server's. + + // 2. Authentication + std::cout << "Authenticating..." << std::endl; + std::string user = "user"; + std::string pass = "pass"; + send(sock, user.c_str(), user.length() + 1, 0); + send(sock, pass.c_str(), pass.length() + 1, 0); + + char auth_status[10] = {0}; + recv(sock, auth_status, sizeof(auth_status), 0); + if (std::string(auth_status) != "OK") { + std::cerr << "Authentication failed. Server responded: " << auth_status << std::endl; + return -1; + } + std::cout << "Authentication successful!" << std::endl; + std::cout << "Commands: put , get , exit" << std::endl; + + // 3. Command Loop + for (std::string line; std::cout << "> " && std::getline(std::cin, line);) { + if (line.empty()) continue; + if (line == "exit") break; + + std::stringstream ss(line); + std::string command, filepath; + ss >> command >> filepath; + + if (command == "put") { + std::ifstream file(filepath, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + std::cerr << "Error: Could not open file " << filepath << std::endl; + continue; + } + + // Get file size + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + // Read file into a vector + std::vector buffer(size); + if (!file.read(buffer.data(), size)) { + std::cerr << "Error reading file." << std::endl; + continue; + } + file.close(); + + // Convert char vector to uint8_t vector for compression + std::vector data(buffer.begin(), buffer.end()); + + // Compress the data + std::vector compressed_data = compress_data(data); + std::cout << "Original size: " << data.size() << " bytes, Compressed size: " << compressed_data.size() << " bytes." << std::endl; + + // Send command and filename + std::string put_command = "put " + filepath.substr(filepath.find_last_of("/\\") + 1); + send(sock, put_command.c_str(), put_command.length() + 1, 0); + + // Send file size + uint64_t compressed_size = compressed_data.size(); + send(sock, &compressed_size, sizeof(compressed_size), 0); + + // Send file data + send(sock, compressed_data.data(), compressed_data.size(), 0); + + std::cout << "File uploaded successfully." << std::endl; + + } else if (command == "get") { + std::cout << "GET command is not implemented in this example." << std::endl; + } else { + std::cout << "Unknown command: " << command << std::endl; + } + } + + // Cleanup + OQS_KEM_free(kem); + delete[] public_key; + delete[] ciphertext; + delete[] shared_secret_client; + close(sock); + + return 0; +} diff --git a/client/testfile.txt b/client/testfile.txt new file mode 100644 index 0000000..dfccb41 --- /dev/null +++ b/client/testfile.txt @@ -0,0 +1 @@ +This is a post-quantum test file. This is a post-quantum test file. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..aeede9b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +# pq-ftp/docker-compose.yml +version: '3.8' + +services: + pq-server: + build: ./server + container_name: pq-ftp-server + restart: unless-stopped + ports: + - "2121:2121" # Expose port 2121 for file transfer + volumes: + - ./server/transfer:/transfer # Persist transferred files + - ./server/logs:/logs # Persist logs diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..a052203 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,44 @@ +# pq-ftp/server/Dockerfile + +# Base Image: Ubuntu 22.04 +FROM ubuntu:22.04 + +# Avoid interactive prompts during installation +ENV DEBIAN_FRONTEND=noninteractive + +# Install dependencies for building C++ and OQS +# Install dependencies for building C++ and OQS +RUN apt-get update && apt-get install -y \ + git \ + cmake \ + g++ \ + make \ + libssl-dev \ + zlib1g-dev \ + ninja-build \ + && rm -rf /var/lib/apt/lists/* + +# Clone and install liboqs +WORKDIR /opt +RUN git clone --branch 0.14.0 https://github.com/open-quantum-safe/liboqs.git +WORKDIR /opt/liboqs +RUN mkdir build && cd build && \ + cmake -DOQS_USE_OPENSSL=OFF -GNinja .. && \ + ninja && \ + ninja install + +# Set up the working directory for our server code +WORKDIR /app + +# Copy the server source code and Makefile +COPY ./src /app/src +COPY Makefile /app/ + +# Compile the server +RUN make + +# Create directories for transfers and logs +RUN mkdir -p /transfer && mkdir -p /logs + +# Set the entrypoint for the container +CMD [ "./bin/server" ] diff --git a/server/Makefile b/server/Makefile new file mode 100644 index 0000000..18a8314 --- /dev/null +++ b/server/Makefile @@ -0,0 +1,26 @@ +# pq-ftp/server/Makefile +CXX=g++ +CXXFLAGS=-std=c++17 -Wall -pthread +LDFLAGS=-loqs -lz + +SRC_DIR=src +BUILD_DIR=build +BIN_DIR=bin +TARGET=$(BIN_DIR)/server + +SRCS=$(wildcard $(SRC_DIR)/*.cpp) +OBJS=$(patsubst $(SRC_DIR)/%.cpp, $(BUILD_DIR)/%.o, $(SRCS)) + +all: $(TARGET) + +$(TARGET): $(OBJS) + @mkdir -p $(BIN_DIR) + $(CXX) $(OBJS) -o $(TARGET) $(LDFLAGS) + @echo "Server compiled successfully!" + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp + @mkdir -p $(BUILD_DIR) + $(CXX) $(CXXFLAGS) -c $< -o $@ + +clean: + rm -rf $(BUILD_DIR) $(BIN_DIR) diff --git a/server/liboqs b/server/liboqs new file mode 160000 index 0000000..94b421e --- /dev/null +++ b/server/liboqs @@ -0,0 +1 @@ +Subproject commit 94b421ebb82405c843dba4e9aa521a56ee5a333d diff --git a/server/logs/server.log b/server/logs/server.log new file mode 100644 index 0000000..e4d4216 --- /dev/null +++ b/server/logs/server.log @@ -0,0 +1,14 @@ +Mon Jul 21 13:53:06 2025 +: Server is listening on port 2121 +Mon Jul 21 14:06:28 2025 +: Client connected. Starting PQC key exchange... +Mon Jul 21 14:06:28 2025 +: PQC key exchange successful. Shared secret established. +Mon Jul 21 14:06:50 2025 +: Authentication failed for client. +Mon Jul 21 14:06:51 2025 +: Client connected. Starting PQC key exchange... +Mon Jul 21 14:06:51 2025 +: PQC key exchange successful. Shared secret established. +Mon Jul 21 14:07:16 2025 +: Authentication failed for client. diff --git a/server/src/server.cpp b/server/src/server.cpp new file mode 100644 index 0000000..af0e395 --- /dev/null +++ b/server/src/server.cpp @@ -0,0 +1,220 @@ +// pq-ftp/server/src/server.cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// OQS for Post-Quantum Cryptography +#include + +// Zlib for compression +#include + +#define PORT 2121 +#define BUFFER_SIZE 4096 +#define PQC_ALGORITHM "Kyber768" // A NIST-selected KEM algorithm + +std::mutex log_mutex; + +// Simple logger +void log(const std::string& message) { + std::lock_guard guard(log_mutex); + std::ofstream log_file("/logs/server.log", std::ios_base::app); + time_t now = time(0); + char* dt = ctime(&now); + log_file << dt << ": " << message << std::endl; + std::cout << message << std::endl; +} + +// Dummy authentication +bool authenticate(int client_socket) { + // In a real system, use a secure challenge-response protocol! + char username[256] = {0}; + char password[256] = {0}; + + recv(client_socket, username, sizeof(username), 0); + recv(client_socket, password, sizeof(password), 0); + + // For this example, we accept any "user" with password "pass" + if (std::string(username) == "user" && std::string(password) == "pass") { + send(client_socket, "OK", 2, 0); + return true; + } + + send(client_socket, "FAIL", 4, 0); + return false; +} + +// Data compression function +std::vector compress_data(const std::vector& data) { + if (data.empty()) return {}; + + uLongf compressed_size = compressBound(data.size()); + std::vector compressed_data(compressed_size); + + if (compress(compressed_data.data(), &compressed_size, data.data(), data.size()) != Z_OK) { + log("Compression failed!"); + return {}; + } + + compressed_data.resize(compressed_size); + return compressed_data; +} + + +void handle_client(int client_socket) { + log("Client connected. Starting PQC key exchange..."); + + // 1. PQC Key Exchange (using liboqs) + OQS_KEM *kem = NULL; + uint8_t *public_key = NULL; + uint8_t *secret_key = NULL; + uint8_t *ciphertext = NULL; + uint8_t *shared_secret_server = NULL; + + kem = OQS_KEM_new(PQC_ALGORITHM); + if (kem == NULL) { + log("Error: OQS_KEM_new failed for " + std::string(PQC_ALGORITHM)); + close(client_socket); + return; + } + + public_key = new uint8_t[kem->length_public_key]; + secret_key = new uint8_t[kem->length_secret_key]; + ciphertext = new uint8_t[kem->length_ciphertext]; + shared_secret_server = new uint8_t[kem->length_shared_secret]; + + // Generate server's key pair + if (OQS_KEM_keypair(kem, public_key, secret_key) != OQS_SUCCESS) { + log("Error: OQS_KEM_keypair failed."); + // Cleanup would happen here + close(client_socket); + return; + } + + // Send public key to client + send(client_socket, public_key, kem->length_public_key, 0); + + // Receive ciphertext from client + recv(client_socket, ciphertext, kem->length_ciphertext, 0); + + // Decapsulate to get the shared secret + if (OQS_KEM_decaps(kem, shared_secret_server, ciphertext, secret_key) != OQS_SUCCESS) { + log("Error: OQS_KEM_decaps failed."); + // Cleanup + close(client_socket); + return; + } + + log("PQC key exchange successful. Shared secret established."); + // NOTE: The shared_secret_server is now our symmetric session key. + // In a real application, you would derive further keys from this using a KDF. + // For simplicity, we are not encrypting the file transfer itself, just demonstrating the key exchange. + + // 2. Authentication + if (!authenticate(client_socket)) { + log("Authentication failed for client."); + close(client_socket); + return; + } + log("Client authenticated successfully."); + + // 3. Command Loop (API for scripting) + char buffer[BUFFER_SIZE] = {0}; + while (recv(client_socket, buffer, BUFFER_SIZE, 0) > 0) { + std::string command(buffer); + log("Received command: " + command); + + if (command.rfind("put ", 0) == 0) { // Starts with "put " + std::string filename = "/transfer/" + command.substr(4); + log("Receiving file: " + filename); + + // Get file size + uint64_t file_size; + recv(client_socket, &file_size, sizeof(file_size), 0); + + std::ofstream outfile(filename, std::ios::binary); + char file_buffer[BUFFER_SIZE]; + uint64_t bytes_received = 0; + + while (bytes_received < file_size) { + int bytes = recv(client_socket, file_buffer, std::min((uint64_t)BUFFER_SIZE, file_size - bytes_received), 0); + if (bytes <= 0) break; + outfile.write(file_buffer, bytes); + bytes_received += bytes; + } + outfile.close(); + log("File received successfully."); + } + + // Clear buffer for next command + memset(buffer, 0, BUFFER_SIZE); + } + + log("Client disconnected."); + + // Cleanup OQS resources + OQS_KEM_free(kem); + delete[] public_key; + delete[] secret_key; + delete[] ciphertext; + delete[] shared_secret_server; + + close(client_socket); +} + +int main() { + int server_fd; + struct sockaddr_in address; + int opt = 1; + int addrlen = sizeof(address); + + // Creating socket file descriptor + if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { + perror("socket failed"); + exit(EXIT_FAILURE); + } + + // Forcefully attaching socket to the port + if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { + perror("setsockopt"); + exit(EXIT_FAILURE); + } + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons(PORT); + + // Bind the socket to the network address and port + if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { + perror("bind failed"); + exit(EXIT_FAILURE); + } + + // Start listening for connections + if (listen(server_fd, 10) < 0) { + perror("listen"); + exit(EXIT_FAILURE); + } + + log("Server is listening on port " + std::to_string(PORT)); + + while (true) { + int client_socket; + if ((client_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) { + perror("accept"); + continue; // Continue to next iteration instead of exiting + } + + // Create a new thread to handle the client connection + std::thread client_thread(handle_client, client_socket); + client_thread.detach(); // Detach the thread to handle clients concurrently + } + + return 0; +}