Skip to main content

Crate rustlavel_ldap

Crate rustlavel_ldap 

Source
Expand description

rustlavel-ldap: authenticate a person against a directory.

LDAP is how most organisations answer “who is this and what is their password”, and an application that needs that answer should not need to learn ASN.1 to get it. So the package has two faces: directory, which is one call, and protocol and ber underneath it for anything that call does not cover.

ⓘ
use rustlavel_ldap::prelude::*;

let directory = Directory::new("ldaps://dc.example.test")?
    .service_account("cn=svc,dc=example,dc=test", &service_password)
    .base_dn("ou=people,dc=example,dc=test")
    .username_attribute("uid")
    .filter(Filter::equals("objectClass", "inetOrgPerson"))
    .attributes(["cn", "mail"]);

let user = directory.authenticate(&username, &password).await?;
println!("{} is {}", user.dn(), user.value("cn").unwrap_or("unnamed"));

§The protocol is written here

LDAP v3 (RFC 4511) is ASN.1 BER over TCP. BER is a serialisation format rather than cryptography, so rule one applies and it is written in ber: a small, strict subset that refuses everything RFC 4511 says a directory may not send. TLS is the exception, and comes from rustls with the aws_lc_rs provider and prefer-post-quantum, like every other TLS in this framework.

§Three things this package is firm about

A simple bind sends the password in the clear. That is what a simple bind is — RFC 4511 §4.2 puts the password in an octet string with nothing around it. So one is refused over an unencrypted connection unless the caller has said, in as many words, that it is acceptable (LdapConfig::allow_plaintext_password). This is the most common LDAP deployment mistake there is, and it is invisible from the outside because everything works.

An empty password must not authenticate. LDAP treats a simple bind with a zero-length password as an anonymous bind, which succeeds — so a login form with a blank password field would report a successful login for any username that exists. This has been a real vulnerability in real products more than once. It is refused in Operation::simple_bind, where the bytes are built, and again in Directory::authenticate before any I/O happens.

A username is a value, not text. The classic LDAP injection is a filter template with a name pasted into it: a username of * turns a lookup into a wildcard that matches every account. Filter is a type, and Filter::encode writes each value as a length-delimited BER octet string where * and ) have no syntax at all. escape_filter_value exists for the cases that genuinely need the RFC 4515 string form.

Re-exports§

pub use connection::Encryption;
pub use connection::LdapConfig;
pub use connection::LdapConnection;
pub use directory::AccountProblem;
pub use directory::AuthenticationError;
pub use directory::Directory;
pub use directory::User;
pub use protocol::Attribute;
pub use protocol::Control;
pub use protocol::DerefAliases;
pub use protocol::Filter;
pub use protocol::LdapMessage;
pub use protocol::LdapResult;
pub use protocol::Operation;
pub use protocol::ProtocolOp;
pub use protocol::ResultCode;
pub use protocol::Scope;
pub use protocol::SearchEntry;
pub use protocol::SearchOutcome;
pub use protocol::SearchRequest;
pub use protocol::escape_dn_value;
pub use protocol::escape_filter_value;

Modules§

ber
Just enough BER to speak LDAP.
connection
One connection to a directory: TCP, optionally encrypted, and the loop that matches an answer to the question that asked it.
directory
The part an application actually calls.
prelude
What an application importing this package normally wants.
protocol
LDAP v3 messages, as RFC 4511 defines them.

Enums§

Error
Every failure the framework itself can produce.

Type Aliases§

Result
The framework-wide result type.