Initial commit

This commit is contained in:
concise7113
2025-07-21 14:23:21 +00:00
commit 558cae10c6
15 changed files with 658 additions and 0 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "client/liboqs"]
path = client/liboqs
url = https://github.com/open-quantum-safe/liboqs.git

47
LICENSE Normal file
View File

@ -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.

90
README.md Normal file
View File

@ -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.

28
client/Makefile Normal file
View File

@ -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)

BIN
client/bin/client Executable file

Binary file not shown.

BIN
client/build/client.o Normal file

Binary file not shown.

1
client/liboqs Submodule

Submodule client/liboqs added at 94b421ebb8

170
client/src/client.cpp Normal file
View File

@ -0,0 +1,170 @@
// pq-ftp/client/src/client.cpp
#include <iostream>
#include <string>
#include <vector>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fstream>
#include <sstream>
#include <oqs/oqs.h>
#include <zlib.h>
#define SERVER_IP "127.0.0.1"
#define PORT 2121
#define BUFFER_SIZE 4096
#define PQC_ALGORITHM "Kyber768"
// Data compression function
std::vector<uint8_t> compress_data(const std::vector<uint8_t>& data) {
if (data.empty()) return {};
uLongf compressed_size = compressBound(data.size());
std::vector<uint8_t> 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 <filepath>, get <filename>, 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<char> 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<uint8_t> data(buffer.begin(), buffer.end());
// Compress the data
std::vector<uint8_t> 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;
}

1
client/testfile.txt Normal file
View File

@ -0,0 +1 @@
This is a post-quantum test file. This is a post-quantum test file.

13
docker-compose.yml Normal file
View File

@ -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

44
server/Dockerfile Normal file
View File

@ -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" ]

26
server/Makefile Normal file
View File

@ -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)

1
server/liboqs Submodule

Submodule server/liboqs added at 94b421ebb8

14
server/logs/server.log Normal file
View File

@ -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.

220
server/src/server.cpp Normal file
View File

@ -0,0 +1,220 @@
// pq-ftp/server/src/server.cpp
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fstream>
#include <mutex>
#include <cstring>
// OQS for Post-Quantum Cryptography
#include <oqs/oqs.h>
// Zlib for compression
#include <zlib.h>
#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<std::mutex> 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<uint8_t> compress_data(const std::vector<uint8_t>& data) {
if (data.empty()) return {};
uLongf compressed_size = compressBound(data.size());
std::vector<uint8_t> 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;
}