# SPDX-FileCopyrightText: 2022-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only
"""|UDM| pki X.509 DER certificate handling"""
import base64
from M2Crypto import X509
import univention.admin
import univention.admin.localization
import univention.admin.mapping
import univention.admin.syntax
from univention.admin.layout import Group, Tab
from univention.admin.log import log
translation = univention.admin.localization.translation('univention.admin')
_ = translation.translate
[docs]
def pki_option():
return univention.admin.option(
short_description=_('Public key infrastructure account'),
default=False,
editable=True,
objectClasses=['pkiUser'],
)
[docs]
def pki_properties():
return {
'userCertificate': univention.admin.property(
short_description=_('PKI certificate (DER format)'),
long_description=_('Public key infrastructure - certificate'),
syntax=univention.admin.syntax.Base64Upload,
dontsearch=True,
options=['pki'],
),
'certificateIssuerCountry': univention.admin.property(
short_description=_('Issuer Country'),
long_description=_('Certificate Issuer Country'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateIssuerState': univention.admin.property(
short_description=_('Issuer State'),
long_description=_('Certificate Issuer State'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateIssuerLocation': univention.admin.property(
short_description=_('Issuer Location'),
long_description=_('Certificate Issuer Location'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateIssuerOrganisation': univention.admin.property(
short_description=_('Issuer Organisation'),
long_description=_('Certificate Issuer Organisation'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateIssuerOrganisationalUnit': univention.admin.property(
short_description=_('Issuer Organisational Unit'),
long_description=_('Certificate Issuer Organisational Unit'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateIssuerCommonName': univention.admin.property(
short_description=_('Issuer Common Name'),
long_description=_('Certificate Issuer Common Name'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateIssuerMail': univention.admin.property(
short_description=_('Issuer Mail'),
long_description=_('Certificate Issuer Mail'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateSubjectCountry': univention.admin.property(
short_description=_('Subject Country'),
long_description=_('Certificate Subject Country'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateSubjectState': univention.admin.property(
short_description=_('Subject State'),
long_description=_('Certificate Subject State'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateSubjectLocation': univention.admin.property(
short_description=_('Subject Location'),
long_description=_('Certificate Subject Location'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateSubjectOrganisation': univention.admin.property(
short_description=_('Subject Organisation'),
long_description=_('Certificate Subject Organisation'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateSubjectOrganisationalUnit': univention.admin.property(
short_description=_('Subject Organisational Unit'),
long_description=_('Certificate Subject Organisational Unit'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateSubjectCommonName': univention.admin.property(
short_description=_('Subject Common Name'),
long_description=_('Certificate Subject Common Name'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateSubjectMail': univention.admin.property(
short_description=_('Subject Mail'),
long_description=_('Certificate Subject Mail'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateDateNotBefore': univention.admin.property(
short_description=_('Valid from'),
long_description=_('Certificate valid from'),
syntax=univention.admin.syntax.date,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateDateNotAfter': univention.admin.property(
short_description=_('Valid until'),
long_description=_('Certificate valid until'),
syntax=univention.admin.syntax.date,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateVersion': univention.admin.property(
short_description=_('Version'),
long_description=_('Certificate Version'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
'certificateSerial': univention.admin.property(
short_description=_('Serial'),
long_description=_('Certificate Serial'),
syntax=univention.admin.syntax.string,
dontsearch=True,
editable=False,
options=['pki'],
),
}
[docs]
def register_pki_mapping(mapping):
mapping.register('userCertificate', 'userCertificate;binary', univention.admin.mapping.mapBase64, univention.admin.mapping.unmapBase64)
[docs]
def pki_tab():
return Tab(_('Certificate'), _('Certificate'), advanced=True, layout=[
Group(_('General'), '', [
'userCertificate',
]),
Group(_('Subject'), '', [
['certificateSubjectCommonName', 'certificateSubjectMail'],
['certificateSubjectOrganisation', 'certificateSubjectOrganisationalUnit'],
'certificateSubjectLocation',
['certificateSubjectState', 'certificateSubjectCountry'],
]),
Group(_('Issuer'), '', [
['certificateIssuerCommonName', 'certificateIssuerMail'],
['certificateIssuerOrganisation', 'certificateIssuerOrganisationalUnit'],
'certificateIssuerLocation',
['certificateIssuerState', 'certificateIssuerCountry'],
]),
Group(_('Validity'), '', [
['certificateDateNotBefore', 'certificateDateNotAfter'],
]),
Group(_('Misc'), '', [
['certificateVersion', 'certificateSerial'],
]),
]) # fmt: skip
[docs]
def register_pki_integration(property_descriptions, mapping, options, layout):
"""
Register the PKI integration for the given object type.
.. deprecated: 5.0-3
Warning: Using this function the property descriptions, mapping, options and layout are updated without atomicity.
"""
options['pki'] = pki_option()
property_descriptions.update(pki_properties())
layout.append(pki_tab())
register_pki_mapping(mapping)
[docs]
def load_certificate(user_certificate):
"""Import a certificate in DER format"""
if not user_certificate:
return {}
try:
certificate = base64.b64decode(user_certificate)
except base64.binascii.Error:
return {}
try:
x509 = X509.load_cert_string(certificate, X509.FORMAT_DER)
values = {
'certificateDateNotBefore': x509.get_not_before().get_datetime().date().isoformat(),
'certificateDateNotAfter': x509.get_not_after().get_datetime().date().isoformat(),
'certificateVersion': str(x509.get_version()),
'certificateSerial': str(x509.get_serial_number()),
}
X509.m2.XN_FLAG_SEP_MULTILINE & ~X509.m2.ASN1_STRFLGS_ESC_MSB | X509.m2.ASN1_STRFLGS_UTF8_CONVERT
for entity, prefix in (
(x509.get_issuer(), 'certificateIssuer'),
(x509.get_subject(), 'certificateSubject'),
):
for key, attr in load_certificate.ATTR.items():
try:
value = getattr(entity, key)
except TypeError: # not expecting type '<class 'NoneType'>'
value = None
values[prefix + attr] = value
except (X509.X509Error, AttributeError):
return {}
log.trace('certificate', value=values)
return values
load_certificate.ATTR = {
'C': 'Country',
'ST': 'State',
'L': 'Location',
'O': 'Organisation',
'OU': 'OrganisationalUnit',
'CN': 'CommonName',
'emailAddress': 'Mail',
}
[docs]
class PKIIntegration:
[docs]
def pki_open(self):
if self.exists():
self.reload_certificate()
[docs]
def reload_certificate(self):
"""Reload user certificate."""
if 'pki' not in self.options:
return
self.info['certificateSubjectCountry'] = ''
self.info['certificateSubjectState'] = ''
self.info['certificateSubjectLocation'] = ''
self.info['certificateSubjectOrganisation'] = ''
self.info['certificateSubjectOrganisationalUnit'] = ''
self.info['certificateSubjectCommonName'] = ''
self.info['certificateSubjectMail'] = ''
self.info['certificateIssuerCountry'] = ''
self.info['certificateIssuerState'] = ''
self.info['certificateIssuerLocation'] = ''
self.info['certificateIssuerOrganisation'] = ''
self.info['certificateIssuerOrganisationalUnit'] = ''
self.info['certificateIssuerCommonName'] = ''
self.info['certificateIssuerMail'] = ''
self.info['certificateDateNotBefore'] = ''
self.info['certificateDateNotAfter'] = ''
self.info['certificateVersion'] = ''
self.info['certificateSerial'] = ''
_certificate = self.info.get('userCertificate')
certificate = _certificate[0] if isinstance(_certificate, list) else _certificate
values = load_certificate(certificate)
if values:
self.info.update(values)
else:
self.info['userCertificate'] = ''