Coverage for jbank/wspki.py : 0%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import logging
2import traceback
3from typing import Callable, Optional
4import requests
5from django.utils.timezone import now
6from django.utils.translation import ugettext as _
7from jbank.csr_helpers import create_private_key, get_private_key_pem, strip_pem_header_and_footer, create_csr_pem, \
8 write_private_key_pem_file, load_private_key_from_pem_file
9from jbank.models import WsEdiConnection, WsEdiSoapCall, PayoutParty
10from lxml import etree # type: ignore # pytype: disable=import-error
11from jbank.x509_helpers import get_x509_cert_from_file, write_cert_pem_file
12from jutil.admin import admin_log
13from jutil.format import get_media_full_path, format_xml_bytes
16logger = logging.getLogger(__name__)
19def etree_find_element(el: etree.Element, ns: str, tag: str) -> Optional[etree.Element]:
20 """
21 :param el: Root Element
22 :param ns: Target namespace
23 :param tag: Target tag
24 :return: Element if found
25 """
26 if not ns.startswith('{'):
27 ns = '{' + ns + '}'
28 els = list(el.iter('{}{}'.format(ns, tag)))
29 if not els:
30 return None
31 if len(els) > 1:
32 return None
33 return els[0]
36def etree_get_element(el: etree.Element, ns: str, tag: str) -> etree.Element:
37 """
38 :param el: Root Element
39 :param ns: Target namespace
40 :param tag: Target tag
41 :return: Found Element
42 """
43 if not ns.startswith('{'):
44 ns = '{' + ns + '}'
45 els = list(el.iter('{}{}'.format(ns, tag)))
46 if not els:
47 raise Exception('{} not found from {}'.format(tag, el))
48 if len(els) > 1:
49 raise Exception('{} found from {} more than once'.format(tag, el))
50 return els[0]
53def generate_wspki_request(soap_call: WsEdiSoapCall, payout_party: PayoutParty, **kwargs) -> bytes: # pylint: disable=too-many-locals,too-many-statements
54 ws = soap_call.connection
55 command = soap_call.command
57 soap_body_bytes = ws.get_pki_template('jbank/pki_soap_template.xml', soap_call, **kwargs)
58 envelope = etree.fromstring(soap_body_bytes)
59 for ns_name in ['elem', 'pkif']:
60 if ns_name not in envelope.nsmap:
61 raise Exception("WS-PKI {} SOAP template invalid, '{}' namespace missing".format(command, ns_name))
62 pkif_ns = '{' + envelope.nsmap['pkif'] + '}'
63 elem_ns = '{' + envelope.nsmap['elem'] + '}'
64 req_hdr_el = etree_get_element(envelope, pkif_ns, 'RequestHeader')
65 cmd_el = req_hdr_el.getparent()
67 if command == 'GetBankCertificate':
68 if not ws.bank_root_cert_full_path:
69 raise Exception('Bank root certificate missing')
71 req_el = etree.SubElement(cmd_el, '{}{}Request'.format(elem_ns, command))
72 cert = get_x509_cert_from_file(ws.bank_root_cert_full_path)
73 logger.info('BankRootCertificateSerialNo %s', cert.serial_number)
74 el = etree.SubElement(req_el, '{}BankRootCertificateSerialNo'.format(elem_ns))
75 el.text = str(cert.serial_number)
76 el = etree.SubElement(req_el, '{}Timestamp'.format(elem_ns))
77 el.text = soap_call.timestamp.isoformat()
78 el = etree.SubElement(req_el, '{}RequestId'.format(elem_ns))
79 el.text = soap_call.request_identifier
81 elif command in ['CreateCertificate', 'RenewCertificate']:
82 is_create = command == 'CreateCertificate'
83 is_renew = command == 'RenewCertificate'
85 if is_create:
86 encryption_pk = create_private_key()
87 signing_pk = create_private_key()
88 encryption_pk_pem = get_private_key_pem(encryption_pk)
89 signing_pk_pem = get_private_key_pem(signing_pk)
90 encryption_pk_filename = 'certs/ws{}-{}-{}.pem'.format(ws.id, soap_call.timestamp_digits, 'EncryptionKey')
91 signing_pk_filename = 'certs/ws{}-{}-{}.pem'.format(ws.id, soap_call.timestamp_digits, 'SigningKey')
92 ws.encryption_key_file.name = encryption_pk_filename
93 ws.signing_key_file.name = signing_pk_filename
94 ws.save()
95 admin_log([ws], 'Encryption and signing private keys set as {} and {}'.format(encryption_pk_filename, signing_pk_filename))
96 write_private_key_pem_file(get_media_full_path(encryption_pk_filename), encryption_pk_pem)
97 write_private_key_pem_file(get_media_full_path(signing_pk_filename), signing_pk_pem)
98 else:
99 encryption_pk = load_private_key_from_pem_file(ws.encryption_key_full_path)
100 signing_pk = load_private_key_from_pem_file(ws.signing_key_full_path)
102 csr_params = {
103 'common_name': payout_party.name,
104 'organization_name': payout_party.name,
105 'country_name': payout_party.country_code,
106 'organizational_unit_name': 'IT-services',
107 'locality_name': 'Helsinki',
108 'state_or_province_name': 'Uusimaa',
109 }
110 encryption_csr = create_csr_pem(encryption_pk, **csr_params)
111 signing_csr = create_csr_pem(signing_pk, **csr_params)
112 req = ws.get_pki_template('jbank/pki_create_certificate_request_template.xml', soap_call, **{
113 'encryption_cert_pkcs10': strip_pem_header_and_footer(encryption_csr).decode().replace('\n', ''),
114 'signing_cert_pkcs10': strip_pem_header_and_footer(signing_csr).decode().replace('\n', ''),
115 'old_signing_cert': ws.signing_cert if is_renew else None,
116 })
117 logger.info('%s request:\n%s', command, format_xml_bytes(req).decode())
119 if is_renew:
120 req = ws.sign_pki_request(req)
121 logger.info('%s request signed:\n%s', command, format_xml_bytes(req).decode())
123 enc_req_bytes = ws.encrypt_pki_request(req)
124 logger.info('%s request encrypted:\n%s', command, format_xml_bytes(enc_req_bytes).decode())
125 req_el = etree.fromstring(enc_req_bytes)
127 cmd_el.insert(cmd_el.index(req_hdr_el)+1, req_el)
129 elif command in ['CertificateStatus', 'GetOwnCertificateList']:
130 cert = get_x509_cert_from_file(ws.signing_cert_full_path)
131 req = ws.get_pki_template('jbank/pki_certificate_status_request_template.xml', soap_call, **{
132 'certs': [cert],
133 })
134 logger.info('%s request:\n%s', command, format_xml_bytes(req).decode())
136 req = ws.sign_pki_request(req)
137 logger.info('%s request signed:\n%s', command, format_xml_bytes(req).decode())
138 req_el = etree.fromstring(req)
139 cmd_el.insert(cmd_el.index(req_hdr_el)+1, req_el)
141 else:
142 raise Exception('{} not implemented'.format(command))
144 body_bytes = etree.tostring(envelope)
145 return body_bytes
148def process_wspki_response(content: bytes, soap_call: WsEdiSoapCall): # noqa
149 ws = soap_call.connection
150 command = soap_call.command
151 envelope = etree.fromstring(content)
153 # check for errors
154 return_code: str = ''
155 return_text: str = ''
156 for el in envelope.iter():
157 if el.tag and el.tag.endswith('}ReturnCode'):
158 return_code = el.text
159 return_text_el = list(envelope.iter(el.tag[:-4] + 'Text'))[0]
160 return_text = return_text_el.text if return_text_el is not None else ''
161 if return_code != '00':
162 raise Exception("WS-PKI {} call failed, ReturnCode {} ({})".format(command, return_code, return_text))
164 # find namespaces
165 pkif_ns = ''
166 elem_ns = ''
167 for ns_name, ns_url in envelope.nsmap.items():
168 assert isinstance(ns_name, str)
169 if ns_url.endswith('PKIFactoryService/elements'):
170 elem_ns = '{' + ns_url + '}'
171 elif ns_url.endswith('PKIFactoryService'):
172 pkif_ns = '{' + ns_url + '}'
173 if not pkif_ns:
174 raise Exception("WS-PKI {} SOAP response invalid, PKIFactoryService namespace missing".format(command))
175 if not elem_ns:
176 raise Exception("WS-PKI {} SOAP response invalid, PKIFactoryService/elements namespace missing".format(command))
178 # find response element
179 res_el = etree_get_element(envelope, elem_ns, command + 'Response')
181 if command == 'GetBankCertificate':
182 for cert_name in ['BankEncryptionCert', 'BankSigningCert', 'BankRootCert']:
183 data_base64 = etree_get_element(res_el, elem_ns, cert_name).text
184 filename = 'certs/ws{}-{}-{}.pem'.format(ws.id, soap_call.timestamp_digits, cert_name)
185 write_cert_pem_file(get_media_full_path(filename), data_base64.encode())
186 if cert_name == 'BankEncryptionCert':
187 ws.bank_encryption_cert_file.name = filename
188 elif cert_name == 'BankSigningCert':
189 ws.bank_signing_cert_file.name = filename
190 elif cert_name == 'BankRootCert':
191 ws.bank_root_cert_file.name = filename
192 ws.save()
193 admin_log([ws], '{} set by system from SOAP call response id={}'.format(cert_name, soap_call.id))
195 elif command in ['CreateCertificate', 'RenewCertificate']:
196 for cert_name in ['EncryptionCert', 'SigningCert', 'CACert']:
197 data_base64 = etree_get_element(res_el, elem_ns, cert_name).text
198 filename = 'certs/ws{}-{}-{}.pem'.format(ws.id, soap_call.timestamp_digits, cert_name)
199 write_cert_pem_file(get_media_full_path(filename), data_base64.encode())
200 if cert_name == 'EncryptionCert':
201 ws.encryption_cert_file.name = filename
202 admin_log([ws], 'soap_call(id={}): encryption_cert_file={}'.format(soap_call.id, filename))
203 elif cert_name == 'SigningCert':
204 ws.signing_cert_file.name = filename
205 admin_log([ws], 'soap_call(id={}): signing_cert_file={}'.format(soap_call.id, filename))
206 elif cert_name == 'CACert':
207 ws.ca_cert_file.name = filename
208 admin_log([ws], 'soap_call(id={}): ca_cert_file={}'.format(soap_call.id, filename))
209 ws.save()
211 elif command in ['CertificateStatus', 'GetOwnCertificateList']:
212 pass
214 else:
215 raise Exception('{} not implemented'.format(command))
218def wspki_execute(ws: WsEdiConnection, payout_party: PayoutParty, command: str,
219 verbose: bool = False, cls: Callable = WsEdiSoapCall, **kwargs) -> bytes:
220 """
221 :param ws:
222 :param payout_party:
223 :param command:
224 :param verbose:
225 :param cls:
226 :return: str
227 """
228 if ws and not ws.enabled:
229 raise Exception(_('ws.edi.connection.not.enabled').format(ws=ws))
231 soap_call = cls(connection=ws, command=command, **kwargs)
232 assert isinstance(soap_call, WsEdiSoapCall)
233 soap_call.full_clean()
234 soap_call.save()
235 call_str = 'WsEdiSoapCall({})'.format(soap_call.id)
236 try:
237 body_bytes: bytes = generate_wspki_request(soap_call, payout_party, **kwargs)
238 if verbose:
239 logger.info('------------------------------------------------------ %s body_bytes\n%s', call_str, body_bytes.decode())
240 debug_output = command in ws.debug_command_list or 'ALL' in ws.debug_command_list
241 if debug_output:
242 with open(soap_call.debug_request_full_path, 'wb') as fp:
243 fp.write(body_bytes)
245 http_headers = {
246 'Connection': 'Close',
247 'Content-Type': 'text/xml',
248 'Method': 'POST',
249 'SOAPAction': '',
250 'User-Agent': 'Kajala WS',
251 }
252 if verbose:
253 logger.info('HTTP POST %s', ws.pki_endpoint)
254 res = requests.post(ws.pki_endpoint, data=body_bytes, headers=http_headers)
255 if debug_output:
256 with open(soap_call.debug_response_full_path, 'wb') as fp:
257 fp.write(res.content)
258 if verbose:
259 logger.info('------------------------------------------------------ %s HTTP response %s\n%s', call_str,
260 res.status_code, format_xml_bytes(res.content).decode())
261 if res.status_code >= 300:
262 logger.error('------------------------------------------------------ %s HTTP response %s\n%s', call_str,
263 res.status_code, format_xml_bytes(res.content).decode())
264 raise Exception("WS-PKI {} HTTP {}".format(command, res.status_code))
266 process_wspki_response(res.content, soap_call)
268 soap_call.executed = now()
269 soap_call.save(update_fields=['executed'])
270 return res.content
271 except Exception:
272 soap_call.error = traceback.format_exc()
273 soap_call.save(update_fields=['error'])
274 raise