-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()
--- /dev/null
+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()
+
--- /dev/null
+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')
--- /dev/null
+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
--- /dev/null
+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])
-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 + "'"
--- /dev/null
+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)
+++ /dev/null
-# 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):
-# 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)