Example #1
0
 def test_duration2timedelta(self):
     for expr, secs in TestDuration.DURATIONS:
         td = duration2timedelta(expr)
         print "timedelta: %s" % td
         print "duration: %s" % expr
         print "expected seconds: %s" % secs
         assert(int(td.total_seconds()) == secs)
         assert(int(total_seconds(td)) == secs)
Example #2
0
 def test_duration2timedelta(self):
     for expr, secs in TestDuration.DURATIONS:
         td = duration2timedelta(expr)
         print("timedelta: %s" % td)
         print("duration: %s" % expr)
         print("expected seconds: %s" % secs)
         assert(int(td.total_seconds()) == secs)
         assert(int(total_seconds(td)) == secs)
Example #3
0
def certreport(req, *opts):
    """
Generate a report of the certificates (optionally limited by expiration time or key size) found in the selection.

:param req: The request
:param opts: Options (not used)
:return: always returns the unmodified working document

**Examples**

.. code-block:: yaml

    - certreport:
         error_seconds: 0
         warning_seconds: 864000
         error_bits: 1024
         warning_bits: 2048

For key size checking this will report keys with a size *less* than the size specified, defaulting to errors
for keys smaller than 1024 bits and warnings for keys smaller than 2048 bits. It should be understood as the
minimum key size for each report level, as such everything below will create report entries.

Remember that you need a 'publish' or 'emit' call after certreport in your plumbing to get useful output. PyFF
ships with a couple of xslt transforms that are useful for turning metadata with certreport annotation into
HTML.
    """

    if req.t is None:
        raise PipeException("Your pipeline is missing a select statement.")

    if not req.args:
        req.args = {}

    if type(req.args) is not dict:
        raise PipeException("usage: certreport {warning: 864000, error: 0}")

    error_seconds = int(req.args.get('error_seconds', "0"))
    warning_seconds = int(req.args.get('warning_seconds', "864000"))
    error_bits = int(req.args.get('error_bits', "1024"))
    warning_bits = int(req.args.get('warning_bits', "2048"))

    seen = {}
    for eid in req.t.xpath("//md:EntityDescriptor/@entityID",
                           namespaces=NS,
                           smart_strings=False):
        for cd in req.t.xpath("md:EntityDescriptor[@entityID='%s']//ds:X509Certificate" % eid,
                              namespaces=NS,
                              smart_strings=False):
            try:
                cert_pem = cd.text
                cert_der = base64.b64decode(cert_pem)
                m = hashlib.sha1()
                m.update(cert_der)
                fp = m.hexdigest()
                if not seen.get(fp, False):
                    seen[fp] = True
                    cdict = xmlsec.utils.b642cert(cert_pem)
                    keysize = cdict['modulus'].bit_length()
                    cert = cdict['cert']
                    if keysize < error_bits:
                        e = cd.getparent().getparent().getparent().getparent().getparent()
                        req.md.annotate(e,
                                        "certificate-error",
                                        "keysize too small",
                                        "%s has keysize of %s bits (less than %s)" % (cert.getSubject(),
                                                                                      keysize,
                                                                                      error_bits))
                        log.error("%s has keysize of %s" % (eid, keysize))
                    elif keysize < warning_bits:
                        e = cd.getparent().getparent().getparent().getparent().getparent()
                        req.md.annotate(e,
                                        "certificate-warning",
                                        "keysize small",
                                        "%s has keysize of %s bits (less than %s)" % (cert.getSubject(),
                                                                                      keysize,
                                                                                      warning_bits))
                        log.warn("%s has keysize of %s" % (eid, keysize))
                    et = datetime.strptime("%s" % cert.getNotAfter(), "%y%m%d%H%M%SZ")
                    now = datetime.now()
                    dt = et - now
                    if total_seconds(dt) < error_seconds:
                        e = cd.getparent().getparent().getparent().getparent().getparent()
                        req.md.annotate(e,
                                        "certificate-error",
                                        "certificate has expired",
                                        "%s expired %s ago" % (cert.getSubject(), -dt))
                        log.error("%s expired %s ago" % (eid, -dt))
                    elif total_seconds(dt) < warning_seconds:
                        e = cd.getparent().getparent().getparent().getparent().getparent()
                        req.md.annotate(e,
                                        "certificate-warning",
                                        "certificate about to expire",
                                        "%s expires in %s" % (cert.getSubject(), dt))
                        log.warn("%s expires in %s" % (eid, dt))
            except Exception, ex:
                log.error(ex)
