Hinweis: Vage Einträge ohne messbares Ziel, Interface-Spezifikation oder Teststrategie mit
<!-- TODO: add measurable target, interface spec, test strategy -->markieren.
The ThemisDB authentication module (src/auth/, include/auth/) is a full-stack identity and access control subsystem covering: JWT/OIDC bearer-token validation and issuance, Kerberos/GSSAPI enterprise authentication, TOTP and WebAuthn/FIDO2 multi-factor authentication, OAuth 2.0 (authorization-code, device, PKCE, client-credentials), SAML 2.0 SP/IdP-initiated SSO, LDAP/AD directory bind, mTLS service-identity verification, federated identity bridging, session management, token blacklisting, rate limiting, audit logging, and zero-trust continuous verification. The module consists of 31 source files and a matching set of public headers.
[ ]JWT validation hot path must remain stateless — no database or network call when the JWKS cache is warm (jwt_validator.cpp:fetchJWKS()already caches, but the cache itself is unprotected by a mutex)[ ]All synchronous LDAP/HTTP calls must not block the event-loop thread — needs either a dedicated thread pool or an async wrapper[ ]All token/secret comparisons must use constant-time primitives (CRYPTO_memcmp) —api_key_authenticator.cppalready complies; remaining callers must follow[ ]Token blacklist must survive process restart — currently in-memory only (token_blacklist.cpp)[ ]Token cache (JWKS, session store) must be bounded — no hard memory cap enforced today[ ]No secret material in plain-text config structs —jwks_security.cpp:274-276passesclient_key_passworddirectly viaCURLOPT_KEYPASSWD[ ]LDAP filter and DN values must be properly escaped before use —ldap_authenticator.cpp:buildUserDN()performs raw string substitution with no LDAP escape[ ]expected_issuerandexpected_audienceshould be required, not optional — silent skip when empty is a misconfiguration footgun (jwt_validator.cpp:506,jwt_validator.cpp:345)
| Interface | Consumer | Notes |
|---|---|---|
ITokenValidator |
Query engine, HTTP gateway | Validates JWT/OIDC bearer tokens; returns Claims or error |
IKerberosAuthenticator |
Enterprise connector | Accepts GSSAPI token, returns authenticated principal |
IMFAProvider |
Login service | TOTP / WebAuthn challenge-response |
IOAuthFlowHandler |
REST API, CLI | Authorization-code, device, and client-credentials flows |
ISAMLHandler |
SSO bridge | Parses/validates SAML assertions, maps attributes to internal roles |
IRBACEvaluator |
Query engine | Role-permission matrix for principal + resource |
IABACEvaluator |
Query engine | Attribute-based policies (XACML-style) |
ImTLSVerifier |
gRPC/HTTP server | Validates client certificate chain; maps to service identity |
ITokenBlacklist |
JWT validator, session manager | Revocation store; must support distributed backends |
| Feature | Source File | Status |
|---|---|---|
| OAuth 2.0 Device Flow (RFC 8628) | src/auth/oauth_device_flow.cpp |
✅ Production-Ready |
| OAuth 2.0 PKCE Flow (RFC 7636) | src/auth/oauth_pkce_flow.cpp |
✅ Production-Ready |
| WebAuthn/FIDO2 hardware token support | src/auth/webauthn_authenticator.cpp |
✅ Production-Ready |
| SAML 2.0 SP- and IdP-initiated SSO | src/auth/saml_authenticator.cpp |
✅ Production-Ready |
| Concurrent session management and remote logout | src/auth/session_manager.cpp |
✅ Production-Ready |
Constant-time API key comparison via CRYPTO_memcmp |
src/auth/api_key_authenticator.cpp:272 |
✅ Production-Ready |
alg:none rejection in JWT validation |
src/auth/jwt_validator.cpp:413 |
✅ Production-Ready |
exp, nbf, iat, iss, aud claim validation |
src/auth/jwt_validator.cpp:458-518 |
✅ Production-Ready |
| JTI-based per-token revocation against blacklist | src/auth/jwt_validator.cpp:558-564 |
✅ Production-Ready |
| LDAP username/password/DN length bounds checking | src/auth/ldap_authenticator.cpp:133-173 |
✅ Production-Ready |
| TOTP replay protection with mutex-guarded cache | src/auth/totp_replay_cache.cpp |
✅ Production-Ready |
Priority: Critical Target Version: v1.1.0
include/auth/jwt_validator.h declares jwks_cache_ (line 192) and jwks_cache_time_ (line 193) as plain non-atomic member fields. There is no mutable std::mutex guarding them in the header or in jwt_validator.cpp. When multiple threads call JWTValidator::validate() concurrently and the cache expires, they all race into fetchJWKS() simultaneously — writing jwks_cache_ and jwks_cache_time_ from multiple threads is a data race (undefined behaviour under C++11 and later).
Implementation Notes:
[x]Addmutable std::shared_mutex jwks_cache_mutex_tojwt_validator.halongsidejwks_cache_(jwt_validator.h:192)[x]Wrap all reads ofjwks_cache_infetchJWKS()withstd::shared_lockand all writes withstd::unique_lock(jwt_validator.cpp:98-174)[x]Implement "double-checked locking" pattern: acquire shared lock first, check staleness, upgrade to unique lock only if refresh is needed, then re-check to avoid thundering-herd on cache expiry[x]Add unit test: spawn 32 threads each callingvalidate()concurrently with cache TTL of 0 — verify no crash under Thread Sanitizer (TSAN)
Performance Targets:
- Zero-overhead on warm-cache path (shared_lock is reader-writer; concurrent readers proceed in parallel)
- At most one actual HTTP fetch per cache expiry under concurrent load
Priority: High Target Version: v1.2.0
ldap_authenticator.cpp uses exclusively synchronous blocking calls: ldap_simple_bind_s() (line 222), ldap_search_s() (line 257), ldap_search_ext_s() (line 379), ldap_start_tls_s() (line 333). jwt_validator.cpp:132 calls curl_easy_perform() synchronously with an inline std::this_thread::sleep_for retry loop (lines 118, 145). oidc_provider.cpp:230 and oauth_pkce_flow.cpp:214 and oauth_device_flow.cpp:198 each call curl_easy_perform() / httpPost() on the caller's thread. This means any network timeout or LDAP server slowdown stalls the entire calling thread.
Implementation Notes:
[x]Introduce a dedicatedAuthWorkerThreadPool(min 4, max 32 threads) inldap_authenticator.cppso thatauthenticate()dispatches to the pool and returns astd::future<LDAPAuthResult>[x]Replacecurl_easy_perform()injwt_validator.cpp:fetchJWKS(),oidc_provider.cpp:httpGet(),oauth_pkce_flow.cpp, andoauth_device_flow.cppwithcurl_multi_perform()calls on a shared multi-handle.curl_multi_info_read()is used to retrieve per-transferCURLcoderesults.[x]Move the HTTP fetch infetchJWKS()entirely outsidejwks_cache_mutex_. Only a brief exclusive lock is taken to write the result. A single-flight mutex (jwks_refresh_mutex_) prevents thundering-herd stampedes.std::this_thread::sleep_forback-off remains on the worker thread (not on the caller).[x]Expose async variants (authenticateAsync(),validateAsync()) on existing public interfaces so callers can useco_await/std::future
Status: [x] Implemented in v1.2.0
Performance Targets:
- LDAP bind latency P99 ≤ 50 ms visible to callers even when backend latency is 200 ms (no head-of-line blocking)
- JWT JWKS refresh never blocks the validation hot path for more than 1 ms
Priority: Critical (Security) Target Version: v1.1.0
ldap_authenticator.cpp:buildUserDN() (lines 90-97) substitutes the raw username string into a DN template by replacing the {username} placeholder with no escaping at all. An attacker supplying a username containing DN special characters (,, =, +, <, >, #, ;, \, ") can manipulate the constructed DN to bind as a different directory entry. This is a textbook LDAP injection vulnerability.
Implementation Notes:
[x]ImplementescapeLDAPDNComponent(const std::string& value)inldap_authenticator.cppfollowing RFC 4514 Section 2.4: escape characters,,+,",\,<,>,;, and leading/trailing spaces and#[x]ImplementescapeLDAPFilterValue(const std::string& value)following RFC 4515 Section 3: escape*,(,),\, NUL[x]CallescapeLDAPDNComponent()onusernameinsidebuildUserDN()before string substitution (line 96)[x]CallescapeLDAPFilterValue()on all user-controlled values inserted into LDAP search filter strings (lines 257, 379)[x]AddLDAP_OPT_REFERRALS = LDAP_OPT_OFFto both the Windows path (line 208) and the POSIX path (line 317) — referral chasing with attacker-controlled usernames can redirect authentication to a rogue LDAP server[x]Add fuzz test (libFuzzer) targetingbuildUserDN()with adversarial username inputs
Performance Targets:
- Escaping adds < 5 µs overhead per authentication call
Priority: High (Security) Target Version: v1.1.0
api_key_authenticator.cpp:272 already uses CRYPTO_memcmp() for secret comparison — correct. However, other comparators in the module are not constant-time:
mfa_authenticator.cpp:173: recovery code lookup usesstd::find/ iterator comparison (it != enrollment.recovery_codes.end()). An attacker who can measure sub-microsecond timing differences can deduce the position of the matching recovery code in the list via early-exit short-circuit.session_manager.cpp:205: session ID lookup usesunordered_map::find, which comparesstd::stringkeys withoperator==— subject to timing oracle for session token brute-force.totp_replay_cache.cpp: TOTP code comparison insidemarkUsedusesstd::unordered_setinteger hash lookup — effectively constant-time for integers, but worth documenting explicitly.
Implementation Notes:
[x]Inmfa_authenticator.cpp, replacestd::findover recovery codes with a loop usingCRYPTO_memcmp()that always iterates all entries regardless of match, then returns true/false after full traversal (prevents early-exit timing leak) (line 173)[x]Insession_manager.cpp, store session IDs as their SHA-256 hash in the lookup map; compare incoming tokens by hashing them first, which normalises comparison time regardless of input content (session_manager.cpp:205,sessions_member)[x]Add microbenchmark that measures TOTP/recovery-code verification latency variance under ThreadSanitizer to confirm constant-time behaviour
Performance Targets:
- Recovery code verification time must vary by < 100 ns regardless of match position in a list of 10 codes (production hardware target; CI gate uses < 100 µs to accommodate sanitizer/scheduler overhead)
Priority: High (Security) Target Version: v1.1.0 Status: ✅ Implemented (v1.7.0)
JWTValidatorConfig now uses std::optional<std::string> for expected_issuer and expected_audience with bool require_issuer_validation = true / bool require_audience_validation = true flags. The constructor throws std::runtime_error("Issuer validation not configured") when a require flag is true but the field is unset.
Implementation Notes:
[x]InJWTValidator::Config(jwt_validator.h:91-99), replacedexpected_issuer/expected_audienceplain strings withstd::optional<std::string>and addedrequire_issuer_validation/require_audience_validationflags[x]In theJWTValidator(JWTValidatorConfig)constructor, throwstd::runtime_errorifrequire_issuer_validationis true butexpected_issueris unset[x]Emit aspdlog::warnwhen either field is unset and the correspondingrequire_*flag is false[x]Unit tests: validate token with correct/wrong issuer, correct/wrong audience, missing issuer, missing audience
Priority: Medium (Security) Target Version: v1.2.0 Status: ✅ Implemented (v1.7.0)
JWTValidatorConfig now has bool require_jti = false. When token_blacklist_ is set and the incoming token has no jti, a one-time spdlog::warn is emitted (guarded by warned_blacklist_no_jti_ atomic flag to prevent per-request log flooding).
Implementation Notes:
[x]Addedbool require_jti = falsetoJWTValidator::Config(jwt_validator.h:100)[x]Whenrequire_jtiis true and token has nojti, reject withstd::runtime_error("Missing required jti claim")[x]Whentoken_blacklist_is set but token has nojti, emit one-timespdlog::warnviawarned_blacklist_no_jti_atomic flag
Priority: High Target Version: v1.3.0 Status: ✅ Implemented (v1.7.0)
ITokenBlacklist abstract interface with three implementations: in-memory TokenBlacklist (Bloom filter pre-check, bounded max_entries), RedisTokenBlacklist (Redis SET jti EX ttl NX), and RocksDBTokenBlacklist (single-node persistence with background expiry thread).
Implementation Notes:
[x]Defined abstractITokenBlacklistinterface ininclude/auth/token_blacklist.hwithadd(jti, expiry),isRevoked(jti),purgeExpired()[x]ImplementedRedisTokenBlacklist : ITokenBlacklistbacked by RedisSET jti EX ttl NX(src/auth/redis_token_blacklist.cpp)[x]ImplementedRocksDBTokenBlacklist : ITokenBlacklistwith dedicated CF and background expiry thread (src/auth/rocksdb_token_blacklist.cpp)[x]Added hand-rolled Bloom filter (FNV-1a + djb2 double-hashing, 7 hash probes, ~1% false-positive rate) for non-revoked fast path inTokenBlacklist[x]BoundedTokenBlacklisttomax_entries = 1,000,000with earliest-expiry eviction
Performance Targets:
isRevoked()hot path (non-revoked token, warm Bloom filter): ≤ 1 µs- Redis-backed
isRevoked(): ≤ 2 ms P99 on local network
Priority: High Target Version: v1.2.0 Status: ✅ Implemented
ldap_authenticator.cpp opens a new LDAP connection (TCP + TLS handshake + bind) for every authentication call (performBind(), lines 188-286 on Windows; lines 307-395 on POSIX). LDAP connection setup including TLS typically takes 10–50 ms. Under load (e.g., 500 concurrent logins) this exhausts file descriptors and introduces severe latency.
Implementation Notes:
[x]ImplementLDAPConnectionPoolclass in newldap_connection_pool.cpp: pool of pre-boundLDAP*handles, protected bystd::mutex+std::condition_variable, configurablemin_idle,max_size, andcheckout_timeout[x]Onauthenticate(), checkout a connection from the pool (blocking up tocheckout_timeout), perform bind/search, return connection to pool (RAII viaPooledConnectionwrapper)[x]Implement connection health check: on checkout, test the connection withldap_search_ext_sto""base with scopeLDAP_SCOPE_BASErequestingsupportedLDAPVersion; evict and re-create if stale[x]Exposepool_size,idle_connections,active_connectionsviaauth_metrics.cppcounters
Performance Targets:
- Average LDAP authentication latency reduced from ~30 ms to < 5 ms under sustained load via connection reuse
Priority: Medium Target Version: v1.3.0 Status: ✅ Implemented (v1.8.0)
jwt_validator.cpp now supports ES384 (P-384/SHA-384), ES512 (P-521/SHA-512), RS384 (RSA/SHA-384), and RS512 (RSA/SHA-512) in addition to the existing ES256, RS256, and EdDSA.
Implementation Notes:
[x]RefactoredverifySignatureES256()to delegate to newverifySignatureEC(alg, jwk)dispatcher that selects curve (NID_X9_62_prime256v1/NID_secp384r1/NID_secp521r1), coordinate size (32 / 48 / 66 bytes), and digest (EVP_sha256()/EVP_sha384()/EVP_sha512()) based on thealgparameter[x]AddedverifySignatureRSA(alg, jwk)dispatcher that selects SHA-256/384/512 for RS256/RS384/RS512;verifySignatureRS256()now delegates to it[x]Updated algorithm allow-list inparseAndValidate()to include RS384, RS512, ES384, ES512[x]Updated dispatch block to callverifySignatureRSAfor RS256/RS384/RS512 andverifySignatureECfor ES256/ES384/ES512[x]Added comprehensive test coverage intests/test_jwt_ec_curves_comprehensive.cpp(ES384 happy-path, ES512 happy-path, expired token, wrong signature, tampered payload, kid revocation, cross-curve attacks, RS384, RS512)
Priority: High (Security) Target Version: v1.2.0 Status: ✅ Implemented (v1.7.0)
SecureString and SecureVector wrappers in include/auth/secure_memory.h use OPENSSL_cleanse() on destruction and call mlock() / VirtualLock() to prevent key material from being swapped to disk.
Implementation Notes:
[x]SecureString/SecureVector<T>wrappers ininclude/auth/secure_memory.hwithOPENSSL_cleanse()in destructor[x]mlock()(Linux/macOS) andVirtualLock()(Windows) called insecure_mlock()helper[x]Key material fields inJWKSSecurityConfig,JWTKeyRotationManager, andTOTPSecretEncryptionuseSecureString[x]client_key_passwordexcluded from any serialised or logged structs
Priority: Medium Target Version: v1.2.0 Status: ✅ Implemented (v1.7.0)
MFAAuthenticator::Config now has uint8_t max_window_steps = 1 capped at an absolute hard limit of 2. The constructor rejects configurations where time_window > max_window_steps via std::invalid_argument. Non-zero step offsets emit audit log entries via auth_audit_logger.cpp.
Implementation Notes:
[x]Addeduint8_t max_window_steps = 1toMFAAuthenticator::Config(mfa_authenticator.h:78)[x]Constructor enforcestime_window <= std::min(max_window_steps, uint8_t{2})[x]Non-zero TOTP step offset emits audit event with subject, offset, and timestamp[x]totp_drift_histogramcounter exposed inauth_metrics.cpp
Priority: Medium Target Version: v1.3.0 Status: ✅ Implemented (v1.7.0)
IRateLimiterBackend abstract interface in include/auth/rate_limiter_backend.h with two built-in implementations: InMemoryRateLimiterBackend (single-node default) and RedisRateLimiterBackend (multi-node; atomic Lua sliding-window script).
Implementation Notes:
[x]DefinedIRateLimiterBackendinterface withincrement(key, window),getCount(key, window),reset(key)(include/auth/rate_limiter_backend.h)[x]ImplementedRedisRateLimiterBackendusing atomic Lua sorted-set script (ZREMRANGEBYSCORE + ZADD + EXPIRE + ZCARD), avoiding TOCTOU[x]InMemoryRateLimiterBackendkept as default for single-node deployments[x]Integration test intests/test_auth_rate_limiter_distributed.cpp: two instances sharing a backend observe combined request count
Priority: Medium Target Version: v1.3.0
auth_rate_limiter.cpp:463 gates credential stuffing detection on config_.enable_credential_stuffing_detection but the underlying counters are in-memory only. Cross-session detection (tracking a user across multiple login sessions over hours) requires persisted, time-windowed counters. Currently, process restart resets all detection state.
Implementation Notes:
[x]Store credential-stuffing counters in the sameIRateLimiterBackend(Redis) with a dedicated key namespacecs:{user_id}:{day}[x]Implement exponential back-off lock-out: first breach triggers CAPTCHA requirement, second triggers email OTP, third triggers 24-hour account lock (auth_rate_limiter.cpp:282-300)[x]Exposecredential_stuffing_attempts_totalmetric counter inauth_metrics.cppwith labels{user_id, ip, outcome}
Priority: Medium Target Version: v1.4.0
zero_trust_auth_verifier.cpp currently performs synchronous policy evaluation. For long-lived connections (WebSocket, gRPC streaming, DB connection pool), the zero-trust posture of a session must be re-evaluated periodically without dropping the connection.
Implementation Notes:
[x]Addstd::chrono::seconds re_evaluation_interval{300}toZeroTrustConfigininclude/auth/zero_trust_auth_verifier.h[x]Implement background re-evaluation loop: per-session timer fires everyre_evaluation_interval; if policy check fails, signal the session manager to revoke the session viasession_manager.cpp:terminateSession()[x]Re-evaluation must not block the data-plane thread; dispatch toAuthWorkerThreadPool(see Feature 2)[x]Emit audit eventzero_trust/re_evaluation_failedviaauth_audit_logger.cppwhen continuous check revokes an active session
Status: [x] Implemented in v1.4.0
Priority: Low Target Version: v1.4.0 Status: ✅ Implemented (v1.4.0)
Encrypted SAML assertions (<EncryptedAssertion>) are now supported. The implementation uses OpenSSL (AES-128-CBC / AES-256-CBC with RSA-OAEP or RSA-PKCS1-v1.5 key transport) and the pugixml parser that is already a codebase dependency, avoiding the need for a separate xmlsec library.
Implementation Notes:
[x]ImplementdecryptAssertion(const pugi::xml_node& encrypted_assertion_node)using OpenSSL EVP APIs (RSA-OAEP + AES-CBC) insaml_authenticator.cpp[x]Load SP private key viaSAMLConfig::sp_private_key_loadercallback (not a plain-text PEM config field) — callers supply a closure that loads the key from their HSM, KMS, or secrets manager[x]After decryption, the plaintext assertion XML is re-parsed with pugixml and passed through the existing signature verification path inprocessResponseImpl()[x]All decryption failures throwAuthErrorCode::SAML_DECRYPTION_FAILEDwith an explicit diagnostic message — no silent empty return
Priority: Low Target Version: v1.4.0 Status: ✅ Implemented (v1.4.0)
FederatedIdentityManager::exchangeToken() implements RFC 8693 (OAuth 2.0 Token Exchange) for service-to-service impersonation and delegation in federated scenarios.
Implementation Notes:
[x]ImplementexchangeToken(subject_token, subject_token_type, requested_token_type)infederated_identity_manager.cppcalling the IdP'stoken_endpointwithgrant_type=urn:ietf:params:oauth:grant-type:token-exchange[x]Validate the returned token through the existingJWTValidatorpipeline[x]Scope the exchanged token to the minimum required permissions for the target service
[x]Thread-sanitizer (TSAN) clean under 64-thread concurrentJWTValidator::validate()load (Feature 1)[x]LDAP injection fuzz-test passes with 1,000,000 adversarial username inputs (Feature 3)[x]All secret comparison paths verified constant-time via valgrind/memcheck + microbenchmark (Feature 4)[x]Token blacklist survives process restart and is synchronised across 2+ nodes (Feature 7)[x]LDAP connection pool stress-tested at 500 concurrent authentications (Feature 8)[x]Allexpected_issuer/expected_audienceempty-string misconfiguration cases covered by integration tests (Feature 5)[x]Key material pages locked withmlock()/VirtualLock()— verified via/proc/self/smaps(Feature 10)[x]Rate limiter cross-node bypass attack tested with two nodes sharing Redis backend (Feature 12)[x]ES384 (P-384), ES512 (P-521), RS384, RS512 JWT validation tested with comprehensive test suite (Feature 9)
JWTValidatorJWKS cache is mutex-protected —std::shared_mutex jwks_cache_mutex_guards all reads/writes; double-checked locking viajwks_refresh_mutex_prevents thundering-herd stampedes. ✅ Fixed in v1.7.0.- LDAP calls are non-blocking —
ldap_simple_bind_s/ldap_search_ext_sexecute on theAuthWorkerThreadPool; callers receivestd::future<LDAPAuthResult>. ✅ Fixed in v1.7.0. - LDAP DN injection —
buildUserDN()inldap_authenticator.cppnow uses RFC 4514escapeLDAPDNComponent()and RFC 4515escapeLDAPFilterValue(). ✅ Fixed in v1.7.0. - Token blacklist is now persistent —
RedisTokenBlacklistandRocksDBTokenBlacklistprovide cross-restart and cross-node persistence. ✅ Fixed in v1.7.0. expected_issuer/expected_audienceare now mandatory by default —require_issuer_validation = true/require_audience_validation = truethrow at construction if the field is unset. Deployments using the legacy URL-only constructor have both require flags set tofalsefor backward compatibility. ✅ Fixed in v1.7.0.- EC JWT validation now supports P-256, P-384 (ES384), and P-521 (ES512) as well as RSA RS384/RS512. ✅ Fixed in v1.8.0.
- SAML encrypted assertions return empty silently — no error surfaced to caller (
saml_authenticator.cpp:428,443). ✅ Fixed in v1.4.0 (see Feature 15).
- Feature 5 (mandatory issuer/audience): adding
require_issuer_validation = trueas default is a breaking configuration change — deployments with emptyexpected_issuerwill fail to start. Must be gated behind an explicit opt-in flag for one release cycle. - Feature 7 (ITokenBlacklist interface): changes
TokenBlacklistfrom a concrete class to an interface. Callers that constructTokenBlacklistdirectly must be updated to use factory methods. - Feature 2 (async LDAP):
LDAPAuthenticator::authenticate()signature change from synchronous return tostd::future<LDAPAuthResult>is a breaking API change for all consumers.
- ✅ RFC 6238 (TOTP)
- ✅ RFC 7519 (JWT)
- ✅ RFC 7517 (JWK)
- ✅ RFC 7518 (JWA) — RS256, RS384, RS512, ES256, ES384, ES512, EdDSA
- ✅ RFC 4120 (Kerberos v5)
- ✅ OpenID Connect Core 1.0
- ✅ RFC 7636 (PKCE)
- ✅ RFC 8628 (Device Flow)
[ ]RFC 8693 (Token Exchange) — partially stubbed infederated_identity_manager.cpp:187[x]RFC 4514 (LDAP DN string representation / escaping) —escapeLDAPDNComponent()inldap_authenticator.cpp[x]RFC 4515 (LDAP filter string representation / escaping) —escapeLDAPFilterValue()inldap_authenticator.cpp
GAP-003 – identified via static analysis (2026-04-21). Reference:
docs/governance/SOURCECODE_COMPLIANCE_GOVERNANCE.md.
Scope: src/auth/saml_authenticator.cpp:338
- SHA-256 SAML flows must not be affected
- A grace-period escape hatch (
THEMIS_SAML_ALLOW_SHA1=1env var) is allowed for operators upgrading their IdP, but must log a CRITICAL startup warning and be clearly documented as a security risk
// In SamlAuthenticator constructor / init:
bool allow_sha1_ = (std::getenv("THEMIS_SAML_ALLOW_SHA1") != nullptr);
if (allow_sha1_) {
THEMIS_CRITICAL("SAML: SHA-1 acceptance enabled via THEMIS_SAML_ALLOW_SHA1 – "
"this is a security risk; upgrade your IdP to SHA-256");
}- In
verifySAMLAssertion(), replace the SHA-1 branch:
} else if (digest_algorithm_uri.find("sha1") != std::string::npos) {
if (!allow_sha1_) {
THEMIS_ERROR("SAML: SHA-1 digest rejected – set THEMIS_SAML_ALLOW_SHA1=1 to override");
return false;
}
digest_md = EVP_sha1();
}- RFC 8211 §4 prohibits SHA-1 for XML Digital Signatures in new deployments
- NIST SP 800-131A Rev. 2 §9 prohibits SHA-1 for digital signature generation after 2017
- The SHAttered (2017) attack demonstrated a practical SHA-1 chosen-prefix collision
- Unit test: SHA-1 signed assertion →
false(default, no env var) - Unit test: SHA-1 signed assertion +
THEMIS_SAML_ALLOW_SHA1=1→true+ CRITICAL log - Unit test: SHA-256 signed assertion →
true(unaffected)
- No performance impact (early-exit path)
- Fail-closed by default; no silent downgrade
- Operator must opt-in explicitly; the env var is documented in SECURITY.md
Stub: src/auth/redis_token_blacklist.cpp — #else !THEMIS_ENABLE_REDIS block
Risk: Token revocations not persisted across restarts; revoked JWTs accepted until natural expiry.
- Enable hiredis via the
redisvcpkg feature → defineTHEMIS_ENABLE_REDISin CMake. - The real implementation (above the
#elseblock) already covers connection pooling,SETEX+EXISTSsemantics, TTL alignment with JWT expiry, and thepurgeExpired()scheduler. - Add
tests/test_redis_token_blacklist.cppintegration test against a Redis 7.x container (Docker Compose or GitHub Actions service container).
- No breaking API changes to
ITokenBlacklist; consumers (auth middleware) unchanged. - Reconnect logic must retry up to 3 times with exponential back-off before surfacing the error.
- Integration:
add("jti1", now+60s)→isRevoked("jti1")returns true; after TTL: false. - Regression:
!THEMIS_ENABLE_REDISpath continues to compile andisRevoked()returns false.
- Redis connection must use TLS (AUTH + TLS_VERIFY_PEER) in production deployments.
- If Redis is unavailable, fall back to in-memory blacklist with WARN log (fail-open, not fail-closed, by policy).
Stub: src/auth/ldap_authenticator.cpp — #else !THEMIS_HAS_LDAP block
Risk: LDAP-based logins fail; organizations that use AD/LDAP cannot authenticate.
- Install libldap-dev (OpenLDAP) and build with
-DTHEMIS_ENABLE_LDAP=ON. - The real implementation already handles TLS/StartTLS, paging, group membership, and RFC 4515-escaped attribute values.
- Integration: run against a Samba/OpenLDAP Docker container; assert successful bind with a valid credential and rejected bind with an invalid one.
- LDAP injection: supply username
*)(uid=*))(|(uid=*; assertperformBind()returns Failed.
- Enable the LDAP injection fix (see §LDAP Injection in this file) before activating the library.
Stub: src/auth/rate_limiter_backend.cpp — !THEMIS_ENABLE_REDIS: increment() returns 0 (fail-open); all Redis ops are no-ops
Risk: Distributed rate limiting disabled; DoS / rate-limit bypass possible; no cross-replica coordination.
- Install libhiredis (
apt install libhiredis-devor vcpkg redis feature). - Set
-DTHEMIS_ENABLE_REDIS=1in CMake. - The full Redis-backed
RedisRateLimiterBackend(above#elseinrate_limiter_backend.cpp) is then compiled. - Configure
THEMIS_REDIS_HOST/THEMIS_REDIS_PORTenv vars or setRedisRateLimiterBackend::Configfrom YAML.
increment()must be atomic: the Lua sorted-set script (kIncrScript) is already correct for atomicity.- Redis connection loss must fail-open (log WARN; allow request) to avoid blocking auth on cache outage.
- Use
REQUIREPASSor ACL + TLS in production Redis.
- Distributed: two
RedisRateLimiterBackendinstances sharing a real Redis → combined counter equals sum of increments. - Reconnect: simulate Redis restart →
reconnect()succeeds → subsequent increments work.
Stub: src/auth/kerberos_security.cpp — returns empty string for service principal; no ASN.1 GSSAPI token parsing.
Risk: Service-principal-based policy enforcement cannot distinguish between different Kerberos services; all tokens look identical.
- Parse the GSSAPI token: check OID 1.2.840.113554.1.2.2 (KRB5).
- Decode the AP-REQ DER structure using Heimdal or MIT KRB5 ASN.1 APIs.
- Extract the service principal (sname) from the ticket's EncTicketPart.