Skip to content

Commit

Permalink
[IMP] account: New tool to manage certificate
Browse files Browse the repository at this point in the history
task-3806683

Co-authored-by: Thomas Becquevort (thbe) <thbe@odoo.com>
  • Loading branch information
aboo-odoo and Megaaaaaa committed Mar 1, 2024
1 parent a9d70a8 commit 8d61cde
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 17 deletions.
62 changes: 62 additions & 0 deletions addons/account/tools/certificate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from types import SimpleNamespace
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import pkcs12
from OpenSSL import crypto


def load_key_and_certificates(content, password):
private_key, certificate, _dummy = pkcs12.load_key_and_certificates(content, password, backend=default_backend())

def public_key():
public_key = certificate.public_key()

def public_numbers():
public_numbers = public_key.public_numbers()
return SimpleNamespace(
n=public_numbers.n,
e=public_numbers.e,
)
return SimpleNamespace(
public_numbers=public_numbers,
public_bytes=public_key.public_bytes,
)

simple_private_key = SimpleNamespace(
sign=private_key.sign,
private_bytes=private_key.private_bytes,
)

simple_certificate = SimpleNamespace(
fingerprint=certificate.fingerprint,
issuer=SimpleNamespace(
rfc4514_string=certificate.issuer.rfc4514_string,
rdns=[
SimpleNamespace(rfc4514_string=item.rfc4514_string)
for item in certificate.issuer.rdns
],
get_attributes_for_oid=lambda oid: [
SimpleNamespace(value=item.value)
for item in certificate.issuer.get_attributes_for_oid(oid)
]
),
not_valid_after=certificate.not_valid_after,
not_valid_before=certificate.not_valid_before,
public_key=public_key,
public_bytes=certificate.public_bytes,
serial_number=certificate.serial_number,
)
return simple_private_key, simple_certificate


def crypto_load_certificate(cer_pem):
certificate = crypto.load_certificate(crypto.FILETYPE_PEM, cer_pem)
simple_certificate = SimpleNamespace(
get_notAfter=certificate.get_notAfter,
get_notBefore=certificate.get_notBefore,
get_serial_number=certificate.get_serial_number,
get_subject=lambda: SimpleNamespace(
CN=certificate.get_subject().CN,
serialNumber=certificate.get_subject().serialNumber,
),
)
return simple_certificate
6 changes: 3 additions & 3 deletions addons/l10n_eg_edi_eta/models/eta_thumb_drive.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,8 @@ def set_signature_data(self, invoices):
invoice_id = self.env['account.move'].browse(int(key))
eta_invoice_json = json.loads(invoice_id.l10n_eg_eta_json_doc_id.raw)

cades_bes = self._generate_cades_bes_signature(eta_invoice_json['request'], invoice_id.l10n_eg_signing_time,
signature = self._generate_cades_bes_signature(eta_invoice_json['request'], invoice_id.l10n_eg_signing_time,
base64.b64decode(value))
signature = base64.b64encode(cades_bes.dump()).decode()

eta_invoice_json['request']['signatures'] = [{'signatureType': 'I', 'value': signature}]
invoice_id.l10n_eg_eta_json_doc_id.raw = json.dumps(eta_invoice_json)
Expand Down Expand Up @@ -172,4 +171,5 @@ def _generate_cades_bes_signature(self, eta_invoice, signing_time, signature):
self._generate_signer_info(eta_invoice, signing_time, signature),
],
}
return cms.ContentInfo({'content_type': cms.ContentType('signed_data'), 'content': cms.SignedData(signed_data),})
content_info = cms.ContentInfo({'content_type': cms.ContentType('signed_data'), 'content': cms.SignedData(signed_data)})
return base64.b64encode(content_info.dump()).decode()
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
from copy import deepcopy
from hashlib import sha1

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.serialization import pkcs12
from lxml import etree

from odoo import _, api, fields, models
from odoo.addons.account.tools.certificate import load_key_and_certificates
from odoo.addons.l10n_es_edi_facturae import xml_utils
from odoo.exceptions import UserError
from odoo.tools import cleanup_xml_node
Expand All @@ -34,8 +33,7 @@ def _decode_certificate(self):
"""
self.ensure_one()
content, password = b64decode(self.with_context(bin_size=False).content), self.password.encode() if self.password else None
private_key, certificate, *_dummy = pkcs12.load_key_and_certificates(content, password, backend=default_backend())
return private_key, certificate
return load_key_and_certificates(content, password)

# -------------------------------------------------------------------------
# LOW-LEVEL METHODS
Expand Down Expand Up @@ -75,8 +73,7 @@ def _sign_xml(self, edi_data, signature_data):
root = deepcopy(edi_data)
cert_private, cert_public = self._decode_certificate()
public_key_numbers = cert_public.public_key().public_numbers()
rdns_pattern = re.compile(r'(?<=\()([A-Z]{1,2}=.*)(?=\))')
issuer = ', '.join(sorted(match.group() for match in (rdns_pattern.search(str(element)) for element in cert_public.issuer.rdns) if match))
issuer = ', '.join(sorted(element.rfc4514_string() for element in cert_public.issuer.rdns if re.match(r'[A-Z]{1,2}', element.rfc4514_string())))

# Identifiers
document_id = f"Document-{sha1(etree.tostring(edi_data)).hexdigest()}"
Expand Down
7 changes: 3 additions & 4 deletions addons/l10n_es_edi_sii/models/l10n_es_edi_certificate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
from base64 import b64decode
from pytz import timezone
from datetime import datetime
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat, pkcs12
from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat


from odoo import _, api, fields, models, tools
from odoo.exceptions import ValidationError
from odoo.addons.account.tools.certificate import load_key_and_certificates


class Certificate(models.Model):
Expand Down Expand Up @@ -43,10 +43,9 @@ def _decode_certificate(self):
if not self.password:
return None, None, None

private_key, certificate, _additional_certificates = pkcs12.load_key_and_certificates(
private_key, certificate = load_key_and_certificates(
b64decode(self.content),
self.password.encode(),
backend=default_backend(),
)

pem_certificate = certificate.public_bytes(Encoding.PEM)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@

from base64 import b64decode

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import pkcs12
from odoo import models
from odoo.addons.account.tools.certificate import load_key_and_certificates


class Certificate(models.Model):
Expand All @@ -21,10 +20,9 @@ def _get_key_pair(self):
if not self.password:
return None, None

private_key, certificate, dummy = pkcs12.load_key_and_certificates(
private_key, certificate = load_key_and_certificates(
b64decode(self.with_context(bin_size=False).content), # Without bin_size=False, size is returned instead of content
self.password.encode(),
backend=default_backend(),
)

return private_key, certificate

0 comments on commit 8d61cde

Please sign in to comment.