Skip to content

Commit f5e2db4

Browse files
henrykraphaelm
authored andcommitted
(Re-)Add one step PIN mechanism
1 parent 4f4d0d8 commit f5e2db4

File tree

8 files changed

+225
-76
lines changed

8 files changed

+225
-76
lines changed

fints/client.py

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from .dialog import FinTSDialogOLD, FinTSDialog
1111
from .formals import TwoStepParametersCommon
1212
from .message import FinTSMessageOLD
13+
from .security import PinTanDummyEncryptionMechanism, PinTanOneStepAuthenticationMechanism
1314
from .models import (
1415
SEPAAccount, TANChallenge, TANChallenge3,
1516
TANChallenge4, TANChallenge5, TANChallenge6,
@@ -47,6 +48,8 @@ def __init__(self, bank_identifier, user_id, customer_id=None):
4748
self.bpa = None
4849
self.bpd = []
4950
self.upd_version = 0
51+
self.upa = None
52+
self.upd = []
5053
self.product_name = 'pyfints'
5154
self.product_version = '0.2'
5255

@@ -70,8 +73,13 @@ def process_institute_response(self, message):
7073
)
7174
)
7275

73-
for seg in message.find_segments(HIUPA4):
74-
self.upd_version = seg.upd_version
76+
upa = message.find_segment_first(HIUPA4)
77+
if upa:
78+
self.upa = upa
79+
self.upd_version = upa.upd_version
80+
self.upd = list(
81+
message.find_segments('HIUPD')
82+
)
7583

7684
def find_bpd(self, type):
7785
for seg in self.bpd:
@@ -209,28 +217,6 @@ def get_balance(self, account: SEPAAccount):
209217
if seg:
210218
return seg.balance_booked.as_mt940_Balance()
211219

