[Freeipa-devel] [PATCH 0014 v3] Support multiple user and host certificates
Fraser Tweedale
ftweedal at redhat.com
Mon Jun 1 04:40:27 UTC 2015
New version of patch; ``{host,service}-show --out=FILE`` now writes
all certs to FILE. Rebased on latest master.
Thanks,
Fraser
On Thu, May 28, 2015 at 09:18:04PM +1000, Fraser Tweedale wrote:
> Updated patch attached. Notably restores/adds revocation behaviour
> to host-mod and service-mod.
>
> Thanks,
> Fraser
>
> On Wed, May 27, 2015 at 06:12:50PM +0200, Martin Basti wrote:
> > On 27/05/15 15:53, Fraser Tweedale wrote:
> > >This patch adds supports for multiple user / host certificates. No
> > >schema change is needed ('usercertificate' attribute is already
> > >multi-value). The revoke-previous-cert behaviour of host-mod and
> > >user-mod has been removed but revocation behaviour of -del and
> > >-disable is preserved.
> > >
> > >The latest profiles/caacl patchset (0001..0013 v5) depends on this
> > >patch for correct cert-request behaviour.
> > >
> > >There is one design question (or maybe more, let me know): the
> > >`--out=FILENAME' option to {host,service} show saves ONE certificate
> > >to the named file. I propose to either:
> > >
> > >a) write all certs, suffixing suggested filename with either a
> > > sequential numerical index, e.g. "cert.pem" becomes
> > > "cert.pem.1", "cert.pem.2", and so on; or
> > >
> > >b) as above, but suffix with serial number and, if there are
> > > different issues, some issuer-identifying information.
> > >
> > >Let me know your thoughts.
> > >
> > >Thanks,
> > >Fraser
> > >
> > >
> > Is there a possible way how to store certificates into one file?
> > I read about possibilities to have multiple certs in one .pem file, but I'm
> > not cert guru :)
> >
> > I personally vote for serial number in case there are multiple certificates,
> > if ^ is no possible.
> >
> >
> > 1)
> > + if len(certs) > 0:
> >
> > please use only,
> > if certs:
> >
> > 2)
> > You need to re-generate API/ACI.txt in this patch
> >
> > 3)
> > syntax error:
> > + for dercert in certs_der
> >
> >
> > 4)
> > command
> > ipa user-mod ca_user --certificate=<ceritifcate>
> >
> > removes the current certificate from the LDAP, by design.
> > Should be the old certificate(s) revoked? You removed that part in the code.
> >
> > only the --addattr='usercertificate=<cert>' appends new value there
> >
> > --
> > Martin Basti
> >
-------------- next part --------------
From edc7108a6b22fbc74b449b4b71d5c9541191cb7c Mon Sep 17 00:00:00 2001
From: Fraser Tweedale <ftweedal at redhat.com>
Date: Wed, 27 May 2015 08:02:08 -0400
Subject: [PATCH] Support multiple host and service certificates
Update the framework to support multiple host and service
certificates.
host-mod and service-mod revoke existing certificates that are not
included in the modified entry. Using addattr=certificate=... will
result in no certificates being revoked.
The existing behaviour of host-disable, host-del, service-disable
and service-del (revoke existing certificate) is preserved but now
applies to all certificates in the host or service entry.
Also update host-show and service-show to write all the principal's
certificates to the file given by the ``--out=FILE`` option.
Part of: http://www.freeipa.org/page/V4/User_Certificates
---
API.txt | 10 ++---
ipalib/plugins/host.py | 106 +++++++++++++++++++++++++---------------------
ipalib/plugins/service.py | 93 +++++++++++++++++++++++++---------------
3 files changed, 120 insertions(+), 89 deletions(-)
diff --git a/API.txt b/API.txt
index da69f32de5c12c0d85a7d61d9027385aa3c0ee05..3cfcf34939a58d6888de8f0a7a6ef0c7779c993e 100644
--- a/API.txt
+++ b/API.txt
@@ -1812,7 +1812,7 @@ option: Str('nsosversion', attribute=True, cli_name='os', multivalue=False, requ
option: Flag('random', attribute=False, autofill=True, cli_name='random', default=False, multivalue=False, required=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('setattr*', cli_name='setattr', exclude='webui')
-option: Bytes('usercertificate', attribute=True, cli_name='certificate', multivalue=False, required=False)
+option: Bytes('usercertificate', attribute=True, cli_name='certificate', multivalue=True, required=False)
option: Str('userclass', attribute=True, cli_name='class', multivalue=True, required=False)
option: Str('userpassword', attribute=True, cli_name='password', multivalue=False, required=False)
option: Str('version?', exclude='webui')
@@ -1935,7 +1935,7 @@ option: Flag('pkey_only?', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Int('sizelimit?', autofill=False, minvalue=0)
option: Int('timelimit?', autofill=False, minvalue=0)
-option: Bytes('usercertificate', attribute=True, autofill=False, cli_name='certificate', multivalue=False, query=True, required=False)
+option: Bytes('usercertificate', attribute=True, autofill=False, cli_name='certificate', multivalue=True, query=True, required=False)
option: Str('userclass', attribute=True, autofill=False, cli_name='class', multivalue=True, query=True, required=False)
option: Str('userpassword', attribute=True, autofill=False, cli_name='password', multivalue=False, query=True, required=False)
option: Str('version?', exclude='webui')
@@ -1966,7 +1966,7 @@ option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui
option: Flag('rights', autofill=True, default=False)
option: Str('setattr*', cli_name='setattr', exclude='webui')
option: Flag('updatedns?', autofill=True, default=False)
-option: Bytes('usercertificate', attribute=True, autofill=False, cli_name='certificate', multivalue=False, required=False)
+option: Bytes('usercertificate', attribute=True, autofill=False, cli_name='certificate', multivalue=True, required=False)
option: Str('userclass', attribute=True, autofill=False, cli_name='class', multivalue=True, required=False)
option: Str('userpassword', attribute=True, autofill=False, cli_name='password', multivalue=False, required=False)
option: Str('version?', exclude='webui')
@@ -3584,7 +3584,7 @@ option: Bool('ipakrbrequirespreauth', attribute=False, cli_name='requires_pre_au
option: Flag('no_members', autofill=True, default=False, exclude='webui')
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('setattr*', cli_name='setattr', exclude='webui')
-option: Bytes('usercertificate', attribute=True, cli_name='certificate', multivalue=False, required=False)
+option: Bytes('usercertificate', attribute=True, cli_name='certificate', multivalue=True, required=False)
option: Str('version?', exclude='webui')
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
@@ -3702,7 +3702,7 @@ option: Flag('no_members', autofill=True, default=False, exclude='webui')
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Flag('rights', autofill=True, default=False)
option: Str('setattr*', cli_name='setattr', exclude='webui')
-option: Bytes('usercertificate', attribute=True, autofill=False, cli_name='certificate', multivalue=False, required=False)
+option: Bytes('usercertificate', attribute=True, autofill=False, cli_name='certificate', multivalue=True, required=False)
option: Str('version?', exclude='webui')
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py
index c47439743da45b8629d1b2afbd210d87591784ce..2ad1aaada69bd9c6a768e0fd3290217d63b652ea 100644
--- a/ipalib/plugins/host.py
+++ b/ipalib/plugins/host.py
@@ -493,7 +493,7 @@ class host(LDAPObject):
label=_('Random password'),
flags=('no_create', 'no_update', 'no_search', 'virtual_attribute'),
),
- Bytes('usercertificate?', validate_certificate,
+ Bytes('usercertificate*', validate_certificate,
cli_name='certificate',
label=_('Certificate'),
doc=_('Base-64 encoded server certificate'),
@@ -640,11 +640,11 @@ class host_add(LDAPCreate):
entry_attrs['userpassword'] = ipa_generate_password(characters=host_pwd_chars)
# save the password so it can be displayed in post_callback
setattr(context, 'randompassword', entry_attrs['userpassword'])
- cert = options.get('usercertificate')
- if cert:
- cert = x509.normalize_certificate(cert)
+ certs = options.get('usercertificate', [])
+ certs_der = map(x509.normalize_certificate, certs)
+ for cert in certs_der:
x509.verify_cert_subject(ldap, keys[-1], cert)
- entry_attrs['usercertificate'] = cert
+ entry_attrs['usercertificate'] = certs_der
entry_attrs['managedby'] = dn
entry_attrs['objectclass'].append('ieee802device')
entry_attrs['objectclass'].append('ipasshhost')
@@ -786,8 +786,7 @@ class host_del(LDAPDelete):
entry_attrs = ldap.get_entry(dn, ['usercertificate'])
except errors.NotFound:
self.obj.handle_not_found(*keys)
- cert = entry_attrs.single_value.get('usercertificate')
- if cert:
+ for cert in entry_attrs.get('usercertificate', []):
cert = x509.normalize_certificate(cert)
try:
serial = unicode(x509.get_serial_number(cert, x509.DER))
@@ -864,39 +863,42 @@ class host_mod(LDAPUpdate):
if 'krbprincipalaux' not in obj_classes:
obj_classes.append('krbprincipalaux')
entry_attrs['objectclass'] = obj_classes
- cert = x509.normalize_certificate(entry_attrs.get('usercertificate'))
- if cert:
- if self.api.Command.ca_is_enabled()['result']:
- x509.verify_cert_subject(ldap, keys[-1], cert)
- entry_attrs_old = ldap.get_entry(dn, ['usercertificate'])
- oldcert = entry_attrs_old.single_value.get('usercertificate')
- if oldcert:
- oldcert = x509.normalize_certificate(oldcert)
+
+ # verify certificates
+ certs = entry_attrs.get('usercertificate') or []
+ certs_der = map(x509.normalize_certificate, certs)
+ for cert in certs_der:
+ x509.verify_cert_subject(ldap, keys[-1], cert)
+
+ # revoke removed certificates
+ if self.api.Command.ca_is_enabled()['result']:
+ entry_attrs_old = ldap.get_entry(dn, ['usercertificate'])
+ old_certs = entry_attrs_old.get('usercertificate', [])
+ removed_certs = set(old_certs) - set(certs)
+ removed_certs_der = map(x509.normalize_certificate, removed_certs)
+ for cert in removed_certs_der:
+ try:
+ serial = unicode(x509.get_serial_number(cert, x509.DER))
try:
- serial = x509.get_serial_number(oldcert, x509.DER)
- serial = unicode(serial)
- try:
- result = api.Command['cert_show'](serial)['result']
- if 'revocation_reason' not in result:
- try:
- api.Command['cert_revoke'](
- serial, revocation_reason=4)
- except errors.NotImplementedError:
- # some CA's might not implement revoke
- pass
- except errors.NotImplementedError:
- # some CA's might not implement revoke
- pass
- except NSPRError, nsprerr:
- if nsprerr.errno == -8183:
- # If we can't decode the cert them proceed with
- # modifying the host.
- self.log.info("Problem decoding certificate %s" %
- nsprerr.args[1])
- else:
- raise nsprerr
-
- entry_attrs['usercertificate'] = cert
+ result = api.Command['cert_show'](serial)['result']
+ if 'revocation_reason' not in result:
+ try:
+ api.Command['cert_revoke'](
+ serial, revocation_reason=4)
+ except errors.NotImplementedError:
+ # some CA's might not implement revoke
+ pass
+ except errors.NotImplementedError:
+ # some CA's might not implement revoke
+ pass
+ except NSPRError, nsprerr:
+ if nsprerr.errno == -8183:
+ # If we can't decode the cert them proceed with
+ # modifying the host.
+ self.log.info("Problem decoding certificate %s" %
+ nsprerr.args[1])
+ else:
+ raise nsprerr
if options.get('random'):
entry_attrs['userpassword'] = ipa_generate_password(characters=host_pwd_chars)
@@ -1093,8 +1095,14 @@ class host_show(LDAPRetrieve):
util.check_writable_file(options['out'])
result = super(host_show, self).forward(*keys, **options)
if 'usercertificate' in result['result']:
- x509.write_certificate(result['result']['usercertificate'][0], options['out'])
- result['summary'] = _('Certificate stored in file \'%(file)s\'') % dict(file=options['out'])
+ x509.write_certificate_list(
+ result['result']['usercertificate'],
+ options['out']
+ )
+ result['summary'] = (
+ _('Certificate(s) stored in file \'%(file)s\'')
+ % dict(file=options['out'])
+ )
return result
else:
raise errors.NoCertificateError(entry=keys[-1])
@@ -1148,10 +1156,9 @@ class host_disable(LDAPQuery):
entry_attrs = ldap.get_entry(dn, ['usercertificate'])
except errors.NotFound:
self.obj.handle_not_found(*keys)
- cert = entry_attrs.single_value.get('usercertificate')
- if cert:
- if self.api.Command.ca_is_enabled()['result']:
- cert = x509.normalize_certificate(cert)
+ if self.api.Command.ca_is_enabled()['result']:
+ certs = entry_attrs.get('usercertificate', [])
+ for cert in map(x509.normalize_certificate, certs):
try:
serial = unicode(x509.get_serial_number(cert, x509.DER))
try:
@@ -1175,10 +1182,11 @@ class host_disable(LDAPQuery):
else:
raise nsprerr
- # Remove the usercertificate altogether
- entry_attrs['usercertificate'] = None
- ldap.update_entry(entry_attrs)
- done_work = True
+ if certs:
+ # Remove the usercertificate altogether
+ entry_attrs['usercertificate'] = None
+ ldap.update_entry(entry_attrs)
+ done_work = True
self.obj.get_password_attributes(ldap, dn, entry_attrs)
if entry_attrs['has_keytab']:
diff --git a/ipalib/plugins/service.py b/ipalib/plugins/service.py
index b37dc7b4bf56b69df204fd29e9487f1390197bbe..668e016bd9f56fa75f791355fb9d8d3bd02f8f1a 100644
--- a/ipalib/plugins/service.py
+++ b/ipalib/plugins/service.py
@@ -437,7 +437,7 @@ class service(LDAPObject):
primary_key=True,
normalizer=lambda value: normalize_principal(value),
),
- Bytes('usercertificate?', validate_certificate,
+ Bytes('usercertificate*', validate_certificate,
cli_name='certificate',
label=_('Certificate'),
doc=_('Base-64 encoded server certificate'),
@@ -503,11 +503,11 @@ class service_add(LDAPCreate):
self.obj.validate_ipakrbauthzdata(entry_attrs)
- cert = options.get('usercertificate')
- if cert:
- dercert = x509.normalize_certificate(cert)
+ certs = options.get('usercertificate', [])
+ certs_der = map(x509.normalize_certificate, certs)
+ for dercert in certs_der:
x509.verify_cert_subject(ldap, hostname, dercert)
- entry_attrs['usercertificate'] = dercert
+ entry_attrs['usercertificate'] = certs_der
if not options.get('force', False):
# We know the host exists if we've gotten this far but we
@@ -555,9 +555,7 @@ class service_del(LDAPDelete):
entry_attrs = ldap.get_entry(dn, ['usercertificate'])
except errors.NotFound:
self.obj.handle_not_found(*keys)
- cert = entry_attrs.get('usercertificate')
- if cert:
- cert = cert[0]
+ for cert in entry_attrs.get('usercertificate', []):
try:
serial = unicode(x509.get_serial_number(cert, x509.DER))
try:
@@ -597,25 +595,43 @@ class service_mod(LDAPUpdate):
self.obj.validate_ipakrbauthzdata(entry_attrs)
- if 'usercertificate' in options:
- (service, hostname, realm) = split_principal(keys[-1])
- cert = options.get('usercertificate')
- if cert:
- dercert = x509.normalize_certificate(cert)
- x509.verify_cert_subject(ldap, hostname, dercert)
+ (service, hostname, realm) = split_principal(keys[-1])
+
+ # verify certificates
+ certs = options.get('usercertificate') or []
+ certs_der = map(x509.normalize_certificate, certs)
+ for dercert in certs_der:
+ x509.verify_cert_subject(ldap, hostname, dercert)
+
+ # revoke removed certificates
+ if self.api.Command.ca_is_enabled()['result']:
+ entry_attrs_old = ldap.get_entry(dn, ['usercertificate'])
+ old_certs = entry_attrs_old.get('usercertificate', [])
+ removed_certs = set(old_certs) - set(certs)
+ removed_certs_der = map(x509.normalize_certificate, removed_certs)
+ for cert in removed_certs_der:
try:
- entry_attrs_old = ldap.get_entry(dn, ['usercertificate'])
- except errors.NotFound:
- self.obj.handle_not_found(*keys)
- if 'usercertificate' in entry_attrs_old:
- # FIXME: what to do here? do we revoke the old cert?
- fmt = 'entry already has a certificate, serial number: %s' % (
- x509.get_serial_number(entry_attrs_old['usercertificate'][0], x509.DER)
- )
- raise errors.GenericError(format=fmt)
- entry_attrs['usercertificate'] = dercert
- else:
- entry_attrs['usercertificate'] = None
+ serial = unicode(x509.get_serial_number(cert, x509.DER))
+ try:
+ result = api.Command['cert_show'](serial)['result']
+ if 'revocation_reason' not in result:
+ try:
+ api.Command['cert_revoke'](
+ serial, revocation_reason=4)
+ except errors.NotImplementedError:
+ # some CA's might not implement revoke
+ pass
+ except errors.NotImplementedError:
+ # some CA's might not implement revoke
+ pass
+ except NSPRError, nsprerr:
+ if nsprerr.errno == -8183:
+ # If we can't decode the cert them proceed with
+ # modifying the host.
+ self.log.info("Problem decoding certificate %s" %
+ nsprerr.args[1])
+ else:
+ raise nsprerr
update_krbticketflags(ldap, entry_attrs, attrs_list, options, True)
@@ -695,8 +711,14 @@ class service_show(LDAPRetrieve):
util.check_writable_file(options['out'])
result = super(service_show, self).forward(*keys, **options)
if 'usercertificate' in result['result']:
- x509.write_certificate(result['result']['usercertificate'][0], options['out'])
- result['summary'] = _('Certificate stored in file \'%(file)s\'') % dict(file=options['out'])
+ x509.write_certificate_list(
+ result['result']['usercertificate'],
+ options['out']
+ )
+ result['summary'] = (
+ _('Certificate(s) stored in file \'%(file)s\'')
+ % dict(file=options['out'])
+ )
return result
else:
raise errors.NoCertificateError(entry=keys[-1])
@@ -815,9 +837,9 @@ class service_disable(LDAPQuery):
# See if we do any work at all here and if not raise an exception
done_work = False
- if 'usercertificate' in entry_attrs:
- if self.api.Command.ca_is_enabled()['result']:
- cert = x509.normalize_certificate(entry_attrs.get('usercertificate')[0])
+ if self.api.Command.ca_is_enabled()['result']:
+ certs = entry_attrs.get('usercertificate', [])
+ for cert in map(x509.normalize_certificate, certs):
try:
serial = unicode(x509.get_serial_number(cert, x509.DER))
try:
@@ -839,10 +861,11 @@ class service_disable(LDAPQuery):
else:
raise nsprerr
- # Remove the usercertificate altogether
- entry_attrs['usercertificate'] = None
- ldap.update_entry(entry_attrs)
- done_work = True
+ if len(certs) > 0:
+ # Remove the usercertificate altogether
+ entry_attrs['usercertificate'] = None
+ ldap.update_entry(entry_attrs)
+ done_work = True
self.obj.get_password_attributes(ldap, dn, entry_attrs)
if entry_attrs['has_keytab']:
--
2.1.0
More information about the Freeipa-devel
mailing list