autls: add per-identity PSK key binding to TLS client ACL - #543
Merged
Conversation
Extend the ACL data structures and parser to support per-identity pre-shared keys. Each ACL entry can now carry its own key material loaded from a file specified via a key=/path column. Add psk_key, psk_key_len, and key_file fields to autls_acl_entry, and a has_per_identity_keys flag to autls_acl_table. The parser recognizes key= only when followed by '/' (absolute path), preserving backward compatibility with existing ACL files that use free-form notes after the status field. When any entry has a key= path, all entries must have one (all-or-nothing enforcement prevents mixed single-PSK/per-identity configurations). Duplicate key file paths and duplicate key content across entries are both rejected to enforce the identity-key binding invariant. Add autls_acl_lookup() which returns the full entry for callers that need access to key material. Rewrite autls_acl_check() to delegate to autls_acl_lookup(), preserving the existing boolean API. Extract acl_entry_free() to cleanse per-identity key material via OPENSSL_cleanse on all free paths, including partial-construction error paths during parsing. This commit adds the library support; the server-side callback changes that use per-identity keys follow in the next commit. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Restructure the TLS server initialization and PSK callback to select per-identity key material when available, eliminating the identity confusion vulnerability where a disabled collector could authenticate as the enabled identity using the shared global PSK. Restructure init_tls_server_context() from a single monolithic if(tls_psk_file) block into four independent steps: global PSK loading, expected identity setup, ACL loading, and callback registration. This decoupling allows per-identity key mode to operate without a global tls_psk_file. In tls_psk_find_session_cb(), replace autls_acl_check() with autls_acl_lookup() and add mode-aware key selection. In per-identity mode, always use the entry's own key material and hard-reject if it is missing (no silent fallback to the global key). In single-PSK mode, continue using server_psk_key. In reload_tls_client_acl(), add guards for per-identity-only mode ACL removal, reject mode transitions between single-PSK and per-identity on SIGHUP, enforce enabled_count using the new ACL's has_per_identity_keys flag, and evict connected clients whose identity is disabled or absent in the reloaded ACL. Client eviction follows the periodic_handler pattern: save next pointer, stop the ev_io watcher, then release and free. Add a post-handshake re-check in tls_handshake_handler() against the current ACL before admitting a client, closing the window where a SIGHUP swaps the ACL between the PSK callback and handshake completion. Warn at init and reload when the ACL has zero enabled identities. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Relax the configuration validation to allow tls_allowed_clients as an alternative to tls_psk_file. When per-identity keys are specified via key= entries in the ACL file, a global tls_psk_file is unnecessary. The actual validation that per-identity key material exists happens in init_tls_server_context() after ACL loading. A diagnostic notice is logged when tls_psk_file is absent to guide operators. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Document the key= column for per-identity key file paths, permission requirements, the distinction between single-PSK and per-identity mode, SIGHUP client eviction behavior, and the operational requirement to provision unique per-identity keys. Note that disabling an identity in single-PSK mode does not revoke a client that still possesses the shared key, and that switching between modes requires a daemon restart. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add test_autls_acl_per_identity_keys() with 9 test cases covering the new per-identity key ACL functionality: 1. Per-identity key loading with key= paths 2. Mixed format (some key=, some not) rejected 3. Duplicate key file paths rejected 4. Missing key file rejected 5. Partial-load cleanup (ASAN-safe error path) 6. Multiple enabled identities accepted with per-identity keys 7. Backward compat (no key= columns, has_per_identity_keys=0) 8. Notes starting with key= (no /) treated as notes 9. Trailing tokens after key= path rejected Also exercises autls_acl_lookup() return values and verifies that per-identity keys are distinct across entries. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
Add the essential regression test for the per-identity PSK fix: verify that cross-pairing an identity with a different identity's key fails at the TLS binder level, not just at the ACL label level. Uses in-memory BIO pairs with psk_ke mode (no certificate, no DHE) to exercise actual TLS 1.3 external-PSK handshakes through OpenSSL. The server callback selects per-identity keys via autls_acl_lookup, mirroring the production tls_psk_find_session_cb key selection logic. Five test cases: 1. Correct identity+key pairing succeeds 2. Cross-identity key pairing fails (the essential regression) 3. Disabled identity with own key is rejected by ACL 4. Unknown identity is rejected 5. Global key does not match per-identity entry Without this test, a regression in the PSK callback that falls back to a shared key would pass all unit tests while leaving the identity confusion vulnerability exploitable. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The TLS external-PSK listener uses one global key for all client identities.
A disabled collector that still possesses the shared PSK can claim the enabled
collector's label and be admitted — the ACL checks the label but the PSK callback
installs the same key regardless. This series adds per-identity key material to ACL
entries via a key=/path column, so the TLS 1.3 binder cryptographically binds each
identity to its own key. Cross-identity key pairing now fails at the protocol level.
Includes SIGHUP client eviction, mode transition guards, and a BIO-pair regression
test that exercises actual TLS handshakes with mismatched keys