Skip to content

Consorsbank: detect login SCA on HKIDN and decoupled 0030+3955 app approval#218

Open
ArlindNocaj wants to merge 8 commits into
raphaelm:masterfrom
ArlindNocaj:fix/consorsbank-login-sca-hkidn
Open

Consorsbank: detect login SCA on HKIDN and decoupled 0030+3955 app approval#218
ArlindNocaj wants to merge 8 commits into
raphaelm:masterfrom
ArlindNocaj:fix/consorsbank-login-sca-hkidn

Conversation

@ArlindNocaj

@ArlindNocaj ArlindNocaj commented Jun 13, 2026

Copy link
Copy Markdown

Summary

Two protocol-level fixes that make login and SEPA transfers work against Consorsbank (BLZ 76030080), plus a refreshed sample_consorsbank.py covering both current TAN methods. Both fixes are small and backwards-compatible (all existing tests pass) and were verified end-to-end with a real SEPA transfer that was accepted and booked.

1. Detect login-SCA response attached to HKIDN

For PSD2 strong customer authentication the client sends an HKTAN (process 4) in the dialog-init. Consorsbank attaches the resulting 0030/3955 response to the HKIDN segment, not the HKTAN segment. FinTSDialog.init() only inspected responses(tan_seg) (HKTAN), so init_tan_response was never set, the client proceeded to send the next command without completing login SCA, and the bank aborted the dialog with 9800/9120 — no TAN challenge ever surfaced. We now check both the HKTAN and HKIDN references.

2. Flag decoupled when 0030 and 3955 arrive together

When an order is approved via the Consorsbank App, the bank returns 0030 ("Auftrag empfangen – Sicherheitsfreigabe erforderlich") together with 3955 ("Sicherheitsfreigabe erfolgt über anderen Kanal") on the same (HKCCS) segment. The code set NeedTANResponse.decoupled from the first matching code (0030), so the challenge was wrongly treated as a typed TAN and the user was asked to type a TAN that doesn't exist. We now set decoupled=True whenever 3955 is present among the segment's responses, regardless of order. Behaviour is unchanged when only one code is present (0030 → typed, 3955 → decoupled).

3. sample_consorsbank.py

Rewritten to cover both current Consorsbank TAN mechanisms with one TAN handler (which checks decoupled before challenge_matrix):

  • 901 "Consorsbank/myPrivateBank App" (zka_id="Decoupled"): typed 9-digit login TAN + decoupled transfer approval in the app.
  • 900 "SecurePlus TAN Generator" (zka_id="photoTAN"): no login SCA (bank answers dialog-init with 3076) + an order-bound photoTAN QR (challenge_matrix) that is scanned, then the TAN is typed.

Verification

  • pytest: 69 passed, 1 skipped, 1 xfailed (no regressions).
  • Live against Consorsbank: login via Consorsbank App (typed 9-digit TAN) + decoupled SEPA transfer approval → transfer accepted and booked. The 900 photoTAN path was also exercised: login 3076 (no TAN) and a valid order-bound photoTAN QR is produced and decoded correctly.

Note on the SecurePlus App (relevant to mechanism 900)

The SecurePlus App (the smartphone app — distinct from the physical SecurePlus TAN-generator device) was shut down for Consorsbank online banking on 2026-04-25. Since then any TAN it produces — including ones scanned from the 900 photoTAN QR — is rejected with 9941 TAN ungültig; effectively the app-based photoTAN sunset together with the SecurePlus App. Only the new Consorsbank App (901) or the physical SecurePlus TAN-generator device (900) produce valid TANs. The 900 code path in this library is correct; only the decommissioned app's TAN is refused by the bank.

Source: https://www.kritische-anleger.de/consorsbank/consorsbank-schafft-secureplus-app-zum-05-08-ab/ (and the official Consorsbank HBCI FAQ).

Notes

The three earlier Consorsbank compatibility fixes (HNSHK security_method_version=2, full KTI1.from_sepa_account, force_twostep_tan) are already on master; this PR adds only the login-SCA-on-HKIDN detection and the 0030+3955 decoupled detection.

nocajar and others added 8 commits February 28, 2026 09:20
These three issues were discovered by comparing mitmdump traces of the
working hbci4j Java library against python-fints when connecting to
Consorsbank (BLZ 76030080). After applying all three fixes, transactions
are fetched successfully, matching the Java output exactly.

1. security.py: Use security_method_version=2 for two-step TAN auth
   Per the ZKA FinTS spec (page 58), two-step TAN methods
   (security_function != '999') require version 2 in the
   SecurityProfile of the HNSHK signature header. The previous
   hardcoded value of 1 caused Consorsbank to reject the request.
   Ref: raphaelm#99

2. formals.py: Include full account details in KTI1.from_sepa_account
   KTI1.from_sepa_account only populated iban and bic, but Consorsbank
   requires the full account details (account_number, subaccount_number,
   bank_identifier). Other classes like KTZ1 already include these
   fields — KTI1 was the only one missing them.

3. client.py: Add force_twostep_tan parameter for banks that require
   HKTAN despite HIPINS saying otherwise
   Some banks (Consorsbank) report HKKAZ:N in HIPINS yet reject
   requests without HKTAN (error 9075). The new opt-in
   force_twostep_tan parameter (set of segment types) allows users
   to override HIPINS for specific segments. Defaults to empty set,
   so existing behavior is unchanged.

