From: Raphael Michel Date: Mon, 2 Jan 2017 15:25:03 +0000 (+0100) Subject: Take inspiration from fints4fun X-Git-Tag: v0.1.0~11 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=526678e44bbdeec16ce77e0809794b268869dd00;p=thirdparty%2Fpython-fints.git Take inspiration from fints4fun --- 526678e44bbdeec16ce77e0809794b268869dd00 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd96a5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__/ +env +.idea/ +test*.py + diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba1818d --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +Inspiration from https://github.com/barnslig/fints4fun + +Only FinTS 3.0 supported at the moment, only reading data and only PIN/TAN diff --git a/fints/__init__.py b/fints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fints/protocol/__init__.py b/fints/protocol/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fints/protocol/fints3/__init__.py b/fints/protocol/fints3/__init__.py new file mode 100644 index 0000000..b266940 --- /dev/null +++ b/fints/protocol/fints3/__init__.py @@ -0,0 +1,61 @@ +from .segments.crypto import FinTS3CryptoHeader, FinTS3CryptoBody +from .segments.message import FinTS3Footer, FinTS3Header +from .utils import segments_to_ascii + + +class FinTS3: + version = 300 + + def __init__(self): + self.segments = [] + + # global segment counter; used for "sub-segments" in the crypto body + self.i = 1 + + # these segments are special and have to be controlled by this class + self.header = FinTS3Header(self.version) + self.footer = FinTS3Footer() + + def append(self, segment): + self.segments.append(segment) + + def to_ascii(self): + self.i, ascii = segments_to_ascii(self.segments) + size = len(ascii) + + # footer stuff for correct size calculation + self.footer.set_counter(self.i + 1) + footer = self.footer.to_ascii() + "'" + size += len(footer) + + # prepend header with correct size + self.header.set_counter(1) + self.header.set_size(size + 1) + ascii = self.header.to_ascii() + "'" + ascii + + # append footer + ascii += footer + + return ascii + + +class FinTS3PinTan(FinTS3): + def __init__(self, customer, blz, server=None): + self.server = server + + super().__init__() + + self.crypto_header = FinTS3CryptoHeader(customer, blz) + self.crypto_body = FinTS3CryptoBody() + self.append(self.crypto_header) + self.append(self.crypto_body) + + def append(self, segment): + self.segments.append(segment) + + def to_ascii(self): + # create the crypto body + self.i, ascii = segments_to_ascii(self.segments, self.i) + self.crypto_body.set_data(ascii) + + return super().to_ascii() diff --git a/fints/protocol/fints3/segments/__init__.py b/fints/protocol/fints3/segments/__init__.py new file mode 100644 index 0000000..86081bb --- /dev/null +++ b/fints/protocol/fints3/segments/__init__.py @@ -0,0 +1,52 @@ +from collections import OrderedDict + + +class FinTS3Segment: + def __init__(self): + raise NotImplementedError() + + def get_counter(self): + return self.elements['head']['counter'] + + def set_counter(self, counter): + self.elements['head']['counter'] = counter + + # Parse/encode binary dataelements while ignoring the size + def __parse_binary(self, DE): + if DE[0:1] == '@': + bdata = DE.split('@') + return bytes(bdata[2], 'ISO-8859-2') + return DE + + def __encode_binary(self, DE): + if type(DE) is bytes: + return str('@{0}@{1}'.format(len(DE), str(DE, 'ISO-8859-2'))) + return str(DE) + + def from_ascii(self, ascii): + for i, DG in enumerate(ascii.split('+')): + key = list(self.elements.keys())[i] + + if type(self.elements[key]) is OrderedDict: + + for ii, DE in enumerate(DG.split(':')): + kkey = list(self.elements[key].keys())[ii] + self.elements[key][kkey] = self.__parse_binary(DE) + + else: + self.elements[key] = self.__parse_binary(DG) + + def to_ascii(self): + ascii = '' + + for key in self.elements.keys(): + if type(self.elements[key]) is OrderedDict: + for element in self.elements[key]: + ascii += self.__encode_binary(self.elements[key][element]) + ':' + ascii = ascii[:-1] + else: + ascii += self.__encode_binary(self.elements[key]) + ascii += '+' + ascii = ascii[:-1] + + return ascii diff --git a/fints/protocol/fints3/segments/crypto.py b/fints/protocol/fints3/segments/crypto.py new file mode 100644 index 0000000..85c8f13 --- /dev/null +++ b/fints/protocol/fints3/segments/crypto.py @@ -0,0 +1,75 @@ +# See FinTS_3.0_Security_Sicherheitsverfahren_HBCI_Rel_20130718_final_version.pdf +import time +from collections import OrderedDict + +from . import FinTS3Segment + + +# See B.5.3 +# Currently PINTAN only +class FinTS3CryptoHeader(FinTS3Segment): + def __init__(self, customer=None, blz=None): + self.elements = OrderedDict([ + ('head', OrderedDict([ + ('identifier', 'HNVSK'), + ('counter', 998), # See B.8 + ('version', 3) + ])), + ('profile', OrderedDict([ + ('mode', 'PIN'), + ('version', 1) + ])), + ('function', 998), # kinda magic, but this is according to the example E.3.1 + ('role', '1'), + ('identification', OrderedDict([ + ('identifier', 1), + ('cid', ''), # according to the documentation we can ignore this + ('part', 0) # according to the documentation we can ignore this + ])), + ('datetime', OrderedDict([ + ('identifier', 1), # Sicherheitszeitstempel + ('date', time.strftime('%Y%m%d')), # YYYYMMDD + ('time', time.strftime('%H%M%S')) # HHMMSS + ])), + # p. 191 + ('encryption', OrderedDict([ + ('usage', 2), # Owner Symmetric (OSY) + ('mode', 2), # Cipher Block Chaining (CBC) + ('algorithm', 13), # 2-Key-Triple-DES + ('algparams', b'NOKEY'), # Do we care? + ('algkeyidentifier', 6), # Must be right, according to aqBanking! + ('algparamidentifier', 1) # Only possible value --> Clear text (IVC) + ])), + # p. 184 + ('keyname', OrderedDict([ + ('foo', 280), + ('bank', blz), # so-called Bankleitzahl + ('customer', customer), # Username, in Germany often the account number + ('keytype', 'V'), # Chiffrierschlüssel + ('keynumber', 1), + ('keyversion', 1) + ])), + ('compression', 0) # No compression + ]) + + +# See B.5.4 +class FinTS3CryptoBody(FinTS3Segment): + def __init__(self, data=None): + self.elements = OrderedDict([ + ('head', OrderedDict([ + ('identifier', 'HNVSD'), + ('counter', 999), # See B.8 + ('version', 1) + ])), + ('data', b'') + ]) + + def set_data(self, data): + self.elements['data'] = bytes(data, 'ISO-8859-2') + + def get_data(self, data): + return self.elements['data'] + +# See B.5.1 +# class FinTS3SignatureHeader(FinTS3Segment): diff --git a/fints/protocol/fints3/segments/message.py b/fints/protocol/fints3/segments/message.py new file mode 100644 index 0000000..1a6fac9 --- /dev/null +++ b/fints/protocol/fints3/segments/message.py @@ -0,0 +1,40 @@ +# See FinTS_3.0_Security_Sicherheitsverfahren_HBCI_Rel_20130718_final_version.pdf +from collections import OrderedDict + +from . import FinTS3Segment + + +# See B.5.2 +class FinTS3Header(FinTS3Segment): + def __init__(self, version=300): + self.elements = OrderedDict([ + ('head', OrderedDict([ + ('identifier', 'HNHBK'), + ('counter', 0), + ('version', 3) + ])), + ('size', 0), + ('version', version), + ('dialog', 0), + ('msg', 0) + ]) + + def set_size(self, size): + selfSize = len(self.to_ascii()) + 1 + self.elements['size'] = size + selfSize + + def to_ascii(self): + self.elements['size'] = str(self.elements['size']).zfill(12) + return super(FinTS3Header, self).to_ascii() + + +# See B.5.3 +class FinTS3Footer(FinTS3Segment): + def __init__(self): + self.elements = OrderedDict([ + ('head', OrderedDict([ + ('identifier', 'HNHBS'), + ('counter', 0), + ('version', 1) + ])), + ]) diff --git a/fints/protocol/fints3/utils.py b/fints/protocol/fints3/utils.py new file mode 100644 index 0000000..28eb3a9 --- /dev/null +++ b/fints/protocol/fints3/utils.py @@ -0,0 +1,13 @@ +def segments_to_ascii(segments, counter=1): + ascii = '' + + for segment in segments: + # do only set counter if is 0; see B.8 + if segment.get_counter() == 0: + counter += 1 + segment.set_counter(counter) + + s = segment.to_ascii() + ascii += s + "'" + + return counter, ascii