init
This commit is contained in:
3
.env.sample
Normal file
3
.env.sample
Normal file
@@ -0,0 +1,3 @@
|
||||
SECURITYHUB_ID=1
|
||||
PORT=8081
|
||||
GENERATE_RANDOM_BITS_NUM=1000
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/target
|
||||
database*.json
|
||||
.env
|
||||
1764
Cargo.lock
generated
Normal file
1764
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "dske_poc_server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4.3.1"
|
||||
dotenv = "0.15.0"
|
||||
reqwest = { version = "0.11.17", features = ["json"] }
|
||||
serde = { version = "1.0.160", features = ["derive"] }
|
||||
serde_json = "1.0.96"
|
||||
tokio = { version = "1.28.0", features = ["full"] }
|
||||
async-trait = "0.1.68"
|
||||
actix-cors = "0.6.4"
|
||||
rand = "0.8.5"
|
||||
419
README.md
Normal file
419
README.md
Normal file
@@ -0,0 +1,419 @@
|
||||
# DSKE POC (Security Hub Server)
|
||||
|
||||
## Introduction
|
||||
|
||||
This is a proof of concept for the DSKE system. The main goal is to demonstrate the algorithm in DSKE. There are a few assumptions and limitations in this proof-of-concept project:
|
||||
|
||||
1. This is not a working protocol, this project is just to prove the algorithm concept.
|
||||
2. This project is demonstrating a simple DSKE protocol, which means n = k. It is a (n, n) secret sharing scheme.
|
||||
3. The code assumes that the final key sender chooses all common security hubs with the receiver that they both register.
|
||||
|
||||
This project is separated into two parts: the client and the security hub server.
|
||||
|
||||
- [client](https://gitea.devchristam.com/devchristam/dske_poc_client)
|
||||
- [server](https://gitea.devchristam.com/devchristam/dske_poc_server)
|
||||
|
||||
You need to host multiple clients and servers to make this proof-of-concept DSKE system work.
|
||||
|
||||
## DSKE Main Phases
|
||||
|
||||
1. PSKM Generation and Distribution
|
||||
|
||||
The security hub's `POST /register_client` endpoint will generate random bits and record the client on the server side. This endpoint will return the generated random bits. The client's `POST /register_securityhub` endpoint will store these random bits so that both the security hub and the client have a copy. Note that the distribution process needs to be done manually because real PSKM distribution is done physically.
|
||||
|
||||
2. Peer Identity Establishment
|
||||
|
||||
When a client wants to share a key with another client using `POST /make_connection`, the sender client will send a request to the receiver client's `POST /agree_connection` endpoint with a list of security hubs to use in the key exchange. The receiver client will check if both the sender and receiver are registered in those security hubs using the security hub's `POST /user_list` endpoint. This endpoint will display every client ID registered in the security hub, allowing the client to identify common security hubs.
|
||||
|
||||
3. Key Agreement
|
||||
|
||||
The sender will use the `POST /make_connection` endpoint to exchange keys with the receiver.
|
||||
|
||||
1. Share Generation
|
||||
|
||||
The proof-of-concept project will use an (n, n) secret sharing scheme in a simple DSKE protocol. For simplicity, the sender will choose all the common security hubs with the receiver.
|
||||
|
||||
2. Share Distribution
|
||||
|
||||
The sender will send a request to the security hub's `POST /generate_key_instruction` endpoint to generate a key instruction A (sender's random bits) XOR B (receiver's random bits).
|
||||
|
||||
3. Key Reconstruction
|
||||
|
||||
After the security hub sends a request to the client's `POST /receive_key_instruction` endpoint, the client will calculate A ^ B ^ B to retrieve A in each request and store the calculated A in a key-value pair using the final key hash as the key. The receiver can reconstruct the final key S by executing XOR on every share in the simple DSKE protocol (n, n) secret sharing scheme.
|
||||
|
||||
4. Key Validation
|
||||
|
||||
The receiver will only reconstruct the final key if the number of received shares equals the total number of shares. The exchange process will abort if the numbers do not match.
|
||||
|
||||
## Step-by-Step Setup Guide
|
||||
|
||||
This guide will walk you through the steps to set up and run the DSKE POC server and client.
|
||||
|
||||
### Steps
|
||||
|
||||
#### 1. Clone the Repository
|
||||
|
||||
Clone the repository to your local machine using the following commands:
|
||||
|
||||
```bash
|
||||
# Clone the DSKE POC server
|
||||
git clone https://gitea.devchristam.com/devchristam/dske_poc_server.git
|
||||
|
||||
# Clone the DSKE POC client
|
||||
git clone https://gitea.devchristam.com/devchristam/dske_poc_client.git
|
||||
```
|
||||
|
||||
#### 2. Build the Project
|
||||
|
||||
Navigate to the project directory and build the project using Cargo:
|
||||
|
||||
```sh
|
||||
cargo build
|
||||
```
|
||||
|
||||
#### 3. Run the Server and Client
|
||||
|
||||
Run the server and client using the following commands. You need to use Tmux or open multiple terminal applications to host multiple servers and clients on the same machine.
|
||||
|
||||
For server:
|
||||
|
||||
```sh
|
||||
# The code supports .env for port and security hub ID settings.
|
||||
# Even if not using the .env file, please copy the .env because missing variables will show a runtime error.
|
||||
cp .env.sample .env
|
||||
# set the environment variables in .env if needed
|
||||
# cargo run -- <port> <security hub id>
|
||||
# e.g.
|
||||
cargo run -- 8081 1
|
||||
cargo run -- 8082 2
|
||||
cargo run -- 8083 3
|
||||
```
|
||||
|
||||
For client:
|
||||
|
||||
```sh
|
||||
# The code supports .env for port and security hub ID settings.
|
||||
# Even if not using the .env file, please copy the .env because missing variables will show a runtime error.
|
||||
cp .env.sample .env
|
||||
# set the environment variables in .env if needed
|
||||
# cargo run -- <port> <user id>
|
||||
# e.g.
|
||||
cargo run -- 8001 1
|
||||
cargo run -- 8002 2
|
||||
```
|
||||
|
||||
## 4. Exchange Key
|
||||
|
||||
### Steps
|
||||
|
||||
All API calls use `content-type: "application/json"`.
|
||||
|
||||
#### 1. Register Client in Security Hub
|
||||
|
||||
Use the security hub endpoint `POST /register_client` to record the client.
|
||||
|
||||
- Example (record a client running on `cargo run -- 8001 1`):
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"username": "alice",
|
||||
"url": "http://localhost:8001"
|
||||
}
|
||||
```
|
||||
|
||||
- This request will return a payload. You need to copy the `random_bits` array for step 2:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"username": "alice",
|
||||
"url": "http://localhost:8001",
|
||||
"random_bits": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Register Security Hub in the Client
|
||||
|
||||
Copy the `random_bits` array and input the same content into the client endpoint `POST /register_securityhub`.
|
||||
|
||||
- Example (record a security hub running on `cargo run -- 8081 1`):
|
||||
|
||||
```json
|
||||
{
|
||||
"securityhub_id": 1,
|
||||
"securityhub_url": "http://localhost:8081",
|
||||
"random_bits": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
After repeating steps 1 and 2, you can create a proof-of-concept DSKE system network. Then the client can exchange keys within the network.
|
||||
|
||||
#### 3. Exchange Key
|
||||
|
||||
In the client endpoint `POST /make_connection`, you can exchange the final key with another client.
|
||||
|
||||
- Example (from client `cargo run -- 8001 1` exchanging key with `cargo run -- 8002 2`):
|
||||
```json
|
||||
{
|
||||
"client_id": 2,
|
||||
"client_url": "http://localhost:8002",
|
||||
"keysize": 5
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
## API Documentation
|
||||
|
||||
### Server
|
||||
|
||||
#### `POST /register_client`
|
||||
|
||||
Registers a client in the security hub.
|
||||
|
||||
- **Input:**
|
||||
|
||||
`content-type: "application/json"`
|
||||
|
||||
```json
|
||||
{
|
||||
"id": Integer,
|
||||
"username": String,
|
||||
"url": String
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 999,
|
||||
"username": "test",
|
||||
"url": "http://localhost:8888"
|
||||
}
|
||||
```
|
||||
|
||||
- **Output:**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": Integer,
|
||||
"username": String,
|
||||
"url": String,
|
||||
"random_bits": Boolean[]
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 999,
|
||||
"username": "test",
|
||||
"url": "http://localhost:8888",
|
||||
"random_bits": [true, false, true, ...]
|
||||
}
|
||||
```
|
||||
|
||||
#### `POST /user_list`
|
||||
|
||||
Retrieves the list of registered user IDs from the security hub.
|
||||
|
||||
- **Input:**
|
||||
|
||||
No input required.
|
||||
|
||||
- **Output:**
|
||||
|
||||
```json
|
||||
Integer[]
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
[1, 2, 3, ...]
|
||||
```
|
||||
|
||||
#### `POST /generate_key_instruction`
|
||||
|
||||
Generates a key instruction for the key exchange process.
|
||||
|
||||
- **Input:**
|
||||
|
||||
`content-type: "application/json"`
|
||||
|
||||
```json
|
||||
{
|
||||
"sender_id": Integer,
|
||||
"receiver_id": Integer,
|
||||
"hash": Integer,
|
||||
"keysize": Integer
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"sender_id": 1,
|
||||
"receiver_id": 2,
|
||||
"hash": 12345,
|
||||
"keysize": 5
|
||||
}
|
||||
```
|
||||
|
||||
- **Output:**
|
||||
|
||||
```json
|
||||
Boolean[]
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
[true, false, true, ...]
|
||||
```
|
||||
|
||||
### Client
|
||||
|
||||
#### `POST /register_securityhub`
|
||||
|
||||
Registers a security hub in the client.
|
||||
|
||||
- **Input:**
|
||||
|
||||
`content-type: "application/json"`
|
||||
|
||||
```json
|
||||
{
|
||||
"securityhub_id": Integer,
|
||||
"securityhub_url": String,
|
||||
"random_bits": Boolean[]
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"securityhub_id": 1,
|
||||
"securityhub_url": "http://localhost:8081",
|
||||
"random_bits": [true, false, true, ...]
|
||||
}
|
||||
```
|
||||
|
||||
- **Output:**
|
||||
|
||||
Null
|
||||
|
||||
#### `POST /make_connection`
|
||||
|
||||
Initiates a key exchange with another client.
|
||||
|
||||
- **Input:**
|
||||
|
||||
`content-type: "application/json"`
|
||||
|
||||
```json
|
||||
{
|
||||
"client_id": Integer,
|
||||
"client_url": String,
|
||||
"keysize": Integer
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"client_id": 2,
|
||||
"client_url": "http://localhost:8002",
|
||||
"keysize": 5
|
||||
}
|
||||
```
|
||||
|
||||
- **Output:**
|
||||
|
||||
Null
|
||||
|
||||
#### `POST /agree_connection`
|
||||
|
||||
Agrees to a key exchange initiated by another client. Check the `securityhub_list` are both registered or not.
|
||||
|
||||
- **Input:**
|
||||
|
||||
`content-type: "application/json"`
|
||||
|
||||
```json
|
||||
{
|
||||
"client_id": Integer,
|
||||
"securityhub_list": Integer[],
|
||||
"keysize": Integer
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"client_id": 1,
|
||||
"securityhub_list": [1, 2],
|
||||
"keysize": 5
|
||||
}
|
||||
```
|
||||
|
||||
- **Output:**
|
||||
|
||||
Null
|
||||
|
||||
If the exchange cannot process, the API will not return `200 OK`.
|
||||
|
||||
|
||||
#### `POST /receive_key_instruction`
|
||||
|
||||
Receives a key instruction from the security hub.
|
||||
|
||||
- **Input:**
|
||||
|
||||
`content-type: "application/json"`
|
||||
|
||||
```json
|
||||
{
|
||||
"sender_id": Integer,
|
||||
"hash": Integer,
|
||||
"key_instruction": Boolean[],
|
||||
"total": Integer,
|
||||
"securityhub_id": Integer
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"sender_id": 1,
|
||||
"hash": 1234,
|
||||
"key_instruction": [true, false, ...],
|
||||
"total": 2,
|
||||
"securityhub_id": 1
|
||||
}
|
||||
```
|
||||
|
||||
- **Output:**
|
||||
|
||||
Null
|
||||
BIN
Screenshot.png
Normal file
BIN
Screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
228
src/main.rs
Normal file
228
src/main.rs
Normal file
@@ -0,0 +1,228 @@
|
||||
use actix_cors::Cors;
|
||||
use actix_web::{ http::header, web, App, HttpServer, Responder, HttpResponse };
|
||||
use serde::{ Deserialize, Serialize };
|
||||
use reqwest::Client as HttpClient;
|
||||
use std::sync::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
use dotenv::dotenv;
|
||||
use rand::Rng;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct User {
|
||||
id: u64,
|
||||
username: String,
|
||||
url: String,
|
||||
random_bits: Vec<bool>
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct RegisterUser {
|
||||
id: u64,
|
||||
username: String,
|
||||
url: String
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct GenerateKeyInstruction {
|
||||
sender_id: u64,
|
||||
receiver_id: u64,
|
||||
hash: u64,
|
||||
total: u64,
|
||||
keysize: u64
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct Database {
|
||||
users: HashMap<u64, User>
|
||||
}
|
||||
|
||||
impl Database {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
users: HashMap::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_user(&self, id: &u64) -> Option<&User> {
|
||||
self.users.get(id)
|
||||
}
|
||||
|
||||
fn update_user(&mut self, user: User) {
|
||||
self.users.insert(user.id, user);
|
||||
}
|
||||
|
||||
fn insert_user(&mut self, user: User) {
|
||||
self.users.insert(user.id, user);
|
||||
}
|
||||
|
||||
fn save_to_file(&self) -> std::io::Result<()> {
|
||||
let data: String = serde_json::to_string(&self)?;
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let file_name = format!("database-{}.json", args.get(2).unwrap_or(&env::var("SECURITYHUB_ID").unwrap()));
|
||||
let mut file: fs::File = fs::File::create(file_name)?;
|
||||
file.write_all(data.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_from_file() -> std::io::Result<Self> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let file_name = format!("database-{}.json", args.get(2).unwrap_or(&env::var("SECURITYHUB_ID").unwrap()));
|
||||
let file_content: String = fs::read_to_string(file_name)?;
|
||||
let db: Database = serde_json::from_str(&file_content)?;
|
||||
Ok(db)
|
||||
}
|
||||
}
|
||||
|
||||
struct AppState {
|
||||
db: Mutex<Database>
|
||||
}
|
||||
|
||||
async fn user_list(app_state: web::Data<AppState>) -> impl Responder {
|
||||
let db: std::sync::MutexGuard<Database> = app_state.db.lock().unwrap();
|
||||
let user_ids: Vec<u64> = db.users.keys().cloned().collect();
|
||||
HttpResponse::Ok().json(user_ids)
|
||||
}
|
||||
|
||||
async fn register_client(app_state: web::Data<AppState>, user: web::Json<RegisterUser>) -> impl Responder {
|
||||
let mut db: std::sync::MutexGuard<Database> = app_state.db.lock().unwrap();
|
||||
let mut random_bits: Vec<bool> = Vec::new();
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
// number of random bits to generate
|
||||
let num_bits = env::var("GENERATE_RANDOM_BITS_NUM").unwrap().parse::<u64>().unwrap();
|
||||
for _ in 0..num_bits {
|
||||
let random_bit: u8 = rng.gen_range(0..=1);
|
||||
random_bits.push(random_bit == 1);
|
||||
}
|
||||
|
||||
let create_user = User {
|
||||
id: user.id,
|
||||
username: user.username.clone(),
|
||||
url: user.url.clone(),
|
||||
random_bits
|
||||
};
|
||||
|
||||
db.insert_user(create_user.clone());
|
||||
let _ = db.save_to_file();
|
||||
HttpResponse::Ok().json(create_user)
|
||||
}
|
||||
|
||||
async fn generate_key_instruction(app_state: web::Data<AppState>, generate_key_instruction: web::Json<GenerateKeyInstruction>) -> impl Responder {
|
||||
let mut db: std::sync::MutexGuard<Database> = app_state.db.lock().unwrap();
|
||||
|
||||
let sender = db.get_user(&generate_key_instruction.sender_id);
|
||||
match sender {
|
||||
Some(_) => (),
|
||||
None => return HttpResponse::NotFound().finish()
|
||||
}
|
||||
let mut sender = sender.unwrap().clone();
|
||||
|
||||
let receiver = db.get_user(&generate_key_instruction.receiver_id);
|
||||
match receiver {
|
||||
Some(_) => (),
|
||||
None => return HttpResponse::NotFound().finish()
|
||||
}
|
||||
let mut receiver = receiver.unwrap().clone();
|
||||
|
||||
// check remaining random bits
|
||||
if sender.random_bits.len() < generate_key_instruction.keysize as usize || receiver.random_bits.len() < generate_key_instruction.keysize as usize{
|
||||
return HttpResponse::BadRequest().finish()
|
||||
}
|
||||
|
||||
// calculate key instruction A ^ B
|
||||
let key_instruction: Vec<bool> = (0..generate_key_instruction.keysize)
|
||||
.map(|i| sender.random_bits[i as usize] ^ receiver.random_bits[i as usize])
|
||||
.collect();
|
||||
|
||||
// reduce the sender random bits by keysize
|
||||
sender.random_bits = sender.random_bits.iter().skip(generate_key_instruction.keysize as usize).cloned().collect();
|
||||
db.update_user(sender.clone());
|
||||
|
||||
let client = HttpClient::new();
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let securityhub_id_env = env::var("SECURITYHUB_ID").unwrap();
|
||||
let securityhub_id: u64 = args.get(2).unwrap_or(&securityhub_id_env).parse().unwrap();
|
||||
let res = client.post(&format!("{}/receive_key_instruction", receiver.url))
|
||||
.json(&serde_json::json!({
|
||||
"sender_id": sender.id,
|
||||
"securityhub_id": securityhub_id,
|
||||
"hash": generate_key_instruction.hash,
|
||||
"total": generate_key_instruction.total,
|
||||
"key_instruction": key_instruction.clone()
|
||||
}))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
let mut flag = false;
|
||||
match res {
|
||||
Ok(_) => {
|
||||
println!("Key instruction sent to client {}", receiver.id);
|
||||
receiver.random_bits = receiver.random_bits.iter().skip(generate_key_instruction.keysize as usize).cloned().collect();
|
||||
db.update_user(receiver.clone());
|
||||
flag = true;
|
||||
},
|
||||
Err(err) => {
|
||||
println!("Error connecting to client {}: {:?}", receiver.id, err);
|
||||
}
|
||||
}
|
||||
let _ = db.save_to_file();
|
||||
if flag {
|
||||
HttpResponse::Ok().json(key_instruction)
|
||||
} else {
|
||||
HttpResponse::NotFound().finish()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
dotenv().ok();
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let db: Database = match Database::load_from_file() {
|
||||
Ok(db) => db,
|
||||
Err(_) => Database::new()
|
||||
};
|
||||
|
||||
let data: web::Data<AppState> = web::Data::new(AppState {
|
||||
db: Mutex::new(db)
|
||||
});
|
||||
|
||||
// if args[1] is set, use the port from the args
|
||||
// else use the port from the .env
|
||||
let port_env = env::var("PORT").unwrap();
|
||||
let port = args.get(1).unwrap_or(&port_env);
|
||||
println!("Starting server on port {}", port);
|
||||
|
||||
// if args[2] is set, use the securityhub id from the args
|
||||
// else use the securityhub id from the .env
|
||||
let securityhub_id_env = env::var("SECURITYHUB_ID").unwrap();
|
||||
let securityhub_id = args.get(2).unwrap_or(&securityhub_id_env);
|
||||
println!("Security Hub ID: {}", securityhub_id);
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap(
|
||||
Cors::permissive()
|
||||
.allowed_origin_fn(|origin, _req_head| {
|
||||
origin.as_bytes().starts_with(b"http://localhost") || origin == "null"
|
||||
})
|
||||
.allowed_methods(vec!["GET", "POST", "PUT", "DELETE"])
|
||||
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
|
||||
.allowed_header(header::CONTENT_TYPE)
|
||||
.supports_credentials()
|
||||
.max_age(3600)
|
||||
)
|
||||
.app_data(data.clone())
|
||||
.route("/register_client", web::post().to(register_client))
|
||||
.route("/generate_key_instruction", web::post().to(generate_key_instruction))
|
||||
.route("/user_list", web::post().to(user_list))
|
||||
})
|
||||
|
||||
.bind(format!("127.0.0.1:{}", port))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
Reference in New Issue
Block a user