<reponame>weltonrodrigo/origin
from ct.crypto import cert
from ct.crypto.asn1 import oid
from ct.cert_analysis.observation import Observation

# From BR v1.2.3
# (extension_oid, critical?), if crtical is None then it means it wasn't
# specified in BR.
_CA_EXTENSIONS = [(oid.ID_CE_BASIC_CONSTRAINTS, True),
                  (oid.ID_CE_KEY_USAGE, True),]


_CA_PROHIBITED_EXTENSIONS = [oid.ID_CE_CERTIFICATE_POLICIES,
                             oid.ID_CE_EXT_KEY_USAGE,]


_SUBORDINATE_EXTENSIONS = [(oid.ID_CE_CERTIFICATE_POLICIES, False),
                           (oid.ID_CE_CRL_DISTRIBUTION_POINTS, False),
                           (oid.ID_PE_AUTHORITY_INFO_ACCESS, False),
                           (oid.ID_CE_BASIC_CONSTRAINTS, True),
                           (oid.ID_CE_KEY_USAGE, True),]


_SUBORDINATE_OPTIONAL = [(oid.ID_CE_NAME_CONSTRAINTS, True),
                         (oid.ID_CE_EXT_KEY_USAGE, False),]


_SUBSCRIBER_EXTENSIONS = [(oid.ID_CE_CERTIFICATE_POLICIES, False),
                          (oid.ID_PE_AUTHORITY_INFO_ACCESS, False),
                          (oid.ID_CE_EXT_KEY_USAGE, None),]


_SUBSCRIBER_OPTIONAL = [(oid.ID_CE_CRL_DISTRIBUTION_POINTS, False),
                        (oid.ID_CE_BASIC_CONSTRAINTS, None),
                        (oid.ID_CE_KEY_USAGE, None),]


_SUBSCRIBER = 0
_SUBORDINATE = 1
_ROOT = 2


class ExtensionObservation(Observation):
    def __init__(self, description, cert_type, *args, **kwargs):
        super(ExtensionObservation, self).__init__(
                "Extensions: %s - %s" % (self._type_to_string(cert_type),
                                         description) , *args, **kwargs)

    @staticmethod
    def _type_to_string(type_):
        if type_ == _SUBSCRIBER:
            return "subscriber certificate"
        elif type_ == _SUBORDINATE:
            return "subordinate certificate"
        elif type_ == _ROOT:
            return "root certificate"


class LackOfRequiredExtension(ExtensionObservation):
    def __init__(self, cert_type, reason):
        """Required extension is missing.

        Args:
            cert_type: certificate type, one of _ROOT, _SUBORDINATE, _SUBSCRIBER
            reason:    string representing extension OID
        """
        super(LackOfRequiredExtension, self).__init__(
                "required extension is missing", cert_type, reason)

class ContainsProhibitedExtension(ExtensionObservation):
    def __init__(self, cert_type, reason):
        """Contains prohibited extension.

        Args:
            cert_type: certificate type, one of _ROOT, _SUBORDINATE, _SUBSCRIBER
            reason:    string representing extension OID
        """
        super(ContainsProhibitedExtension, self).__init__(
                "contains prohibited extension", cert_type, reason)


class WrongCritical(ExtensionObservation):
    def __init__(self, cert_type, reason, details):
        """Critical field is set to wrong value.

        Args:
            cert_type: certificate type, one of _ROOT, _SUBORDINATE, _SUBSCRIBER
            reason:    string representing extension OID
            details:   boolean representing extension critical field
        """
        super(WrongCritical, self).__init__(
                "certificate extension 'critical' field is set to wrong value",
                cert_type, reason, details)

        def _format_details():
            return "%s instead of %s" % (self.details, not self.details)

def _oid_to_string(oid_):
    return '.'.join([str(val) for val in oid_.value])


class CheckCorrectExtensions(object):
    @classmethod
    def check(cls, certificate):
        required = _SUBSCRIBER_EXTENSIONS
        optional = _SUBSCRIBER_OPTIONAL
        prohibited = []
        cert_type = _SUBSCRIBER
        # Is it root certificate?
        if certificate.is_self_signed():
            required = _CA_EXTENSIONS
            optional = []
            prohibited = _CA_PROHIBITED_EXTENSIONS
            cert_type = _ROOT
        else:
            # Is it subordinate certificate?
            try:
                ca = certificate.basic_constraint_ca()
            except cert.CertificateError:
                # if it fails let's assume that it is leaf cert
                pass
            else:
                if ca and ca.value == True:
                    required = _SUBORDINATE_EXTENSIONS
                    optional = _SUBORDINATE_OPTIONAL
                    cert_type = _SUBORDINATE

        observations = []
        extensions = certificate.get_extensions()
        oid_crit = {ex['extnID']: ex['critical'] for ex in extensions}
        for req in required:
            crit = oid_crit.get(req[0], None)
            if crit == None:
                observations += [LackOfRequiredExtension(cert_type,
                                                         _oid_to_string(req[0]))]
            elif crit != req[1] and req[1] != None:
                observations += [WrongCritical(cert_type, _oid_to_string(req[0]),
                                               crit)]
        for opt in optional:
            if opt[1] == None:
                continue
            crit = oid_crit.get(opt[0], None)
            # if certificate contains optional extension, check if it has
            # correct critical.
            if crit != None and crit != opt[1]:
                observations += [WrongCritical(cert_type, _oid_to_string(opt[0]), crit)]
        for proh in prohibited:
            crit = oid_crit.get(proh, None)
            if crit != None:
                observations += [ContainsProhibitedExtension(cert_type,
                                                             _oid_to_string(proh))]
        return observations