Example #4
0
                e.set('validUntil', dt.strftime("%Y-%m-%dT%H:%M:%SZ"))
            except ValueError, ex:
                log.error("Unable to parse validUntil: %s (%s)" % (valid_until, ex))

            # set a reasonable default: 50% of the validity
        # we replace this below if we have cacheDuration set
        req.state['cache'] = int(total_seconds(offset) / 50)

    cache_duration = req.args.get('cacheDuration', e.get('cacheDuration', None))
    if cache_duration is not None and len(cache_duration) > 0:
        offset = duration2timedelta(cache_duration)
        if offset is None:
            raise PipeException("Unable to parse %s as xs:duration" % cache_duration)

        e.set('cacheDuration', cache_duration)
        req.state['cache'] = int(total_seconds(offset))

    return req.t

@pipe(name='reginfo')
def _reginfo(req, *opts):
    """
Sets registration info extension on EntityDescription element

:param req: The request
:param opts: Options (not used)
:return: A modified working document

Transforms the working document by setting the specified attribute on all of the EntityDescriptor
elements of the active document.
Example #5
0
def finalize(req, *opts):
    """
Prepares the working document for publication/rendering.

:param req: The request
:param opts: Options (not used)
:return: returns the working document with @Name, @cacheDuration and @validUntil set

Set Name, ID, cacheDuration and validUntil on the toplevel EntitiesDescriptor element of the working document. Unless
explicit provided the @Name is set from the request URI if the pipeline is executed in the pyFF server. The @ID is set
to a string representing the current date/time and will be prefixed with the string provided, which defaults to '_'. The
@cacheDuration element must be a valid xsd duration (eg PT5H for 5 hrs) and @validUntil can be either an absolute
ISO 8601 time string or (more comonly) a relative time on the form

.. code-block:: none

    \+?([0-9]+d)?\s*([0-9]+h)?\s*([0-9]+m)?\s*([0-9]+s)?


For instance +45d 2m results in a time delta of 45 days and 2 minutes. The '+' sign is optional.

If operating on a single EntityDescriptor then @Name is ignored (cf :py:mod:`pyff.pipes.builtins.first`).

**Examples**

.. code-block:: yaml

    - finalize:
        cacheDuration: PT8H
        validUntil: +10d
        ID: pyff
    """
    if req.t is None:
        raise PipeException("Your plumbing is missing a select statement.")

    e = root(req.t)
    if e.tag == "{%s}EntitiesDescriptor" % NS['md']:
        name = req.args.get('name', None)
        if name is None or not len(name):
            name = req.args.get('Name', None)
        if name is None or not len(name):
            name = req.state.get('url', None)
        if name is None or not len(name):
            name = e.get('Name', None)
        if name is not None and len(name):
            e.set('Name', name)

    now = datetime.utcnow()

    mdid = req.args.get('ID', 'prefix _')
    if re.match('(\s)*prefix(\s)*', mdid):
        prefix = re.sub('^(\s)*prefix(\s)*', '', mdid)
        ID = now.strftime(prefix + "%Y%m%dT%H%M%SZ")
    else:
        ID = mdid

    if not e.get('ID'):
        e.set('ID', ID)

    valid_until = str(req.args.get('validUntil', e.get('validUntil', None)))
    if valid_until is not None and len(valid_until) > 0:
        offset = duration2timedelta(valid_until)
        if offset is not None:
            dt = now + offset
            e.set('validUntil', dt.strftime("%Y-%m-%dT%H:%M:%SZ"))
        elif valid_until is not None:
            try:
                dt = iso8601.parse_date(valid_until)
                dt = dt.replace(tzinfo=None) # make dt "naive" (tz-unaware)
                offset = dt - now
                e.set('validUntil', dt.strftime("%Y-%m-%dT%H:%M:%SZ"))
            except ValueError, ex:
                log.error("Unable to parse validUntil: %s (%s)" % (valid_until, ex))

            # set a reasonable default: 50% of the validity
        # we replace this below if we have cacheDuration set
        req.state['cache'] = int(total_seconds(offset) / 50)
Example #6
0
def finalize(req, *opts):
    """
