validators.py 5.22 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 6
#  Available flags for get (cert) request from clients
AVAIL_CERTS_FLAGS = {"renew"}

7
AVAIL_STATES = {"ok", "fail", "error"}
Martin Prudek's avatar
Martin Prudek committed
8

9
#  Params of auth session saved in Redis, general
Martin Prudek's avatar
Martin Prudek committed
10 11 12 13
SESSION_PARAMS = {
    "auth_type",
    "nonce",
    "digest",
14
    "action",
Martin Prudek's avatar
Martin Prudek committed
15 16
    "flags",
}
17 18 19 20 21 22
#  Params of auth session saved in Redis, action-specific
ACTION_SPEC_SESSION_PARAMS = {
    "certs": {"csr_str"},
}

#  Params of auth state saved in redis
Martin Prudek's avatar
Martin Prudek committed
23 24
AUTH_STATE_PARAMS = {
    "status",
25 26
    "message",
}
27 28

# Params of request send by clients, general
29 30 31 32 33 34
GENERAL_REQ_PARAMS = {
    "type",
    "auth_type",
    "sid",
    "sn",
}
35 36 37 38 39
# Params of request send by clients, get
GET_REQ_PARAMS = {
    "flags",
}
# Params of request send by clients, get - cert
40 41 42
GET_CERT_REQ_PARAMS = {
    "csr",
}
43
# Params of request send by clients, auth
44 45
AUTH_REQ_PARAMS = {
    "digest",
Martin Prudek's avatar
Martin Prudek committed
46 47 48 49 50
}


def validate_sn_atsha(sn):
    if len(sn) != 16:
Martin Prudek's avatar
Martin Prudek committed
51
        raise RequestConsistencyError("SN has invalid length.")
Martin Prudek's avatar
Martin Prudek committed
52
    if sn[0:5] != "00000":
Martin Prudek's avatar
Martin Prudek committed
53
        raise RequestConsistencyError("SN has invalid format.")
Martin Prudek's avatar
Martin Prudek committed
54 55 56
    try:
        sn_value = int(sn, 16)
    except ValueError:
Martin Prudek's avatar
Martin Prudek committed
57
        raise RequestConsistencyError("SN has invalid format.")
Martin Prudek's avatar
Martin Prudek committed
58
    if sn_value % 11 != 0:
Martin Prudek's avatar
Martin Prudek committed
59
        raise RequestConsistencyError("SN has invalid format.")
Martin Prudek's avatar
Martin Prudek committed
60 61 62 63 64 65 66 67 68 69


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
70
        raise RequestConsistencyError("CSR has not exactly one CommonName")
Martin Prudek's avatar
Martin Prudek committed
71 72 73

    common_name = common_names[0].value
    if common_name != identity:
Martin Prudek's avatar
Martin Prudek committed
74
        raise RequestConsistencyError("CSR CommonName ({}) does not match desired identity".format(common_name))
Martin Prudek's avatar
Martin Prudek committed
75 76 77 78 79


def validate_csr_hash(csr):
    h = csr.signature_hash_algorithm.name
    if h not in AVAIL_HASHES:
Martin Prudek's avatar
Martin Prudek committed
80
        raise RequestConsistencyError("CSR is signed with not allowed hash ({})".format(h))
Martin Prudek's avatar
Martin Prudek committed
81 82 83 84


def validate_csr_signature(csr):
    if not csr.is_signature_valid:
Martin Prudek's avatar
Martin Prudek committed
85
        raise RequestConsistencyError("Request signature is not valid")
Martin Prudek's avatar
Martin Prudek committed
86 87 88 89 90 91 92 93 94


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


95
def validate_certs_flags(flags):
Martin Prudek's avatar
Martin Prudek committed
96
    for flag in flags:
97
        if flag not in AVAIL_CERTS_FLAGS:
Martin Prudek's avatar
Martin Prudek committed
98
            raise RequestConsistencyError("Flag not available: {}".format(flag))
Martin Prudek's avatar
Martin Prudek committed
99 100 101 102 103 104


def validate_sid(sid):
    if sid == "":
        return
    if (len(sid) != 64 or not sid.islower()):
