]> git.ipfire.org Git - thirdparty/python-fints.git/commitdiff
Bootstrap communication
authorRaphael Michel <mail@raphaelmichel.de>
Mon, 2 Jan 2017 16:56:08 +0000 (17:56 +0100)
committerRaphael Michel <mail@raphaelmichel.de>
Mon, 2 Jan 2017 16:56:08 +0000 (17:56 +0100)
fints/protocol/fints3/__init__.py
fints/protocol/fints3/client.py [new file with mode: 0644]
fints/protocol/fints3/connection.py [new file with mode: 0644]
fints/protocol/fints3/dialog.py [new file with mode: 0644]
fints/protocol/fints3/message.py [new file with mode: 0644]
fints/protocol/fints3/segments/__init__.py
fints/protocol/fints3/segments/auth.py [new file with mode: 0644]
fints/protocol/fints3/segments/crypto.py [deleted file]
fints/protocol/fints3/segments/message.py

index b2669406a31478339885e11efa8080271a4ecc2c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 (file)
@@ -1,61 +0,0 @@
-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/client.py b/fints/protocol/fints3/client.py
new file mode 100644 (file)
index 0000000..8dd1765
--- /dev/null
@@ -0,0 +1,25 @@
+from .connection import FinTSHTTPSConnection
+from .dialog import FinTSDialog
+
+
+class FinTS3Client:
+    version = 300
+
+
+class FinTS3PinTanClient(FinTS3Client):
+    def __init__(self, blz, username, pin, server):
+        self.username = username
+        self.blz = blz
+        self.pin = pin
+        self.connection = FinTSHTTPSConnection(server)
+        self.systemid = 0
+        super().__init__()
+
+    def _new_dialog(self):
+        dialog = FinTSDialog(self.blz, self.username, self.pin, self.systemid, self.connection)
+        return dialog
+
+    def get_sepa_accounts(self):
+        dialog = self._new_dialog()
+        dialog.sync()
+
diff --git a/fints/protocol/fints3/connection.py b/fints/protocol/fints3/connection.py
new file mode 100644 (file)
index 0000000..3845a68
--- /dev/null
@@ -0,0 +1,22 @@
+import base64
+
+import requests
+
+from fints.protocol.fints3.message import FinTSMessage
+
+
+class FinTSConnectionError(Exception):
+    pass
+
+
+class FinTSHTTPSConnection:
+    def __init__(self, url):
+        self.url = url
+
+    def send(self, msg: FinTSMessage):
+        r = requests.post(
+            self.url, data=base64.b64encode(str(msg).encode('iso-8859-1')),
+        )
+        if r.status_code < 200 or r.status_code > 299:
+            raise FinTSConnectionError('Bad status code {}'.format(r.status_code))
+        return base64.b64decode(r.content.decode('iso-8859-1')).decode('iso-8859-1')
diff --git a/fints/protocol/fints3/dialog.py b/fints/protocol/fints3/dialog.py
new file mode 100644 (file)
index 0000000..bd7cd0b
--- /dev/null
@@ -0,0 +1,47 @@
+import logging
+
+from .segments.auth import HKIDN, HKSYN, HKVVB
+from .message import FinTSMessage
+
+logger = logging.getLogger(__name__)
+
+
+class FinTSDialog:
+    def __init__(self, blz, username, pin, systemid, connection):
+        self.blz = blz
+        self.username = username
+        self.pin = pin
+        self.systemid = systemid
+        self.connection = connection
+        self.msgno = 1
+        self.dialogid = 0
+
+    def sync(self):
+        logger.info('Initialize SYNC')
+
+        seg_identification = HKIDN(3, self.blz, self.username, 0)
+        seg_prepare = HKVVB(4)
+        seg_sync = HKSYN(5)
+
+        msg_sync = FinTSMessage(self.blz, self.username, self.pin, self.systemid, self.dialogid, self.msgno, [
+            seg_identification,
+            seg_prepare,
+            seg_sync
+        ])
+
+        logger.debug('Sending SYNC: {}'.format(msg_sync))
+        resp = self.send(msg_sync)
+        logger.debug('Got SYNC response: {}'.format(resp))
+
+    def send(self, msg):
+        logger.info('Sending Message')
+        msg.msgno = self.msgno
+        msg.dialogid = self.dialogid
+
+        try:
+            resp = self.connection.send(msg)
+            self.msgno += 1
+            return resp
+        except:
+            # TODO: Error handlign
+            raise
diff --git a/fints/protocol/fints3/message.py b/fints/protocol/fints3/message.py
new file mode 100644 (file)
index 0000000..65ada9e
--- /dev/null
@@ -0,0 +1,51 @@
+import random
+
+from fints.protocol.fints3.segments.message import HNHBK, HNSHK, HNVSK, HNVSD, HNSHA, HNHBS
+
+
+class FinTSMessage:
+    def __init__(self, blz, username, pin, systemid, dialogid, msgno, encrypted_segments):
+        self.blz = blz
+        self.username = username
+        self.pin = pin
+        self.systemid = systemid
+        self.dialogid = dialogid
+        self.msgno = msgno
+        self.segments = []
+        self.encrypted_segments = []
+
+        sig_head = self.build_signature_head()
+        enc_head = self.build_encryption_head()
+        self.segments.append(enc_head)
+
+        self.enc_envelop = HNVSD(999, '')
+        self.segments.append(self.enc_envelop)
+
+        self.append_enc_segment(sig_head)
+        for s in encrypted_segments:
+            self.append_enc_segment(s)
+
+        cur_count = len(encrypted_segments) + 3
+
+        sig_end = HNSHA(cur_count, self.secref, self.pin)
+        self.append_enc_segment(sig_end)
+        self.segments.append(HNHBS(cur_count + 1, msgno))
+
+    def append_enc_segment(self, seg):
+        self.encrypted_segments.append(seg)
+        self.enc_envelop.set_data(self.enc_envelop.encoded_data + str(seg))
+
+    def build_signature_head(self):
+        rand = random.SystemRandom()
+        self.secref = rand.randint(1000000, 9999999)
+        return HNSHK(2, self.secref, self.blz, self.username, self.systemid)
+
+    def build_encryption_head(self):
+        return HNVSK(998, self.blz, self.username, self.systemid)
+
+    def build_header(self):
+        l = sum([len(str(s)) for s in self.segments])
+        return HNHBK(l, self.dialogid, self.msgno)
+
+    def __str__(self):
+        return str(self.build_header()) + ''.join([str(s) for s in self.segments])
index 86081bb4796f13b73c1853292c4c284a749de024..86926010d3ec5001160d24ee3e365f35daf97b20 100644 (file)
@@ -1,52 +1,14 @@
-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
+    type = '???'
+    country_code = 280
+    version = 2
+
+    def __init__(self, segmentno, data):
+        self.segmentno = segmentno
+        self.data = data
+
+    def __str__(self):
+        res = '{}:{}:{}'.format(self.type, self.segmentno, self.version)
+        for d in self.data:
+            res += '+' + str(d)
+        return res + "'"
diff --git a/fints/protocol/fints3/segments/auth.py b/fints/protocol/fints3/segments/auth.py
new file mode 100644 (file)
index 0000000..6cc366e
--- /dev/null
@@ -0,0 +1,60 @@
+from . import FinTS3Segment
+
+
+class HKIDN(FinTS3Segment):
+    """
+    HKIDN (Identifikation)
+    Section C.3.1.2
+    """
+    type = 'HKIDN'
+    version = 2
+
+    def __init__(self, segmentno, blz, username, systemid=0, customerid=1):
+        data = [
+            '{}:{}'.format(self.country_code, blz),
+            username,
+            systemid,
+            customerid
+        ]
+        super().__init__(segmentno, data)
+
+
+class HKVVB(FinTS3Segment):
+    """
+    HKVVB (Verarbeitungsvorbereitung)
+    Section C.3.1.3
+    """
+    type = 'HKVVB'
+    version = 3
+
+    LANG_DE = 1
+    LANG_EN = 2
+    LANG_FR = 3
+
+    PRODUCT_NAME = 'pyfints'
+    PRODUCT_VERSION = '0.1'
+
+    def __init__(self, segmentno, lang=LANG_EN):
+        data = [
+            0, 0, lang, self.PRODUCT_NAME, self.PRODUCT_VERSION
+        ]
+        super().__init__(segmentno, data)
+
+
+class HKSYN(FinTS3Segment):
+    """
+    HKSYN (Synchronisation)
+    Section C.8.1.2
+    """
+    type = 'HKSYN'
+    version = 3
+
+    SYNC_MODE_NEW_CUSTOMER_ID = 0
+    SYNC_MODE_LAST_MSG_NUMBER = 1
+    SYNC_MODE_SIGNATURE_ID = 2
+
+    def __init__(self, segmentno, mode=SYNC_MODE_NEW_CUSTOMER_ID):
+        data = [
+            mode
+        ]
+        super().__init__(segmentno, data)
diff --git a/fints/protocol/fints3/segments/crypto.py b/fints/protocol/fints3/segments/crypto.py
deleted file mode 100644 (file)
index 85c8f13..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-# 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):
index 1a6fac9a69359c05f66b197cfb5edb35bdcec1d1..a6f2ad2354f5bbf1d4ec267943f956183a9c4825 100644 (file)
-# See FinTS_3.0_Security_Sicherheitsverfahren_HBCI_Rel_20130718_final_version.pdf
-from collections import OrderedDict
-
+import time
 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)
