diff options
-rw-r--r-- | API.txt | 23 | ||||
-rw-r--r-- | VERSION | 2 | ||||
-rw-r--r-- | ipalib/plugins/cert.py | 137 | ||||
-rw-r--r-- | ipaserver/plugins/dogtag.py | 138 | ||||
-rw-r--r-- | ipaserver/plugins/rabase.py | 8 | ||||
-rw-r--r-- | tests/test_xmlrpc/test_cert.py | 312 |
6 files changed, 598 insertions, 22 deletions
@@ -425,6 +425,29 @@ args: 1,0,2 arg: Any('methods*') output: Output('count', <type 'int'>, None) output: Output('results', (<type 'list'>, <type 'tuple'>), None) +command: cert_find +args: 0,17,4 +option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui') +option: Flag('exactly?', autofill=True, default=False) +option: Str('issuedon_from?', autofill=False) +option: Str('issuedon_to?', autofill=False) +option: Int('max_serial_number?', autofill=False, maxvalue=2147483647) +option: Int('min_serial_number?', autofill=False, minvalue=0) +option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui') +option: Int('revocation_reason?', autofill=False, maxvalue=10, minvalue=0) +option: Str('revokedon_from?', autofill=False) +option: Str('revokedon_to?', autofill=False) +option: Int('sizelimit?', default=100, minvalue=0) +option: Str('subject?', autofill=False) +option: Str('validnotafter_from?', autofill=False) +option: Str('validnotafter_to?', autofill=False) +option: Str('validnotbefore_from?', autofill=False) +option: Str('validnotbefore_to?', autofill=False) +option: Str('version?', exclude='webui') +output: Output('count', <type 'int'>, None) +output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None)) +output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None) +output: Output('truncated', <type 'bool'>, None) command: cert_remove_hold args: 1,0,1 arg: Str('serial_number') @@ -89,4 +89,4 @@ IPA_DATA_VERSION=20100614120000 # # ######################################################## IPA_API_VERSION_MAJOR=2 -IPA_API_VERSION_MINOR=50 +IPA_API_VERSION_MINOR=51 diff --git a/ipalib/plugins/cert.py b/ipalib/plugins/cert.py index 3aa01621d..51493c34e 100644 --- a/ipalib/plugins/cert.py +++ b/ipalib/plugins/cert.py @@ -24,18 +24,20 @@ if api.env.enable_ra is not True: # In this case, abort loading this plugin module... raise SkipPluginModule(reason='env.enable_ra is not True') import os +import time from ipalib import Command, Str, Int, Bytes, Flag, File from ipalib import errors from ipalib import pkcs10 from ipalib import x509 from ipalib import util +from ipalib import ngettext from ipalib.plugins.virtual import * from ipalib.plugins.service import split_principal import base64 import traceback from ipalib.text import _ from ipalib.request import context -from ipalib.output import Output +from ipalib import output from ipalib.plugins.service import validate_principal import nss.nss as nss from nss.error import NSPRError @@ -60,6 +62,18 @@ In order to request a certificate: * The host must exist * The service must exist (or you use the --add option to automatically add it) +SEARCHING: + +Certificates may be searched on by certificate subject, serial number, +revocation reason, validity dates and the issued date. + +When searching on dates the _from date does a >= search and the _to date +does a <= search. When combined these are done as an AND. + +Dates are treated as GMT to match the dates in the certificates. + +The date format is YYYY-mm-dd. + EXAMPLES: Request a new certificate and add the principal: @@ -77,6 +91,15 @@ EXAMPLES: Check the status of a signing request: ipa cert-status 10 + Search for certificates by hostname: + ipa cert-find --subject=ipaserver.example.com + + Search for revoked certificates by reason: + ipa cert-find --revocation-reason=5 + + Search for certificates based on issuance date + ipa cert-find --issuedon-from=2013-02-01 --issuedon-to=2013-02-07 + IPA currently immediately issues (or declines) all certificate requests so the status of a request is not normally useful. This is for future use or the case where a CA does not immediately issue a certificate. @@ -100,6 +123,17 @@ http://www.ietf.org/rfc/rfc5280.txt """) +def validate_pkidate(ugettext, value): + """ + A date in the format of %Y-%m-%d + """ + try: + ts = time.strptime(value, '%Y-%m-%d') + except ValueError, e: + return str(e) + + return None + def get_csr_hostname(csr): """ Return the value of CN in the subject of the request or None @@ -262,7 +296,7 @@ class cert_request(VirtualCommand): ) has_output = ( - Output('result', + output.Output('result', type=dict, doc=_('Dictionary mapping variable name to value'), ), @@ -593,3 +627,102 @@ class cert_remove_hold(VirtualCommand): ) api.register(cert_remove_hold) + + +class cert_find(Command): + __doc__ = _('Search for existing certificates.') + + takes_options = ( + Str('subject?', + label=_('Subject'), + doc=_('Subject'), + autofill=False, + ), + Int('revocation_reason?', + label=_('Reason'), + doc=_('Reason for revoking the certificate (0-10)'), + minvalue=0, + maxvalue=10, + autofill=False, + ), + Int('min_serial_number?', + doc=_("minimum serial number"), + autofill=False, + minvalue=0, + ), + Int('max_serial_number?', + doc=_("maximum serial number"), + autofill=False, + maxvalue=2147483647, + ), + Flag('exactly?', + doc=_('match the common name exactly'), + autofill=False, + ), + Str('validnotafter_from?', validate_pkidate, + doc=_('Valid not after from this date (YYYY-mm-dd)'), + autofill=False, + ), + Str('validnotafter_to?', validate_pkidate, + doc=_('Valid not after to this date (YYYY-mm-dd)'), + autofill=False, + ), + Str('validnotbefore_from?', validate_pkidate, + doc=_('Valid not before from this date (YYYY-mm-dd)'), + autofill=False, + ), + Str('validnotbefore_to?', validate_pkidate, + doc=_('Valid not before to this date (YYYY-mm-dd)'), + autofill=False, + ), + Str('issuedon_from?', validate_pkidate, + doc=_('Issued on from this date (YYYY-mm-dd)'), + autofill=False, + ), + Str('issuedon_to?', validate_pkidate, + doc=_('Issued on to this date (YYYY-mm-dd)'), + autofill=False, + ), + Str('revokedon_from?', validate_pkidate, + doc=_('Revoked on from this date (YYYY-mm-dd)'), + autofill=False, + ), + Str('revokedon_to?', validate_pkidate, + doc=_('Revoked on to this date (YYYY-mm-dd)'), + autofill=False, + ), + Int('sizelimit?', + label=_('Size Limit'), + doc=_('Maximum number of certs returned'), + flags=['no_display'], + minvalue=0, + default=100, + ), + ) + + has_output = output.standard_list_of_entries + has_output_params = ( + Str('serial_number_hex', + label=_('Serial number (hex)'), + ), + Str('serial_number', + label=_('Serial number'), + ), + Str('status', + label=_('Status'), + ), + ) + + msg_summary = ngettext( + '%(count)d certificate matched', '%(count)d certificates matched', 0 + ) + + def execute(self, **options): + ret = dict( + result=self.Backend.ra.find(options) + ) + ret['count'] = len(ret['result']) + ret['truncated'] = False + return ret + +api.register(cert_find) diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py index d52bb7e98..28bf754cb 100644 --- a/ipaserver/plugins/dogtag.py +++ b/ipaserver/plugins/dogtag.py @@ -237,9 +237,14 @@ digits and nothing else follows. ''' from lxml import etree +import urllib +import urllib2 import datetime +import time from ipapython.dn import DN from ldap.filter import escape_filter_chars +import ipapython.dogtag +from ipapython import ipautil # These are general status return values used when # CMSServlet.outputError() is invoked. @@ -1715,4 +1720,137 @@ class ra(rabase.rabase): return cmd_result + def find(self, options): + """ + Search for certificates + + :param options: dictionary of search options + """ + + def convert_time(value): + """ + Convert time to milliseconds to pass to dogtag + """ + ts = time.strptime(value, '%Y-%m-%d') + return int(time.mktime(ts) * 1000) + + self.debug('%s.find()', self.fullname) + + # Create the root element + page = etree.Element('CertSearchRequest') + + # Make a new document tree + doc = etree.ElementTree(page) + + # This matches the default configuration of the pki tool. + booloptions = {'serialNumberRangeInUse': True, + 'subjectInUse': False, + 'matchExactly': False, + 'revokedByInUse': False, + 'revokedOnInUse': False, + 'revocationReasonInUse': False, + 'issuedByInUse': False, + 'issuedOnInUse': False, + 'validNotBeforeInUse': False, + 'validNotAfterInUse': False, + 'validityLengthInUse': False, + 'certTypeInUse': False} + + if options.get('exactly', False): + booloptions['matchExactly'] = True + + if 'subject' in options: + node = etree.SubElement(page, 'commonName') + node.text = options['subject'] + booloptions['subjectInUse'] = True + + if 'revocation_reason' in options: + node = etree.SubElement(page, 'revocationReason') + node.text = unicode(options['revocation_reason']) + booloptions['revocationReasonInUse'] = True + + if 'min_serial_number' in options: + node = etree.SubElement(page, 'serialFrom') + node.text = unicode(options['min_serial_number']) + + if 'max_serial_number' in options: + node = etree.SubElement(page, 'serialTo') + node.text = unicode(options['max_serial_number']) + + # date_types is a tuple that consists of: + # 1. attribute name passed from IPA API + # 2. attribute name used by REST API + # 3. boolean to set in the REST API + + date_types = ( + ('validnotbefore_from', 'validNotBeforeFrom', 'validNotBeforeInUse'), + ('validnotbefore_to', 'validNotBeforeTo', 'validNotBeforeInUse'), + ('validnotafter_from', 'validNotAfterFrom', 'validNotAfterInUse'), + ('validnotafter_to', 'validNotAfterTo', 'validNotAfterInUse'), + ('issuedon_from', 'issuedOnFrom','issuedOnInUse'), + ('issuedon_to', 'issuedOnTo','issuedOnInUse'), + ('revokedon_from', 'revokedOnFrom','revokedOnInUse'), + ('revokedon_to', 'revokedOnTo','revokedOnInUse'), + ) + + for (attr, dattr, battr) in date_types: + if attr in options: + epoch = convert_time(options[attr]) + node = etree.SubElement(page, dattr) + node.text = unicode(epoch) + booloptions[battr] = True + + # Add the boolean options to our XML document + for opt in booloptions: + e = etree.SubElement(page, opt) + e.text = str(booloptions[opt]).lower() + + payload = etree.tostring(doc, pretty_print=False, xml_declaration=True, encoding='UTF-8') + self.debug('%s.find(): request: %s', self.fullname, payload) + + url = 'http://%s/ca/rest/certs/search?size=%d' % (ipautil.format_netloc(self.ca_host, ipapython.dogtag.configured_constants().UNSECURE_PORT), options.get('sizelimit', 100)) + + opener = urllib2.build_opener() + opener.addheaders = [('Accept-Encoding', 'gzip, deflate'), + ('User-Agent', 'IPA')] + + req = urllib2.Request(url=url, data=payload, headers={'Content-Type': 'application/xml'}) + try: + response = opener.open(req) + except urllib2.HTTPError, e: + self.raise_certificate_operation_error('find', + detail=e.msg) + except urllib2.URLError, e: + self.raise_certificate_operation_error('find', + detail=e.reason) + + data = response.readlines() + self.debug('%s.find(): response: %s', self.fullname, data) + parser = etree.XMLParser() + try: + doc = etree.fromstring(data[0], parser) + except etree.XMLSyntaxError, e: + self.raise_certificate_operation_error('find', + detail=e.msg) + + # Grab all the certificates + certs = doc.xpath('//CertDataInfo') + + results = [] + + for cert in certs: + response_request = {} + response_request['serial_number'] = int(cert.get('id'), 16) # parse as hex + response_request['serial_number_hex'] = u'0x%X' % response_request['serial_number'] + + dn = cert.xpath('SubjectDN') + if len(dn) == 1: + response_request['subject'] = unicode(dn[0].text) + status = cert.xpath('Status') + if len(status) == 1: + response_request['status'] = unicode(status[0].text) + results.append(response_request) + + return results + api.register(ra) diff --git a/ipaserver/plugins/rabase.py b/ipaserver/plugins/rabase.py index 369027b43..1d8713f4a 100644 --- a/ipaserver/plugins/rabase.py +++ b/ipaserver/plugins/rabase.py @@ -111,3 +111,11 @@ class rabase(Backend): """ raise errors.NotImplementedError(name='%s.take_certificate_off_hold' % self.name) + + def find(self, options): + """ + Search for certificates + + :param options: dictionary of search options + """ + raise errors.NotImplementedError(name='%s.find' % self.name) diff --git a/tests/test_xmlrpc/test_cert.py b/tests/test_xmlrpc/test_cert.py index 4f95ffe19..906d1977a 100644 --- a/tests/test_xmlrpc/test_cert.py +++ b/tests/test_xmlrpc/test_cert.py @@ -1,7 +1,7 @@ # Authors: # Rob Crittenden <rcritten@redhat.com> # -# Copyright (C) 2009 Red Hat +# Copyright (C) 2009,2013 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or modify @@ -23,7 +23,7 @@ Test the `ipalib/plugins/cert.py` module against the selfsign plugin. import sys import os import shutil -from nose.tools import assert_raises # pylint: disable=E0611 +from nose.tools import raises, assert_raises # pylint: disable=E0611 from xmlrpc_test import XMLRPC_test, assert_attr_equal from ipalib import api @@ -39,29 +39,50 @@ from ipapython.dn import DN cert = None newcert = None +def is_db_configured(): + """ + Raise an exception if we are testing against lite-server and the + developer cert database is configured. + """ + aliasdir = api.env.dot_ipa + os.sep + 'alias' + os.sep + '.pwd' + + if (api.env.xmlrpc_uri == u'http://localhost:8888/ipa/xml' and + not ipautil.file_exists(aliasdir)): + raise nose.SkipTest('developer CA not configured in %s' % aliasdir) + # Test setup # # This test needs a configured CA behind it in order to work properly -# It currently specifically tests for a self-signed CA but there is no -# reason the test wouldn't work with a dogtag CA as well with some -# additional work. This will change when selfsign is no longer the default CA. # -# To set it up grab the 3 NSS db files from a self-signed CA from -# /etc/httpd/alias to ~/.ipa/alias. Copy /etc/httpd/alias/pwdfile.txt to -# ~/.ipa/alias/.pwd. Change ownership of these files too. That should do it. +# To test against Apache directly then no changes are required. Just be +# sure the xmlrpc_uri in ~/.ipa/default.conf points to Apache. +# +# To test against a selfsign or dogtag CA in the lite-server: +# +# - Copy the 3 NSS db files from /etc/httpd/alias to ~/.ipa/alias +# - Copy /etc/httpd/alias/pwdfile.txt to ~/.ipa/alias/.pwd. +# - Change ownership of these files to be readable by you. +# +# The API tested depends on the value of ~/.ipa/default/ra_plugin when +# running as the lite-server. class test_cert(XMLRPC_test): + @classmethod + def setUpClass(cls): + super(test_cert, cls).setUpClass() + + if 'cert_request' not in api.Command: + raise nose.SkipTest('cert_request not registered') + + is_db_configured() + def run_certutil(self, args, stdin=None): new_args = ["/usr/bin/certutil", "-d", self.reqdir] new_args = new_args + args return ipautil.run(new_args, stdin) def setUp(self): - if 'cert_request' not in api.Command: - raise nose.SkipTest('cert_request not registered') - if not ipautil.file_exists(api.env.dot_ipa + os.sep + 'alias' + os.sep + '.pwd'): - raise nose.SkipTest('developer self-signed CA not configured') super(test_cert, self).setUp() self.reqdir = tempfile.mkdtemp(prefix = "tmp-") self.reqfile = self.reqdir + "/test.csr" @@ -99,7 +120,7 @@ class test_cert(XMLRPC_test): host_fqdn = u'ipatestcert.%s' % api.env.domain service_princ = u'test/%s@%s' % (host_fqdn, api.env.realm) - def test_1_cert_add(self): + def test_0001_cert_add(self): """ Test the `xmlrpc.cert_request` method without --add. @@ -112,7 +133,7 @@ class test_cert(XMLRPC_test): with assert_raises(errors.NotFound): res = api.Command['cert_request'](csr, principal=self.service_princ) - def test_2_cert_add(self): + def test_0002_cert_add(self): """ Test the `xmlrpc.cert_request` method with --add. """ @@ -125,7 +146,7 @@ class test_cert(XMLRPC_test): # save the cert for the service_show/find tests cert = res['certificate'] - def test_3_service_show(self): + def test_0003_service_show(self): """ Verify that service-show has the right certificate using service-show. """ @@ -134,7 +155,7 @@ class test_cert(XMLRPC_test): res = api.Command['service_show'](self.service_princ)['result'] assert base64.b64encode(res['usercertificate'][0]) == cert - def test_4_service_find(self): + def test_0004_service_find(self): """ Verify that service-find has the right certificate using service-find. """ @@ -144,7 +165,7 @@ class test_cert(XMLRPC_test): res = api.Command['service_find'](self.service_princ)['result'] assert base64.b64encode(res[0]['usercertificate'][0]) == cert - def test_5_cert_renew(self): + def test_0005_cert_renew(self): """ Issue a new certificate for a service """ @@ -156,7 +177,7 @@ class test_cert(XMLRPC_test): # save the cert for the service_show/find tests newcert = res['certificate'] - def test_6_service_show(self): + def test_0006_service_show(self): """ Verify the new certificate with service-show. """ @@ -168,7 +189,7 @@ class test_cert(XMLRPC_test): # And it should match the new one assert base64.b64encode(res['usercertificate'][0]) == newcert - def test_7_cleanup(self): + def test_0007_cleanup(self): """ Clean up cert test data """ @@ -178,3 +199,256 @@ class test_cert(XMLRPC_test): # Verify that the service is gone res = api.Command['service_find'](self.service_princ) assert res['count'] == 0 + +class test_cert_find(XMLRPC_test): + + @classmethod + def setUpClass(cls): + super(test_cert_find, cls).setUpClass() + + if 'cert_find' not in api.Command: + raise nose.SkipTest('cert_find not registered') + + if api.env.ra_plugin != 'dogtag': + raise nose.SkipTest('cert_find for dogtag CA only') + + is_db_configured() + + """ + Test the `cert-find` command. + """ + short = api.env.host.replace('.' + api.env.domain, '') + + def test_0001_find_all(self): + """ + Search for all certificates. + + We don't know how many we'll get but there should be at least 10 + by default. + """ + res = api.Command['cert_find']() + assert 'count' in res and res['count'] >= 10 + + def test_0002_find_CA(self): + """ + Search for the CA certificate. + """ + res = api.Command['cert_find'](subject=u'Certificate Authority') + assert 'count' in res and res['count'] == 1 + + def test_0003_find_OCSP(self): + """ + Search for the OCSP certificate. + """ + res = api.Command['cert_find'](subject=u'OCSP Subsystem') + + def test_0004_find_this_host(self): + """ + Find all certificates for this IPA server + """ + res = api.Command['cert_find'](subject=api.env.host) + assert 'count' in res and res['count'] > 1 + + def test_0005_find_this_host_exact(self): + """ + Find all certificates for this IPA server (exact) + """ + res = api.Command['cert_find'](subject=api.env.host, exactly=True) + assert 'count' in res and res['count'] > 1 + + def test_0006_find_this_short_host_exact(self): + """ + Find all certificates for this IPA server short name (exact) + """ + res = api.Command['cert_find'](subject=self.short, exactly=True) + assert 'count' in res and res['count'] == 0 + + def test_0007_find_revocation_reason_0(self): + """ + Find all certificates with revocation reason 0 + """ + res = api.Command['cert_find'](revocation_reason=0) + assert 'count' in res and res['count'] == 0 + + def test_0008_find_revocation_reason_1(self): + """ + Find all certificates with revocation reason 1 + """ + res = api.Command['cert_find'](revocation_reason=1) + assert 'count' in res and res['count'] == 0 + + def test_0009_find_revocation_reason_2(self): + """ + Find all certificates with revocation reason 2 + """ + res = api.Command['cert_find'](revocation_reason=2) + assert 'count' in res and res['count'] == 0 + + def test_0010_find_revocation_reason_3(self): + """ + Find all certificates with revocation reason 3 + """ + res = api.Command['cert_find'](revocation_reason=3) + assert 'count' in res and res['count'] == 0 + + def test_0011_find_revocation_reason_4(self): + """ + Find all certificates with revocation reason 4 + + There is no way to know in advance how many revoked certificates + we'll have but in the context of make-test we'll have at least one. + """ + res = api.Command['cert_find'](revocation_reason=4) + assert 'count' in res and res['count'] >= 1 + + def test_0012_find_revocation_reason_5(self): + """ + Find all certificates with revocation reason 5 + """ + res = api.Command['cert_find'](revocation_reason=5) + assert 'count' in res and res['count'] == 0 + + def test_0013_find_revocation_reason_6(self): + """ + Find all certificates with revocation reason 6 + """ + res = api.Command['cert_find'](revocation_reason=6) + assert 'count' in res and res['count'] == 0 + + # There is no revocation reason #7 + + def test_0014_find_revocation_reason_8(self): + """ + Find all certificates with revocation reason 8 + """ + res = api.Command['cert_find'](revocation_reason=8) + assert 'count' in res and res['count'] == 0 + + def test_0015_find_revocation_reason_9(self): + """ + Find all certificates with revocation reason 9 + """ + res = api.Command['cert_find'](revocation_reason=9) + assert 'count' in res and res['count'] == 0 + + def test_0016_find_revocation_reason_10(self): + """ + Find all certificates with revocation reason 10 + """ + res = api.Command['cert_find'](revocation_reason=10) + assert 'count' in res and res['count'] == 0 + + def test_0017_find_by_issuedon(self): + """ + Find all certificates issued since 2008 + """ + res = api.Command['cert_find'](issuedon_from=u'2008-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 10 + + def test_0018_find_through_issuedon(self): + """ + Find all certificates issued through 2008 + """ + res = api.Command['cert_find'](issuedon_to=u'2008-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 0 + + def test_0019_find_notvalid_before(self): + """ + Find all certificates valid not before 2008 + """ + res = api.Command['cert_find'](validnotbefore_from=u'2008-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 10 + + def test_0020_find_notvalid_before(self): + """ + Find all certificates valid not before to 2100 + """ + res = api.Command['cert_find'](validnotbefore_to=u'2100-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 10 + + def test_0021_find_notvalid_before(self): + """ + Find all certificates valid not before 2100 + """ + res = api.Command['cert_find'](validnotbefore_from=u'2100-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 0 + + def test_0022_find_notvalid_before(self): + """ + Find all certificates valid not before to 2008 + """ + res = api.Command['cert_find'](validnotbefore_to=u'2008-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 0 + + def test_0023_find_notvalid_after(self): + """ + Find all certificates valid not after 2008 + """ + res = api.Command['cert_find'](validnotafter_from=u'2008-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 10 + + def test_0024_find_notvalid_after(self): + """ + Find all certificates valid not after to 2100 + """ + res = api.Command['cert_find'](validnotafter_to=u'2100-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 10 + + def test_0025_find_notvalid_after(self): + """ + Find all certificates valid not after 2100 + """ + res = api.Command['cert_find'](validnotafter_from=u'2100-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 0 + + def test_0026_find_notvalid_after(self): + """ + Find all certificates valid not after to 2008 + """ + res = api.Command['cert_find'](validnotafter_to=u'2008-01-01', + sizelimit=10) + assert 'count' in res and res['count'] == 0 + + def test_0027_sizelimit_zero(self): + """ + Search with a sizelimit of 0 + """ + res = api.Command['cert_find'](sizelimit=0) + assert 'count' in res and res['count'] == 0 + + @raises(errors.ValidationError) + def test_0028_find_negative_size(self): + """ + Search with a negative sizelimit + """ + res = api.Command['cert_find'](sizelimit=-100) + + def test_0029_search_for_notfound(self): + """ + Search for a host that isn't there. + """ + res = api.Command['cert_find'](subject=u'notfound') + assert 'count' in res and res['count'] == 0 + + def test_0030_search_for_testcerts(self): + """ + Search for certs created in other tests + """ + res = api.Command['cert_find'](subject=u'ipatestcert.%s' % api.env.domain) + assert 'count' in res and res['count'] >= 1 + + @raises(errors.ValidationError) + def test_0031_search_on_invalid_date(self): + """ + Search using invalid date format + """ + res = api.Command['cert_find'](issuedon_from=u'xyz') |