diff options
author | Stanislav Laznicka <slaznick@redhat.com> | 2017-07-03 17:10:34 +0200 |
---|---|---|
committer | Pavel Vomacka <pvomacka@redhat.com> | 2017-07-27 10:28:58 +0200 |
commit | 5a44ca638310913ab6b0c239374f4b0ddeeedeb3 (patch) | |
tree | 25f60f76b808e7a7d04639d83bbd8f8bf49a4fea /ipalib | |
parent | 5ff1de84909dd65c44b9aa48000b9e73a7a93716 (diff) | |
download | freeipa-5a44ca638310913ab6b0c239374f4b0ddeeedeb3.tar.gz freeipa-5a44ca638310913ab6b0c239374f4b0ddeeedeb3.tar.xz freeipa-5a44ca638310913ab6b0c239374f4b0ddeeedeb3.zip |
Create a Certificate parameter
Up until now, Bytes parameter was used for certificate parameters
throughout the framework. However, the Bytes parameter does nothing
special for certificates, like validation, so this had to be done
for each of the parameters which were supposed to represent a
certificate.
This commit introduces a special Certificate parameter which takes
care of certificate validation so this does not have to be done
separately. It also makes sure that the certificates represented by
this parameter are always converted to DER format so that we can work
with them in a unified manner throughout the framework.
This commit also makes it possible to pass bytes directly during
instantiation of the Certificate parameter and they are still
represented correctly after their conversion in the _convert_scalar()
method.
https://pagure.io/freeipa/issue/4985
Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Martin Basti <mbasti@redhat.com>
Diffstat (limited to 'ipalib')
-rw-r--r-- | ipalib/install/certstore.py | 8 | ||||
-rw-r--r-- | ipalib/parameters.py | 42 | ||||
-rw-r--r-- | ipalib/rpc.py | 10 | ||||
-rw-r--r-- | ipalib/util.py | 3 | ||||
-rw-r--r-- | ipalib/x509.py | 40 |
5 files changed, 58 insertions, 45 deletions
diff --git a/ipalib/install/certstore.py b/ipalib/install/certstore.py index 89302f6d8..b26b8a7d2 100644 --- a/ipalib/install/certstore.py +++ b/ipalib/install/certstore.py @@ -310,12 +310,11 @@ def get_ca_certs(ldap, base_dn, compat_realm, compat_ipa_ca, for cert in entry.get('cACertificate;binary', []): try: - cert_obj = x509.load_der_x509_certificate(cert) - _parse_cert(cert_obj) + _parse_cert(cert) except ValueError: certs = [] break - certs.append((cert_obj, nickname, trusted, ext_key_usage)) + certs.append((cert, nickname, trusted, ext_key_usage)) except errors.NotFound: try: ldap.get_entry(container_dn, ['']) @@ -324,8 +323,7 @@ def get_ca_certs(ldap, base_dn, compat_realm, compat_ipa_ca, dn = DN(('cn', 'CAcert'), config_dn) entry = ldap.get_entry(dn, ['cACertificate;binary']) - cert = x509.load_der_x509_certificate( - entry.single_value['cACertificate;binary']) + cert = entry.single_value['cACertificate;binary'] try: subject, _issuer_serial, _public_key_info = _parse_cert(cert) except ValueError: diff --git a/ipalib/parameters.py b/ipalib/parameters.py index 7f19642e2..462e6e35e 100644 --- a/ipalib/parameters.py +++ b/ipalib/parameters.py @@ -108,15 +108,19 @@ import six # pylint: disable=import-error from six.moves.xmlrpc_client import MAXINT, MININT # pylint: enable=import-error +from cryptography import x509 as crypto_x509 from ipalib.text import _ as ugettext from ipalib.base import check_name from ipalib.plugable import ReadOnly, lock from ipalib.errors import ConversionError, RequirementError, ValidationError -from ipalib.errors import PasswordMismatch, Base64DecodeError +from ipalib.errors import ( + PasswordMismatch, Base64DecodeError, CertificateFormatError +) from ipalib.constants import TYPE_ERROR, CALLABLE_ERROR, LDAP_GENERALIZED_TIME_FORMAT from ipalib.text import Gettext, FixMe from ipalib.util import json_serialize, validate_idna_domain +from ipalib.x509 import load_der_x509_certificate, IPACertificate from ipapython import kerberos from ipapython.dn import DN from ipapython.dnsutil import DNSName @@ -1407,6 +1411,42 @@ class Bytes(Data): return super(Bytes, self)._convert_scalar(value) +class Certificate(Param): + type = crypto_x509.Certificate + type_error = _('must be a certificate') + allowed_types = (IPACertificate, bytes, unicode) + + def _convert_scalar(self, value, index=None): + """ + :param value: either DER certificate or base64 encoded certificate + :returns: bytes representing value converted to DER format + """ + if isinstance(value, bytes): + try: + value = value.decode('ascii') + except UnicodeDecodeError: + # value is possibly a DER-encoded certificate + pass + + if isinstance(value, unicode): + # if we received unicodes right away or we got them after the + # decoding, we will now try to receive DER-certificate + try: + value = base64.b64decode(value) + except (TypeError, ValueError) as e: + raise Base64DecodeError(reason=str(e)) + + if isinstance(value, bytes): + # we now only have either bytes or an IPACertificate object + # if it's bytes, make it an IPACertificate object + try: + value = load_der_x509_certificate(value) + except ValueError as e: + raise CertificateFormatError(error=str(e)) + + return super(Certificate, self)._convert_scalar(value) + + class Str(Data): """ A parameter for Unicode text (stored in the ``unicode`` type). diff --git a/ipalib/rpc.py b/ipalib/rpc.py index 8635894a5..ffa2b92f9 100644 --- a/ipalib/rpc.py +++ b/ipalib/rpc.py @@ -42,6 +42,7 @@ import json import re import socket import gzip +from cryptography import x509 as crypto_x509 import gssapi from dns import resolver, rdatatype @@ -56,6 +57,7 @@ from ipalib.errors import (public_errors, UnknownError, NetworkError, XMLRPCMarshallError, JSONError) from ipalib import errors, capabilities from ipalib.request import context, Connection +from ipalib.x509 import Encoding as x509_Encoding from ipapython import ipautil from ipapython import session_storage from ipapython.cookie import Cookie @@ -191,6 +193,10 @@ def xml_wrap(value, version): if isinstance(value, Principal): return unicode(value) + if isinstance(value, crypto_x509.Certificate): + return base64.b64encode( + value.public_bytes(x509_Encoding.DER)).encode('ascii') + assert type(value) in (unicode, float, bool, type(None)) + six.integer_types return value @@ -318,6 +324,7 @@ class _JSONPrimer(dict): list: self._enc_list, tuple: self._enc_list, dict: self._enc_dict, + crypto_x509.Certificate: self._enc_certificate, }) # int, long for t in six.integer_types: @@ -384,6 +391,9 @@ class _JSONPrimer(dict): result[k] = v if func is _identity else func(v) return result + def _enc_certificate(self, val): + return self._enc_bytes(val.public_bytes(x509_Encoding.DER)) + def json_encode_binary(val, version, pretty_print=False): """Serialize a Python object structure to JSON diff --git a/ipalib/util.py b/ipalib/util.py index 880d2bc21..cb77104bf 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -129,7 +129,8 @@ def normalize_name(name): def isvalid_base64(data): """ - Validate the incoming data as valid base64 data or not. + Validate the incoming data as valid base64 data or not. This is only + used in the ipalib.Parameters module which expects ``data`` to be unicode. The character set must only include of a-z, A-Z, 0-9, + or / and be padded with = to be a length divisible by 4 (so only 0-2 =s are diff --git a/ipalib/x509.py b/ipalib/x509.py index 87f0edad4..7877df328 100644 --- a/ipalib/x509.py +++ b/ipalib/x509.py @@ -19,8 +19,7 @@ # Certificates should be stored internally DER-encoded. We can be passed # a certificate several ways: read if from LDAP, read it from a 3rd party -# app (dogtag, candlepin, etc) or as user input. The ensure_der_format() -# function will convert an incoming certificate to DER-encoding. +# app (dogtag, candlepin, etc) or as user input. # Conventions # @@ -51,8 +50,6 @@ from pyasn1.codec.der import decoder, encoder from pyasn1_modules import rfc2315, rfc2459 import six -from ipalib import api -from ipalib import util from ipalib import errors from ipapython.dn import DN from ipapython.dnsutil import DNSName @@ -82,6 +79,7 @@ SAN_KRB5PRINCIPALNAME = '1.3.6.1.5.2.2' _subject_base = None def subject_base(): + from ipalib import api global _subject_base if _subject_base is None: @@ -504,40 +502,6 @@ def pkcs7_to_certs(data, datatype=PEM): return result -def ensure_der_format(rawcert): - """ - Incoming certificates should be DER-encoded. If not it is converted to - DER-format. - - Note that this can't be a normalizer on a Param because only unicode - variables are normalized. - """ - if not rawcert: - return None - - try: - if isinstance(rawcert, bytes): - # base64 must work with utf-8, otherwise it is raw bin certificate - decoded_cert = rawcert.decode('utf-8') - else: - decoded_cert = rawcert - except UnicodeDecodeError: - dercert = rawcert - else: - if util.isvalid_base64(decoded_cert): - try: - dercert = base64.b64decode(decoded_cert) - except Exception as e: - raise errors.Base64DecodeError(reason=str(e)) - else: - dercert = rawcert - - # At this point we should have a DER certificate. - # Attempt to decode it. - validate_der_x509_certificate(dercert) - return dercert - - def validate_pem_x509_certificate(cert): """ Perform cert validation by trying to load it via python-cryptography. |