-            ])),
-        ])
+class HNHBK(FinTS3Segment):
+    """
+    HNHBK (Nachrichtenkopf)
+    Section B.5.2
+    """
+    type = 'HNHBK'
+    version = 3
+
+    HEADER_LENGTH = 29
+
+    def __init__(self, msglen, dialogid, msgno):
+
+        if len(str(msglen)) != 12:
+            msglen = str(int(msglen) + self.HEADER_LENGTH + len(str(dialogid)) + len(str(msgno))).zfill(12)
+
+        data = [
+            msglen,
+            300,
+            dialogid,
+            msgno
+        ]
+        super().__init__(1, data)
+
+
+class HNSHK(FinTS3Segment):
+    """
+    HNSHK (Signaturkopf)
+    Section B.5.1
+    """
+    type = 'HNSHK'
+    version = 4
+
+    SECURITY_FUNC = 999
+    SECURITY_BOUNDARY = 1  # SHM
+    SECURITY_SUPPLIER_ROLE = 1  # ISS
+    PINTAN_VERSION = 1  # 1-step
+
+    def __init__(self, segno, secref, blz, username, systemid):
+        data = [
+            ':'.join(['PIN', str(self.PINTAN_VERSION)]),
+            self.SECURITY_FUNC,
+            secref,
+            self.SECURITY_BOUNDARY,
+            self.SECURITY_SUPPLIER_ROLE,
+            ':'.join(['1', '', str(systemid)]),
+            1,
+            ':'.join(['1', time.strftime('%Y%m%d'), time.strftime('%H%M%S')]),
+            ':'.join(['1', '999', '1']),  # Negotiate hash algorithm
+            ':'.join(['6', '10', '16']),  # RSA mode
+            ':'.join([str(self.country_code), blz, username, 'S', '0', '0']),
+        ]
+        super().__init__(segno, data)
+
+
+class HNVSK(FinTS3Segment):
+    """
+    HNVSK (Verschlüsslungskopf)
+    Section B.5.3
+    """
+    type = 'HNVSK'
+    version = 3
+
+    COMPRESSION_NONE = 0
+    SECURITY_SUPPLIER_ROLE = 1  # ISS
+    PINTAN_VERSION = 1  # 1-step
+
+    def __init__(self, segno, blz, username, systemid):
+        data = [
+            ':'.join(['PIN', str(self.PINTAN_VERSION)]),
+            998,
+            self.SECURITY_SUPPLIER_ROLE,
+            ':'.join(['1', '', str(systemid)]),
+            ':'.join(['1', time.strftime('%Y%m%d'), time.strftime('%H%M%S')]),
+            ':'.join(['2', '2', '13', '@8@00000000', '5', '1']),  # Crypto algorithm
+            ':'.join([str(self.country_code), blz, username, 'S', '0', '0']),
+            self.COMPRESSION_NONE
+        ]
+        super().__init__(segno, data)
+
+
+class HNVSD(FinTS3Segment):
+    """
+    HNVSD (Verschlüsselte Daten)
+    Section B.5.4
+    """
+    type = 'HNVSD'
+    version = 1
+
+    def __init__(self, segno, encoded_data):
+        self.encoded_data = encoded_data
+        data = [
+            '@{}@{}'.format(len(encoded_data), encoded_data)
+        ]
+        super().__init__(segno, data)
+
+    def set_data(self, encoded_data):
+        self.encoded_data = encoded_data
+        self.data = [
+            '@{}@{}'.format(len(encoded_data), encoded_data)
+        ]
+
+
+class HNSHA(FinTS3Segment):
+    """
+    HNSHA (Signaturabschluss)
+    Section B.5.2
+    """
+    type = 'HNSHA'
+    version = 2
+
+    SECURITY_FUNC = 999
+    SECURITY_BOUNDARY = 1  # SHM
+    SECURITY_SUPPLIER_ROLE = 1  # ISS
+    PINTAN_VERSION = 1  # 1-step
+
+    def __init__(self, segno, secref, pin):
+        data = [
+            secref,
+            '',
+            pin
+        ]
+        super().__init__(segno, data)
+
+
+class HNHBS(FinTS3Segment):
+    """
+    HNHBS (Nachrichtenabschluss)
+    Section B.5.3
+    """
+    type = 'HNHBS'
+    version = 1
+
+    def __init__(self, segno, msgno):
+        data = [
+            str(msgno)
+        ]
+        super().__init__(segno, data)