Skip to content

comdirect: HKIDN gets two-step signed after _ensure_system_id() auto-selects a security function, rejected with "9340 Ungueltige Signatur" #222

Description

@Benny1907

Environment:

python-fints 5.0.0
Bank: comdirect (BLZ 20041133), FinTS 3.0/PSD2
FinTS3PinTanClient, standard with client: usage, no system_id passed in (fresh client each time)
Symptom:

Every dialog open + get_transactions() call fails with:

FinTSClientPINError: Error during dialog initialization, PIN wrong?
The bank-side responses (captured via the fints.client logger) show:

9050 - Die Nachricht enthaelt Fehler.
9800 - Dialog abgebrochen
9340 - Ungueltige Auftragsnachricht: Ungueltige Signatur.
Notably, the mobile TAN is delivered to the phone before the failure — the connection gets quite far before failing.

Root cause analysis:

Since system_id is unassigned, FinTS3Client.enter() → _get_dialog() calls _ensure_system_id(), which opens its own preliminary lazy dialog (HKSYN3).
The response to that preliminary dialog appears to already include response code 3920, which _process_response() (client.py, _process_response) handles by auto-selecting a real two-step selected_security_function (e.g. "901") — this succeeds safely since self._standing_dialog is not yet set at that point.
Back in _get_dialog(), self._new_dialog(lazy_init=False) is then called to build the real dialog. Since selected_security_function is no longer None/'999' at this point, _new_dialog() (client.py ~line 1270) now builds the dialog's outer envelope with PinTanTwoStepAuthenticationMechanism instead of the one-step mechanism — meaning the dialog-init (HKIDN) itself gets two-step signed.
comdirect appears to reject a two-step-signed dialog-init outright (only accepting two-step signing for the actual TAN-requiring business command, not for opening the dialog), resulting in 9340.
Attempted workaround and its own failure mode:

Explicitly persisting/passing a known system_id into the client constructor skips step 1/2 above (no preliminary dialog, so selected_security_function stays None going into _new_dialog(), keeping the dialog-init one-step as desired). However, FinTSDialog.init() (dialog.py ~line 74) also unconditionally embeds an HKTAN segment into the INIT message whenever self.client.get_tan_mechanisms() is non-empty — and once the real dialog's own INIT response later triggers response code 3920 for the first time, _process_response()'s auto-select tries to call set_tan_mechanism() while self._standing_dialog is already set, raising:

Exception: Cannot change TAN mechanism with a standing dialog
So there appears to be no way, via the public API, to get both a one-step-signed dialog-init and a correctly negotiated TAN mechanism for the subsequent command against this bank — one approach two-step-signs the INIT (rejected by comdirect), the other crashes on the standing-dialog guard.

Question: Is this a known interaction? Is there a supported way to suppress the two-step auto-selection specifically for dialog-init while still allowing it for the actual TAN-requiring command afterward?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions