summaryrefslogtreecommitdiffstats
path: root/ipalib
diff options
context:
space:
mode:
authorStanislav Laznicka <slaznick@redhat.com>2017-07-03 17:10:34 +0200
committerPavel Vomacka <pvomacka@redhat.com>2017-07-27 10:28:58 +0200
commit5a44ca638310913ab6b0c239374f4b0ddeeedeb3 (patch)
tree25f60f76b808e7a7d04639d83bbd8f8bf49a4fea /ipalib
parent5ff1de84909dd65c44b9aa48000b9e73a7a93716 (diff)
downloadfreeipa-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.py8
-rw-r--r--ipalib/parameters.py42
-rw-r--r--ipalib/rpc.py10
-rw-r--r--ipalib/util.py3
-rw-r--r--ipalib/x509.py40
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.