validators.py 4.56 KB
Newer Older
1
from .crypto import AVAIL_HASHES, get_common_names, csr_from_str
Martin Prudek's avatar
Martin Prudek committed
2
from .exceptions import RequestConsistencyError, InvalidRedisDataError
Martin Prudek's avatar
Martin Prudek committed
3 4 5

AVAIL_REQUEST_TYPES = {"get_cert", "auth"}
AVAIL_FLAGS = {"renew"}
6
AVAIL_STATES = {"ok", "fail", "error"}
Martin Prudek's avatar
Martin Prudek committed
7 8 9 10 11 12 13 14 15 16

SESSION_PARAMS = {
    "auth_type",
    "nonce",
    "digest",
    "csr_str",
    "flags",
}
AUTH_STATE_PARAMS = {
    "status",
17 18 19 20 21 22 23 24 25 26 27 28 29 30
    "message",
}
GENERAL_REQ_PARAMS = {
    "type",
    "auth_type",
    "sid",
    "sn",
}
GET_CERT_REQ_PARAMS = {
    "csr",
    "flags",
}
AUTH_REQ_PARAMS = {
    "digest",
Martin Prudek's avatar
Martin Prudek committed
31 32 33 34 35
}


def validate_sn_atsha(sn):
    if len(sn) != 16:
Martin Prudek's avatar
Martin Prudek committed
36
        raise RequestConsistencyError("SN has invalid length.")
Martin Prudek's avatar
Martin Prudek committed
37
    if sn[0:5] != "00000":
Martin Prudek's avatar
Martin Prudek committed
38
        raise RequestConsistencyError("SN has invalid format.")
Martin Prudek's avatar
Martin Prudek committed
39 40 41
    try:
        sn_value = int(sn, 16)
    except ValueError:
Martin Prudek's avatar
Martin Prudek committed
42
        raise RequestConsistencyError("SN has invalid format.")
Martin Prudek's avatar
Martin Prudek committed
43
    if sn_value % 11 != 0:
Martin Prudek's avatar
Martin Prudek committed
44
        raise RequestConsistencyError("SN has invalid format.")
Martin Prudek's avatar
Martin Prudek committed
45 46 47 48 49 50 51 52 53 54


sn_validators = {
    "atsha204": validate_sn_atsha,
}


def validate_csr_common_name(csr, identity):
    common_names = get_common_names(csr)
    if len(common_names) != 1:
Martin Prudek's avatar
Martin Prudek committed
55
        raise RequestConsistencyError("CSR has not exactly one CommonName")
Martin Prudek's avatar
Martin Prudek committed
56 57 58

    common_name = common_names[0].value
    if common_name != identity:
Martin Prudek's avatar
Martin Prudek committed
59
        raise RequestConsistencyError("CSR CommonName ({}) does not match desired identity".format(common_name))
Martin Prudek's avatar
Martin Prudek committed
60 61 62 63 64


def validate_csr_hash(csr):
    h = csr.signature_hash_algorithm.name
    if h not in AVAIL_HASHES:
Martin Prudek's avatar
Martin Prudek committed
65
        raise RequestConsistencyError("CSR is signed with not allowed hash ({})".format(h))
Martin Prudek's avatar
Martin Prudek committed
66 67 68 69


def validate_csr_signature(csr):
    if not csr.is_signature_valid:
Martin Prudek's avatar
Martin Prudek committed
70
        raise RequestConsistencyError("Request signature is not valid")
Martin Prudek's avatar
Martin Prudek committed
71 72 73 74 75 76 77 78 79 80 81 82


def validate_csr(csr, sn):
    csr = csr_from_str(csr)
    validate_csr_common_name(csr, sn)
    validate_csr_hash(csr)
    validate_csr_signature(csr)


def validate_flags(flags):
    for flag in flags:
        if flag not in AVAIL_FLAGS:
Martin Prudek's avatar
Martin Prudek committed
83
            raise RequestConsistencyError("Flag not available: {}".format(flag))
Martin Prudek's avatar
Martin Prudek committed
84 85 86 87


def validate_req_type(req_type):
    if req_type not in AVAIL_REQUEST_TYPES:
Martin Prudek's avatar
Martin Prudek committed
88
        raise RequestConsistencyError("Invalid request type: {}".format(req_type))
Martin Prudek's avatar
Martin Prudek committed
89 90 91 92 93 94


def validate_sid(sid):
    if sid == "":
        return
    if (len(sid) != 64 or not sid.islower()):
Martin Prudek's avatar
Martin Prudek committed
95
        raise RequestConsistencyError("Bad format of sid: {}".format(sid))
Martin Prudek's avatar
Martin Prudek committed
96 97 98
    try:
        sid = int(sid, 16)
    except ValueError:
Martin Prudek's avatar
Martin Prudek committed
99
        raise RequestConsistencyError("Bad format of sid: {}".format(sid))
Martin Prudek's avatar
Martin Prudek committed
100 101 102 103


def validate_digest(digest):
    if len(digest) != 64:
Martin Prudek's avatar
Martin Prudek committed
104
        raise RequestConsistencyError("Bad format of digest: {}".format(digest))
Martin Prudek's avatar
Martin Prudek committed
105 106 107
    try:
        digest = int(digest, 16)
    except ValueError:
Martin Prudek's avatar
Martin Prudek committed
108
        raise RequestConsistencyError("Bad format of digest: {}".format(digest))
Martin Prudek's avatar
Martin Prudek committed
109 110 111 112


def validate_auth_type(auth_type):
    if auth_type not in sn_validators:
Martin Prudek's avatar
Martin Prudek committed
113
        raise RequestConsistencyError("Invalid auth type: {}".format(auth_type))
Martin Prudek's avatar
Martin Prudek committed
114 115 116


def check_session(session):
117
    if type(session) is not dict:
Martin Prudek's avatar
Martin Prudek committed
118
        raise InvalidRedisDataError("Must be a dict!")
Martin Prudek's avatar
Martin Prudek committed
119 120
    for param in SESSION_PARAMS:
        if param not in session:
Martin Prudek's avatar
Martin Prudek committed
121
            raise InvalidRedisDataError("Parameter {} missing".format(param))
Martin Prudek's avatar
Martin Prudek committed
122 123


124 125
def validate_auth_state(auth_state):
    if type(auth_state) is not dict:
Martin Prudek's avatar
Martin Prudek committed
126
        raise InvalidRedisDataError("Must be a dict!")
127 128
    for param in AUTH_STATE_PARAMS:
        if param not in auth_state:
Martin Prudek's avatar
Martin Prudek committed
129
            raise InvalidRedisDataError("Parameter {} missing".format(param))
130
    if auth_state["status"] not in AVAIL_STATES:
Martin Prudek's avatar
Martin Prudek committed
131
        raise InvalidRedisDataError("Invalid status '{}'".format(auth_state["status"]))
132 133 134 135 136


def check_params_exist(req, params):
    for param in GENERAL_REQ_PARAMS:
        if param not in req:
Martin Prudek's avatar
Martin Prudek committed
137
            raise RequestConsistencyError("'{}' is missing in the request".format(param))
138 139 140 141


def check_request(req):
    if type(req) is not dict:
Martin Prudek's avatar
Martin Prudek committed
142
        raise RequestConsistencyError("Request not a valid JSON with correct content type")
143 144 145 146 147 148 149 150 151 152 153 154 155
    check_params_exist(req, GENERAL_REQ_PARAMS)
    validate_req_type(req["type"])
    validate_auth_type(req["auth_type"])
    validate_sn = sn_validators[req["auth_type"]]
    validate_sn(req["sn"])
    validate_sid(req["sid"])

    if req["type"] == "get_cert":
        check_params_exist(req, AUTH_REQ_PARAMS)
        validate_csr(req["csr"], req["sn"])
        validate_flags(req["flags"])

        if "renew" in req["flags"] and req["sid"]:
Martin Prudek's avatar
Martin Prudek committed
156
            raise RequestConsistencyError("Renew allowed only in the first request")
157 158 159 160

    elif req["type"] == "auth":
        check_params_exist(req, GET_CERT_REQ_PARAMS)
        validate_digest(req["digest"])