All three fixes are backwards-compatible and all existing tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Some banks (e.g. Consorsbank) attach the 0030/3955 response code to
the original command segment (HKCCS) rather than to the HKTAN segment.
This caused _send_pay_with_possible_retry() to miss the TAN challenge
and return a plain TransactionResponse instead of NeedTANResponse.

Added fallback: after checking tan_seg responses, also check
command_seg responses for 0030/3955 codes.

Also:
- Add photoTAN QR code handling to transfers.rst full example
- Fix typo (result.decoupled → res.decoupled) in transfers.rst
- Add Consorsbank to tested.rst (Transactions + Transfer)
- Add security function 900 (photoTAN / SecurePlus)
- Add sample_consorsbank.py showing photoTAN transfer flow

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…sponse

1. Implement VoP polling (FinTS spec E.8.3.1): when the bank returns
   HIVPP with a polling_id but no vop_id, re-send HKVPP with
   polling_id + aufsetzpunkt (from HIRMS 3040) until the VoP check
   resolves and a vop_id is returned.

2. Broaden 3945 response code detection in VoP flow: check all
   HIRMG/HIRMS segments, not just tan_seg responses, since some banks
   attach it to different segments.

3. Add TAN fallback in approve_vop_response: after VoP approval,
   check command_seg and global HIRMG/HIRMS segments for 0030/3955
   TAN-required codes (mirrors Fix 4 from PR raphaelm#210 but in the VoP
   approval path).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Consorsbank (BLZ 76030080) attaches the dialog-init strong-authentication
response code 0030 to the HKIDN segment rather than the HKTAN segment. The
previous dialog.init() only inspected responses(tan_seg) (HKTAN), so
init_tan_response was never set, the client proceeded to send the next
command without completing login SCA, and the bank aborted the dialog with
9800/9120 (no TAN challenge ever surfaced).

Check both the HKTAN and HKIDN references for the 0030/3955 login-SCA code.
Verified end-to-end against Consorsbank: login TAN is now surfaced and a
SEPA transfer completes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a SEPA transfer (or other order) is authorised via the new
Consorsbank App, the bank returns the order-received code 0030 together
with 3955 ("Sicherheitsfreigabe erfolgt über anderen Kanal") attached to
the same (HKCCS command) segment. The previous code iterated the
responses and set NeedTANResponse.decoupled from the FIRST matching code,
which is 0030, so the challenge was wrongly treated as a typed TAN and
the user was asked to enter a TAN that does not exist.

Set decoupled=True whenever 3955 is present among the segment's
responses, regardless of order. Behaviour is unchanged when only one of
the codes is present (0030 alone -> typed, 3955 alone -> decoupled), so
this is a safe, minimal change. Applied consistently in
_send_with_possible_retry, _send_pay_with_possible_retry and the
dialog-init login-SCA handler.

Also rewrite sample_consorsbank.py for the post-2025 TAN migration:
the old SecurePlus App is decommissioned (its TANs are rejected with
9941), so the sample now uses the Consorsbank App (mechanism 901,
zka_id 'Decoupled'). Login uses a typed 9-digit app TAN; the SEPA
transfer is approved in the app (decoupled) and polled to completion.
The physical SecurePlus TAN generator (mechanism 900) remains selectable
via FINTS_TAN_MECHANISM. Verified end-to-end: a real SEPA transfer was
accepted and booked.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… and SecurePlus App shutdown

Clarify that Consorsbank exposes two two-step TAN mechanisms and both are
handled by the sample's single TAN handler (which checks decoupled before
challenge_matrix):

* 901 Consorsbank App: typed 9-digit login TAN + decoupled transfer approval.
* 900 SecurePlus TAN Generator: no login SCA (3076) + order-bound photoTAN QR
  (challenge_matrix) typed for the transfer.

Note the SecurePlus *App* (the smartphone app, not the hardware generator)
was shut down for Consorsbank online banking on 2026-04-25; since then any
TAN it produces — including ones scanned from the 900 photoTAN QR — is
rejected with 9941. Only the new Consorsbank App or the physical SecurePlus
TAN-generator device produce valid TANs. The 900 code path itself is correct.

Source: https://www.kritische-anleger.de/consorsbank/consorsbank-schafft-secureplus-app-zum-05-08-ab/

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Consorsbank (BLZ 76030080) reports some operations as not requiring TANs in
HIPINS, but rejects them without an HKTAN envelope. Keep this bank-specific
compatibility default inside python-fints so applications can remain simple
consumers and only override force_twostep_tan when they explicitly need to.

Default segments: HKCCS, HKKAZ and HKSAL. Passing force_twostep_tan explicitly
still overrides the default.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Some Fiducia/Atruvia banks return FinTS response 9010 with text 'Anzahl der Unterschriften ist nicht ausreichend' while a SecureGo plus distributed-signature approval is still pending. This is not a BPD/bootstrap error and should not abort the dialog.

Keep the existing BPD safeguard for 9010 during dialog initialization, but allow 9010 inside an open dialog and map the insufficient-signatures response to a pending NeedTANResponse so callers can continue polling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.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

Development

Successfully merging this pull request may close these issues.

1 participant