This commit is contained in:
		
							
								
								
									
										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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user