Develop first version #1
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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
112
src/basic_state_manager.rs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
@ -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
19
src/time_utils.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user