Prepares the working document for publication/rendering.

:param req: The request
:param opts: Options (not used)
:return: returns the working document with @Name, @cacheDuration and @validUntil set

Set Name, cacheDuration and validUntil on the toplevel EntitiesDescriptor element of the working document. Unless
explicit provided the @Name is set from the request URI if the pipeline is executed in the pyFF server. The
@cacheDuration element must be a valid xsd duration (eg PT5H for 5 hrs) and @validUntil can be either an absolute
ISO 8601 time string or (more comonly) a relative time on the form

.. code-block:: none

    \+?([0-9]+d)?\s*([0-9]+h)?\s*([0-9]+m)?\s*([0-9]+s)?


For instance +45d 2m results in a time delta of 45 days and 2 minutes. The '+' sign is optional.

If operating on a single EntityDescriptor then @Name is ignored (cf :py:mod:`pyff.pipes.builtins.first`).

**Examples**

.. code-block:: yaml

    - finalize:
        cacheDuration: PT8H
        validUntil: +10d
    """
    if req.t is None:
        raise PipeException("Your plumbing is missing a select statement.")

    e = root(req.t)
    if e.tag == "{%s}EntitiesDescriptor" % NS['md']:
        name = req.args.get('name', None)
        if name is None or not len(name):
            name = req.args.get('Name', None)
        if name is None or not len(name):
            name = req.state.get('url', None)
        if name is None or not len(name):
            name = e.get('Name', None)
        if name is not None and len(name):
            e.set('Name', name)

    if not e.get('ID'):
        e.set('ID', datetime.now().strftime("pyff%Y%m%dT%H%M%S%Z"))

    validUntil = req.args.get('validUntil', e.get('validUntil', None))
    if validUntil is not None and len(validUntil) > 0:
        offset = duration2timedelta(validUntil)
        if offset is not None:
            dt = datetime.now() + offset
            e.set('validUntil', dt.isoformat())
        elif validUntil is not None:
            dt = iso8601.parse_date(validUntil)
            offset = dt - datetime.now()
            # set a reasonable default: 50% of the validity
        # we replace this below if we have cacheDuration set
        req.state['cache'] = int(total_seconds(offset) / 50)

    cacheDuration = req.args.get('cacheDuration', e.get('cacheDuration', None))
    if cacheDuration is not None and len(cacheDuration) > 0:
        offset = duration2timedelta(cacheDuration)
        if offset is None:
            raise PipeException("Unable to parse %s as xs:duration" % cacheDuration)

        e.set('cacheDuration', cacheDuration)
        req.state['cache'] = int(total_seconds(offset))

    return req.t
Example #7
0
def certreport(req, *opts):
    """
Generate a report of the certificates (optionally limited by expiration time) found in the selection.

:param req: The request
:param opts: Options (not used)
:return: always returns the unmodified working document

**Examples**

.. code-block:: yaml

    - certreport:
         error_seconds: 0
         warning_seconds: 864000

Remember that you need a 'publish' or 'emit' call after certreport in your plumbing to get useful output. PyFF
ships with a couple of xslt transforms that are useful for turning metadata with certreport annotation into
HTML.
    """

    if req.t is None:
        raise PipeException("Your plumbing is missing a select statement.")

    if not req.args:
        req.args = {}

    if type(req.args) is not dict:
        raise PipeException("usage: certreport {warning: 864000, error: 0}")

    error_seconds = int(req.args.get('error', "0"))
    warning_seconds = int(req.args.get('warning', "864000"))

    seen = {}
    for eid in req.t.xpath("//md:EntityDescriptor/@entityID", namespaces=NS):
        for cd in req.t.xpath("md:EntityDescriptor[@entityID='%s']//ds:X509Certificate" % eid, namespaces=NS):
            try:
                cert_pem = cd.text
                cert_der = base64.b64decode(cert_pem)
                m = hashlib.sha1()
                m.update(cert_der)
                fp = m.hexdigest()
                if not seen.get(fp, False):
                    seen[fp] = True
                    cdict = xmlsec.b642cert(cert_pem)
                    cert = cdict['cert']
                    et = datetime.strptime("%s" % cert.getNotAfter(), "%Y%m%d%H%M%SZ")
                    now = datetime.now()
                    dt = et - now
                    if total_seconds(dt) < error_seconds:
                        e = cd.getparent().getparent().getparent().getparent().getparent()
                        req.md.annotate(e, "certificate-error", "certificate has expired",
                                        "%s expired %s ago" % (cert.getSubject(), -dt))
                        log.error("%s expired %s ago" % (eid, -dt))
                    elif total_seconds(dt) < warning_seconds:
                        e = cd.getparent().getparent().getparent().getparent().getparent()
                        req.md.annotate(e, "certificate-warning", "certificate about to expire",
                                        "%s expires in %s" % (cert.getSubject(), dt))
                        log.warn("%s expires in %s" % (eid, dt))
            except Exception, ex:
                log.error(ex)
Example #8
0
def certreport(req, *opts):
    """
