From 2b646b82542cebb055d4e372296a6fcd6554e4a6 Mon Sep 17 00:00:00 2001 From: "markus.rauscher" Date: Tue, 21 Apr 2026 17:48:02 +0200 Subject: [PATCH] Fix HNSHK security profile version for two-step PIN/TAN authentication The HNSHK signature header always used SecurityProfile(PIN, 1) (one-step) even when two-step TAN authentication was active. This caused an inconsistency: HNVSK (encryption) correctly used PIN:2 for two-step, but HNSHK (signature) still said PIN:1. Banks that strictly validate the security profile version (notably HypoVereinsbank/UniCredit) rejected the HKTAN segment with error 9210 ("Auftrag abgelehnt") because the signature header claimed one-step authentication while a two-step TAN segment was present in the message. This also fixes _bootstrap_mode never being reset after fetch_tan_mechanisms(), which caused 9075 SCA errors to be silently swallowed instead of properly raised. Fixes #213 --- fints/client.py | 3 +++ fints/security.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/fints/client.py b/fints/client.py index 213972a..61ab21c 100644 --- a/fints/client.py +++ b/fints/client.py @@ -1305,6 +1305,9 @@ def fetch_tan_mechanisms(self): else: self.set_tan_mechanism('999') self._ensure_system_id() + # Bootstrap phase (anonymous dialog with sf=999) is complete. + # Reset so that errors in subsequent authenticated dialogs are properly raised. + self._bootstrap_mode = False if self.get_current_tan_mechanism(): # We already got a reply through _ensure_system_id return self.get_current_tan_mechanism() diff --git a/fints/security.py b/fints/security.py index e9f14e1..bab41ac 100644 --- a/fints/security.py +++ b/fints/security.py @@ -99,13 +99,14 @@ def __init__(self, pin): self.pin = pin self.pending_signature = None self.security_function = None + self.security_method_version = 1 def sign_prepare(self, message: FinTSMessage): _now = datetime.datetime.now() rand = random.SystemRandom() self.pending_signature = HNSHK4( - security_profile=SecurityProfile(SecurityMethod.PIN, 1), + security_profile=SecurityProfile(SecurityMethod.PIN, self.security_method_version), security_function=self.security_function, security_reference=rand.randint(1000000, 9999999), security_application_area=SecurityApplicationArea.SHM, @@ -178,6 +179,7 @@ def __init__(self, client, security_function, *args, **kwargs): super().__init__(*args, **kwargs) self.client = client self.security_function = security_function + self.security_method_version = 2 def _get_tan(self): retval = self.client._pending_tan