mirror of
https://github.com/lldap/lldap.git
synced 2026-07-25 17:58:44 +00:00
auth: move auth crate to crates folder
This commit is contained in:
committed by
nitnelave
parent
dd0ba5975e
commit
b5e87c7226
@@ -0,0 +1,52 @@
|
||||
[package]
|
||||
authors = ["Valentin Tolmer <valentin@tolmer.fr>"]
|
||||
description = "Authentication protocol for LLDAP"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/lldap/lldap"
|
||||
license = "GPL-3.0-only"
|
||||
name = "lldap_auth"
|
||||
repository = "https://github.com/lldap/lldap"
|
||||
version = "0.6.0"
|
||||
|
||||
[features]
|
||||
default = ["opaque_server", "opaque_client"]
|
||||
opaque_server = []
|
||||
opaque_client = []
|
||||
js = []
|
||||
sea_orm = ["dep:sea-orm"]
|
||||
|
||||
[dependencies]
|
||||
rust-argon2 = "0.8"
|
||||
curve25519-dalek = "3"
|
||||
digest = "0.9"
|
||||
generic-array = "0.14"
|
||||
rand = "0.8"
|
||||
serde = "*"
|
||||
sha2 = "0.9"
|
||||
thiserror = "*"
|
||||
|
||||
[dependencies.derive_more]
|
||||
features = ["debug", "display"]
|
||||
default-features = false
|
||||
version = "1"
|
||||
|
||||
[dependencies.opaque-ke]
|
||||
version = "0.7"
|
||||
|
||||
[dependencies.chrono]
|
||||
version = "*"
|
||||
features = ["serde"]
|
||||
|
||||
[dependencies.sea-orm]
|
||||
version = "0.12"
|
||||
default-features = false
|
||||
features = ["macros"]
|
||||
optional = true
|
||||
|
||||
# For WASM targets, use the JS getrandom.
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.getrandom]
|
||||
version = "0.2"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.getrandom]
|
||||
version = "0.2"
|
||||
features = ["js"]
|
||||
@@ -0,0 +1,214 @@
|
||||
#![forbid(non_ascii_idents)]
|
||||
#![allow(clippy::nonstandard_macro_braces)]
|
||||
use chrono::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
|
||||
pub mod opaque;
|
||||
|
||||
/// The messages for the 3-step OPAQUE and simple login process.
|
||||
pub mod login {
|
||||
use super::{types::UserId, *};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ServerData {
|
||||
pub username: UserId,
|
||||
pub server_login: opaque::server::login::ServerLogin,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ClientLoginStartRequest {
|
||||
pub username: UserId,
|
||||
pub login_start_request: opaque::server::login::CredentialRequest,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ServerLoginStartResponse {
|
||||
/// Base64, encrypted ServerData to be passed back to the server.
|
||||
pub server_data: String,
|
||||
pub credential_response: opaque::client::login::CredentialResponse,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ClientLoginFinishRequest {
|
||||
/// Encrypted ServerData from the previous step.
|
||||
pub server_data: String,
|
||||
pub credential_finalization: opaque::client::login::CredentialFinalization,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ClientSimpleLoginRequest {
|
||||
pub username: UserId,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ClientSimpleLoginRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ClientSimpleLoginRequest")
|
||||
.field("username", &self.username.as_str())
|
||||
.field("password", &"***********")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ServerLoginResponse {
|
||||
pub token: String,
|
||||
#[serde(rename = "refreshToken", skip_serializing_if = "Option::is_none")]
|
||||
pub refresh_token: Option<String>,
|
||||
}
|
||||
}
|
||||
|
||||
/// The messages for the 3-step OPAQUE registration process.
|
||||
/// It is used to reset a user's password.
|
||||
pub mod registration {
|
||||
use super::{types::UserId, *};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ServerData {
|
||||
pub username: UserId,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ClientRegistrationStartRequest {
|
||||
pub username: UserId,
|
||||
pub registration_start_request: opaque::server::registration::RegistrationRequest,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ServerRegistrationStartResponse {
|
||||
/// Base64, encrypted ServerData to be passed back to the server.
|
||||
pub server_data: String,
|
||||
pub registration_response: opaque::client::registration::RegistrationResponse,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ClientRegistrationFinishRequest {
|
||||
/// Encrypted ServerData from the previous step.
|
||||
pub server_data: String,
|
||||
pub registration_upload: opaque::server::registration::RegistrationUpload,
|
||||
}
|
||||
}
|
||||
|
||||
/// The messages for the 3-step OPAQUE registration process.
|
||||
/// It is used to reset a user's password.
|
||||
pub mod password_reset {
|
||||
use super::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ServerPasswordResetResponse {
|
||||
#[serde(rename = "userId")]
|
||||
pub user_id: String,
|
||||
pub token: String,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod types {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "sea_orm")]
|
||||
use sea_orm::{DbErr, DeriveValueType, TryFromU64, Value};
|
||||
|
||||
#[derive(
|
||||
PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Default, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
#[cfg_attr(feature = "sea_orm", derive(DeriveValueType))]
|
||||
#[serde(from = "String")]
|
||||
pub struct CaseInsensitiveString(String);
|
||||
|
||||
impl CaseInsensitiveString {
|
||||
pub fn new(s: &str) -> Self {
|
||||
Self(s.to_ascii_lowercase())
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> String {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for CaseInsensitiveString {
|
||||
fn from(mut s: String) -> Self {
|
||||
s.make_ascii_lowercase();
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&String> for CaseInsensitiveString {
|
||||
fn from(s: &String) -> Self {
|
||||
Self::new(s.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for CaseInsensitiveString {
|
||||
fn from(s: &str) -> Self {
|
||||
Self::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Clone,
|
||||
Default,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
derive_more::Debug,
|
||||
derive_more::Display,
|
||||
)]
|
||||
#[cfg_attr(feature = "sea_orm", derive(DeriveValueType))]
|
||||
#[serde(from = "CaseInsensitiveString")]
|
||||
#[debug(r#""{}""#, _0.as_str())]
|
||||
#[display("{}", _0.as_str())]
|
||||
pub struct UserId(CaseInsensitiveString);
|
||||
|
||||
impl UserId {
|
||||
pub fn new(s: &str) -> Self {
|
||||
s.into()
|
||||
}
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
pub fn into_string(self) -> String {
|
||||
self.0.into_string()
|
||||
}
|
||||
}
|
||||
impl<T> From<T> for UserId
|
||||
where
|
||||
T: Into<CaseInsensitiveString>,
|
||||
{
|
||||
fn from(s: T) -> Self {
|
||||
Self(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sea_orm")]
|
||||
impl From<&UserId> for Value {
|
||||
fn from(user_id: &UserId) -> Self {
|
||||
user_id.as_str().into()
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "sea_orm")]
|
||||
impl TryFromU64 for UserId {
|
||||
fn try_from_u64(_n: u64) -> Result<Self, DbErr> {
|
||||
Err(DbErr::ConvertFromU64(
|
||||
"UserId cannot be constructed from u64",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct JWTClaims {
|
||||
pub exp: DateTime<Utc>,
|
||||
pub iat: DateTime<Utc>,
|
||||
pub user: String,
|
||||
pub groups: HashSet<String>,
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
use crate::types::UserId;
|
||||
use opaque_ke::ciphersuite::CipherSuite;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum AuthenticationError {
|
||||
#[error("Protocol error: `{0}`")]
|
||||
ProtocolError(#[from] opaque_ke::errors::ProtocolError),
|
||||
}
|
||||
|
||||
pub type AuthenticationResult<T> = std::result::Result<T, AuthenticationError>;
|
||||
|
||||
pub use opaque_ke::keypair::{PrivateKey, PublicKey};
|
||||
pub type KeyPair = opaque_ke::keypair::KeyPair<<DefaultSuite as CipherSuite>::Group>;
|
||||
|
||||
/// A wrapper around argon2 to provide the [`opaque_ke::slow_hash::SlowHash`] trait.
|
||||
pub struct ArgonHasher;
|
||||
|
||||
/// The Argon hasher used for bruteforce protection.
|
||||
///
|
||||
/// Note that it isn't used to "hash the passwords", so it doesn't need a variable salt. Instead,
|
||||
/// it's used as part of the OPAQUE protocol to add a slow hashing method, making bruteforce
|
||||
/// attacks prohibitively more expensive.
|
||||
impl ArgonHasher {
|
||||
/// Fixed salt, doesn't affect the security. It is only used to make attacks more
|
||||
/// computationally intensive, it doesn't serve any security purpose.
|
||||
const SALT: &'static [u8] = b"lldap_opaque_salt";
|
||||
/// Config for the argon hasher. Security enthusiasts may want to tweak this for their system.
|
||||
const CONFIG: &'static argon2::Config<'static> = &argon2::Config {
|
||||
ad: &[],
|
||||
hash_length: 128,
|
||||
lanes: 1,
|
||||
mem_cost: 50 * 1024, // 50 MB, in KB
|
||||
secret: &[],
|
||||
thread_mode: argon2::ThreadMode::Sequential,
|
||||
time_cost: 1,
|
||||
variant: argon2::Variant::Argon2id,
|
||||
version: argon2::Version::Version13,
|
||||
};
|
||||
}
|
||||
|
||||
impl<D: opaque_ke::hash::Hash> opaque_ke::slow_hash::SlowHash<D> for ArgonHasher {
|
||||
fn hash(
|
||||
input: generic_array::GenericArray<u8, <D as digest::Digest>::OutputSize>,
|
||||
) -> Result<Vec<u8>, opaque_ke::errors::InternalPakeError> {
|
||||
argon2::hash_raw(&input, Self::SALT, Self::CONFIG)
|
||||
.map_err(|_| opaque_ke::errors::InternalPakeError::HashingFailure)
|
||||
}
|
||||
}
|
||||
|
||||
/// The ciphersuite trait allows to specify the underlying primitives
|
||||
/// that will be used in the OPAQUE protocol
|
||||
#[allow(dead_code)]
|
||||
pub struct DefaultSuite;
|
||||
impl CipherSuite for DefaultSuite {
|
||||
type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
type Hash = sha2::Sha512;
|
||||
/// Use argon2 as the slow hashing algorithm for our CipherSuite.
|
||||
type SlowHash = ArgonHasher;
|
||||
}
|
||||
|
||||
/// Client-side code for OPAQUE protocol handling, to register a new user and login. All methods'
|
||||
/// results must be sent to the server using the serialized `.message`. Incoming messages can be
|
||||
/// deserialized using the type's `deserialize` method.
|
||||
#[cfg(feature = "opaque_client")]
|
||||
pub mod client {
|
||||
pub use super::*;
|
||||
/// Methods to register a new user, from the client side.
|
||||
pub mod registration {
|
||||
pub use super::*;
|
||||
pub type ClientRegistration = opaque_ke::ClientRegistration<DefaultSuite>;
|
||||
pub type ClientRegistrationStartResult =
|
||||
opaque_ke::ClientRegistrationStartResult<DefaultSuite>;
|
||||
pub type ClientRegistrationFinishResult =
|
||||
opaque_ke::ClientRegistrationFinishResult<DefaultSuite>;
|
||||
pub type RegistrationResponse = opaque_ke::RegistrationResponse<DefaultSuite>;
|
||||
pub use opaque_ke::ClientRegistrationFinishParameters;
|
||||
/// Initiate the registration negotiation.
|
||||
pub fn start_registration<R: RngCore + CryptoRng>(
|
||||
password: &[u8],
|
||||
rng: &mut R,
|
||||
) -> AuthenticationResult<ClientRegistrationStartResult> {
|
||||
Ok(ClientRegistration::start(rng, password)?)
|
||||
}
|
||||
|
||||
/// Finalize the registration negotiation.
|
||||
pub fn finish_registration<R: RngCore + CryptoRng>(
|
||||
registration_start: ClientRegistration,
|
||||
registration_response: RegistrationResponse,
|
||||
rng: &mut R,
|
||||
) -> AuthenticationResult<ClientRegistrationFinishResult> {
|
||||
Ok(registration_start.finish(
|
||||
rng,
|
||||
registration_response,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Methods to login, from the client side.
|
||||
pub mod login {
|
||||
pub use super::*;
|
||||
pub type ClientLogin = opaque_ke::ClientLogin<DefaultSuite>;
|
||||
pub type ClientLoginFinishResult = opaque_ke::ClientLoginFinishResult<DefaultSuite>;
|
||||
pub type ClientLoginStartResult = opaque_ke::ClientLoginStartResult<DefaultSuite>;
|
||||
pub type CredentialResponse = opaque_ke::CredentialResponse<DefaultSuite>;
|
||||
pub type CredentialFinalization = opaque_ke::CredentialFinalization<DefaultSuite>;
|
||||
pub use opaque_ke::ClientLoginFinishParameters;
|
||||
|
||||
/// Initiate the login negotiation.
|
||||
pub fn start_login<R: RngCore + CryptoRng>(
|
||||
password: &str,
|
||||
rng: &mut R,
|
||||
) -> AuthenticationResult<ClientLoginStartResult> {
|
||||
Ok(ClientLogin::start(rng, password.as_bytes())?)
|
||||
}
|
||||
|
||||
/// Finalize the client login negotiation.
|
||||
pub fn finish_login(
|
||||
login_start: ClientLogin,
|
||||
login_response: CredentialResponse,
|
||||
) -> AuthenticationResult<ClientLoginFinishResult> {
|
||||
Ok(login_start.finish(login_response, ClientLoginFinishParameters::default())?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Server-side code for OPAQUE protocol handling, to register a new user and login. The
|
||||
/// intermediate results must be sent to the client using the serialized `.message`.
|
||||
#[cfg(feature = "opaque_server")]
|
||||
pub mod server {
|
||||
pub use super::*;
|
||||
pub type ServerRegistration = opaque_ke::ServerRegistration<DefaultSuite>;
|
||||
pub type ServerSetup = opaque_ke::ServerSetup<DefaultSuite>;
|
||||
/// Methods to register a new user, from the server side.
|
||||
pub mod registration {
|
||||
pub use super::*;
|
||||
pub type RegistrationRequest = opaque_ke::RegistrationRequest<DefaultSuite>;
|
||||
pub type RegistrationUpload = opaque_ke::RegistrationUpload<DefaultSuite>;
|
||||
pub type ServerRegistrationStartResult =
|
||||
opaque_ke::ServerRegistrationStartResult<DefaultSuite>;
|
||||
/// Start a registration process, from a request sent by the client.
|
||||
///
|
||||
/// The result must be kept for the next step.
|
||||
pub fn start_registration(
|
||||
server_setup: &ServerSetup,
|
||||
registration_request: RegistrationRequest,
|
||||
username: &UserId,
|
||||
) -> AuthenticationResult<ServerRegistrationStartResult> {
|
||||
Ok(ServerRegistration::start(
|
||||
server_setup,
|
||||
registration_request,
|
||||
username.as_str().as_bytes(),
|
||||
)?)
|
||||
}
|
||||
|
||||
/// Finish to register a new user, and get the data to store in the database.
|
||||
pub fn get_password_file(registration_upload: RegistrationUpload) -> ServerRegistration {
|
||||
ServerRegistration::finish(registration_upload)
|
||||
}
|
||||
}
|
||||
|
||||
/// Methods to handle user login, from the server-side.
|
||||
pub mod login {
|
||||
pub use super::*;
|
||||
pub type CredentialFinalization = opaque_ke::CredentialFinalization<DefaultSuite>;
|
||||
pub type CredentialRequest = opaque_ke::CredentialRequest<DefaultSuite>;
|
||||
pub type ServerLogin = opaque_ke::ServerLogin<DefaultSuite>;
|
||||
pub type ServerLoginStartResult = opaque_ke::ServerLoginStartResult<DefaultSuite>;
|
||||
pub type ServerLoginFinishResult = opaque_ke::ServerLoginFinishResult<DefaultSuite>;
|
||||
pub use opaque_ke::ServerLoginStartParameters;
|
||||
|
||||
/// Start a login process, from a request sent by the client.
|
||||
///
|
||||
/// The result must be kept for the next step.
|
||||
pub fn start_login<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
server_setup: &ServerSetup,
|
||||
password_file: Option<ServerRegistration>,
|
||||
credential_request: CredentialRequest,
|
||||
username: &UserId,
|
||||
) -> AuthenticationResult<ServerLoginStartResult> {
|
||||
Ok(ServerLogin::start(
|
||||
rng,
|
||||
server_setup,
|
||||
password_file,
|
||||
credential_request,
|
||||
username.as_str().as_bytes(),
|
||||
ServerLoginStartParameters::default(),
|
||||
)?)
|
||||
}
|
||||
|
||||
/// Finish to authorize a new user, and get the session key to decrypt associated data.
|
||||
pub fn finish_login(
|
||||
login_start: ServerLogin,
|
||||
credential_finalization: CredentialFinalization,
|
||||
) -> AuthenticationResult<ServerLoginFinishResult> {
|
||||
Ok(login_start.finish(credential_finalization)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ default-features = false
|
||||
version = "0.24"
|
||||
|
||||
[dependencies.lldap_auth]
|
||||
path = "../../auth"
|
||||
path = "../auth"
|
||||
features = ["opaque_server", "opaque_client", "sea_orm"]
|
||||
|
||||
[dependencies.sea-orm]
|
||||
|
||||
Reference in New Issue
Block a user