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>
This commit is contained in:
Stanislav Laznicka 2017-07-03 17:10:34 +02:00 committed by Pavel Vomacka
parent 5ff1de8490
commit 5a44ca6383
19 changed files with 178 additions and 188 deletions

58
API.txt
View File

@ -744,7 +744,7 @@ args: 1,29,4
arg: Str('criteria?')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('cacn?', cli_name='ca')
option: Bytes('certificate?', autofill=False)
option: Certificate('certificate?', autofill=False)
option: Flag('exactly?', autofill=True, default=False)
option: Str('host*', cli_name='hosts')
option: DateTime('issuedon_from?', autofill=False)
@ -828,7 +828,7 @@ output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: certmap_match/1
args: 1,3,4
arg: Bytes('certificate', cli_name='certificate')
arg: Certificate('certificate', cli_name='certificate')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('version?')
@ -2434,7 +2434,7 @@ option: Str('nsosversion?', cli_name='os')
option: Flag('random?', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('setattr*', cli_name='setattr')
option: Bytes('usercertificate*', cli_name='certificate')
option: Certificate('usercertificate*', cli_name='certificate')
option: Str('userclass*', cli_name='class')
option: Str('userpassword?', cli_name='password')
option: Str('version?')
@ -2447,7 +2447,7 @@ arg: Str('fqdn', cli_name='hostname')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Bytes('usercertificate+', alwaysask=True, cli_name='certificate')
option: Certificate('usercertificate+', alwaysask=True, cli_name='certificate')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
@ -2580,7 +2580,7 @@ option: Flag('pkey_only?', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Int('sizelimit?', autofill=False)
option: Int('timelimit?', autofill=False)
option: Bytes('usercertificate*', autofill=False, cli_name='certificate')
option: Certificate('usercertificate*', autofill=False, cli_name='certificate')
option: Str('userclass*', autofill=False, cli_name='class')
option: Str('userpassword?', autofill=False, cli_name='password')
option: Str('version?')
@ -2613,7 +2613,7 @@ option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Flag('rights', autofill=True, default=False)
option: Str('setattr*', cli_name='setattr')
option: Flag('updatedns?', autofill=True, default=False)
option: Bytes('usercertificate*', autofill=False, cli_name='certificate')
option: Certificate('usercertificate*', autofill=False, cli_name='certificate')
option: Str('userclass*', autofill=False, cli_name='class')
option: Str('userpassword?', autofill=False, cli_name='password')
option: Str('version?')
@ -2626,7 +2626,7 @@ arg: Str('fqdn', cli_name='hostname')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Bytes('usercertificate+', alwaysask=True, cli_name='certificate')
option: Certificate('usercertificate+', alwaysask=True, cli_name='certificate')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
@ -2862,7 +2862,7 @@ option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('setattr*', cli_name='setattr')
option: Str('uid?', cli_name='login')
option: Int('uidnumber?', cli_name='uid')
option: Bytes('usercertificate*', cli_name='certificate')
option: Certificate('usercertificate*', cli_name='certificate')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
@ -2874,7 +2874,7 @@ arg: Str('ipaanchoruuid', cli_name='anchor')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('fallback_to_ldap?', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Bytes('usercertificate+', alwaysask=True, cli_name='certificate')
option: Certificate('usercertificate+', alwaysask=True, cli_name='certificate')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
@ -2934,7 +2934,7 @@ option: Flag('rights', autofill=True, default=False)
option: Str('setattr*', cli_name='setattr')
option: Str('uid?', autofill=False, cli_name='login')
option: Int('uidnumber?', autofill=False, cli_name='uid')
option: Bytes('usercertificate*', autofill=False, cli_name='certificate')
option: Certificate('usercertificate*', autofill=False, cli_name='certificate')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
@ -2946,7 +2946,7 @@ arg: Str('ipaanchoruuid', cli_name='anchor')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('fallback_to_ldap?', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Bytes('usercertificate+', alwaysask=True, cli_name='certificate')
option: Certificate('usercertificate+', alwaysask=True, cli_name='certificate')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
@ -4470,7 +4470,7 @@ option: Str('krbprincipalauthind*', cli_name='auth_ind')
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('setattr*', cli_name='setattr')
option: Bytes('usercertificate*', cli_name='certificate')
option: Certificate('usercertificate*', cli_name='certificate')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
@ -4481,7 +4481,7 @@ arg: Principal('krbcanonicalname', cli_name='canonical_principal')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Bytes('usercertificate+', alwaysask=True, cli_name='certificate')
option: Certificate('usercertificate+', alwaysask=True, cli_name='certificate')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
@ -4615,7 +4615,7 @@ option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Flag('rights', autofill=True, default=False)
option: Str('setattr*', cli_name='setattr')
option: Bytes('usercertificate*', autofill=False, cli_name='certificate')
option: Certificate('usercertificate*', autofill=False, cli_name='certificate')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
@ -4626,7 +4626,7 @@ arg: Principal('krbcanonicalname', cli_name='canonical_principal')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Bytes('usercertificate+', alwaysask=True, cli_name='certificate')
option: Certificate('usercertificate+', alwaysask=True, cli_name='certificate')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
@ -4880,7 +4880,7 @@ option: Str('street?', cli_name='street')
option: Str('telephonenumber*', cli_name='phone')
option: Str('title?')
option: Int('uidnumber?', cli_name='uid')
option: Bytes('usercertificate*', cli_name='certificate')
option: Certificate('usercertificate*', cli_name='certificate')
option: Str('userclass*', cli_name='class')
option: Password('userpassword?', cli_name='password')
option: Str('version?')
@ -4893,7 +4893,7 @@ arg: Str('uid', cli_name='login')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Bytes('usercertificate+', alwaysask=True, cli_name='certificate')
option: Certificate('usercertificate+', alwaysask=True, cli_name='certificate')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
@ -4903,7 +4903,7 @@ args: 2,7,3
arg: Str('uid', cli_name='login')
arg: Str('ipacertmapdata*', alwaysask=False, cli_name='certmapdata')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Bytes('certificate*', cli_name='certificate')
option: Certificate('certificate*', cli_name='certificate')
option: DNParam('issuer?', cli_name='issuer')
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
@ -4995,7 +4995,7 @@ option: Int('timelimit?', autofill=False)
option: Str('title?', autofill=False)
option: Str('uid?', autofill=False, cli_name='login')
option: Int('uidnumber?', autofill=False, cli_name='uid')
option: Bytes('usercertificate*', autofill=False, cli_name='certificate')
option: Certificate('usercertificate*', autofill=False, cli_name='certificate')
option: Str('userclass*', autofill=False, cli_name='class')
option: Password('userpassword?', autofill=False, cli_name='password')
option: Str('version?')
@ -5049,7 +5049,7 @@ option: Str('street?', autofill=False, cli_name='street')
option: Str('telephonenumber*', autofill=False, cli_name='phone')
option: Str('title?', autofill=False)
option: Int('uidnumber?', autofill=False, cli_name='uid')
option: Bytes('usercertificate*', autofill=False, cli_name='certificate')
option: Certificate('usercertificate*', autofill=False, cli_name='certificate')
option: Str('userclass*', autofill=False, cli_name='class')
option: Password('userpassword?', autofill=False, cli_name='password')
option: Str('version?')
@ -5062,7 +5062,7 @@ arg: Str('uid', cli_name='login')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Bytes('usercertificate+', alwaysask=True, cli_name='certificate')
option: Certificate('usercertificate+', alwaysask=True, cli_name='certificate')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
@ -5072,7 +5072,7 @@ args: 2,7,3
arg: Str('uid', cli_name='login')
arg: Str('ipacertmapdata*', alwaysask=False, cli_name='certmapdata')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Bytes('certificate*', cli_name='certificate')
option: Certificate('certificate*', cli_name='certificate')
option: DNParam('issuer?', cli_name='issuer')
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
@ -5947,7 +5947,7 @@ option: Str('street?', cli_name='street')
option: Str('telephonenumber*', cli_name='phone')
option: Str('title?')
option: Int('uidnumber?', cli_name='uid')
option: Bytes('usercertificate*', cli_name='certificate')
option: Certificate('usercertificate*', cli_name='certificate')
option: Str('userclass*', cli_name='class')
option: Password('userpassword?', cli_name='password')
option: Str('version?')
@ -5960,7 +5960,7 @@ arg: Str('uid', cli_name='login')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Bytes('usercertificate+', alwaysask=True, cli_name='certificate')
option: Certificate('usercertificate+', alwaysask=True, cli_name='certificate')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
@ -5970,7 +5970,7 @@ args: 2,7,3
arg: Str('uid', cli_name='login')
arg: Str('ipacertmapdata*', alwaysask=False, cli_name='certmapdata')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Bytes('certificate*', cli_name='certificate')
option: Certificate('certificate*', cli_name='certificate')
option: DNParam('issuer?', cli_name='issuer')
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
@ -6079,7 +6079,7 @@ option: Int('timelimit?', autofill=False)
option: Str('title?', autofill=False)
option: Str('uid?', autofill=False, cli_name='login')
option: Int('uidnumber?', autofill=False, cli_name='uid')
option: Bytes('usercertificate*', autofill=False, cli_name='certificate')
option: Certificate('usercertificate*', autofill=False, cli_name='certificate')
option: Str('userclass*', autofill=False, cli_name='class')
option: Password('userpassword?', autofill=False, cli_name='password')
option: Str('version?')
@ -6135,7 +6135,7 @@ option: Str('street?', autofill=False, cli_name='street')
option: Str('telephonenumber*', autofill=False, cli_name='phone')
option: Str('title?', autofill=False)
option: Int('uidnumber?', autofill=False, cli_name='uid')
option: Bytes('usercertificate*', autofill=False, cli_name='certificate')
option: Certificate('usercertificate*', autofill=False, cli_name='certificate')
option: Str('userclass*', autofill=False, cli_name='class')
option: Password('userpassword?', autofill=False, cli_name='password')
option: Str('version?')
@ -6148,7 +6148,7 @@ arg: Str('uid', cli_name='login')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Bytes('usercertificate+', alwaysask=True, cli_name='certificate')
option: Certificate('usercertificate+', alwaysask=True, cli_name='certificate')
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
@ -6158,7 +6158,7 @@ args: 2,7,3
arg: Str('uid', cli_name='login')
arg: Str('ipacertmapdata*', alwaysask=False, cli_name='certmapdata')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Bytes('certificate*', cli_name='certificate')
option: Certificate('certificate*', cli_name='certificate')
option: DNParam('issuer?', cli_name='issuer')
option: Flag('no_members', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)

View File

@ -73,8 +73,8 @@ define(IPA_DATA_VERSION, 20100614120000)
# #
########################################################
define(IPA_API_VERSION_MAJOR, 2)
define(IPA_API_VERSION_MINOR, 228)
# Last change: Expose ipaNTAdditionalSuffixes in trust-mod
define(IPA_API_VERSION_MINOR, 229)
# Last change: Added the Certificate parameter
########################################################

View File

@ -66,8 +66,8 @@ class CertRetrieveOverride(MethodOverride):
certs = result['result']['certificate_chain']
else:
certs = [result['result']['certificate']]
certs = (x509.load_der_x509_certificate(
x509.ensure_der_format(cert)) for cert in certs)
certs = (x509.load_der_x509_certificate(base64.b64decode(cert))
for cert in certs)
x509.write_certificate_list(certs, certificate_out)
return result

View File

@ -12,6 +12,8 @@ import tempfile
import types
import zipfile
from cryptography import x509 as crypto_x509
import six
from ipaclient.frontend import ClientCommand, ClientMethod
@ -44,6 +46,7 @@ _TYPES = {
'list': list,
'tuple': tuple,
'unicode': unicode,
'Certificate': crypto_x509.Certificate,
}
_PARAMS = {
@ -57,6 +60,7 @@ _PARAMS = {
'dict': parameters.Dict,
'int': parameters.Int,
'str': parameters.Str,
'Certificate': parameters.Certificate,
}

View File

@ -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:

View File

@ -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).

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -34,6 +34,8 @@ import pwd
from six.moves.urllib.parse import urlparse
# pylint: enable=import-error
from cryptography import x509 as crypto_x509
import ldap
import ldap.sasl
import ldap.filter
@ -41,7 +43,7 @@ from ldap.controls import SimplePagedResultsControl
import six
# pylint: disable=ipa-forbidden-import
from ipalib import errors, _
from ipalib import errors, x509, _
from ipalib.constants import LDAP_GENERALIZED_TIME_FORMAT
# pylint: enable=ipa-forbidden-import
from ipapython.ipautil import format_netloc, CIDict
@ -670,6 +672,10 @@ class LDAPClient(object):
'dnszoneidnsname': DNSName,
'krbcanonicalname': Principal,
'krbprincipalname': Principal,
'usercertificate': crypto_x509.Certificate,
'usercertificate;binary': crypto_x509.Certificate,
'cACertificate': crypto_x509.Certificate,
'cACertificate;binary': crypto_x509.Certificate,
'nsds5replicalastupdatestart': unicode,
'nsds5replicalastupdateend': unicode,
'nsds5replicalastinitstart': unicode,
@ -842,7 +848,7 @@ class LDAPClient(object):
def encode(self, val):
"""
Encode attribute value to LDAP representation (str).
Encode attribute value to LDAP representation (str/bytes).
"""
# Booleans are both an instance of bool and int, therefore
# test for bool before int otherwise the int clause will be
@ -869,6 +875,8 @@ class LDAPClient(object):
return dct
elif isinstance(val, datetime.datetime):
return val.strftime(LDAP_GENERALIZED_TIME_FORMAT).encode('utf-8')
elif isinstance(val, crypto_x509.Certificate):
return val.public_bytes(x509.Encoding.DER)
elif val is None:
return None
else:
@ -876,7 +884,7 @@ class LDAPClient(object):
def decode(self, val, attr):
"""
Decode attribute value from LDAP representation (str).
Decode attribute value from LDAP representation (str/bytes).
"""
if isinstance(val, bytes):
target_type = self.get_attribute_type(attr)
@ -892,6 +900,8 @@ class LDAPClient(object):
return DNSName.from_text(val.decode('utf-8'))
elif target_type in (DN, Principal):
return target_type(val.decode('utf-8'))
elif target_type is crypto_x509.Certificate:
return x509.load_der_x509_certificate(val)
else:
return target_type(val)
except Exception:

View File

@ -376,7 +376,7 @@ class DogtagInstance(service.Service):
if conn is not None:
conn.unbind()
return base64.b64encode(admin_cert)
return admin_cert
def handle_setup_error(self, e):
logger.critical("Failed to configure %s instance: %s",

View File

@ -22,6 +22,7 @@ import os
import pwd
import shutil
import tempfile
import base64
import six
# pylint: disable=import-error
@ -268,13 +269,15 @@ class KRAInstance(DogtagInstance):
"https://%s" % ipautil.format_netloc(self.master_host, 443))
else:
# the admin cert file is needed for the first instance of KRA
cert = DogtagInstance.get_admin_cert(self)
cert = self.get_admin_cert()
# First make sure that the directory exists
parentdir = os.path.dirname(paths.ADMIN_CERT_PATH)
if not os.path.exists(parentdir):
os.makedirs(parentdir)
with open(paths.ADMIN_CERT_PATH, "w") as admin_path:
admin_path.write(cert)
admin_path.write(
base64.b64encode(cert.public_bytes(x509.Encoding.DER))
)
# Generate configuration file
with open(cfg_file, "w") as f:

View File

@ -19,10 +19,10 @@
import six
from ipalib import api, errors, x509
from ipalib import api, errors
from ipalib import (
Flag, Int, Password, Str, Bool, StrEnum, DateTime, Bytes, DNParam)
from ipalib.parameters import Principal
Flag, Int, Password, Str, Bool, StrEnum, DateTime, DNParam)
from ipalib.parameters import Principal, Certificate
from ipalib.plugable import Registry
from .baseldap import (
DN, LDAPObject, LDAPCreate, LDAPUpdate, LDAPSearch, LDAPDelete,
@ -30,8 +30,7 @@ from .baseldap import (
LDAPAddMember, LDAPRemoveMember,
LDAPAddAttributeViaOption, LDAPRemoveAttributeViaOption,
add_missing_object_class)
from ipaserver.plugins.service import (
validate_certificate, validate_realm, normalize_principal)
from ipaserver.plugins.service import (validate_realm, normalize_principal)
from ipalib.request import context
from ipalib import _
from ipalib.constants import PATTERN_GROUPUSER_NAME
@ -363,7 +362,7 @@ class baseuser(LDAPObject):
+ '(\s*,\s*[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?(;q\=((0(\.[0-9]{0,3})?)|(1(\.0{0,3})?)))?)*)|(\*))$',
pattern_errmsg='must match RFC 2068 - 14.4, e.g., "da, en-gb;q=0.8, en;q=0.7"',
),
Bytes('usercertificate*', validate_certificate,
Certificate('usercertificate*',
cli_name='certificate',
label=_('Certificate'),
doc=_('Base-64 encoded user certificate'),
@ -762,8 +761,8 @@ class ModCertMapData(LDAPModAttribute):
doc=_('Subject of the certificate'),
flags=['virtual_attribute']
),
Bytes(
'certificate*', validate_certificate,
Certificate(
'certificate*',
cli_name='certificate',
label=_('Certificate'),
doc=_('Base-64 encoded user certificate'),
@ -798,8 +797,7 @@ class ModCertMapData(LDAPModAttribute):
if issuer or subject:
data.append(cls._build_mapdata(subject, issuer))
for dercert in certificates:
cert = x509.load_der_x509_certificate(dercert)
for cert in certificates:
issuer = DN(cert.issuer)
subject = DN(cert.subject)
if not subject:

View File

@ -39,7 +39,9 @@ from ipalib import ngettext
from ipalib.constants import IPA_CA_CN
from ipalib.crud import Create, PKQuery, Retrieve, Search
from ipalib.frontend import Method, Object
from ipalib.parameters import Bytes, DateTime, DNParam, DNSNameParam, Principal
from ipalib.parameters import (
Bytes, Certificate, DateTime, DNParam, DNSNameParam, Principal
)
from ipalib.plugable import Registry
from .virtual import VirtualCommand
from .baseldap import pkey_to_value
@ -324,10 +326,6 @@ def ca_kdc_check(api_instance, hostname):
% dict(hostname=hostname))
def validate_certificate(value):
return x509.validate_der_x509_certificate(value)
def bind_principal_can_manage_cert(cert):
"""Check that the bind principal can manage the given cert.
@ -362,11 +360,10 @@ class BaseCertObject(Object):
doc=_('Name of issuing CA'),
flags={'no_create', 'no_update', 'no_search'},
),
Bytes(
'certificate', validate_certificate,
Certificate(
'certificate',
label=_("Certificate"),
doc=_("Base-64 encoded certificate."),
normalizer=x509.ensure_der_format,
flags={'no_create', 'no_update', 'no_search'},
),
Bytes(
@ -1438,17 +1435,7 @@ class cert_find(Search, CertMethod):
)
def _get_cert_key(self, cert):
try:
cert_obj = x509.load_der_x509_certificate(cert)
except ValueError as e:
message = messages.SearchResultTruncated(
reason=_("failed to load certificate: %s") % e,
)
self.add_message(message)
raise
return (DN(cert_obj.issuer), cert_obj.serial_number)
return (DN(cert.issuer), cert.serial_number)
def _cert_search(self, pkey_only, **options):
result = collections.OrderedDict()
@ -1458,16 +1445,12 @@ class cert_find(Search, CertMethod):
except KeyError:
return result, False, False
try:
issuer, serial_number = self._get_cert_key(cert)
except ValueError:
return result, True, True
obj = {'serial_number': serial_number}
obj = {'serial_number': cert.serial_number}
if not pkey_only:
obj['certificate'] = base64.b64encode(cert).decode('ascii')
obj['certificate'] = base64.b64encode(
cert.public_bytes(x509.Encoding.DER)).decode('ascii')
result[issuer, serial_number] = obj
result[self._get_cert_key(cert)] = obj
return result, False, True
@ -1570,7 +1553,8 @@ class cert_find(Search, CertMethod):
cert = options.get('certificate')
if cert is not None:
filter = ldap.make_filter_from_attr('usercertificate', cert)
filter = ldap.make_filter_from_attr(
'usercertificate', cert.public_bytes(x509.Encoding.DER))
else:
filter = '(usercertificate=*)'
filters.append(filter)
@ -1598,20 +1582,18 @@ class cert_find(Search, CertMethod):
for entry in entries:
for attr in ('usercertificate', 'usercertificate;binary'):
for cert in entry.get(attr, []):
cert_key = self._get_cert_key(cert)
try:
issuer, serial_number = self._get_cert_key(cert)
except ValueError:
truncated = True
continue
try:
obj = result[issuer, serial_number]
obj = result[cert_key]
except KeyError:
obj = {'serial_number': serial_number}
obj = {'serial_number': cert.serial_number}
if not pkey_only and all:
obj['certificate'] = (
base64.b64encode(cert).decode('ascii'))
result[issuer, serial_number] = obj
base64.b64encode(
cert.public_bytes(x509.Encoding.DER))
.decode('ascii'))
result[cert_key] = obj
if not pkey_only and (all or not no_members):
owners = obj.setdefault('owner', [])
@ -1695,10 +1677,7 @@ class cert_find(Search, CertMethod):
obj['certificate'].replace('\r\n', ''))
if 'certificate_chain' in ca_obj:
cert = x509.load_der_x509_certificate(
obj['certificate'])
cert_der = (
cert.public_bytes(serialization.Encoding.DER))
cert_der = base64.b64decode(obj['certificate'])
obj['certificate_chain'] = (
[cert_der] + ca_obj['certificate_chain'])

View File

@ -23,10 +23,9 @@ import dbus
import six
from ipalib import api, errors, x509
from ipalib import Bytes
from ipalib.crud import Search
from ipalib.frontend import Object
from ipalib.parameters import Bool, DNSNameParam, Flag, Int, Str
from ipalib.parameters import Bool, DNSNameParam, Flag, Int, Str, Certificate
from ipalib.plugable import Registry
from .baseldap import (
LDAPCreate,
@ -39,7 +38,6 @@ from .baseldap import (
pkey_to_value)
from ipalib import _, ngettext
from ipalib import output
from ipaserver.plugins.service import validate_certificate
if six.PY3:
@ -521,8 +519,8 @@ class certmap_match(Search):
if arg.name == 'criteria':
continue
yield arg
yield Bytes(
'certificate', validate_certificate,
yield Certificate(
'certificate',
cli_name='certificate',
label=_('Certificate'),
doc=_('Base-64 encoded user certificate'),

View File

@ -27,9 +27,10 @@ import dns.resolver
import six
from ipalib import api, errors, util
from ipalib.x509 import Encoding as x509_Encoding
from ipalib import messages
from ipalib import Str, Flag, Bytes
from ipalib.parameters import Principal
from ipalib import Str, Flag
from ipalib.parameters import Principal, Certificate
from ipalib.plugable import Registry
from .baseldap import (LDAPQuery, LDAPObject, LDAPCreate,
LDAPDelete, LDAPUpdate, LDAPSearch,
@ -40,7 +41,7 @@ from .baseldap import (LDAPQuery, LDAPObject, LDAPCreate,
LDAPAddAttributeViaOption,
LDAPRemoveAttributeViaOption)
from .service import (
validate_realm, normalize_principal, validate_certificate,
validate_realm, normalize_principal,
set_certificate_attrs, ticket_flags_params, update_krbticketflags,
set_kerberos_attrs, rename_ipaallowedtoperform_from_ldap,
rename_ipaallowedtoperform_to_ldap, revoke_certs)
@ -48,7 +49,6 @@ from .dns import (dns_container_exists,
add_records_for_host_validation, add_records_for_host,
get_reverse_zone)
from ipalib import _, ngettext
from ipalib import x509
from ipalib import output
from ipalib.request import context
from ipalib.util import (normalize_sshpubkey, validate_sshpubkey_no_options,
@ -485,7 +485,7 @@ class host(LDAPObject):
label=_('Random password'),
flags=('no_create', 'no_update', 'no_search', 'virtual_attribute'),
),
Bytes('usercertificate*', validate_certificate,
Certificate('usercertificate*',
cli_name='certificate',
label=_('Certificate'),
doc=_('Base-64 encoded host certificate'),
@ -690,9 +690,8 @@ class host_add(LDAPCreate):
entropy_bits=TMP_PWD_ENTROPY_BITS)
# save the password so it can be displayed in post_callback
setattr(context, 'randompassword', entry_attrs['userpassword'])
certs = options.get('usercertificate', [])
certs_der = [x509.ensure_der_format(c) for c in certs]
entry_attrs['usercertificate'] = certs_der
entry_attrs['usercertificate'] = options.get('usercertificate', [])
entry_attrs['managedby'] = dn
entry_attrs['objectclass'].append('ieee802device')
entry_attrs['objectclass'].append('ipasshhost')
@ -895,7 +894,6 @@ class host_mod(LDAPUpdate):
# verify certificates
certs = entry_attrs.get('usercertificate') or []
certs_der = [x509.ensure_der_format(c) for c in certs]
# revoke removed certificates
ca_is_enabled = self.api.Command.ca_is_enabled()['result']
@ -905,14 +903,13 @@ class host_mod(LDAPUpdate):
except errors.NotFound:
self.obj.handle_not_found(*keys)
old_certs = entry_attrs_old.get('usercertificate', [])
old_certs_der = [x509.ensure_der_format(c) for c in old_certs]
removed_certs_der = set(old_certs_der) - set(certs_der)
removed_certs_der = set(old_certs) - set(certs)
for der in removed_certs_der:
rm_certs = api.Command.cert_find(certificate=der)['result']
revoke_certs(rm_certs)
if certs:
entry_attrs['usercertificate'] = certs_der
entry_attrs['usercertificate'] = certs
if options.get('random'):
entry_attrs['userpassword'] = ipa_generate_password(
@ -1344,7 +1341,8 @@ class host_remove_cert(LDAPRemoveAttributeViaOption):
assert isinstance(dn, DN)
for cert in options.get('usercertificate', []):
revoke_certs(api.Command.cert_find(certificate=cert)['result'])
revoke_certs(api.Command.cert_find(
certificate=cert.public_bytes(x509_Encoding.DER))['result'])
return dn

View File

@ -27,8 +27,10 @@ from .baseldap import (LDAPQuery, LDAPObject, LDAPCreate,
LDAPRemoveAttributeViaOption,
LDAPRetrieve, global_output_params)
from .hostgroup import get_complete_hostgroup_member_list
from .service import validate_certificate
from ipalib import api, Str, Int, Bytes, Flag, _, ngettext, errors, output
from ipalib import (
api, Str, Int, Flag, _, ngettext, errors, output
)
from ipalib.parameters import Certificate
from ipalib.constants import (
IPA_ANCHOR_PREFIX,
SID_ANCHOR_PREFIX,
@ -922,7 +924,7 @@ class idoverrideuser(baseidoverride):
normalizer=normalize_sshpubkey,
flags=['no_search'],
),
Bytes('usercertificate*', validate_certificate,
Certificate('usercertificate*',
cli_name='certificate',
label=_('Certificate'),
doc=_('Base-64 encoded user certificate'),

View File

@ -25,8 +25,8 @@ from cryptography.hazmat.primitives import hashes
import six
from ipalib import api, errors, messages
from ipalib import Bytes, StrEnum, Bool, Str, Flag
from ipalib.parameters import Principal
from ipalib import StrEnum, Bool, Str, Flag
from ipalib.parameters import Principal, Certificate
from ipalib.plugable import Registry
from .baseldap import (
host_is_master,
@ -215,13 +215,6 @@ def normalize_principal(value):
return unicode(principal)
def validate_certificate(ugettext, cert):
"""
Check whether the certificate is properly encoded to DER
"""
if api.env.in_server:
x509.validate_der_x509_certificate(cert)
def revoke_certs(certs):
"""
@ -269,8 +262,6 @@ def set_certificate_attrs(entry_attrs):
cert = entry_attrs['usercertificate'][0]
else:
cert = entry_attrs['usercertificate']
cert = x509.ensure_der_format(cert)
cert = x509.load_der_x509_certificate(cert)
entry_attrs['subject'] = unicode(DN(cert.subject))
entry_attrs['serial_number'] = unicode(cert.serial_number)
entry_attrs['serial_number_hex'] = u'0x%X' % cert.serial_number
@ -478,7 +469,7 @@ class service(LDAPObject):
require_service=True,
flags={'no_create'}
),
Bytes('usercertificate*', validate_certificate,
Certificate('usercertificate*',
cli_name='certificate',
label=_('Certificate'),
doc=_('Base-64 encoded service certificate'),
@ -632,9 +623,7 @@ class service_add(LDAPCreate):
self.obj.validate_ipakrbauthzdata(entry_attrs)
certs = options.get('usercertificate', [])
certs_der = [x509.ensure_der_format(c) for c in certs]
entry_attrs['usercertificate'] = certs_der
entry_attrs['usercertificate'] = options.get('usercertificate', [])
if not options.get('force', False):
# We know the host exists if we've gotten this far but we
@ -705,7 +694,6 @@ class service_mod(LDAPUpdate):
# verify certificates
certs = entry_attrs.get('usercertificate') or []
certs_der = [x509.ensure_der_format(c) for c in certs]
# revoke removed certificates
ca_is_enabled = self.api.Command.ca_is_enabled()['result']
if 'usercertificate' in options and ca_is_enabled:
@ -714,14 +702,14 @@ class service_mod(LDAPUpdate):
except errors.NotFound:
self.obj.handle_not_found(*keys)
old_certs = entry_attrs_old.get('usercertificate', [])
old_certs_der = [x509.ensure_der_format(c) for c in old_certs]
removed_certs_der = set(old_certs_der) - set(certs_der)
for der in removed_certs_der:
rm_certs = api.Command.cert_find(certificate=der)['result']
removed_certs = set(old_certs) - set(certs)
for cert in removed_certs:
rm_certs = api.Command.cert_find(
certificate=cert.public_bytes(x509.Encoding.DER))['result']
revoke_certs(rm_certs)
if certs:
entry_attrs['usercertificate'] = certs_der
entry_attrs['usercertificate'] = certs
update_krbticketflags(ldap, entry_attrs, attrs_list, options, True)
@ -997,7 +985,8 @@ class service_remove_cert(LDAPRemoveAttributeViaOption):
assert isinstance(dn, DN)
for cert in options.get('usercertificate', []):
revoke_certs(api.Command.cert_find(certificate=cert)['result'])
revoke_certs(api.Command.cert_find(
certificate=cert.public_bytes(x509.Encoding.DER))['result'])
return dn

View File

@ -35,7 +35,7 @@ import six
from ipaplatform.paths import paths
from ipaserver.plugins.ldap2 import ldap2, AUTOBIND_DISABLED
from ipalib import api, x509, create_api, errors
from ipalib import api, create_api, errors
from ipapython import ipautil
from ipapython.dn import DN
@ -77,8 +77,7 @@ class test_ldap(object):
self.conn = ldap2(api)
self.conn.connect(autobind=AUTOBIND_DISABLED)
entry_attrs = self.conn.get_entry(self.dn, ['usercertificate'])
cert = entry_attrs.get('usercertificate')
cert = x509.load_der_x509_certificate(cert[0])
cert = entry_attrs.get('usercertificate')[0]
assert cert.serial_number is not None
def test_simple(self):
@ -94,8 +93,7 @@ class test_ldap(object):
self.conn = ldap2(api)
self.conn.connect(bind_dn=DN(('cn', 'directory manager')), bind_pw=dm_password)
entry_attrs = self.conn.get_entry(self.dn, ['usercertificate'])
cert = entry_attrs.get('usercertificate')
cert = x509.load_der_x509_certificate(cert[0])
cert = entry_attrs.get('usercertificate')[0]
assert cert.serial_number is not None
def test_Backend(self):
@ -120,8 +118,7 @@ class test_ldap(object):
result = myapi.Command['service_show']('ldap/%s@%s' % (api.env.host, api.env.realm,))
entry_attrs = result['result']
cert = entry_attrs.get('usercertificate')
cert = x509.load_der_x509_certificate(cert[0])
cert = entry_attrs.get('usercertificate')[0]
assert cert.serial_number is not None
def test_autobind(self):
@ -134,8 +131,7 @@ class test_ldap(object):
except errors.ACIError:
raise nose.SkipTest("Only executed as root")
entry_attrs = self.conn.get_entry(self.dn, ['usercertificate'])
cert = entry_attrs.get('usercertificate')
cert = x509.load_der_x509_certificate(cert[0])
cert = entry_attrs.get('usercertificate')[0]
assert cert.serial_number is not None