Use crypto wrapper & state manager from light-openid
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Pierre HUBERT 2023-04-29 09:19:10 +02:00
parent 228d4a06e0
commit 7323852a7e
7 changed files with 19 additions and 198 deletions

10
Cargo.lock generated
View File

@ -1043,12 +1043,15 @@ checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
[[package]]
name = "light-openid"
version = "0.1.0-alpha"
version = "0.3.0-alpha"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1745cac605f9565d6fffdcf9b18ee6e51d95b16a8304533fc88e06e30537dc6f"
checksum = "7e5cec4a7a2327170125f62ce851e730ff9c44ad32389c20652ff7dcb9f719cf"
dependencies = [
"aes-gcm",
"base64",
"bincode",
"log",
"rand",
"reqwest",
"serde",
"serde_json",
@ -1199,9 +1202,7 @@ name = "oidc-test-client"
version = "0.1.0"
dependencies = [
"actix-web",
"aes-gcm",
"askama",
"base64",
"bincode",
"clap",
"env_logger",
@ -1209,7 +1210,6 @@ dependencies = [
"lazy_static",
"light-openid",
"log",
"rand",
"reqwest",
"serde",
"serde_json",

View File

@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
light-openid = "0.1.0-alpha"
light-openid = { version = "0.3.0-alpha", features=["crypto-wrapper"] }
log = "0.4.17"
env_logger = "0.10.0"
clap = { version = "4.2.4", features = ["derive", "env"] }
@ -17,7 +17,4 @@ serde = { version = "1.0.160", features = ["derive"] }
serde_json = "1.0.96"
reqwest = { version = "0.11.16", features = ["json"] }
futures-util = "0.3.28"
aes-gcm = "0.10.1"
base64 = "0.21.0"
rand = "0.8.5"
bincode = {version="2.0.0-rc.3",features=["serde"]}
bincode = "2.0.0-rc.3"

View File

@ -1,97 +0,0 @@
use std::io::ErrorKind;
use crate::Res;
use aes_gcm::aead::{Aead, OsRng};
use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce};
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use base64::Engine as _;
use bincode::{Decode, Encode};
use rand::Rng;
const NONCE_LEN: usize = 12;
pub struct CryptoWrapper {
key: Key<Aes256Gcm>,
}
impl CryptoWrapper {
/// Generate a new memory wrapper
pub fn new_random() -> Self {
Self {
key: Aes256Gcm::generate_key(&mut OsRng),
}
}
/// Encrypt some data
pub fn encrypt<T: Encode + Decode>(&self, data: &T) -> Res<String> {
let aes_key = Aes256Gcm::new(&self.key);
let nonce_bytes = rand::thread_rng().gen::<[u8; NONCE_LEN]>();
let serialized_data = bincode::encode_to_vec(data, bincode::config::standard())?;
let mut enc = aes_key
.encrypt(Nonce::from_slice(&nonce_bytes), serialized_data.as_slice())
.unwrap();
enc.extend_from_slice(&nonce_bytes);
Ok(BASE64_STANDARD.encode(enc))
}
/// Decrypt some data previously encrypted using the [`CryptoWrapper::encrypt`] method
pub fn decrypt<T: Decode>(&self, input: &str) -> Res<T> {
let bytes = BASE64_STANDARD.decode(input)?;
if bytes.len() < NONCE_LEN {
return Err(Box::new(std::io::Error::new(
ErrorKind::Other,
"Input string is smaller than nonce!",
)));
}
let (enc, nonce) = bytes.split_at(bytes.len() - NONCE_LEN);
assert_eq!(nonce.len(), NONCE_LEN);
let aes_key = Aes256Gcm::new(&self.key);
let dec = match aes_key.decrypt(Nonce::from_slice(nonce), enc) {
Ok(d) => d,
Err(e) => {
log::error!("Failed to decrypt wrapped data! {:#?}", e);
return Err(Box::new(std::io::Error::new(
ErrorKind::Other,
"Failed to decrypt wrapped data!",
)));
}
};
Ok(bincode::decode_from_slice(&dec, bincode::config::standard())?.0)
}
}
#[cfg(test)]
mod test {
use crate::crypto_wrapper::CryptoWrapper;
use bincode::{Decode, Encode};
#[derive(Encode, Decode, Eq, PartialEq, Debug)]
struct Message(String);
#[test]
fn encrypt_and_decrypt() {
let wrapper = CryptoWrapper::new_random();
let msg = Message("Pierre was here".to_string());
let enc = wrapper.encrypt(&msg).unwrap();
let dec: Message = wrapper.decrypt(&enc).unwrap();
assert_eq!(dec, msg)
}
#[test]
fn encrypt_and_decrypt_invalid() {
let wrapper_1 = CryptoWrapper::new_random();
let wrapper_2 = CryptoWrapper::new_random();
let msg = Message("Pierre was here".to_string());
let enc = wrapper_1.encrypt(&msg).unwrap();
wrapper_2.decrypt::<Message>(&enc).unwrap_err();
}
}

View File

@ -3,7 +3,4 @@ use std::error::Error;
pub type Res<A = ()> = Result<A, Box<dyn Error>>;
pub mod app_config;
pub mod crypto_wrapper;
pub mod remote_ip;
pub mod state_manager;
pub mod time_utils;

View File

@ -1,11 +1,11 @@
use actix_web::middleware::Logger;
use actix_web::{get, web, App, HttpResponse, HttpServer};
use askama::Template;
use light_openid::basic_state_manager::BasicStateManager;
use light_openid::primitives::OpenIDConfig;
use oidc_test_client::app_config::AppConfig;
use oidc_test_client::remote_ip::RemoteIP;
use oidc_test_client::state_manager::StateManager;
#[get("/assets/bootstrap.min.css")]
async fn bootstrap() -> HttpResponse {
@ -60,7 +60,7 @@ async fn home() -> HttpResponse {
}
#[get("/start")]
async fn start(remote_ip: RemoteIP) -> HttpResponse {
async fn start(remote_ip: RemoteIP, state_manager: web::Data<BasicStateManager>) -> HttpResponse {
let config = match OpenIDConfig::load_from_url(&AppConfig::get().configuration_url).await {
Ok(c) => c,
Err(e) => {
@ -69,7 +69,7 @@ async fn start(remote_ip: RemoteIP) -> HttpResponse {
}
};
let state = match StateManager::gen_state(&remote_ip) {
let state = match state_manager.gen_state(remote_ip.0) {
Ok(s) => s,
Err(e) => {
log::error!("Failed to generate state! {:?}", e);
@ -95,9 +95,13 @@ struct RedirectQuery {
}
#[get("/redirect")]
async fn redirect(remote_ip: RemoteIP, query: web::Query<RedirectQuery>) -> HttpResponse {
async fn redirect(
remote_ip: RemoteIP,
query: web::Query<RedirectQuery>,
state_manager: web::Data<BasicStateManager>,
) -> HttpResponse {
// First, validate state
if let Err(e) = StateManager::validate_state(&remote_ip, &query.state) {
if let Err(e) = state_manager.validate_state(remote_ip.0, &query.state) {
log::error!("Failed to validate state {}: {:?}", query.state, e);
return ErrorTemplate::build("State could not be validated!");
}
@ -158,13 +162,14 @@ async fn main() -> std::io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
log::info!("Init state manager");
StateManager::init();
let state_manager = web::Data::new(BasicStateManager::new());
log::info!("Will listen on {}", AppConfig::get().listen_addr);
HttpServer::new(|| {
HttpServer::new(move || {
App::new()
.wrap(Logger::default())
.app_data(state_manager.clone())
.service(bootstrap)
.service(cover)
.service(home)

View File

@ -1,72 +0,0 @@
use std::error::Error;
use std::fmt;
use crate::crypto_wrapper::CryptoWrapper;
use crate::remote_ip::RemoteIP;
use crate::time_utils::time;
use crate::Res;
use bincode::{Decode, Encode};
use std::net::IpAddr;
pub struct StateManager;
static mut WRAPPER: Option<CryptoWrapper> = None;
#[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)]
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)
}
}
impl StateManager {
pub fn init() {
unsafe {
WRAPPER = Some(CryptoWrapper::new_random());
}
}
/// Generate a new state
pub fn gen_state(ip: &RemoteIP) -> Res<String> {
let state = State::new(ip.0);
unsafe { WRAPPER.as_ref().unwrap() }.encrypt(&state)
}
/// Validate generated state
pub fn validate_state(ip: &RemoteIP, state: &str) -> Res {
let state: State = unsafe { WRAPPER.as_ref().unwrap() }.decrypt(state)?;
if state.ip != ip.0 {
return Err(Box::new(StateError::InvalidIp));
}
if state.expire < time() {
return Err(Box::new(StateError::Expired));
}
Ok(())
}
}

View File

@ -1,9 +0,0 @@
use std::time::{SystemTime, UNIX_EPOCH};
/// Get current time since epoch
pub fn time() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}