212-
def _create_balance_message(self, dialog: FinTSDialogOLD, account: SEPAAccount):
213-
hversion = dialog.hksalversion
214-
215-
if hversion in (1, 2, 3, 4, 5, 6):
216-
acc = ':'.join([
217-
account.accountnumber, account.subaccount or '', str(280), account.blz
218-
])
219-
elif hversion == 7:
220-
acc = ':'.join([
221-
account.iban, account.bic, account.accountnumber, account.subaccount or '', str(280), account.blz
222-
])
223-
else:
224-
raise ValueError('Unsupported HKSAL version {}'.format(hversion))
225-
226-
return self._new_message(dialog, [
227-
HKSAL(
228-
3,
229-
hversion,
230-
acc
231-
)
232-
])
233-
234220
def get_holdings(self, account: SEPAAccount):
235221
"""
236222
Retrieve holdings of an account.
@@ -520,11 +506,11 @@ def _new_dialog(self, lazy_init=False):
520506
if not lazy_init:
521507
self._ensure_system_id()
522508

523-
return FinTSDialog(self, lazy_init=lazy_init)
524-
525-
# FIXME
526-
# dialog = FinTSDialogOLD(self.blz, self.username, self.pin, self.systemid, self.connection)
527-
# return dialog
509+
return FinTSDialog(self,
510+
lazy_init=lazy_init,
511+
enc_mechanism=PinTanDummyEncryptionMechanism(1),
512+
auth_mechanisms=[PinTanOneStepAuthenticationMechanism(self.pin)],
513+
)
528514

529515
def _new_message(self, dialog: FinTSDialogOLD, segments, tan=None):
530516
return FinTSMessageOLD(self.blz, self.username, self.pin, dialog.systemid, dialog.dialogid, dialog.msgno,

fints/dialog.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ class FinTSDialogError(Exception):
1616

1717

1818
class FinTSDialog:
19-
def __init__(self, client=None, lazy_init=False):
19+
def __init__(self, client=None, lazy_init=False, enc_mechanism=None, auth_mechanisms=[]):
2020
self.client = client
2121
self.next_message_number = dict((v, 1) for v in MessageDirection)
2222
self.messages = dict((v, {}) for v in MessageDirection)
23-
self.auth_mechanisms = []
24-
self.enc_mechanism = None
23+
self.auth_mechanisms = auth_mechanisms
24+
self.enc_mechanism = enc_mechanism
2525
self.open = False
2626
self.need_init = True
2727
self.lazy_init = lazy_init
@@ -91,7 +91,7 @@ def send(self, *segments):
9191
response = self.client.connection.send(message)
9292

9393
##assert response.segments[0].message_number == self.next_message_number[response.DIRECTION]
94-
# FIXME Better handling
94+
# FIXME Better handling of HKEND in exception case
9595
self.messages[response.segments[0].message_number] = message
9696
self.next_message_number[response.DIRECTION] += 1
9797

@@ -121,12 +121,12 @@ def new_customer_message(self):
121121
return message
122122

123123
def finish_message(self, message):
124-
message += HNHBS1(message.segments[0].message_number)
125-
126124
# Create signature(s) in reverse order: from inner to outer
127125
for auth_mech in reversed(self.auth_mechanisms):
128126
auth_mech.sign_commit(message)
129127

128+
message += HNHBS1(message.segments[0].message_number)
129+
130130
if self.enc_mechanism:
131131
self.enc_mechanism.encrypt(message)
132132

fints/fields.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,8 @@ class DateField(FixedLengthMixin, NumericField):
333333
_FIXED_LENGTH = [8]
334334

335335
def _parse_value(self, value):
336+
if isinstance(value, datetime.date):
337+
return value
336338
val = super()._parse_value(value)
337339
val = str(val)
338340
return datetime.date(int(val[0:4]), int(val[4:6]), int(val[6:8]))
@@ -348,6 +350,8 @@ class TimeField(FixedLengthMixin, DigitsField):
348350
_FIXED_LENGTH = [6]
349351

350352
def _parse_value(self, value):
353+
if isinstance(value, datetime.time):
354+
return value
351355
val = super()._parse_value(value)
352356
return datetime.time(int(val[0:2]), int(val[2:4]), int(val[4:6]))
353357

fints/security.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import datetime, random
2+
3+
from .message import FinTSMessage
4+
from .types import SegmentSequence
5+
from .formals import SecurityProfile, SecurityRole, IdentifiedRole, DateTimeType, UsageEncryption, SecurityIdentificationDetails, SecurityDateTime, EncryptionAlgorithm, KeyName, CompressionFunction, OperationMode, EncryptionAlgorithmCoded, AlgorithmParameterName, AlgorithmParameterIVName, KeyType, SecurityMethod, SecurityApplicationArea, UserDefinedSignature, HashAlgorithm, SignatureAlgorithm, UserDefinedSignature
6+
from .segments.message import HNVSK3, HNVSD1, HNSHK4, HNSHA2
7+
8+
class EncryptionMechanism:
9+
def encrypt(self, message: FinTSMessage):
10+
raise NotImplemented()
11+
12+
def decrypt(self, message: FinTSMessage):
13+
raise NotImplemented()
14+
15+
class AuthenticationMechanism:
16+
def sign_prepare(self, message: FinTSMessage):
17+
raise NotImplemented()
18+
19+
def sign_commit(self, message: FinTSMessage):
20+
raise NotImplemented()
21+
22+
def verify(self, message: FinTSMessage):
23+
raise NotImplemented()
24+
25+
class PinTanDummyEncryptionMechanism(EncryptionMechanism):
26+
def __init__(self, security_method_version=1):
27+
super().__init__()
28+
self.security_method_version = security_method_version
29+
30+
def encrypt(self, message: FinTSMessage):
31+
assert message.segments[0].header.type == 'HNHBK'
32+
assert message.segments[-1].header.type == 'HNHBS'
33+
34+
plain_segments = message.segments[1:-1]
35+
del message.segments[1:-1]
36+
37+
_now = datetime.datetime.now()
38+
39+
message.segments.insert(1,
40+
HNVSK3(
41+
security_profile=SecurityProfile(SecurityMethod.PIN, self.security_method_version),
42+
security_function='998',
43+
security_role=SecurityRole.ISS,
44+
security_identification_details=SecurityIdentificationDetails(
45+
IdentifiedRole.MS,
46+
identifier=message.dialog.client.system_id,
47+
),
48+
security_datetime=SecurityDateTime(
49+
DateTimeType.STS,
50+
_now.date(),
51+
_now.time(),
52+
),
53+
encryption_algorithm=EncryptionAlgorithm(
54+
UsageEncryption.OSY,
55+
OperationMode.CBC,
56+
EncryptionAlgorithmCoded.TWOKEY3DES,
57+
b'\x00'*8,
58+
AlgorithmParameterName.KYE,
59+
AlgorithmParameterIVName.IVC,
60+
),
61+
key_name=KeyName(
62+
message.dialog.client.bank_identifier,
63+
message.dialog.client.user_id,
64+
KeyType.V,
65+
0,
66+
0,
67+
),
68+
compression_function=CompressionFunction.NULL,
69+
)
70+
)
71+
message.segments[1].header.number = 998
72+
message.segments.insert(2,
73+
HNVSD1(
74+
data=SegmentSequence(segments=plain_segments)
75+
)
76+
)
77+
message.segments[2].header.number = 999
78+
79+
def decrypt(self, message: FinTSMessage):
80+
pass
81+
82+
83+
class PinTanOneStepAuthenticationMechanism(AuthenticationMechanism):
84+
def __init__(self, pin, tan=None):
85+
self.pin=pin
86+
self.tan=tan
87+
self.pending_signature=None
88+
89+
def sign_prepare(self, message: FinTSMessage):
90+
_now = datetime.datetime.now()
91+
rand = random.SystemRandom()
92+
93+
self.pending_signature = HNSHK4(
94+
security_profile = SecurityProfile(SecurityMethod.PIN, 1),
95+
security_function = '999',
96+
security_reference = rand.randint(1000000, 9999999),
97+
security_application_area = SecurityApplicationArea.SHM,
98+
security_role = SecurityRole.ISS,
99+
security_identification_details = SecurityIdentificationDetails(
100+
IdentifiedRole.MS,
101+
identifier=message.dialog.client.system_id,
102+
),
103+
security_reference_number = 1, ## FIXME
104+
security_datetime = SecurityDateTime(
105+
DateTimeType.STS,
106+
_now.date(),
107+
_now.time(),
108+
),
109+
hash_algorithm = HashAlgorithm(
110+
usage_hash = '1',
111+
hash_algorithm = '999',
112+
algorithm_parameter_name = '1',
113+
),
114+
signature_algorithm = SignatureAlgorithm(
115+
usage_signature = '6',
116+
signature_algorithm = '10',
117+
operation_mode = '16',
118+
),
119+
key_name = KeyName(
120+
message.dialog.client.bank_identifier,
121+
message.dialog.client.user_id,
122+
KeyType.S,
123+
0,
124+
0,
125+
),
126+
127+
)
128+
129+
message += self.pending_signature
130+
131+
def sign_commit(self, message: FinTSMessage):
132+
if not self.pending_signature:
133+
raise Error("No signature is pending")
134+
135+
if self.pending_signature not in message.segments:
136+
raise Error("Cannot sign a message that was not prepared")
137+
138+
signature = HNSHA2(
139+
security_reference = self.pending_signature.security_reference,
140+
user_defined_signature = UserDefinedSignature(
141+
pin=self.pin,
142+
tan=self.tan,
143+
),
144+
)
145+
146+
self.pending_signature = None
147+
message += signature
148+
149+
def verify(self, message: FinTSMessage):
150+
pass

fints/segments/__init__.py

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -82,43 +82,6 @@ def find_subclass(cls, segment):
8282
return target_cls
8383

8484

85-
class HNVSD1(FinTS3Segment):
86-
"Verschlüsselte Daten"
87-
data = SegmentSequenceField(_d="Daten, verschlüsselt")
88-
89-
class HNVSK3(FinTS3Segment):
90-
"Verschlüsselungskopf"
91-
security_profile = DataElementGroupField(type=SecurityProfile, _d="Sicherheitsprofil")
92-
security_function = DataElementField(type='code', max_length=3, _d="Sicherheitsfunktion, kodiert")
93-
security_role = CodeField(SecurityRole, max_length=3, _d="Rolle des Sicherheitslieferanten, kodiert")
94-
security_identification_details = DataElementGroupField(type=SecurityIdentificationDetails, _d="Sicherheitsidentifikation, Details")
95-
security_datetime = DataElementGroupField(type=SecurityDateTime, _d="Sicherheitsdatum und -uhrzeit")
96-
encryption_algorithm = DataElementGroupField(type=EncryptionAlgorithm, _d="Verschlüsselungsalgorithmus")
97-
key_name = DataElementGroupField(type=KeyName, _d="Schlüsselname")
98-
compression_function = CodeField(CompressionFunction, max_length=3, _d="Komprimierungsfunktion")
99-
certificate = DataElementGroupField(type=Certificate, required=False, _d="Zertifikat")
100-
101-
class HNSHK4(FinTS3Segment):
102-
"Signaturkopf"
103-
security_profile = DataElementGroupField(type=SecurityProfile, _d="Sicherheitsprofil")
104-
security_function = DataElementField(type='code', max_length=3, _d="Sicherheitsfunktion, kodiert")
105-
security_reference = DataElementField(type='an', max_length=14, _d="Sicherheitskontrollreferenz")
106-
security_application_area = CodeField(SecurityApplicationArea, max_length=3, _d="Bereich der Sicherheitsapplikation, kodiert")
107-
security_role = CodeField(SecurityRole, max_length=3, _d="Rolle des Sicherheitslieferanten, kodiert")
108-
security_identification_details = DataElementGroupField(type=SecurityIdentificationDetails, _d="Sicherheitsidentifikation, Details")
109-
security_reference_number = DataElementField(type='num', max_length=16, _d="Sicherheitsreferenznummer")
110-
security_datetime = DataElementGroupField(type=SecurityDateTime, _d="Sicherheitsdatum und -uhrzeit")
111-
hash_algorithm = DataElementGroupField(type=HashAlgorithm, _d="Hashalgorithmus")
112-
signature_algorithm = DataElementGroupField(type=SignatureAlgorithm, _d="Signaturalgorithmus")
113-
key_name = DataElementGroupField(type=KeyName, _d="Schlüsselname")
114-
certificate = DataElementGroupField(type=Certificate, required=False, _d="Zertifikat")
115-
116-
class HNSHA2(FinTS3Segment):
117-
"Signaturabschluss"
118-
security_reference = DataElementField(type='an', max_length=14, _d="Sicherheitskontrollreferenz")
119-
validation_result = DataElementField(type='bin', max_length=512, required=False, _d="Validierungsresultat")
120-
user_defined_signature = DataElementGroupField(type=UserDefinedSignature, required=False, _d="Benutzerdefinierte Signatur")
121-
12285
class HIRMG2(FinTS3Segment):
12386
"Rückmeldungen zur Gesamtnachricht"
12487
responses = DataElementGroupField(type=Response, min_count=1, max_count=99, _d="Rückmeldung")

fints/segments/message.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from fints.utils import fints_escape
44

55
from . import FinTS3SegmentOLD, FinTS3Segment
6-
from fints.formals import ReferenceMessage
7-
from fints.fields import DataElementGroupField, DataElementField, ZeroPaddedNumericField
6+
from fints.formals import ReferenceMessage, SecurityProfile, SecurityRole, SecurityIdentificationDetails, SecurityDateTime, EncryptionAlgorithm, KeyName, CompressionFunction, Certificate, SecurityApplicationArea, HashAlgorithm, SignatureAlgorithm, UserDefinedSignature
7+
from fints.fields import DataElementGroupField, DataElementField, ZeroPaddedNumericField, CodeField, SegmentSequenceField
88

99
class HNHBK3(FinTS3Segment):
1010
"Nachrichtenkopf"
@@ -157,3 +157,49 @@ def __init__(self, segno, msgno):
157157
str(msgno)
158158
]
159159
super().__init__(segno, data)
160+
161+
class HNVSK3(FinTS3Segment):
162+
"""Verschlüsselungskopf, version 3
163+
164+
Source: FinTS Financial Transaction Services, Sicherheitsverfahren HBCI"""
165+
security_profile = DataElementGroupField(type=SecurityProfile, _d="Sicherheitsprofil")
166+
security_function = DataElementField(type='code', max_length=3, _d="Sicherheitsfunktion, kodiert")
167+
security_role = CodeField(SecurityRole, max_length=3, _d="Rolle des Sicherheitslieferanten, kodiert")
168+
security_identification_details = DataElementGroupField(type=SecurityIdentificationDetails, _d="Sicherheitsidentifikation, Details")
169+
security_datetime = DataElementGroupField(type=SecurityDateTime, _d="Sicherheitsdatum und -uhrzeit")
170+
encryption_algorithm = DataElementGroupField(type=EncryptionAlgorithm, _d="Verschlüsselungsalgorithmus")
171+
key_name = DataElementGroupField(type=KeyName, _d="Schlüsselname")
172+
compression_function = CodeField(CompressionFunction, max_length=3, _d="Komprimierungsfunktion")
173+
certificate = DataElementGroupField(type=Certificate, required=False, _d="Zertifikat")
174+
175+
class HNVSD1(FinTS3Segment):
176+
"""Verschlüsselte Daten, version 1
177+
178+
Source: FinTS Financial Transaction Services, Sicherheitsverfahren HBCI"""
179+
data = SegmentSequenceField(_d="Daten, verschlüsselt")
180+
181+
class HNSHK4(FinTS3Segment):
182+
"""Signaturkopf, version 4
183+
184+
Source: FinTS Financial Transaction Services, Sicherheitsverfahren HBCI"""
185+
security_profile = DataElementGroupField(type=SecurityProfile, _d="Sicherheitsprofil")
186+
security_function = DataElementField(type='code', max_length=3, _d="Sicherheitsfunktion, kodiert")
187+
security_reference = DataElementField(type='an', max_length=14, _d="Sicherheitskontrollreferenz")
188+
security_application_area = CodeField(SecurityApplicationArea, max_length=3, _d="Bereich der Sicherheitsapplikation, kodiert")
189+
security_role = CodeField(SecurityRole, max_length=3, _d="Rolle des Sicherheitslieferanten, kodiert")
190+
security_identification_details = DataElementGroupField(type=SecurityIdentificationDetails, _d="Sicherheitsidentifikation, Details")
191+
security_reference_number = DataElementField(type='num', max_length=16, _d="Sicherheitsreferenznummer")
192+
security_datetime = DataElementGroupField(type=SecurityDateTime, _d="Sicherheitsdatum und -uhrzeit")
193+
hash_algorithm = DataElementGroupField(type=HashAlgorithm, _d="Hashalgorithmus")
194+
signature_algorithm = DataElementGroupField(type=SignatureAlgorithm, _d="Signaturalgorithmus")
195+
key_name = DataElementGroupField(type=KeyName, _d="Schlüsselname")
196+
certificate = DataElementGroupField(type=Certificate, required=False, _d="Zertifikat")
197+
198+
class HNSHA2(FinTS3Segment):
199+
"""Signaturabschluss, version 2
200+
201+
Source: FinTS Financial Transaction Services, Sicherheitsverfahren HBCI"""
202+
security_reference = DataElementField(type='an', max_length=14, _d="Sicherheitskontrollreferenz")
203+
validation_result = DataElementField(type='bin', max_length=512, required=False, _d="Validierungsresultat")
204+
user_defined_signature = DataElementGroupField(type=UserDefinedSignature, required=False, _d="Benutzerdefinierte Signatur")
205+

0 commit comments

Comments
 (0)