On Fri, Aug 26, 2016 at 10:28:58AM +0200, Jan Cholasta wrote: > On 19.8.2016 13:11, Fraser Tweedale wrote: > > Bump for review. > > > > On Wed, Aug 17, 2016 at 12:09:39AM +1000, Fraser Tweedale wrote: > > > On Tue, Aug 16, 2016 at 08:10:08AM +0200, Jan Cholasta wrote: > > > > On 16.8.2016 07:24, Fraser Tweedale wrote: > > > > > On Mon, Aug 15, 2016 at 08:19:33AM +0200, Jan Cholasta wrote: > > > > > > On 9.8.2016 16:47, Fraser Tweedale wrote: > > > > > > > On Mon, Aug 08, 2016 at 10:49:27AM +0200, Jan Cholasta wrote: > > > > > > > > On 8.8.2016 09:06, Fraser Tweedale wrote: > > > > > > > > > On Mon, Aug 08, 2016 at 08:54:05AM +0200, Jan Cholasta wrote: > > > > > > > > > > Hi, > > > > > > > > > > > > > > > > > > > > On 8.8.2016 06:34, Fraser Tweedale wrote: > > > > > > > > > > > Please review the attached patch with adds > > > > > > > > > > > --certificate-out and > > > > > > > > > > > --certificate-chain-out options to `ca-show' command. > > > > > > > > > > > > > > > > > > > > > > Note that --certificate-chain-out currently writes a > > > > > > > > > > > bogus file due > > > > > > > > > > > to a bug in Dogtag that will be fixed in this week's > > > > > > > > > > > build. > > > > > > > > > > > > > > > > > > > > > > https://fedorahosted.org/freeipa/ticket/6178 > > > > > > > > > > > > > > > > > > > > 1) The client-side *-out options should be defined on the > > > > > > > > > > client side, not > > > > > > > > > > on the server side. > > > > > > > > > > > > > > > > > > > Will option defined on client side be propagated to, and > > > > > > > > > observable > > > > > > > > > in the ipaserver plugin? The ipaserver plugin needs to > > > > > > > > > observe that > > > > > > > > > *-out has been requested and executes additional command(s) > > > > > > > > > on that > > > > > > > > > basis. > > > > > > > > > > > > > > > > Is there a reason not to *always* return the certs? > > > > > > > > > > > > > > > We hit Dogtag to retrieve them. > > > > > > > > > > > > I don't think that's an issue in a -show command. > > > > > > > > > > > cert_show is invoked by other commands (cert_find*, cert_show, > > > > > cert_request, cert_status, ca_del) but these all hit Dogtag anyway > > > > > so I suppose that's fine. I'll return the cert *and* the chain in > > > > > separate attributes, unconditionally. > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 2) I don't think there should be additional information > > > > > > > > > > included in summary > > > > > > > > > > (and it definitely should not be multi-line). I would > > > > > > > > > > rather inform the user > > > > > > > > > > via an error message when unable to write the files. > > > > > > > > > > > > > > > > > > > I was just following the pattern of other commands that write > > > > > > > > > certs, > > > > > > > > > profile config, etc. Apart from consistency with other > > > > > > > > > commands I > > > > > > > > > agree that there is no need to have it. So I will remove it. > > > > > > > > > > > > > > > > > > > If you think there is an actual value in informing the user > > > > > > > > > > about > > > > > > > > > > successfully writing the files, please use ipalib.messages > > > > > > > > > > for the job. > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 3) IMO a better format for the certificate chain than > > > > > > > > > > PKCS#7 would be > > > > > > > > > > concatenated PEM, as that's the most commonly used format > > > > > > > > > > in IPA (in > > > > > > > > > > installers, there are no cert chains in API commands ATM). > > > > > > > > > > > > > > > > > > > Sure, but the main use case isn't IPA. Other apps require > > > > > > > > > PKCS #7 > > > > > > > > > or concatenated PEMs, but sometimes they must be concatenated > > > > > > > > > forward, and othertimes backwards. There is no one size fits > > > > > > > > > all. > > > > > > > > > > > > > > > > True, which is exactly why I think we should at least be > > > > > > > > self-consistent and > > > > > > > > use concatenated PEM (and multi-value DER over the wire). > > > > > > > > > > > > > > > Dogtag returns a PKCS7 (either DER or PEM, according to HTTP > > > > > > > Accept > > > > > > > header). > > > > > > > > > > > > > > If we want list-of-PEMs between server and client we have to > > > > > > > convert > > > > > > > on the server. Do we have a good way of doing this without > > > > > > > exec'ing > > > > > > > `openssl pkcs7' on the server? Is it acceptable to exec 'openssl' > > > > > > > to do the conversion on the server? python-nss does not have > > > > > > > PKCS7 > > > > > > > functions and I am not keen on adding a pyasn1 PKCS7 parser just > > > > > > > for > > > > > > > the sake of pushing bits as list-of-PEMs. > > > > > > > > > > > > I'm afraid we can't avoid conversion to/from PKCS#7 one way or the > > > > > > other. > > > > > > For example, if we added a call to retrieve external CA chain using > > > > > > certs > > > > > > from cn=certificates,cn=ipa,cn=etc, we would have to convert the > > > > > > result to > > > > > > PKCS#7 if it was our cert chain format of choice. > > > > > > > > > > > > What we can avoid though is executing "openssl pkcs7" to do the > > > > > > conversion - > > > > > > we can use an approach similar to our DNSSEC code and use > > > > > > python-cffi to > > > > > > call libcrypto's PKCS#7 conversion routines instead. > > > > > > > > > > > I had a look at the OpenSSL API for parsing PKCS #7; now I prefer to > > > > > exec `openssl' to do the job :) > > > > > > > > > > I will transmit DER-encoded PKCS #7 object on the wire; we cannot > > > > > used multi-valued DER attribute because order is important. Client > > > > > will convert to PEMs. > > > > > > > > Well, my point was not to send PKCS#7 over the wire, so that clients > > > > (including 3rd party clients) do not have to convert from PKCS#7 > > > > themselves. > > > > > > > > In fact we can use multi-valued DER - whatever you send over the wire > > > > from > > > > the server will be received in the exact same order by the client. Even > > > > if > > > > it wasn't, you can easily restore the order by matching issuer and > > > > subject > > > > names of the certificates. > > > > > > > > > > > > > > Should have new patch on list this afternoon. > > > > > > > > > > Thanks, > > > > > Fraser > > > > > > > > > > > > > > > > > > > FWIW, man pages and code suggest that PKCS #7 is accepted in > > > > > > > installer, etc. > > > > > > > > > > > > True, but that's a relatively new feature (since 4.1) and the > > > > > > installer > > > > > > internally executes "openssl pkcs7" to convert PKCS #7 to list of > > > > > > certs :-) > > > > > > > > > > > > > > > > > > > > > > We can add an option to control the format later, but for now, > > > > > > > > > Dogtag returns a PKCS #7 (PEM or DER) so let's go with that. > > > > > > > > > Worst > > > > > > > > > case is an admin has to invoke `openssl pkcs7' and concat the > > > > > > > > > certs > > > > > > > > > themselves. > > > > > > > > > > > > > > > > AFAIK none of NSS, OpenSSL or p11-kit can use PKCS#7 cert > > > > > > > > chains directly, > > > > > > > > so I'm afraid the worst case would happen virtually always. > > > > > > > > > > > > > > > If you're OK with invoking OpenSSL on the client to convert PKCS > > > > > > > #7 > > > > > > > to list-of-PEMs (similar to what is done in > > > > > > > ipapython.certdb.NSSDatabase) then we can have the client perform > > > > > > > the conversion. > > > > > > > > > > > > See above. > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 4) Over the wire, the certs should be DER-formatted, as > > > > > > > > > > that's the most > > > > > > > > > > common wire format in other API commands. > > > > > > > > > > > > > > > > > > > OK. > > > > > > > > > > > > > > > > > > > > > > > > > > > > > 5) What is the benefit in having the CA cert and the rest > > > > > > > > > > of the chain > > > > > > > > > > separate? For end-entity certs it makes sense to separate > > > > > > > > > > the cert from the > > > > > > > > > > CA chain, but for CA certs, you usually want the full > > > > > > > > > > chain, no? > > > > > > > > > > > > > > > > > > > If you want to anchor trust directly at a subca (e.g. > > > > > > > > > restrict VPN > > > > > > > > > login to certs issued by VPN sub-CA) then you often just want > > > > > > > > > the > > > > > > > > > cert. The chain option does subsume it, at cost of more work > > > > > > > > > for > > > > > > > > > administrators with this use case. I think it makes sense to > > > > > > > > > keep > > > > > > > > > both options. > > > > > > > > > > > > > > > > Does it? From what you described above, you either want just > > > > > > > > the sub-CA > > > > > > > > cert, or the full chain including the sub-CA cert, in which > > > > > > > > case it might > > > > > > > > make more sense to have a single --out option and a --chain > > > > > > > > flag. > > > > > > > > > > > > > > > How about --certificate-out which defaults to single cert, but > > > > > > > does > > > > > > > chain (as list-of-PEMs) when --chain flag given. > > > > > > > > > > > > > > Per https://fedorahosted.org/freeipa/ticket/5166 let's not add > > > > > > > more > > > > > > > `--out' options. > > > > > > > > > > > > +1 > > > > > > > > > Updated patch 0097-2 attached, and new patch 0099 which must be > > > applied first. > > > > > > I have implemented the suggested changes, except for cffi (I execute > > > `openssl pkcs7' instead). > > I don't like it, but OK. Another alternative would be to use pyasn1. > I don't like it either, but neither did I like the idea of reimplementing the wheel with pyasn1. Now is not the time for busywork :)
> > > > > > There are two new output attributes on the wire, 'certificate' > > > (single-value DER X.509), and 'certificate_chain' (ordered > > > multi-value DER X.509). They are always returned. The first cert > > > in the chain is always the same as 'certificate'; obviously this is > > > redunant but I have left it this way because I think usage is > > > clearer. > > I don't have a strong feeling about this one way or the other, but the same > scheme should be used for cert-show in the future. Does it make sense to do > it this way for cert-show? > > I'm not sure about always returning the chain in cert-show. Now that we have > a --chain flag rather than two out options, maybe we should go back to > returning the chain only if --chain is specified. What do you think? > I think we should go for consistency and always include both over the wire. If we want to hide cert or chain or both at the `ipa' CLI depending on options, I also don't feel strongly either way. For now they're both displayed. > > Patch 0099: > > 1) Please fix this: > > $ git show -U0 | pep8 --diff > ./ipalib/x509.py:59:80: E501 line too long (93 > 79 characters) > Done. > > Patch 0097: > > 1) `certificate` and `certificate_chain` are actually attributes of the ca > object, so they should be defined in ca.takes_params rather than > ca_show.has_output_params. > Done. Out of interest, now that they are part of ca_takes_params is there a way to hide them by default in CLI output, and only show them when `--all' is given? > > 2) Please fix these: > > $ git show -U0 | pep8 --diff > ./ipaclient/plugins/ca.py:21:9: E124 closing bracket does not match visual > indentation > ./ipaclient/plugins/ca.py:23:13: E128 continuation line under-indented for > visual indent > ./ipaclient/plugins/ca.py:24:13: E128 continuation line under-indented for > visual indent > ./ipaclient/plugins/ca.py:25:13: E128 continuation line under-indented for > visual indent > ./ipaclient/plugins/ca.py:26:9: E124 closing bracket does not match visual > indentation > ./ipaclient/plugins/ca.py:38:13: E731 do not assign a lambda expression, use > a def > Done. Updated patches attached. Thanks, Fraser
From 046b3dd078c4ccc3732a0106786bae4c01d30a89 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Tue, 16 Aug 2016 13:16:58 +1000 Subject: [PATCH] Add function for extracting PEM certs from PKCS #7 Add a single function for extracting X.509 certs in PEM format from a PKCS #7 object. Refactor sites that execute ``openssl pkcs7`` to use the new function. Part of: https://fedorahosted.org/freeipa/ticket/6178 --- ipalib/x509.py | 23 +++++++++++++++++- ipapython/certdb.py | 14 ++++------- ipaserver/install/cainstance.py | 52 +++++++++++++++-------------------------- 3 files changed, 45 insertions(+), 44 deletions(-) diff --git a/ipalib/x509.py b/ipalib/x509.py index e986a97a58aafd3aeab08765a397edbf67c7841a..0461553a73e3862c85f1ffcfe4432cabf4fdf7a1 100644 --- a/ipalib/x509.py +++ b/ipalib/x509.py @@ -51,11 +51,14 @@ from ipalib import util from ipalib import errors from ipaplatform.paths import paths from ipapython.dn import DN +from ipapython import ipautil PEM = 0 DER = 1 -PEM_REGEX = re.compile(r'(?<=-----BEGIN CERTIFICATE-----).*?(?=-----END CERTIFICATE-----)', re.DOTALL) +PEM_REGEX = re.compile( + r'-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----', + re.DOTALL) EKU_SERVER_AUTH = '1.3.6.1.5.5.7.3.1' EKU_CLIENT_AUTH = '1.3.6.1.5.5.7.3.2' @@ -148,6 +151,24 @@ def load_certificate_list(data, dbdir=None): certs = [load_certificate(cert, PEM, dbdir) for cert in certs] return certs + +def pkcs7_to_pems(data, datatype=PEM): + """ + Extract certificates from a PKCS #7 object. + + Return a ``list`` of X.509 PEM strings. + + May throw ``ipautil.CalledProcessError`` on invalid data. + + """ + cmd = [ + paths.OPENSSL, "pkcs7", "-print_certs", + "-inform", "PEM" if datatype == PEM else "DER", + ] + result = ipautil.run(cmd, stdin=data, capture_output=True) + return PEM_REGEX.findall(result.output) + + def load_certificate_list_from_file(filename, dbdir=None): """ Load a certificate list from a PEM file. diff --git a/ipapython/certdb.py b/ipapython/certdb.py index e19f712d82f160ebc5de9c5b8d6627cb941c2cef..fd18023794a2daace60efd97aff54180b8409bbd 100644 --- a/ipapython/certdb.py +++ b/ipapython/certdb.py @@ -270,13 +270,11 @@ class NSSDatabase(object): continue if label in ('PKCS7', 'PKCS #7 SIGNED DATA', 'CERTIFICATE'): - args = [ - paths.OPENSSL, 'pkcs7', - '-print_certs', - ] try: - result = ipautil.run( - args, stdin=body, capture_output=True) + certs = x509.pkcs7_to_pems(body) + extracted_certs += '\n'.join(certs) + '\n' + loaded = True + continue except ipautil.CalledProcessError as e: if label == 'CERTIFICATE': root_logger.warning( @@ -287,10 +285,6 @@ class NSSDatabase(object): "Skipping PKCS#7 in %s at line %s: %s", filename, line, e) continue - else: - extracted_certs += result.output + '\n' - loaded = True - continue if label in ('PRIVATE KEY', 'ENCRYPTED PRIVATE KEY', 'RSA PRIVATE KEY', 'DSA PRIVATE KEY', diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index c4b8e9ae326fb7ebda9e927cd4d0b5bad9743db4..f57c724b0273a275f8146f0d6055e2ee2e51192c 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -844,44 +844,30 @@ class CAInstance(DogtagInstance): # makes openssl throw up. data = base64.b64decode(chain) - result = ipautil.run( - [paths.OPENSSL, - "pkcs7", - "-inform", - "DER", - "-print_certs", - ], stdin=data, capture_output=True) - certlist = result.output + certlist = x509.pkcs7_to_pems(data, x509.DER) # Ok, now we have all the certificates in certs, walk through it # and pull out each certificate and add it to our database - st = 1 - en = 0 - subid = 0 ca_dn = DN(('CN','Certificate Authority'), self.subject_base) - while st > 0: - st = certlist.find('-----BEGIN', en) - en = certlist.find('-----END', en+1) - if st > 0: - try: - (chain_fd, chain_name) = tempfile.mkstemp() - os.write(chain_fd, certlist[st:en+25]) - os.close(chain_fd) - (_rdn, subject_dn) = certs.get_cert_nickname(certlist[st:en+25]) - if subject_dn == ca_dn: - nick = get_ca_nickname(self.realm) - trust_flags = 'CT,C,C' - else: - nick = str(subject_dn) - trust_flags = ',,' - self.__run_certutil( - ['-A', '-t', trust_flags, '-n', nick, '-a', - '-i', chain_name] - ) - finally: - os.remove(chain_name) - subid += 1 + for cert in certlist: + try: + (chain_fd, chain_name) = tempfile.mkstemp() + os.write(chain_fd, cert) + os.close(chain_fd) + (_rdn, subject_dn) = certs.get_cert_nickname(cert) + if subject_dn == ca_dn: + nick = get_ca_nickname(self.realm) + trust_flags = 'CT,C,C' + else: + nick = str(subject_dn) + trust_flags = ',,' + self.__run_certutil( + ['-A', '-t', trust_flags, '-n', nick, '-a', + '-i', chain_name] + ) + finally: + os.remove(chain_name) def __request_ra_certificate(self): # Create a noise file for generating our private key -- 2.5.5
From fba36bd2b86c2aee1d77e05aa563ced4633ab182 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Mon, 8 Aug 2016 14:27:20 +1000 Subject: [PATCH] Add options to write lightweight CA cert or chain to file Administrators need a way to retrieve the certificate or certificate chain of an IPA-managed lightweight CA. Add params to the `ca' object for carrying the CA certificate and chain (as multiple DER values), and add the `--certificate-out' option and `--chain' flag as client-side options for writing one or the other to a file. Fixes: https://fedorahosted.org/freeipa/ticket/6178 --- ipaclient/plugins/ca.py | 50 +++++++++++++++++++++++++++++++++++++++++++++ ipaserver/plugins/ca.py | 31 ++++++++++++++++++++++++---- ipaserver/plugins/dogtag.py | 12 +++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 ipaclient/plugins/ca.py diff --git a/ipaclient/plugins/ca.py b/ipaclient/plugins/ca.py new file mode 100644 index 0000000000000000000000000000000000000000..f7e55dec196495f820ebf745eb49e8ddce6b3ee7 --- /dev/null +++ b/ipaclient/plugins/ca.py @@ -0,0 +1,50 @@ +# +# Copyright (C) 2016 FreeIPA Contributors see COPYING for license +# + +import base64 +from ipaclient.frontend import MethodOverride +from ipalib import util, x509, Flag, Str +from ipalib.plugable import Registry +from ipalib.text import _ + +register = Registry() + + +@register(override=True, no_fail=True) +class ca_show(MethodOverride): + + takes_options = ( + Str( + 'certificate_out?', + doc=_('Write certificate to file'), + include='cli', + ), + Flag( + 'chain', + default=False, + doc=_('Write certificate chain instead of single certificate'), + include='cli', + ), + ) + + def forward(self, *keys, **options): + filename = None + if 'certificate_out' in options: + filename = options.pop('certificate_out') + util.check_writable_file(filename) + chain = options.pop('chain', False) + + result = super(ca_show, self).forward(*keys, **options) + if filename: + def to_pem(x): + return x509.make_pem(base64.b64encode(x)) + if chain: + ders = result['result']['certificate_chain'] + data = '\n'.join(map(to_pem, ders)) + else: + data = to_pem(result['result']['certificate']) + with open(filename, 'wb') as f: + f.write(data) + + return result diff --git a/ipaserver/plugins/ca.py b/ipaserver/plugins/ca.py index 966ae2b1bdb4bb0207dfa58f0e9c951bc930f766..0684ddaed0ebfcab8910c1ea356550b504af15e2 100644 --- a/ipaserver/plugins/ca.py +++ b/ipaserver/plugins/ca.py @@ -2,14 +2,14 @@ # Copyright (C) 2016 FreeIPA Contributors see COPYING for license # -from ipalib import api, errors, DNParam, Str +from ipalib import api, errors, Bytes, DNParam, Str from ipalib.constants import IPA_CA_CN from ipalib.plugable import Registry from ipaserver.plugins.baseldap import ( LDAPObject, LDAPSearch, LDAPCreate, LDAPDelete, LDAPUpdate, LDAPRetrieve) from ipaserver.plugins.cert import ca_enabled_check -from ipalib import _, ngettext +from ipalib import _, ngettext, x509 __doc__ = _(""" @@ -79,6 +79,18 @@ class ca(LDAPObject): doc=_('Issuer Distinguished Name'), flags=['no_create', 'no_update'], ), + Bytes( + 'certificate', + label=_("Certificate"), + doc=_("X.509 certificate"), + flags={'no_create', 'no_update', 'no_search', 'no_display'}, + ), + Bytes( + 'certificate_chain*', + label=_("Certificate chain"), + doc=_("PKCS #7 certificate chain"), + flags={'no_create', 'no_update', 'no_search', 'no_display'}, + ), ) permission_filter_objectclasses = ['ipaca'] @@ -140,9 +152,20 @@ class ca_find(LDAPSearch): class ca_show(LDAPRetrieve): __doc__ = _("Display the properties of a CA.") - def execute(self, *args, **kwargs): + def execute(self, *keys, **options): ca_enabled_check() - return super(ca_show, self).execute(*args, **kwargs) + result = super(ca_show, self).execute(*keys, **options) + + ca_id = result['result']['ipacaid'][0] + with self.api.Backend.ra_lightweight_ca as ca_api: + result['result']['certificate'] = ca_api.read_ca_cert(ca_id) + + pkcs7_der = ca_api.read_ca_chain(ca_id) + pems = x509.pkcs7_to_pems(pkcs7_der, x509.DER) + ders = (x509.normalize_certificate(pem) for pem in pems) + result['result']['certificate_chain'] = list(ders) + + return result @register() diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py index aef1e888eb1b6c273c1fd12cbf4912407f8f8132..1fd3106e0ae723eb30dbe32c61e637790f6085d2 100644 --- a/ipaserver/plugins/dogtag.py +++ b/ipaserver/plugins/dogtag.py @@ -2205,6 +2205,18 @@ class ra_lightweight_ca(RestClient): except: raise errors.RemoteRetrieveError(reason=_("Response from CA was not valid JSON")) + def read_ca_cert(self, ca_id): + status, resp_headers, resp_body = self._ssldo( + 'GET', '{}/cert'.format(ca_id), + headers={'Accept': 'application/pkix-cert'}) + return resp_body + + def read_ca_chain(self, ca_id): + status, resp_headers, resp_body = self._ssldo( + 'GET', '{}/chain'.format(ca_id), + headers={'Accept': 'application/pkcs7-mime'}) + return resp_body + def disable_ca(self, ca_id): self._ssldo( 'POST', ca_id + '/disable', -- 2.5.5
-- Manage your subscription for the Freeipa-devel mailing list: https://www.redhat.com/mailman/listinfo/freeipa-devel Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code