Skip to content

Commit

Permalink
Add initial game connection token implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
SirLynix committed Jul 14, 2024
1 parent b6af5af commit 23b7c27
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 16 deletions.
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ edition = "2021"
actix-governor = "0.5"
actix-web = "4.4"
base64 = "0.22"
cached = { version = "0.51", features = ["async"] }
cached = { version = "0.52", features = ["async"] }
chacha20poly1305 = "0.10"
confy = "0.6"
deadpool-postgres = "0.14"
deku = "0.17"
env_logger = "0.11"
futures = "0.3"
octocrab = "0.38"
Expand All @@ -20,8 +22,9 @@ reqwest = { version = "0.12", features = ["charset", "http2", "macos-system-conf
secure-string = { version = "0.3", features = ["serde"] }
semver = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_with = { version = "3.8", features = ["base64", "time_0_3"] }
strum = { version = "0.26", features = ["derive"] }
tokio = "1.37"
tokio-postgres = { version = "0.7", features = ["with-uuid-1"] }
url = "2.5"
uuid = { version = "1.8", features = ["v4", "macro-diagnostics"] }
uuid = { version = "1.8", features = ["v4", "macro-diagnostics"] }
28 changes: 26 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
use std::time::Duration;

use secure_string::SecureString;
use serde::{Deserialize, Serialize};
use serde_with::base64::Base64;
use serde_with::serde_as;
use serde_with::DurationSeconds;

#[serde_as]
#[derive(Serialize, Deserialize)]
pub struct ApiConfig {
pub listen_address: String,
Expand All @@ -9,14 +15,23 @@ pub struct ApiConfig {
pub game_repository: String,
pub updater_repository: String,
pub updater_filename: String,
pub cache_lifespan: u64,
#[serde_as(as = "DurationSeconds<u64>")]
pub cache_lifespan: Duration,
pub github_pat: Option<SecureString>,
pub db_host: String,
pub db_user: String,
pub db_password: SecureString,
pub db_database: String,
pub player_nickname_maxlength: usize,
pub player_allow_non_ascii: bool,
pub game_api_token: String,
pub game_api_url: String,
pub game_server_address: String,
pub game_server_port: u16,
#[serde_as(as = "DurationSeconds<u64>")]
pub game_api_token_duration: Duration,
#[serde_as(as = "Base64")]
pub connection_token_key: [u8; 32],
}

impl Default for ApiConfig {
Expand All @@ -28,14 +43,23 @@ impl Default for ApiConfig {
game_repository: "ThisSpaceOfMine".to_string(),
updater_filename: "this_updater_of_mine".to_string(),
updater_repository: "ThisUpdaterOfMine".to_string(),
cache_lifespan: 5 * 60,
cache_lifespan: Duration::from_secs(5 * 60),
github_pat: None,
db_host: "localhost".to_string(),
db_user: "api".to_string(),
db_password: "password".into(),
db_database: "tsom_db".to_string(),
player_nickname_maxlength: 16,
player_allow_non_ascii: false,
game_api_token: "".into(),
game_api_url: "http://localhost".to_string(),
game_server_address: "localhost".to_string(),
game_server_port: 29536,
game_api_token_duration: Duration::from_secs(5 * 60),
connection_token_key: [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31,
],
}
}
}
34 changes: 34 additions & 0 deletions src/game_connection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use actix_web::{post, web, HttpResponse, Responder};
use uuid::Uuid;

use crate::{
app_data::AppData,
errors::api::RouteError,
game_connection_token::{
GameConnectionToken, GameConnectionTokenPrivate, GamePlayerData, GameServerAddress,
},
};

#[post("/v1/player/test")]
async fn player_test(app_data: web::Data<AppData>) -> Result<impl Responder, RouteError> {
let player_data = GamePlayerData::generate(Uuid::new_v4(), "SirLynix".into());

let server_address = GameServerAddress {
address: app_data.config.game_server_address.clone(),
port: app_data.config.game_server_port,
};

let private_token = GameConnectionTokenPrivate::generate(
app_data.config.game_api_url.clone(),
app_data.config.game_api_token.clone(),
player_data,
);
let token = GameConnectionToken::generate(
app_data.config.connection_token_key.into(),
app_data.config.game_api_token_duration,
server_address,
private_token,
);

Ok(HttpResponse::Ok().json(token))
}
174 changes: 174 additions & 0 deletions src/game_connection_token.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use chacha20poly1305::{
aead::{AeadCore, AeadMutInPlace, KeyInit, OsRng},
XChaCha20Poly1305,
};
use deku::prelude::*;
use serde::Serialize;
use serde_with::{base64::Base64, serde_as};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use uuid::Uuid;

const XCHACHA20POLY1305_IETF_ABYTES: usize = 16; //< todo: Get it from chacha20poly1305::Tag

#[serde_as]
#[derive(Debug, Serialize)]
pub struct GameEncryptionKeys {
#[serde_as(as = "Base64")]
pub client_to_server: chacha20poly1305::Key,
#[serde_as(as = "Base64")]
pub server_to_client: chacha20poly1305::Key,
}

impl GameEncryptionKeys {
pub fn generate() -> Self {
Self {
client_to_server: XChaCha20Poly1305::generate_key(&mut OsRng),
server_to_client: XChaCha20Poly1305::generate_key(&mut OsRng),
}
}
}

#[derive(Debug, Serialize)]
pub struct GameServerAddress {
pub address: String,
pub port: u16,
}

#[derive(Debug, DekuWrite)]
#[deku(endian = "little")]
pub struct GameConnectionTokenAdditionalData {
pub token_version: u32,
pub expire_timestamp: u64,
#[deku(writer = "deku_helper_write_key(deku::writer, &self.client_to_server_key)")]
pub client_to_server_key: chacha20poly1305::Key,
#[deku(writer = "deku_helper_write_key(deku::writer, &self.server_to_client_key)")]
pub server_to_client_key: chacha20poly1305::Key,
}

#[serde_as]
#[derive(Debug, Serialize)]
pub struct GameConnectionToken {
token_version: u32,
#[serde_as(as = "Base64")]
token_nonce: chacha20poly1305::XNonce,
creation_timestamp: u64,
expire_timestamp: u64,
encryption_keys: GameEncryptionKeys,
game_server: GameServerAddress,
#[serde_as(as = "Base64")]
private_token_data: Vec<u8>,
}

impl GameConnectionToken {
pub fn generate(
token_key: chacha20poly1305::Key,
duration: Duration,
server_address: GameServerAddress,
private_token: GameConnectionTokenPrivate,
) -> Self {
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();

let encryption_keys = GameEncryptionKeys::generate();

let token_version = 1u32;
let expire_timestamp = (timestamp + duration).as_secs();

let additional_data = GameConnectionTokenAdditionalData {
token_version,
expire_timestamp,
client_to_server_key: encryption_keys.client_to_server.clone(),
server_to_client_key: encryption_keys.server_to_client.clone(),
};

let additional_data_bytes = additional_data.to_bytes().unwrap();

let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);

let mut private_token_bytes = private_token.to_bytes().unwrap();
private_token_bytes.resize(private_token_bytes.len() + XCHACHA20POLY1305_IETF_ABYTES, 0);

let mut cipher = XChaCha20Poly1305::new(&token_key);
cipher
.encrypt_in_place(
&nonce,
additional_data_bytes.as_slice(),
&mut private_token_bytes,
)
.unwrap();

Self {
token_version,
token_nonce: nonce,
creation_timestamp: timestamp.as_secs(),
expire_timestamp: (timestamp + duration).as_secs(),
encryption_keys,
game_server: server_address,
private_token_data: private_token_bytes,
}
}
}

#[derive(Debug, DekuWrite)]
#[deku(endian = "endian", ctx = "endian: deku::ctx::Endian")]
pub struct GamePlayerData {
#[deku(writer = "deku_helper_write_uuid(deku::writer, &self.uuid)")]
uuid: Uuid,
#[deku(writer = "deku_helper_write_str(deku::writer, &self.nickname)")]
nickname: String,
}

impl GamePlayerData {
pub fn generate(uuid: Uuid, nickname: String) -> Self {
Self { uuid, nickname }
}
}

#[derive(Debug, DekuWrite)]
#[deku(endian = "little")]
pub struct GameConnectionTokenPrivate {
#[deku(writer = "deku_helper_write_str(deku::writer, &self.api_token)")]
api_token: String,
#[deku(writer = "deku_helper_write_str(deku::writer, &self.api_url)")]
api_url: String,
player_data: GamePlayerData,
}

impl GameConnectionTokenPrivate {
pub fn generate(
game_api_url: String,
game_api_token: String,
player_data: GamePlayerData,
) -> Self {
Self {
api_token: game_api_token,
api_url: game_api_url,
player_data,
}
}
}

fn deku_helper_write_key<W: std::io::Write>(
writer: &mut Writer<W>,
value: &chacha20poly1305::Key,
) -> Result<(), DekuError> {
let str_bytes = value.as_slice();
str_bytes.to_writer(writer, ())
}

fn deku_helper_write_str<W: std::io::Write>(
writer: &mut Writer<W>,
value: &str,
) -> Result<(), DekuError> {
let str_bytes = value.as_bytes();
let str_len = str_bytes.len() as u32;
str_len.to_writer(writer, ())?;
str_bytes.to_writer(writer, ())
}

fn deku_helper_write_uuid<W: std::io::Write>(
writer: &mut Writer<W>,
value: &Uuid,
) -> Result<(), DekuError> {
let str = value.to_bytes_le();
str.to_writer(writer, ())
}
6 changes: 5 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use actix_governor::{Governor, GovernorConfig, GovernorConfigBuilder};
use actix_web::{middleware, web, App, HttpServer};
use cached::TimedCache;
use confy::ConfyError;
use game_connection::player_test;
use std::sync::Mutex;

use crate::app_data::AppData;
Expand All @@ -14,6 +15,8 @@ mod app_data;
mod config;
mod errors;
mod fetcher;
mod game_connection;
mod game_connection_token;
mod game_data;
mod players;
mod version;
Expand Down Expand Up @@ -65,7 +68,7 @@ async fn main() -> Result<(), std::io::Error> {
let bind_address = format!("{}:{}", config.listen_address, config.listen_port);

let data_config = web::Data::new(AppData {
cache: Mutex::new(TimedCache::with_lifespan(config.cache_lifespan)), // 5min
cache: Mutex::new(TimedCache::with_lifespan(config.cache_lifespan.as_secs())), // 5min
config,
fetcher,
});
Expand All @@ -86,6 +89,7 @@ async fn main() -> Result<(), std::io::Error> {
.app_data(pg_pool.clone())
.service(game_version)
.service(player_authenticate)
.service(player_test)
.service(
web::scope("")
.wrap(Governor::new(&player_create_governor_conf))
Expand Down
Loading

0 comments on commit 23b7c27

Please sign in to comment.