Skip to content

Kerberos auth, StartTLS, channel binding, --debug, Python 3.13 fixes#86

Open
bandrel wants to merge 30 commits into
dirkjanm:masterfrom
bandrel:kerberos-port
Open

Kerberos auth, StartTLS, channel binding, --debug, Python 3.13 fixes#86
bandrel wants to merge 30 commits into
dirkjanm:masterfrom
bandrel:kerberos-port

Conversation

@bandrel

@bandrel bandrel commented Apr 24, 2026

Copy link
Copy Markdown

Summary

Adds Kerberos authentication and several closely related auth/UX features needed to operate against modern AD where NTLM is disabled or LDAP signing is enforced. Implements the work proposed in #82, #83, #84, #85.

Authentication

CLI / UX

Dependencies

ldap3 is pinned to the ly4k fork in pyproject.toml. Open to switching to a soft dependency / runtime check if you'd prefer not to vendor a fork by default — happy to rework however you'd like.

Test plan

  • -k against a DC using a TGT from KRB5CCNAME
  • -k cross-realm (user in trusted realm)
  • -aesKey AES auth
  • --auth-method ntlm against a DC requiring signing (with ly4k fork)
  • --start-tls with NTLM and with Kerberos
  • --ldap-channel-binding against ldaps:// and StartTLS
  • --debug produces ldap3 EXTENDED logs

🤖 Generated with Claude Code

bandrel and others added 30 commits April 23, 2026 18:30
Design spec for ccache-only Kerberos authentication via impacket, plus
LDAP signing fix for strongerAuthRequired on hardened DCs, and StartTLS
support for environments without LDAPS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nine-task TDD plan covering auth refactor, NTLM sign/seal fix, SIMPLE
error improvement, TLS verify flag, StartTLS, Kerberos CLI, Kerberos
bind via impacket+raw SASL, and README updates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
No behavior change. Moves NTLM/SIMPLE bind logic out of main() into
build_connection(args). Groundwork for Kerberos support and LDAP
signing fixes.
Adds a regression guard verifying that build_connection passes no user,
password, or authentication kwargs to Connection when args.user is None.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Modern AD DCs reject unsigned binds on plain LDAP with
strongerAuthRequired (code 8). NTLM session_security=ENCRYPT negotiates
sign/seal and is a no-op over TLS.

Note: ldap3 2.x (latest 2.9.1) does not export ENCRYPT; the constant is
defined locally in auth.py as a sentinel. When ldap3 gains native ENCRYPT
support this can be swapped to a direct import.
ldap3 2.9.1 has no NTLM signing support (no ENCRYPT constant, no
session_security kwarg on Connection). Verified via introspection.
Implementing it would require a library swap or heavy monkey-patching,
both out of scope. Task 3 is skipped; Task 4 gains an NTLM-specific
error message pointing users at the three working signing paths:
Kerberos, LDAPS, StartTLS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
For SIMPLE and NTLM binds rejected with code 8 on hardened DCs, surface
an auth-type-specific message pointing at Kerberos / LDAPS / StartTLS —
the three signing paths that actually work. ldap3 2.9.1 does not support
NTLM sign/seal, so we guide users to working alternatives instead of
dumping the raw LDAP result dict.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces _build_server() helper and --no-tls-verify escape hatch for
self-signed DC certificates. Extracts argparse into _build_parser() so
CLI-level tests can exercise it without invoking main().
…d test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Supports environments where port 636 is firewalled. Argparse rejects
--start-tls with ldaps:// URLs via _validate_args. Bind helpers now
explicitly open() -> maybe start_tls() -> bind().
Previously _open_and_maybe_start_tls silently discarded the boolean
return of Connection.open() and Connection.start_tls(). A failed
StartTLS upgrade would fall through to a plaintext bind — a silent
downgrade of the very security guarantee the user requested. Both
return values are now checked and surfaced as AuthError.
…tions

Adds BloodHound.py-style authentication flags including Kerberos (-k),
hashes (--hashes), AES key (-aesKey), auth method (--auth-method), domain,
and LDAP channel binding. Replaces -at/--authtype with --auth-method that
defaults to 'auto' for Kerberos-first behavior. Changes -u/--user to
-u/--username for consistency with impacket conventions. Adds -no-pass
flag for non-interactive auth scenarios.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Reject -k with --auth-method ntlm (incompatible)
- Reject -k without username (anonymous Kerberos unsupported)
- Reject --auth-method kerberos without username
- Imply --auth-method kerberos when -k is passed with valid username

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds 5 methods to ADAuthentication class:
- _kerberos_usable() checks if Kerberos credentials are available
- _kerberos_forced() returns True when ccache is in use (no fallback)
- _ensure_tgt() populates self.tgt from ccache or AS-REQ
- _ntlm_login() formats the domain\username string for NTLM bind
- authenticate() orchestrates the full auth flow: Kerberos first, then
  NTLM/anonymous fallback with proper error handling and channel binding

Implements Kerberos-first strategy with graceful fallback to NTLM when
auto mode is used. Validates auth_method to prevent unexpected code paths.

Adds 9 tests covering:
- Kerberos usability checks (username, password, ccache paths)
- Forced vs optional fallback logic
- Anonymous, NTLM, and Kerberos bind paths
- Channel binding validation and error messages
- Safety check for auth_method='kerberos' fallthrough

All 50 tests pass (29 auth + 21 CLI).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…mpt helper

- Rewrite auth.py: ADAuthentication wires into build_connection(args),
  UPN username parsing, down-level rejection, FQDN enforcement for Kerberos
- Extract _prompt_password_if_needed() from main(); -k and -no-pass suppress getpass
- Replace old test_auth.py with 13 new tests covering dispatcher surface
- Add 3 test_cli.py tests for password prompt logic
- All 67 tests pass; entry point imports verified

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mainline ldap3 lacks NTLM sign/seal and channel binding support, both
needed against modern AD with LDAP signing required. Python 3.13's
hashlib also dropped MD4, which ldap3's NTLM path needs via
Crypto.Hash.MD4 from pycryptodome.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The --start-tls CLI flag existed but was never honored. Plumb it through
ADAuthentication and run StartTLS on the open socket before bind, for
both Kerberos and NTLM paths.

ldap_kerberos previously called connection.open() unconditionally; ldap3
treats that as a re-open and tears down the existing socket, which would
discard our StartTLS wrap. Guard it with `if connection.closed`.

Also surface the swallowed AuthError detail when Kerberos falls back to
NTLM, to make misconfigurations debuggable.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Configures root logging at DEBUG and bumps ldap3's library log detail to
EXTENDED so the auth flow (Kerberos referrals, NTLM negotiation, TLS)
can be traced when binds misbehave.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant