initial commit: added very basic symmetric key service api
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "symmetric-key-service"
|
||||
version = "0.1.0"
|
||||
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "symmetric-key-service"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
// Design a Symmetric Key Distribution Service"
|
||||
//
|
||||
// You are building a Key Management Entity (KME)
|
||||
// a service responsible for generating, storing, and distributing pre-shared
|
||||
// symmetric keys to authenticated clients (e.g. network gateways, endpoints).
|
||||
// Design and implement a core slice of this system in Rust.
|
||||
//
|
||||
// Requirements:
|
||||
// Clients authenticate and request a key by providing a session_id and peer_id
|
||||
// The KME returns a fresh 256-bit key, ensuring both sides of a session receive the same key
|
||||
// Keys are single-use: once consumed, they cannot be re-issued
|
||||
// The service must handle concurrent requests safely
|
||||
// Keys not consumed within 60 seconds should expire
|
||||
//
|
||||
// Deliverables:
|
||||
// * A Rust implementation of the core KME logic
|
||||
// (not necessarily a full HTTP server — a library interface is fine)
|
||||
// * A brief design doc covering: your data structures, concurrency strategy,
|
||||
// and how you'd extend this to multiple KME nodes
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
enum KeyEntryState {
|
||||
Active { retrieved_by: Vec<String> },
|
||||
Consumed,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum KmeError {
|
||||
KeyConsumed,
|
||||
KeyExpired,
|
||||
}
|
||||
|
||||
struct KeyEntry {
|
||||
key: [u8; 32], // 256 bits
|
||||
creation: Instant,
|
||||
state: KeyEntryState,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct KeyResponse {
|
||||
pub key_id: String, // stable identifier for this key
|
||||
pub key: [u8; 32],
|
||||
}
|
||||
|
||||
type SessionId = [u8; 16]; // similar to uuid
|
||||
type PeerId = u32;
|
||||
type Peer = (SessionId, PeerId);
|
||||
|
||||
struct Auth {
|
||||
user_count: u32,
|
||||
session_count: u8,
|
||||
}
|
||||
|
||||
impl Auth {
|
||||
pub fn login(&mut self) -> Result<(SessionId, PeerId), String>{
|
||||
self.user_count+=1;
|
||||
self.session_count+=1;
|
||||
// give out two session ids in a row to simulate functional auth service
|
||||
// where two peers who want to communicate will be given the same session id
|
||||
let session_id = self.session_count/2;
|
||||
Ok(([session_id;16], self.user_count))
|
||||
}
|
||||
}
|
||||
|
||||
struct Kme {
|
||||
keys: Arc<Mutex<HashMap<SessionId,KeyEntry>>>,
|
||||
}
|
||||
|
||||
|
||||
impl Kme {
|
||||
fn new() -> Self {
|
||||
Self { keys: Arc::new(Mutex::new(HashMap::new())) }
|
||||
}
|
||||
|
||||
// todo: service should handle concurrent requests safely
|
||||
pub fn request_key(&self, peer: Peer) -> Result<KeyResponse, KmeError> {
|
||||
let (session_id, _peer_id) = peer;
|
||||
let mut keys = self.keys.lock().unwrap();
|
||||
|
||||
// keys should be single use
|
||||
if let Some(entry) = keys.get_mut(&session_id) {
|
||||
let diff = Instant::now() - entry.creation;
|
||||
// keys will timeout
|
||||
if diff > Duration::from_secs(1) { // 1 second for feasible testing
|
||||
return Err(KmeError::KeyExpired);
|
||||
}
|
||||
if let KeyEntryState::Consumed = entry.state {
|
||||
// third peer: key already consumed
|
||||
return Err(KmeError::KeyConsumed);
|
||||
}
|
||||
// second peer: return the same key
|
||||
entry.state = KeyEntryState::Consumed;
|
||||
// todo: update retrieved_by as well? why needed?
|
||||
Ok(KeyResponse { key_id: String::new(), key: entry.key })
|
||||
} else {
|
||||
// first peer: generate and store a new key
|
||||
let key = [0u8; 32]; // TODO: generate real key bytes
|
||||
keys.insert(session_id, KeyEntry {
|
||||
key,
|
||||
creation: Instant::now(),
|
||||
state: KeyEntryState::Active { retrieved_by: vec![] },
|
||||
});
|
||||
Ok(KeyResponse { key_id: String::new(), key })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut auth = Auth{ user_count: 0, session_count: 1};
|
||||
let first_peer = auth.login().unwrap(); // assume fine for now
|
||||
let second_peer = auth.login().unwrap(); // assume fine for now
|
||||
|
||||
let kme = Kme::new();
|
||||
let first_key = kme.request_key((first_peer.0, second_peer.1)).unwrap();
|
||||
let second_key = kme.request_key((second_peer.0, first_peer.1)).unwrap();
|
||||
assert_eq!(first_key, second_key);
|
||||
|
||||
let dupe_attempt = kme.request_key((second_peer.0, first_peer.1)).unwrap_err();
|
||||
assert_eq!(dupe_attempt, KmeError::KeyConsumed);
|
||||
|
||||
let third_peer = auth.login().unwrap();
|
||||
// third_peer somehow hijacks second peer's session_id
|
||||
let third_key = kme.request_key((second_peer.0, third_peer.1)).unwrap_err();
|
||||
assert_eq!(third_key, KmeError::KeyConsumed);
|
||||
|
||||
let forth_peer = auth.login().unwrap();
|
||||
let _third_key = kme.request_key((third_peer.0, forth_peer.1)).unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
let forth_key = kme.request_key((forth_peer.0, third_peer.1)).unwrap_err();
|
||||
assert_eq!(forth_key, KmeError::KeyExpired);
|
||||
}
|
||||
Reference in New Issue
Block a user