Martin Prudek's avatar
Martin Prudek committed
105
        raise RequestConsistencyError("Bad format of sid: {}".format(sid))
Martin Prudek's avatar
Martin Prudek committed
106 107 108
    try:
        sid = int(sid, 16)
    except ValueError:
Martin Prudek's avatar
Martin Prudek committed
109
        raise RequestConsistencyError("Bad format of sid: {}".format(sid))
Martin Prudek's avatar
Martin Prudek committed
110 111 112 113


def validate_digest(digest):
    if len(digest) != 64:
Martin Prudek's avatar
Martin Prudek committed
114
        raise RequestConsistencyError("Bad format of digest: {}".format(digest))
Martin Prudek's avatar
Martin Prudek committed
115 116 117
    try:
        digest = int(digest, 16)
    except ValueError:
Martin Prudek's avatar
Martin Prudek committed
118
        raise RequestConsistencyError("Bad format of digest: {}".format(digest))
Martin Prudek's avatar
Martin Prudek committed
119 120 121 122


def validate_auth_type(auth_type):
    if auth_type not in sn_validators:
Martin Prudek's avatar
Martin Prudek committed
123
        raise RequestConsistencyError("Invalid auth type: {}".format(auth_type))
Martin Prudek's avatar
Martin Prudek committed
124 125 126


def check_session(session):
127
    if type(session) is not dict:
Martin Prudek's avatar
Martin Prudek committed
128
        raise InvalidRedisDataError("Must be a dict!")
129
    for param in SESSION_PARAMS | ACTION_SPEC_SESSION_PARAMS.get(session.get("action"), set()):
Martin Prudek's avatar
Martin Prudek committed
130
        if param not in session:
131 132 133
            raise InvalidRedisDataError("Parameter {} missing in session".format(param))
    if session["action"] not in ACTION_SPEC_SESSION_PARAMS:
        raise InvalidRedisDataError("Invalid action '{}' in session".format(param))
Martin Prudek's avatar
Martin Prudek committed
134 135


136 137
def validate_auth_state(auth_state):
    if type(auth_state) is not dict:
Martin Prudek's avatar
Martin Prudek committed
138
        raise InvalidRedisDataError("Must be a dict!")
139 140
    for param in AUTH_STATE_PARAMS:
        if param not in auth_state:
Martin Prudek's avatar
Martin Prudek committed
141
            raise InvalidRedisDataError("Parameter {} missing".format(param))
142
    if auth_state["status"] not in AVAIL_STATES:
Martin Prudek's avatar
Martin Prudek committed
143
        raise InvalidRedisDataError("Invalid status '{}'".format(auth_state["status"]))
144 145 146


def check_params_exist(req, params):
147
    for param in params:
148
        if param not in req:
Martin Prudek's avatar
Martin Prudek committed
149
            raise RequestConsistencyError("'{}' is missing in the request".format(param))
150 151


152
def check_request(req, action):
153
    if type(req) is not dict:
Martin Prudek's avatar
Martin Prudek committed
154
        raise RequestConsistencyError("Request not a valid JSON with correct content type")
155 156 157 158 159 160
    check_params_exist(req, GENERAL_REQ_PARAMS)
    validate_auth_type(req["auth_type"])
    validate_sn = sn_validators[req["auth_type"]]
    validate_sn(req["sn"])
    validate_sid(req["sid"])

161 162 163 164 165 166
    if req["type"] == "get" or req["type"] == "get_cert":
        check_params_exist(req, GET_REQ_PARAMS)
        if action == "certs":
            check_params_exist(req, GET_CERT_REQ_PARAMS)
            validate_csr(req["csr"], req["sn"])
            validate_certs_flags(req["flags"])
167

168 169
            if "renew" in req["flags"] and req["sid"]:
                raise RequestConsistencyError("Renew allowed only in the first request")
170 171

    elif req["type"] == "auth":
172
        check_params_exist(req, AUTH_REQ_PARAMS)
173
        validate_digest(req["digest"])
174 175 176

    else:
        raise RequestConsistencyError("Invalid request type: {}".format(req["type"]))