Generate a report of the certificates (optionally limited by expiration time or key size) found in the selection.

:param req: The request
:param opts: Options (not used)
:return: always returns the unmodified working document

**Examples**

.. code-block:: yaml

    - certreport:
         error_seconds: 0
         warning_seconds: 864000
         error_bits: 1024
         warning_bits: 2048

For key size checking this will report keys with a size *less* than the size specified, defaulting to errors
for keys smaller than 1024 bits and warnings for keys smaller than 2048 bits. It should be understood as the
minimum key size for each report level, as such everything below will create report entries.

Remember that you need a 'publish' or 'emit' call after certreport in your plumbing to get useful output. PyFF
ships with a couple of xslt transforms that are useful for turning metadata with certreport annotation into
HTML.
    """

    if req.t is None:
        raise PipeException("Your pipeline is missing a select statement.")

    if not req.args:
        req.args = {}

    if type(req.args) is not dict:
        raise PipeException("usage: certreport {warning: 864000, error: 0}")

    error_seconds = int(req.args.get('error_seconds', "0"))
    warning_seconds = int(req.args.get('warning_seconds', "864000"))
    error_bits = int(req.args.get('error_bits', "1024"))
    warning_bits = int(req.args.get('warning_bits', "2048"))

    seen = {}
    for eid in req.t.xpath("//md:EntityDescriptor/@entityID",
                           namespaces=NS,
                           smart_strings=False):
        for cd in req.t.xpath(
                "md:EntityDescriptor[@entityID='%s']//ds:X509Certificate" %
                eid,
                namespaces=NS,
                smart_strings=False):
            try:
                cert_pem = cd.text
                cert_der = base64.b64decode(cert_pem)
                m = hashlib.sha1()
                m.update(cert_der)
                fp = m.hexdigest()
                if not seen.get(fp, False):
                    entity_elt = cd.getparent().getparent().getparent(
                    ).getparent().getparent()
                    seen[fp] = True
                    cdict = xmlsec.utils.b642cert(cert_pem)
                    keysize = cdict['modulus'].bit_length()
                    cert = cdict['cert']
                    if keysize < error_bits:
                        req.md.annotate(
                            entity_elt, "certificate-error",
                            "keysize too small",
                            "%s has keysize of %s bits (less than %s)" %
                            (cert.getSubject(), keysize, error_bits))
                        log.error("%s has keysize of %s" % (eid, keysize))
                    elif keysize < warning_bits:
                        req.md.annotate(
                            entity_elt, "certificate-warning", "keysize small",
                            "%s has keysize of %s bits (less than %s)" %
                            (cert.getSubject(), keysize, warning_bits))
                        log.warn("%s has keysize of %s" % (eid, keysize))

                    notafter = cert.getNotAfter()
                    if notafter is None:
                        req.md.annotate(
                            entity_elt, "certificate-error",
                            "certificate has no expiration time",
                            "%s has no expiration time" % cert.getSubject())
                    else:
                        try:
                            et = datetime.strptime("%s" % notafter,
                                                   "%y%m%d%H%M%SZ")
                            now = datetime.now()
                            dt = et - now
                            if total_seconds(dt) < error_seconds:
                                req.md.annotate(
                                    entity_elt, "certificate-error",
                                    "certificate has expired",
                                    "%s expired %s ago" %
                                    (cert.getSubject(), -dt))
                                log.error("%s expired %s ago" % (eid, -dt))
                            elif total_seconds(dt) < warning_seconds:
                                req.md.annotate(
                                    entity_elt, "certificate-warning",
                                    "certificate about to expire",
                                    "%s expires in %s" %
                                    (cert.getSubject(), dt))
                                log.warn("%s expires in %s" % (eid, dt))
                        except ValueError as ex:
                            req.md.annotate(
                                entity_elt, "certificate-error",
                                "certificate has unknown expiration time",
                                "%s unknown expiration time %s" %
                                (cert.getSubject(), notafter))

            except Exception as ex:
                log.error(ex)
Example #9
0
def finalize(req, *opts):
    """
