blob: c4d431d757f2a9ddbf65499491f9fbe6b6ca8093 [file] [log] [blame]
/*
* Copyright 2021 The HIBA Authors
*
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file or at
* https://developers.google.com/open-source/licenses/bsd
*/
#include <fnmatch.h>
#include <inttypes.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "checks.h"
#include "config.h"
#include "errors.h"
#include "extensions.h"
#include "log.h"
#define _HIBA_CHECK_SUCCESS "accepted"
#define _HIBA_CHECK_FAILURE "refused"
#define _HIBA_CHECK_FATAL "fatal"
/* The hibaenv structure is only suppose to live for the time of a check:
* 1. constuct the hibaenv.
* 2. run the checks against N certificates.
* 3. delete he hibaenv. */
struct hibaenv {
/* Host */
u_int64_t now;
char hostname[HOST_NAME_MAX];
const struct hibaext *identity;
const struct hibagrl *grl;
/* Certificate */
u_int64_t cert_issue_ts;
u_int64_t cert_serial;
/* Grant */
u_int32_t nprincipals;
char **principals;
/* Extension */
u_int32_t version;
u_int32_t min_version;
};
int
hibachk_keycmp(const struct hibaext *identity, const char *key, const char *value) {
int ret = HIBA_OK;
char *must_match;
debug2("hibachk_keycmp: testing %s = %s", key, value);
/* grant key MUST also be available in identity. */
if (hibaext_value_for_key(identity, key, &must_match) < 0)
return HIBA_CHECK_NOKEY;
debug2("hibachk_keycmp: processing against %s", must_match);
/* Compare must_match and value using globs. */
if (fnmatch(value, must_match, 0) != 0)
ret = HIBA_CHECK_DENIED;
free(must_match);
return ret;
}
void
hibachk_register_result(struct hibaext *result, const char *key, int status, int negative_matching) {
char *value;
if (hibaext_value_for_key(result, key, &value) < 0) {
/* First check for key: */
char *on_match = _HIBA_CHECK_SUCCESS;
char *on_miss = _HIBA_CHECK_FAILURE;
if (negative_matching) {
on_match = _HIBA_CHECK_FAILURE;
on_miss = _HIBA_CHECK_SUCCESS;
}
if (status == 0) {
debug2("hibachk_register_result: %s", on_match);
hibaext_add_pair(result, key, on_match);
} else if (status == HIBA_CHECK_NOKEY) {
debug2("hibachk_register_result: fatal");
hibaext_add_pair(result, key, _HIBA_CHECK_FATAL);
} else {
debug2("hibachk_register_result: %s", on_miss);
hibaext_add_pair(result, key, on_miss);
}
} else {
/* Repeated key check, we must implement result merging as OR
* for positive matches, or AND for negative matches.
* Starting with a previously valid constraint: */
if (strcmp(value, _HIBA_CHECK_SUCCESS) == 0) {
if (negative_matching && status == 0) {
/* On negative matching, one match fails the grant: */
hibaext_update_pair(result, key, _HIBA_CHECK_FAILURE);
debug2("hibachk_register_result: refused because negative matching");
} else {
/* Here, either:
* - positive matching and previously granted
* - negative matching, previously granted and
* currently still not matching. */
debug2("hibachk_register_result: still accepted");
}
} else {
/* From here, we know previous contraint where invalid: */
if (!negative_matching && status == 0) {
/* newly valid constraint with positive matching: */
debug2("hibachk_register_result: accepted, overriding previous failures");
hibaext_update_pair(result, key, _HIBA_CHECK_SUCCESS);
} else {
if (negative_matching) {
debug2("hibachk_register_result: negative matching previously refused");
} else {
debug2("hibachk_register_result: positive matching refused again");
}
}
}
free(value);
}
}
int
hibachk_result(struct hibaext *result) {
int ret = HIBA_OK;
u_int32_t i;
char *key = NULL;
char *value = NULL;
debug3("hibachk_result: merging %d results", hibaext_pairs_len(result));
for (i = 0; (ret == HIBA_OK) && (i < hibaext_pairs_len(result)); ++i) {
free(key);
free(value);
if ((ret = hibaext_key_value_at(result, i, &key, &value)) < 0)
return ret;
verbose("hibachk_result: key %s -> %s", key, value);
if (strcmp(value, _HIBA_CHECK_SUCCESS) == 0)
continue;
if (strcmp(value, _HIBA_CHECK_FATAL) == 0)
ret = HIBA_CHECK_NOKEY;
else if (strcmp(key, HIBA_KEY_HOSTNAME) == 0)
ret = HIBA_CHECK_BADHOSTNAME;
else if (strcmp(key, HIBA_KEY_ROLE) == 0)
ret = HIBA_CHECK_BADROLE;
else
ret = HIBA_CHECK_DENIED;
}
free(key);
free(value);
return ret;
}
int
hibachk_authorize(const struct hibaenv *env, const u_int64_t user_serial, const struct hibaext *grant, u_int32_t idx, const char *role) {
int ret;
int expand_self = 0;
long expiration = 0;
long expiration_set = 0;
u_int32_t i;
u_int32_t version;
u_int32_t min_version;
debug2("hibachk_authorize: performing sanity checks");
/* Basic sanity hibachks. */
if ((env == NULL) || (grant == NULL))
return HIBA_BAD_PARAMS;
if ((ret = hibaext_sanity_check(grant)) < 0)
return ret;
/* Test versions compatibility. */
debug2("hibachk_authorize: checking version");
if ((ret = hibaext_versions(grant, &version, &min_version)) < 0)
return ret;
debug2("hibachk_authorize: checking version: grant v%d (min_version=%d)", version, min_version);
if (env->version < min_version)
return HIBA_CHECK_BADVERSION;
if (version < env->min_version)
return HIBA_CHECK_BADVERSION;
/* Test GRL. */
debug2("hibachk_authorize: testing GRL against serial %" PRIx64, user_serial);
if (env->grl != NULL && (ret = hibagrl_check(env->grl, user_serial, idx)) < 0) {
return ret;
}
/* Check key needing special handling: HIBA_KEY_VALIDITY,
* HIBA_KEY_ROLE. */
for (i = 0; i < hibaext_pairs_len(grant); ++i) {
char *key;
char *value;
if ((ret = hibaext_key_value_at(grant, i, &key, &value)) < 0) {
debug2("hibachk_authorize: failed to extract key/pair");
return ret;
} else if (strcmp(key, HIBA_KEY_VALIDITY) == 0) {
long v = strtol(value, NULL, 0);
expiration_set = 1;
/* We look for the expiration that is most distant in
* the future. */
if (v > expiration)
expiration = v;
} else if ((strcmp(key, HIBA_KEY_ROLE) == 0) && (strcmp(value, HIBA_ROLE_PRINCIPALS) == 0)) {
/* A grant using HIBA_ROLE_PRINCIPALS as role must be
* checked against all the principals declared in the
* certificate, on top of other roles declared in the
* grant. */
expand_self = 1;
}
free(key);
free(value);
}
/* Test for expiration. */
if (expiration_set && ((env->cert_issue_ts + expiration) < env->now)) {
debug2("hibachk_authorize: expired");
return HIBA_CHECK_EXPIRED;
}
/* Check for role HIBA_ROLE_PRINCIPALS expansion. */
if (expand_self) {
int role_is_self = 0;
for (i = 0; i < env->nprincipals; ++i) {
if (strcmp(env->principals[i], role) == 0) {
role_is_self = 1;
break;
}
}
if (role_is_self) {
debug2("hibachk_authorize: expanding role as " HIBA_ROLE_PRINCIPALS);
role = HIBA_ROLE_PRINCIPALS;
}
}
/* The grant looks OK, we can run the policy authorization checks. */
return hibachk_query(env->identity, grant, env->hostname, role);
}
int
hibachk_query(const struct hibaext *identity, const struct hibaext *grant, const char *hostname, const char *role) {
int ret;
u_int32_t i;
struct hibaext *result = NULL;
result = hibaext_new();
hibaext_init(result, HIBA_GRANT_EXT);
/* Test all keys from the grant against the identity: */
for (i = 0; i < hibaext_pairs_len(grant); ++i) {
int skip = 0;
int key_offset = 0;
int negative_matching = 0;
char *key;
char *value;
if ((ret = hibaext_key_value_at(grant, i, &key, &value)) < 0) {
debug2("hibachk_query: failed to extract key/pair");
goto err;
}
if (key[0] == HIBA_NEGATIVE_MATCHING) {
debug2("hibachk_query: found negative matching");
negative_matching = 1;
key_offset = 1;
}
if (strcmp(key, HIBA_KEY_OPTIONS) == 0) {
debug2("hibachk_query: skipping 'options' key");
skip = 1;
} else if (strcmp(key, HIBA_KEY_VALIDITY) == 0) {
debug2("hibachk_query: skipping 'validity' key: already verified");
skip = 1;
} else if (strcmp(key+key_offset, HIBA_KEY_HOSTNAME) == 0) {
debug2("hibachk_query: testing hostname %s", value);
ret = fnmatch(value, hostname, 0);
} else if (strcmp(key+key_offset, HIBA_KEY_ROLE) == 0) {
debug2("hibachk_query: testing role %s", value);
ret = fnmatch(value, role, 0);
} else {
debug2("hibachk_query: testing generic key");
ret = hibachk_keycmp(identity, key+key_offset, value);
}
if (!skip) {
hibachk_register_result(result, key, ret, negative_matching);
}
free(key);
free(value);
}
ret = hibachk_result(result);
err:
hibaext_free(result);
return ret;
}
struct hibaenv*
hibaenv_from_host(const struct hibacert *host, const struct hibacert *user, const struct hibagrl *grl) {
int len;
int ret;
uint32_t i;
struct hibaext **exts;
struct hibaenv *env = calloc(sizeof(struct hibaenv), 1);
if ((ret = hibacert_hibaexts(host, &exts, &len)) < 0) {
debug2("hibaenv_from_host: hibacert_hibaexts returned %d: %s", ret, hiba_err(ret));
goto err;
}
if (len != 1) {
debug2("hibaenv_from_host: too many HIBA identities: got %d", len);
goto err;
}
if ((ret = hibaext_sanity_check(exts[0])) < 0) {
debug2("hibaenv_from_host: hibaext_sanity_check returned %d: %s", ret, hiba_err(ret));
goto err;
}
env->identity = exts[0];
env->now = time(NULL);
gethostname(env->hostname, HOST_NAME_MAX);
if ((ret = hibaext_versions(env->identity, &env->version, &env->min_version)) < 0) {
debug2("hibaenv_from_host: hibaext_versions returned %d: %s", ret, hiba_err(ret));
goto err;
}
env->cert_serial = hibacert_cert(host)->serial;
env->cert_issue_ts = hibacert_cert(host)->valid_after;
env->nprincipals = hibacert_cert(user)->nprincipals;
env->principals = hibacert_cert(user)->principals;
env->grl = grl;
if (grl != NULL) {
verbose("hibaenv_from_host: loading GRL v%d", hibagrl_version(env->grl));
verbose(" comment: %s", hibagrl_comment(env->grl));
verbose(" timestamp: %" PRIu64, hibagrl_timestamp(env->grl));
verbose(" entries: %" PRIu64, hibagrl_serials_count(env->grl));
}
verbose("hibaenv_from_host: loading environment");
verbose(" now: %" PRIu64, env->now);
verbose(" hostname: %s", env->hostname);
verbose(" version: %u", env->version);
verbose(" min_version: %u", env->min_version);
verbose(" cert_serial: %" PRIx64, env->cert_serial);
verbose(" cert_issue_ts: %" PRIu64, env->cert_issue_ts);
for (i = 0; i < env->nprincipals; ++i) {
verbose(" user principal: %s", env->principals[i]);
}
return env;
err:
free(env);
return NULL;
}
void
hibaenv_free(struct hibaenv *env) {
free(env);
}
void hibachk_authorized_users(const struct hibaenv *env, const struct hibacert *cert, int idx, FILE *f) {
int len;
u_int32_t i;
struct hibaext *grant;
struct hibaext **grants;
struct sshbuf *options;
if (hibacert_hibaexts(cert, &grants, &len) < 0)
return;
if (idx >= len)
return;
grant = grants[idx];
options = sshbuf_new();
for (i = 0; i < hibaext_pairs_len(grant); ++i) {
char *key;
char *value;
if (hibaext_key_value_at(grant, i, &key, &value)) {
sshbuf_free(options);
return;
}
if (strcmp(key, HIBA_KEY_OPTIONS) == 0) {
int sz = strlen(value);
if (sshbuf_len(options) > 0)
sshbuf_put_u8(options, ',');
sshbuf_put(options, value, sz);
}
free(key);
free(value);
}
if (sshbuf_len(options) > 0)
sshbuf_put_u8(options, ' ');
sshbuf_put_u8(options, '\0');
verbose("hibachk_authorized_users: access granted, generating list of authorized principals");
for (i = 0; i < hibacert_cert(cert)->nprincipals; ++i) {
fprintf(f, "%s%s\n", sshbuf_ptr(options), hibacert_cert(cert)->principals[i]);
}
fflush(f);
sshbuf_free(options);
}