mirror of
https://github.com/nitnelave/lldap.git
synced 2023-04-12 14:25:13 +00:00
Implement password checking using opaque
This commit is contained in:
parent
86bfd37b70
commit
3c916a2530
3
.gitignore
vendored
3
.gitignore
vendored
@ -12,3 +12,6 @@
|
|||||||
*.db
|
*.db
|
||||||
*.db-shm
|
*.db-shm
|
||||||
*.db-wal
|
*.db-wal
|
||||||
|
|
||||||
|
# Server private key
|
||||||
|
server_key
|
||||||
|
75
Cargo.lock
generated
75
Cargo.lock
generated
@ -698,6 +698,72 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.12.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"darling_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.12.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.12.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_builder"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d13202debe11181040ae9063d739fa32cfcaaebe2275fe387703460ae2365b30"
|
||||||
|
dependencies = [
|
||||||
|
"derive_builder_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_builder_core"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "66e616858f6187ed828df7c64a6d71720d83767a7f19740b2d1b6fe6327b36e5"
|
||||||
|
dependencies = [
|
||||||
|
"darling",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_builder_macro"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73"
|
||||||
|
dependencies = [
|
||||||
|
"derive_builder_core",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "0.99.14"
|
version = "0.99.14"
|
||||||
@ -1088,6 +1154,12 @@ version = "1.4.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68"
|
checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ident_case"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@ -1253,6 +1325,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"cron",
|
"cron",
|
||||||
|
"derive_builder",
|
||||||
"figment",
|
"figment",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -1263,8 +1336,8 @@ dependencies = [
|
|||||||
"lldap_model",
|
"lldap_model",
|
||||||
"log",
|
"log",
|
||||||
"mockall",
|
"mockall",
|
||||||
|
"opaque-ke",
|
||||||
"rand 0.8.3",
|
"rand 0.8.3",
|
||||||
"rust-argon2",
|
|
||||||
"sea-query",
|
"sea-query",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -12,11 +12,11 @@ actix-server = "2.0.0-beta.3"
|
|||||||
actix-service = "2.0.0"
|
actix-service = "2.0.0"
|
||||||
actix-web = "4.0.0-beta.3"
|
actix-web = "4.0.0-beta.3"
|
||||||
anyhow = "*"
|
anyhow = "*"
|
||||||
rust-argon2 = "0.8"
|
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
chrono = { version = "*", features = [ "serde" ]}
|
chrono = { version = "*", features = [ "serde" ]}
|
||||||
clap = "3.0.0-beta.2"
|
clap = "3.0.0-beta.2"
|
||||||
cron = "*"
|
cron = "*"
|
||||||
|
derive_builder = "0.10.2"
|
||||||
futures = "*"
|
futures = "*"
|
||||||
futures-util = "*"
|
futures-util = "*"
|
||||||
hmac = "0.10"
|
hmac = "0.10"
|
||||||
@ -25,6 +25,7 @@ jwt = "0.13"
|
|||||||
ldap3_server = "*"
|
ldap3_server = "*"
|
||||||
lldap_model = { path = "model" }
|
lldap_model = { path = "model" }
|
||||||
log = "*"
|
log = "*"
|
||||||
|
opaque-ke = "0.5"
|
||||||
serde = "*"
|
serde = "*"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
sha2 = "0.9"
|
sha2 = "0.9"
|
||||||
|
@ -9,6 +9,41 @@ pub enum AuthenticationError {
|
|||||||
|
|
||||||
pub type AuthenticationResult<T> = std::result::Result<T, AuthenticationError>;
|
pub type AuthenticationResult<T> = std::result::Result<T, AuthenticationError>;
|
||||||
|
|
||||||
|
/// Wrapper around an opaque KeyPair to have type-checked public and private keys.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct KeyPair(pub opaque_ke::keypair::KeyPair<<DefaultSuite as CipherSuite>::Group>);
|
||||||
|
|
||||||
|
pub struct PublicKey<'a>(&'a opaque_ke::keypair::Key);
|
||||||
|
pub struct PrivateKey<'a>(&'a opaque_ke::keypair::Key);
|
||||||
|
|
||||||
|
impl <'a> std::ops::Deref for PublicKey<'a> {
|
||||||
|
type Target = &'a opaque_ke::keypair::Key;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <'a> std::ops::Deref for PrivateKey<'a> {
|
||||||
|
type Target = &'a opaque_ke::keypair::Key;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyPair {
|
||||||
|
pub fn private(&self) -> PrivateKey<'_> {
|
||||||
|
PrivateKey(self.0.private())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn public(&self) -> PublicKey<'_> {
|
||||||
|
PublicKey(self.0.public())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_private_key_slice(input: &[u8]) -> std::result::Result<Self, opaque_ke::errors::InternalPakeError> {
|
||||||
|
opaque_ke::keypair::KeyPair::<<DefaultSuite as CipherSuite>::Group>::from_private_key_slice(input).map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A wrapper around argon2 to provide the [`opaque_ke::slow_hash::SlowHash`] trait.
|
/// A wrapper around argon2 to provide the [`opaque_ke::slow_hash::SlowHash`] trait.
|
||||||
pub struct ArgonHasher;
|
pub struct ArgonHasher;
|
||||||
|
|
||||||
@ -56,11 +91,11 @@ impl CipherSuite for DefaultSuite {
|
|||||||
/// deserialized using the type's `deserialize` method.
|
/// deserialized using the type's `deserialize` method.
|
||||||
#[cfg(feature = "opaque_client")]
|
#[cfg(feature = "opaque_client")]
|
||||||
pub mod client {
|
pub mod client {
|
||||||
use super::*;
|
pub use super::*;
|
||||||
/// Methods to register a new user, from the client side.
|
/// Methods to register a new user, from the client side.
|
||||||
pub mod registration {
|
pub mod registration {
|
||||||
use super::*;
|
pub use super::*;
|
||||||
use opaque_ke::{
|
pub use opaque_ke::{
|
||||||
ClientRegistration, ClientRegistrationFinishParameters, ClientRegistrationFinishResult,
|
ClientRegistration, ClientRegistrationFinishParameters, ClientRegistrationFinishResult,
|
||||||
ClientRegistrationStartResult, RegistrationResponse,
|
ClientRegistrationStartResult, RegistrationResponse,
|
||||||
};
|
};
|
||||||
@ -91,8 +126,8 @@ pub mod client {
|
|||||||
|
|
||||||
/// Methods to login, from the client side.
|
/// Methods to login, from the client side.
|
||||||
pub mod login {
|
pub mod login {
|
||||||
use super::*;
|
pub use super::*;
|
||||||
use opaque_ke::{
|
pub use opaque_ke::{
|
||||||
ClientLogin, ClientLoginFinishParameters, ClientLoginFinishResult,
|
ClientLogin, ClientLoginFinishParameters, ClientLoginFinishResult,
|
||||||
ClientLoginStartParameters, ClientLoginStartResult, CredentialResponse,
|
ClientLoginStartParameters, ClientLoginStartResult, CredentialResponse,
|
||||||
};
|
};
|
||||||
@ -123,24 +158,24 @@ pub mod client {
|
|||||||
/// intermediate results must be sent to the client using the serialized `.message`.
|
/// intermediate results must be sent to the client using the serialized `.message`.
|
||||||
#[cfg(feature = "opaque_server")]
|
#[cfg(feature = "opaque_server")]
|
||||||
pub mod server {
|
pub mod server {
|
||||||
use super::*;
|
pub use super::*;
|
||||||
use opaque_ke::{keypair::Key, ServerRegistration};
|
pub use opaque_ke::ServerRegistration;
|
||||||
/// Methods to register a new user, from the server side.
|
/// Methods to register a new user, from the server side.
|
||||||
pub mod registration {
|
pub mod registration {
|
||||||
use super::*;
|
pub use super::*;
|
||||||
use opaque_ke::{RegistrationRequest, RegistrationUpload, ServerRegistrationStartResult};
|
pub use opaque_ke::{RegistrationRequest, RegistrationUpload, ServerRegistrationStartResult};
|
||||||
/// Start a registration process, from a request sent by the client.
|
/// Start a registration process, from a request sent by the client.
|
||||||
///
|
///
|
||||||
/// The result must be kept for the next step.
|
/// The result must be kept for the next step.
|
||||||
pub fn start_registration<R: RngCore + CryptoRng>(
|
pub fn start_registration<R: RngCore + CryptoRng>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
registration_request: RegistrationRequest<DefaultSuite>,
|
registration_request: RegistrationRequest<DefaultSuite>,
|
||||||
server_public_key: &Key,
|
server_public_key: PublicKey<'_>,
|
||||||
) -> AuthenticationResult<ServerRegistrationStartResult<DefaultSuite>> {
|
) -> AuthenticationResult<ServerRegistrationStartResult<DefaultSuite>> {
|
||||||
Ok(ServerRegistration::<DefaultSuite>::start(
|
Ok(ServerRegistration::<DefaultSuite>::start(
|
||||||
rng,
|
rng,
|
||||||
registration_request,
|
registration_request,
|
||||||
server_public_key,
|
*server_public_key,
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,8 +190,8 @@ pub mod server {
|
|||||||
|
|
||||||
/// Methods to handle user login, from the server-side.
|
/// Methods to handle user login, from the server-side.
|
||||||
pub mod login {
|
pub mod login {
|
||||||
use super::*;
|
pub use super::*;
|
||||||
use opaque_ke::{
|
pub use opaque_ke::{
|
||||||
CredentialFinalization, CredentialRequest, ServerLogin, ServerLoginFinishResult,
|
CredentialFinalization, CredentialRequest, ServerLogin, ServerLoginFinishResult,
|
||||||
ServerLoginStartParameters, ServerLoginStartResult,
|
ServerLoginStartParameters, ServerLoginStartResult,
|
||||||
};
|
};
|
||||||
@ -167,13 +202,13 @@ pub mod server {
|
|||||||
pub fn start_login<R: RngCore + CryptoRng>(
|
pub fn start_login<R: RngCore + CryptoRng>(
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
password_file: ServerRegistration<DefaultSuite>,
|
password_file: ServerRegistration<DefaultSuite>,
|
||||||
server_private_key: &Key,
|
server_private_key: PrivateKey<'_>,
|
||||||
credential_request: CredentialRequest<DefaultSuite>,
|
credential_request: CredentialRequest<DefaultSuite>,
|
||||||
) -> AuthenticationResult<ServerLoginStartResult<DefaultSuite>> {
|
) -> AuthenticationResult<ServerLoginStartResult<DefaultSuite>> {
|
||||||
Ok(ServerLogin::start(
|
Ok(ServerLogin::start(
|
||||||
rng,
|
rng,
|
||||||
password_file,
|
password_file,
|
||||||
server_private_key,
|
*server_private_key,
|
||||||
credential_request,
|
credential_request,
|
||||||
ServerLoginStartParameters::default(),
|
ServerLoginStartParameters::default(),
|
||||||
)?)
|
)?)
|
||||||
|
@ -6,6 +6,8 @@ pub enum Error {
|
|||||||
AuthenticationError(String),
|
AuthenticationError(String),
|
||||||
#[error("Database error: `{0}`")]
|
#[error("Database error: `{0}`")]
|
||||||
DatabaseError(#[from] sqlx::Error),
|
DatabaseError(#[from] sqlx::Error),
|
||||||
|
#[error("Authentication protocol error for `{0}`")]
|
||||||
|
AuthenticationProtocolError(#[from] lldap_model::opaque::AuthenticationError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
@ -3,6 +3,7 @@ use crate::infra::configuration::Configuration;
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use futures_util::TryStreamExt;
|
use futures_util::TryStreamExt;
|
||||||
|
use lldap_model::opaque;
|
||||||
use log::*;
|
use log::*;
|
||||||
use sea_query::{Expr, Iden, Order, Query, SimpleExpr, Value};
|
use sea_query::{Expr, Iden, Order, Query, SimpleExpr, Value};
|
||||||
use sqlx::Row;
|
use sqlx::Row;
|
||||||
@ -20,31 +21,55 @@ impl SqlBackendHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_password_config(pepper: &str) -> argon2::Config {
|
fn get_password_file(
|
||||||
argon2::Config {
|
clear_password: &str,
|
||||||
secret: pepper.as_bytes(),
|
server_public_key: opaque::PublicKey<'_>,
|
||||||
..Default::default()
|
) -> Result<opaque::server::ServerRegistration<opaque::DefaultSuite>> {
|
||||||
}
|
use opaque::{client, server};
|
||||||
|
let mut rng = rand::rngs::OsRng;
|
||||||
|
let client_register_start_result =
|
||||||
|
client::registration::start_registration(clear_password, &mut rng)?;
|
||||||
|
|
||||||
|
let server_register_start_result = server::registration::start_registration(
|
||||||
|
&mut rng,
|
||||||
|
client_register_start_result.message,
|
||||||
|
server_public_key,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let client_registration_result = client::registration::finish_registration(
|
||||||
|
client_register_start_result.state,
|
||||||
|
server_register_start_result.message,
|
||||||
|
&mut rng,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(server::registration::get_password_file(
|
||||||
|
server_register_start_result.state,
|
||||||
|
client_registration_result.message,
|
||||||
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_password(clear_password: &str, salt: &str, pepper: &str) -> String {
|
fn passwords_match(
|
||||||
let config = get_password_config(pepper);
|
password_file_bytes: &[u8],
|
||||||
argon2::hash_encoded(clear_password.as_bytes(), salt.as_bytes(), &config)
|
clear_password: &str,
|
||||||
.map_err(|e| anyhow::anyhow!("Error encoding password: {}", e))
|
server_private_key: opaque::PrivateKey<'_>,
|
||||||
.unwrap()
|
) -> Result<()> {
|
||||||
}
|
use opaque::{client, client::login::*, server, server::login::*, DefaultSuite};
|
||||||
|
let mut rng = rand::rngs::OsRng;
|
||||||
|
let client_login_start_result = client::login::start_login(clear_password, &mut rng)?;
|
||||||
|
|
||||||
fn passwords_match(encrypted_password: &str, clear_password: &str, pepper: &str) -> bool {
|
let password_file = ServerRegistration::<DefaultSuite>::deserialize(password_file_bytes)
|
||||||
argon2::verify_encoded_ext(
|
.map_err(opaque::AuthenticationError::ProtocolError)?;
|
||||||
encrypted_password,
|
let server_login_start_result = server::login::start_login(
|
||||||
clear_password.as_bytes(),
|
&mut rng,
|
||||||
pepper.as_bytes(),
|
password_file,
|
||||||
/*additional_data=*/ b"",
|
server_private_key,
|
||||||
)
|
client_login_start_result.message,
|
||||||
.unwrap_or_else(|e| {
|
)?;
|
||||||
log::error!("Error checking password: {}", e);
|
finish_login(
|
||||||
false
|
client_login_start_result.state,
|
||||||
})
|
server_login_start_result.message,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_filter_expr(filter: RequestFilter) -> SimpleExpr {
|
fn get_filter_expr(filter: RequestFilter) -> SimpleExpr {
|
||||||
@ -85,14 +110,14 @@ impl BackendHandler for SqlBackendHandler {
|
|||||||
.and_where(Expr::col(Users::UserId).eq(request.name.as_str()))
|
.and_where(Expr::col(Users::UserId).eq(request.name.as_str()))
|
||||||
.to_string(DbQueryBuilder {});
|
.to_string(DbQueryBuilder {});
|
||||||
if let Ok(row) = sqlx::query(&query).fetch_one(&self.sql_pool).await {
|
if let Ok(row) = sqlx::query(&query).fetch_one(&self.sql_pool).await {
|
||||||
if passwords_match(
|
if let Err(e) = passwords_match(
|
||||||
&row.get::<String, _>(&*Users::PasswordHash.to_string()),
|
&row.get::<Vec<u8>, _>(&*Users::PasswordHash.to_string()),
|
||||||
&request.password,
|
&request.password,
|
||||||
&self.config.secret_pepper,
|
self.config.get_server_keys().private(),
|
||||||
) {
|
) {
|
||||||
return Ok(());
|
debug!(r#"Invalid password for "{}": {}"#, request.name, e);
|
||||||
} else {
|
} else {
|
||||||
debug!(r#"Invalid password for "{}""#, request.name);
|
return Ok(());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debug!(r#"No user found for "{}""#, request.name);
|
debug!(r#"No user found for "{}""#, request.name);
|
||||||
@ -208,16 +233,9 @@ impl BackendHandler for SqlBackendHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn create_user(&self, request: CreateUserRequest) -> Result<()> {
|
async fn create_user(&self, request: CreateUserRequest) -> Result<()> {
|
||||||
use rand::{distributions::Alphanumeric, rngs::SmallRng, Rng, SeedableRng};
|
let password_hash =
|
||||||
// TODO: Initialize the rng only once. Maybe Arc<Cell>?
|
get_password_file(&request.password, self.config.get_server_keys().public())?
|
||||||
let mut rng = SmallRng::from_entropy();
|
.serialize();
|
||||||
let salt: String = std::iter::repeat(())
|
|
||||||
.map(|()| rng.sample(Alphanumeric))
|
|
||||||
.map(char::from)
|
|
||||||
.take(32)
|
|
||||||
.collect();
|
|
||||||
// The salt is included in the password hash.
|
|
||||||
let password_hash = hash_password(&request.password, &salt, &self.config.secret_pepper);
|
|
||||||
let query = Query::insert()
|
let query = Query::insert()
|
||||||
.into_table(Users::Table)
|
.into_table(Users::Table)
|
||||||
.columns(vec![
|
.columns(vec![
|
||||||
@ -283,6 +301,14 @@ impl BackendHandler for SqlBackendHandler {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::domain::sql_tables::init_table;
|
use crate::domain::sql_tables::init_table;
|
||||||
|
use crate::infra::configuration::ConfigurationBuilder;
|
||||||
|
|
||||||
|
fn get_default_config() -> Configuration {
|
||||||
|
ConfigurationBuilder::default()
|
||||||
|
.verbose(true)
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_in_memory_db() -> Pool {
|
async fn get_in_memory_db() -> Pool {
|
||||||
PoolOptions::new().connect("sqlite::memory:").await.unwrap()
|
PoolOptions::new().connect("sqlite::memory:").await.unwrap()
|
||||||
@ -328,11 +354,11 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_bind_admin() {
|
async fn test_bind_admin() {
|
||||||
let sql_pool = get_in_memory_db().await;
|
let sql_pool = get_in_memory_db().await;
|
||||||
let config = Configuration {
|
let config = ConfigurationBuilder::default()
|
||||||
ldap_user_dn: "admin".to_string(),
|
.ldap_user_dn("admin".to_string())
|
||||||
ldap_user_pass: "test".to_string(),
|
.ldap_user_pass("test".to_string())
|
||||||
..Default::default()
|
.build()
|
||||||
};
|
.unwrap();
|
||||||
let handler = SqlBackendHandler::new(config, sql_pool);
|
let handler = SqlBackendHandler::new(config, sql_pool);
|
||||||
handler
|
handler
|
||||||
.bind(BindRequest {
|
.bind(BindRequest {
|
||||||
@ -343,24 +369,10 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_argon() {
|
|
||||||
let password = b"password";
|
|
||||||
let salt = b"randomsalt";
|
|
||||||
let pepper = b"pepper";
|
|
||||||
let config = argon2::Config {
|
|
||||||
secret: pepper,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let hash = argon2::hash_encoded(password, salt, &config).unwrap();
|
|
||||||
let matches = argon2::verify_encoded_ext(&hash, password, pepper, b"").unwrap();
|
|
||||||
assert!(matches);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_bind_user() {
|
async fn test_bind_user() {
|
||||||
let sql_pool = get_initialized_db().await;
|
let sql_pool = get_initialized_db().await;
|
||||||
let config = Configuration::default();
|
let config = get_default_config();
|
||||||
let handler = SqlBackendHandler::new(config, sql_pool.clone());
|
let handler = SqlBackendHandler::new(config, sql_pool.clone());
|
||||||
insert_user(&handler, "bob", "bob00").await;
|
insert_user(&handler, "bob", "bob00").await;
|
||||||
|
|
||||||
@ -390,7 +402,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_list_users() {
|
async fn test_list_users() {
|
||||||
let sql_pool = get_initialized_db().await;
|
let sql_pool = get_initialized_db().await;
|
||||||
let config = Configuration::default();
|
let config = get_default_config();
|
||||||
let handler = SqlBackendHandler::new(config, sql_pool);
|
let handler = SqlBackendHandler::new(config, sql_pool);
|
||||||
insert_user(&handler, "bob", "bob00").await;
|
insert_user(&handler, "bob", "bob00").await;
|
||||||
insert_user(&handler, "patrick", "pass").await;
|
insert_user(&handler, "patrick", "pass").await;
|
||||||
@ -455,7 +467,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_list_groups() {
|
async fn test_list_groups() {
|
||||||
let sql_pool = get_initialized_db().await;
|
let sql_pool = get_initialized_db().await;
|
||||||
let config = Configuration::default();
|
let config = get_default_config();
|
||||||
let handler = SqlBackendHandler::new(config, sql_pool.clone());
|
let handler = SqlBackendHandler::new(config, sql_pool.clone());
|
||||||
insert_user(&handler, "bob", "bob00").await;
|
insert_user(&handler, "bob", "bob00").await;
|
||||||
insert_user(&handler, "patrick", "pass").await;
|
insert_user(&handler, "patrick", "pass").await;
|
||||||
@ -484,7 +496,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_user_groups() {
|
async fn test_get_user_groups() {
|
||||||
let sql_pool = get_initialized_db().await;
|
let sql_pool = get_initialized_db().await;
|
||||||
let config = Configuration::default();
|
let config = get_default_config();
|
||||||
let handler = SqlBackendHandler::new(config, sql_pool.clone());
|
let handler = SqlBackendHandler::new(config, sql_pool.clone());
|
||||||
insert_user(&handler, "bob", "bob00").await;
|
insert_user(&handler, "bob", "bob00").await;
|
||||||
insert_user(&handler, "patrick", "pass").await;
|
insert_user(&handler, "patrick", "pass").await;
|
||||||
@ -519,7 +531,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_delete_user() {
|
async fn test_delete_user() {
|
||||||
let sql_pool = get_initialized_db().await;
|
let sql_pool = get_initialized_db().await;
|
||||||
let config = Configuration::default();
|
let config = get_default_config();
|
||||||
let handler = SqlBackendHandler::new(config, sql_pool.clone());
|
let handler = SqlBackendHandler::new(config, sql_pool.clone());
|
||||||
|
|
||||||
insert_user(&handler, "val", "s3np4i").await;
|
insert_user(&handler, "val", "s3np4i").await;
|
||||||
|
@ -54,11 +54,7 @@ pub async fn init_table(pool: &Pool) -> sqlx::Result<()> {
|
|||||||
.col(ColumnDef::new(Users::LastName).string_len(255))
|
.col(ColumnDef::new(Users::LastName).string_len(255))
|
||||||
.col(ColumnDef::new(Users::Avatar).binary())
|
.col(ColumnDef::new(Users::Avatar).binary())
|
||||||
.col(ColumnDef::new(Users::CreationDate).date_time().not_null())
|
.col(ColumnDef::new(Users::CreationDate).date_time().not_null())
|
||||||
.col(
|
.col(ColumnDef::new(Users::PasswordHash).binary().not_null())
|
||||||
ColumnDef::new(Users::PasswordHash)
|
|
||||||
.string_len(255)
|
|
||||||
.not_null(),
|
|
||||||
)
|
|
||||||
.col(ColumnDef::new(Users::TotpSecret).string_len(64))
|
.col(ColumnDef::new(Users::TotpSecret).string_len(64))
|
||||||
.col(ColumnDef::new(Users::MfaType).string_len(64))
|
.col(ColumnDef::new(Users::MfaType).string_len(64))
|
||||||
.to_string(DbQueryBuilder {}),
|
.to_string(DbQueryBuilder {}),
|
||||||
|
@ -1,45 +1,61 @@
|
|||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use figment::{
|
use figment::{
|
||||||
providers::{Env, Format, Serialized, Toml},
|
providers::{Env, Format, Serialized, Toml},
|
||||||
Figment,
|
Figment,
|
||||||
};
|
};
|
||||||
|
use lldap_model::{opaque, opaque::KeyPair};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::infra::cli::CLIOpts;
|
use crate::infra::cli::CLIOpts;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize, derive_builder::Builder)]
|
||||||
|
#[builder(
|
||||||
|
pattern = "owned",
|
||||||
|
default = "Configuration::default()",
|
||||||
|
build_fn(name = "private_build", validate = "Self::validate")
|
||||||
|
)]
|
||||||
pub struct Configuration {
|
pub struct Configuration {
|
||||||
pub ldap_port: u16,
|
pub ldap_port: u16,
|
||||||
pub ldaps_port: u16,
|
pub ldaps_port: u16,
|
||||||
pub http_port: u16,
|
pub http_port: u16,
|
||||||
pub secret_pepper: String,
|
|
||||||
pub jwt_secret: String,
|
pub jwt_secret: String,
|
||||||
pub ldap_base_dn: String,
|
pub ldap_base_dn: String,
|
||||||
pub ldap_user_dn: String,
|
pub ldap_user_dn: String,
|
||||||
pub ldap_user_pass: String,
|
pub ldap_user_pass: String,
|
||||||
pub database_url: String,
|
pub database_url: String,
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
|
pub key_file: String,
|
||||||
|
#[serde(skip)]
|
||||||
|
#[builder(field(private), setter(strip_option))]
|
||||||
|
server_keys: Option<KeyPair>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Configuration {
|
impl ConfigurationBuilder {
|
||||||
fn default() -> Self {
|
#[cfg(test)]
|
||||||
Configuration {
|
pub fn build(self) -> Result<Configuration> {
|
||||||
ldap_port: 3890,
|
let server_keys = get_server_keys(
|
||||||
ldaps_port: 6360,
|
&self
|
||||||
http_port: 17170,
|
.key_file
|
||||||
secret_pepper: String::from("secretsecretpepper"),
|
.as_deref()
|
||||||
jwt_secret: String::from("secretjwtsecret"),
|
.unwrap_or("server_key"),
|
||||||
ldap_base_dn: String::from("dc=example,dc=com"),
|
)?;
|
||||||
// cn=admin,dc=example,dc=com
|
Ok(self.server_keys(server_keys).private_build()?)
|
||||||
ldap_user_dn: String::from("admin"),
|
}
|
||||||
ldap_user_pass: String::from("password"),
|
|
||||||
database_url: String::from("sqlite://users.db?mode=rwc"),
|
fn validate(&self) -> Result<(), String> {
|
||||||
verbose: false,
|
if self.server_keys.is_none() {
|
||||||
|
Err("Don't use `private_build`, use `build` instead".to_string())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Configuration {
|
impl Configuration {
|
||||||
|
pub fn get_server_keys(&self) -> &KeyPair {
|
||||||
|
self.server_keys.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
fn merge_with_cli(mut self: Configuration, cli_opts: CLIOpts) -> Configuration {
|
fn merge_with_cli(mut self: Configuration, cli_opts: CLIOpts) -> Configuration {
|
||||||
if cli_opts.verbose {
|
if cli_opts.verbose {
|
||||||
self.verbose = true;
|
self.verbose = true;
|
||||||
@ -55,6 +71,45 @@ impl Configuration {
|
|||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn default() -> Self {
|
||||||
|
Configuration {
|
||||||
|
ldap_port: 3890,
|
||||||
|
ldaps_port: 6360,
|
||||||
|
http_port: 17170,
|
||||||
|
jwt_secret: String::from("secretjwtsecret"),
|
||||||
|
ldap_base_dn: String::from("dc=example,dc=com"),
|
||||||
|
// cn=admin,dc=example,dc=com
|
||||||
|
ldap_user_dn: String::from("admin"),
|
||||||
|
ldap_user_pass: String::from("password"),
|
||||||
|
database_url: String::from("sqlite://users.db?mode=rwc"),
|
||||||
|
verbose: false,
|
||||||
|
key_file: String::from("server_key"),
|
||||||
|
server_keys: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_server_keys(file_path: &str) -> Result<KeyPair> {
|
||||||
|
use opaque_ke::ciphersuite::CipherSuite;
|
||||||
|
use std::path::Path;
|
||||||
|
let path = Path::new(file_path);
|
||||||
|
if path.exists() {
|
||||||
|
let bytes = std::fs::read(file_path)
|
||||||
|
.map_err(|e| anyhow!("Could not read key file `{}`: {}", file_path, e))?;
|
||||||
|
Ok(KeyPair::from_private_key_slice(&bytes)?)
|
||||||
|
} else {
|
||||||
|
let mut rng = rand::rngs::OsRng;
|
||||||
|
let keypair = opaque::DefaultSuite::generate_random_keypair(&mut rng);
|
||||||
|
std::fs::write(path, keypair.private().as_slice()).map_err(|e| {
|
||||||
|
anyhow!(
|
||||||
|
"Could not write the generated server keys to file `{}`: {}",
|
||||||
|
file_path,
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Ok(KeyPair(keypair))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(cli_opts: CLIOpts) -> Result<Configuration> {
|
pub fn init(cli_opts: CLIOpts) -> Result<Configuration> {
|
||||||
@ -65,6 +120,7 @@ pub fn init(cli_opts: CLIOpts) -> Result<Configuration> {
|
|||||||
.merge(Env::prefixed("LLDAP_"))
|
.merge(Env::prefixed("LLDAP_"))
|
||||||
.extract()?;
|
.extract()?;
|
||||||
|
|
||||||
let config = config.merge_with_cli(cli_opts);
|
let mut config = config.merge_with_cli(cli_opts);
|
||||||
|
config.server_keys = Some(get_server_keys(&config.key_file)?);
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,9 @@ async fn index(req: HttpRequest) -> actix_web::Result<NamedFile> {
|
|||||||
|
|
||||||
pub(crate) fn error_to_http_response(error: DomainError) -> HttpResponse {
|
pub(crate) fn error_to_http_response(error: DomainError) -> HttpResponse {
|
||||||
match error {
|
match error {
|
||||||
DomainError::AuthenticationError(_) => HttpResponse::Unauthorized(),
|
DomainError::AuthenticationError(_) | DomainError::AuthenticationProtocolError(_) => {
|
||||||
|
HttpResponse::Unauthorized()
|
||||||
|
}
|
||||||
DomainError::DatabaseError(_) => HttpResponse::InternalServerError(),
|
DomainError::DatabaseError(_) => HttpResponse::InternalServerError(),
|
||||||
}
|
}
|
||||||
.body(error.to_string())
|
.body(error.to_string())
|
||||||
|
Loading…
Reference in New Issue
Block a user