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.