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>
@a1457139709

This comment was marked as resolved.

@bandrel

bandrel commented Jul 28, 2026

Copy link
Copy Markdown
Author

clock skew means your clock is off. Try to update your time on your client to match the time on the DC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants