]> git.ipfire.org Git - thirdparty/python-fints.git/commitdiff
Take inspiration from fints4fun
authorRaphael Michel <mail@raphaelmichel.de>
Mon, 2 Jan 2017 15:25:03 +0000 (16:25 +0100)
committerRaphael Michel <mail@raphaelmichel.de>
Mon, 2 Jan 2017 15:25:03 +0000 (16:25 +0100)
.gitignore [new file with mode: 0644]
README.md [new file with mode: 0644]
fints/__init__.py [new file with mode: 0644]
fints/protocol/__init__.py [new file with mode: 0644]
fints/protocol/fints3/__init__.py [new file with mode: 0644]
fints/protocol/fints3/segments/__init__.py [new file with mode: 0644]
fints/protocol/fints3/segments/crypto.py [new file with mode: 0644]
fints/protocol/fints3/segments/message.py [new file with mode: 0644]
fints/protocol/fints3/utils.py [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..bd96a5a
--- /dev/null
@@ -0,0 +1,5 @@
+__pycache__/
+env
+.idea/
+test*.py
+
diff --git a/README.md b/README.md
new file mode 100644 (file)
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 (file)
index 0000000..e69de29
diff --git a/fints/protocol/__init__.py b/fints/protocol/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/fints/protocol/fints3/__init__.py b/fints/protocol/fints3/__init__.py
new file mode 100644 (file)
index 0000000..b266940
--- /dev/null
@@ -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 (file)
index 0000000..86081bb
--- /dev/null
@@ -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 (file)
index 0000000..85c8f13
--- /dev/null
@@ -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 (file)
index 0000000..1a6fac9
--- /dev/null
@@ -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 (file)
index 0000000..28eb3a9
--- /dev/null
@@ -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