Prepares the working document for publication/rendering.

:param req: The request
:param opts: Options (not used)
:return: returns the working document with @Name, @cacheDuration and @validUntil set

Set Name, ID, cacheDuration and validUntil on the toplevel EntitiesDescriptor element of the working document. Unless
explicit provided the @Name is set from the request URI if the pipeline is executed in the pyFF server. The @ID is set
to a string representing the current date/time and will be prefixed with the string provided, which defaults to '_'. The
@cacheDuration element must be a valid xsd duration (eg PT5H for 5 hrs) and @validUntil can be either an absolute
ISO 8601 time string or (more comonly) a relative time on the form

.. code-block:: none

    \+?([0-9]+d)?\s*([0-9]+h)?\s*([0-9]+m)?\s*([0-9]+s)?


For instance +45d 2m results in a time delta of 45 days and 2 minutes. The '+' sign is optional.

If operating on a single EntityDescriptor then @Name is ignored (cf :py:mod:`pyff.pipes.builtins.first`).

**Examples**

.. code-block:: yaml

    - finalize:
        cacheDuration: PT8H
        validUntil: +10d
        ID: pyff
    """
    if req.t is None:
        raise PipeException("Your plumbing is missing a select statement.")

    e = root(req.t)
    if e.tag == "{%s}EntitiesDescriptor" % NS['md']:
        name = req.args.get('name', None)
        if name is None or not len(name):
            name = req.args.get('Name', None)
        if name is None or not len(name):
            name = req.state.get('url', None)
        if name is None or not len(name):
            name = e.get('Name', None)
        if name is not None and len(name):
            e.set('Name', name)

    now = datetime.utcnow()

    mdid = req.args.get('ID', 'prefix _')
    if re.match('(\s)*prefix(\s)*', mdid):
        prefix = re.sub('^(\s)*prefix(\s)*', '', mdid)
        ID = now.strftime(prefix + "%Y%m%dT%H%M%SZ")
    else:
        ID = mdid

    if not e.get('ID'):
        e.set('ID', ID)

    valid_until = str(req.args.get('validUntil', e.get('validUntil', None)))
    if valid_until is not None and len(valid_until) > 0:
        offset = duration2timedelta(valid_until)
        if offset is not None:
            dt = now + offset
            e.set('validUntil', dt.strftime("%Y-%m-%dT%H:%M:%SZ"))
        elif valid_until is not None:
            try:
                dt = iso8601.parse_date(valid_until)
                dt = dt.replace(tzinfo=None)  # make dt "naive" (tz-unaware)
                offset = dt - now
                e.set('validUntil', dt.strftime("%Y-%m-%dT%H:%M:%SZ"))
            except ValueError, ex:
                log.error("Unable to parse validUntil: %s (%s)" %
                          (valid_until, ex))

                # set a reasonable default: 50% of the validity
        # we replace this below if we have cacheDuration set
        req.state['cache'] = int(total_seconds(offset) / 50)
Example #10
0
                          (valid_until, ex))

                # set a reasonable default: 50% of the validity
        # we replace this below if we have cacheDuration set
        req.state['cache'] = int(total_seconds(offset) / 50)

    cache_duration = req.args.get('cacheDuration',
                                  e.get('cacheDuration', None))
    if cache_duration is not None and len(cache_duration) > 0:
        offset = duration2timedelta(cache_duration)
        if offset is None:
            raise PipeException("Unable to parse %s as xs:duration" %
                                cache_duration)

        e.set('cacheDuration', cache_duration)
        req.state['cache'] = int(total_seconds(offset))

    return req.t


@pipe(name='reginfo')
def _reginfo(req, *opts):
    """
Sets registration info extension on EntityDescription element

:param req: The request
:param opts: Options (not used)
:return: A modified working document

Transforms the working document by setting the specified attribute on all of the EntityDescriptor
elements of the active document.