1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/l10n_cl_edi/models/l10n_cl_certificate.py
2024-12-10 09:04:09 +07:00

117 lines
6.0 KiB
Python

# -*- coding: utf-8 -*-
import base64
import logging
import ssl
from datetime import datetime
from pytz import timezone
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat, load_pem_private_key
from odoo import _, api, fields, models
from odoo.addons.account.tools.certificate import crypto_load_certificate, load_key_and_certificates
from odoo.exceptions import UserError
from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
_logger = logging.getLogger(__name__)
class Certificate(models.Model):
_name = 'l10n_cl.certificate'
_description = 'SII Digital Signature'
_rec_name = 'signature_filename'
_order = 'id desc'
signature_filename = fields.Char('Signature File Name')
signature_key_file = fields.Binary(string='Certificate Key', help='Certificate Key in PFX format', required=True)
signature_pass_phrase = fields.Char(string='Certificate Passkey', help='Passphrase for the certificate key',
copy=False, required=True)
private_key = fields.Text(compute='_check_credentials', string='Private Key', store=True, copy=False,
groups='base.group_system')
certificate = fields.Text(compute='_check_credentials', string='Certificate', store=True, copy=False)
cert_expiration = fields.Datetime(
compute='_check_credentials', string='Expiration date', help='The date on which the certificate expires',
store=True)
subject_serial_number = fields.Char(
compute='_check_serial_number', string='Subject Serial Number', store=True, readonly=False, copy=False,
help='This is the document of the owner of this certificate.'
'Some certificates does not provide this number and you must fill it by hand')
company_id = fields.Many2one(
'res.company', string='Company', default=lambda self: self.env.company.id, required=True, readonly=True)
user_id = fields.Many2one('res.users', 'Certificate Owner',
help='If this certificate has an owner, he will be the only user authorized to use it, '
'otherwise, the certificate will be shared with other users of the current company')
last_token = fields.Char('Last Token')
token_time = fields.Datetime('Token Time')
l10n_cl_is_there_shared_certificate = fields.Boolean(related='company_id.l10n_cl_is_there_shared_certificate')
def _get_data(self):
""" Return the signature_key_file (b64 encoded) and the certificate decrypted """
self.ensure_one()
try:
pkey, cert = load_key_and_certificates(base64.b64decode(self.with_context(bin_size=False).signature_key_file), self.signature_pass_phrase.encode())
except Exception as error:
raise UserError(error)
cer_pem = cert.public_bytes(encoding=Encoding.PEM)
cert = crypto_load_certificate(cer_pem)
for to_del in ['\n', ssl.PEM_HEADER, ssl.PEM_FOOTER]:
cer_pem = cer_pem.replace(to_del.encode('UTF-8'), b'')
return cer_pem, cert, pkey.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())
def _is_valid_certificate(self):
""" Search for a valid certificate that is available and not expired. """
chilean_current_dt = self.env['l10n_cl.edi.util']._get_cl_current_datetime()
return len(self.filtered(lambda x: chilean_current_dt <= fields.Datetime.context_timestamp(
x.with_context(tz='America/Santiago'), x.cert_expiration))) >= 1
@api.depends('signature_key_file', 'signature_pass_phrase')
def _check_serial_number(self):
"""
This method is only for the readonly false compute
"""
for record in self:
if not record.signature_key_file or not record.signature_pass_phrase:
continue
try:
certificate = record._get_data()
except Exception as e:
raise UserError(_('The certificate signature_key_file is invalid: %s.', e)) from e
record.subject_serial_number = certificate[1].get_subject().serialNumber
@api.depends('signature_key_file', 'signature_pass_phrase')
def _check_credentials(self):
"""
Check the validity of signature_key_file/key/signature_pass_phrase and fill the fields
with the certificate values.
"""
chilean_tz = timezone('America/Santiago')
chilean_current_dt = self.env['l10n_cl.edi.util']._get_cl_current_datetime()
date_format = '%Y%m%d%H%M%SZ'
for record in self:
if not record.signature_key_file or not record.signature_pass_phrase:
continue
try:
certificate = record._get_data()
cert_expiration = chilean_tz.localize(
datetime.strptime(certificate[1].get_notAfter().decode('utf-8'), date_format))
except Exception as e:
raise UserError(_('The certificate signature_key_file is invalid: %s.', e)) from e
# Assign extracted values from the certificate
record.cert_expiration = cert_expiration.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
record.certificate = certificate[0]
record.private_key = certificate[2]
if chilean_current_dt > cert_expiration:
raise UserError(_('The certificate is expired since %s', record.cert_expiration))
def _int_to_bytes(self, value, byteorder='big'):
return value.to_bytes((value.bit_length() + 7) // 8, byteorder=byteorder)
def _get_private_key_modulus(self):
key = load_pem_private_key(self.private_key.encode('ascii'), password=None, backend=default_backend())
return base64.b64encode(self._int_to_bytes(key.public_key().public_numbers().n)).decode('utf-8')
def _get_private_key_exponent(self):
key = load_pem_private_key(self.private_key.encode('ascii'), password=None, backend=default_backend())
return base64.b64encode(self._int_to_bytes(key.public_key().public_numbers().e)).decode('utf-8')