Add basic state manager
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Pierre HUBERT 2023-04-29 09:01:58 +02:00
parent 8962c9516a
commit 31c7db4717
5 changed files with 137 additions and 2 deletions

2
Cargo.lock generated
View File

@ -478,7 +478,7 @@ checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]] [[package]]
name = "light-openid" name = "light-openid"
version = "0.2.1-alpha" version = "0.3.0-alpha"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"base64", "base64",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "light-openid" name = "light-openid"
version = "0.2.1-alpha" version = "0.3.0-alpha"
edition = "2021" edition = "2021"
repository = "https://gitea.communiquons.org/pierre/light-openid" repository = "https://gitea.communiquons.org/pierre/light-openid"
authors = ["Pierre HUBERT <pierre.git@communiquons.org>"] authors = ["Pierre HUBERT <pierre.git@communiquons.org>"]

112
src/basic_state_manager.rs Normal file
View File

@ -0,0 +1,112 @@
//! # Basic state manager
//!
//! The state manager included in this module can be used to
//! generate basic and stateless states for applications with
//! minimum security requirements. The states contains the IP
//! address of the client in an encrypted way, and expires 15
//! minutes after issuance.
use std::error::Error;
use std::fmt;
use crate::crypto_wrapper::CryptoWrapper;
use crate::time_utils::time;
use bincode::{Decode, Encode};
use std::net::IpAddr;
#[derive(Encode, Decode, Debug)]
struct State {
ip: IpAddr,
expire: u64,
}
impl State {
pub fn new(ip: IpAddr) -> Self {
Self {
ip,
expire: time() + 15 * 60,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum StateError {
InvalidIp,
Expired,
}
impl Error for StateError {}
impl fmt::Display for StateError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "StateManager error {:?}", self)
}
}
/// Basic state manager. Can be used to prevent CRSF by encrypting
/// a token containing a lifetime and the IP address of the user
pub struct BasicStateManager(CryptoWrapper);
impl BasicStateManager {
/// Initialize the state manager by creating a random encryption key. This function
/// should be called only one, ideally in the main function of the application
pub fn new() -> Self {
Self(CryptoWrapper::new_random())
}
/// Initialize state manager with a given CryptoWrapper
pub fn new_with_wrapper(wrapper: CryptoWrapper) -> Self {
Self(wrapper)
}
/// Generate a new state
pub fn gen_state(&self, ip: IpAddr) -> Result<String, Box<dyn Error>> {
let state = State::new(ip);
self.0.encrypt(&state)
}
/// Validate given state on callback URL
pub fn validate_state(&self, ip: IpAddr, state: &str) -> Result<(), Box<dyn Error>> {
let state: State = self.0.decrypt(state)?;
if state.ip != ip {
return Err(Box::new(StateError::InvalidIp));
}
if state.expire < time() {
return Err(Box::new(StateError::Expired));
}
Ok(())
}
}
impl Default for BasicStateManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod test {
use crate::basic_state_manager::BasicStateManager;
use std::net::{IpAddr, Ipv4Addr};
const IP_1: IpAddr = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
const IP_2: IpAddr = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2));
#[test]
fn valid_state() {
let manager = BasicStateManager::new();
let state = manager.gen_state(IP_1).unwrap();
assert!(manager.validate_state(IP_1, &state).is_ok());
}
#[test]
fn invalid_ip() {
let manager = BasicStateManager::new();
let state = manager.gen_state(IP_1).unwrap();
assert!(manager.validate_state(IP_2, &state).is_err());
}
}

View File

@ -6,6 +6,10 @@
pub mod client; pub mod client;
pub mod primitives; pub mod primitives;
mod time_utils;
#[cfg(feature = "crypto-wrapper")] #[cfg(feature = "crypto-wrapper")]
pub mod crypto_wrapper; pub mod crypto_wrapper;
#[cfg(feature = "crypto-wrapper")]
pub mod basic_state_manager;

19
src/time_utils.rs Normal file
View File

@ -0,0 +1,19 @@
use std::time::{SystemTime, UNIX_EPOCH};
/// Get current time since epoch, in seconds
pub fn time() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
#[cfg(test)]
mod test {
use crate::time_utils::time;
#[test]
fn time_is_recent_enough() {
assert!(time() > 1682750570);
}
}