-
Notifications
You must be signed in to change notification settings - Fork 15
/
checks.c
420 lines (361 loc) · 11.6 KB
/
checks.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
/*
* 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 user_cert_issue_ts;
u_int64_t user_cert_serial;
/* Grant */
u_int32_t nprincipals;
char **principals;
/* Host 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 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 (hibaext_type(grant) != HIBA_GRANT_EXT)
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, env->user_cert_serial);
if (env->grl != NULL && (ret = hibagrl_check(env->grl, env->user_cert_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->user_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->user_cert_serial = hibacert_cert(user)->serial;
env->user_cert_issue_ts = hibacert_cert(user)->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(" user_cert_serial: %" PRIx64, env->user_cert_serial);
verbose(" user_cert_issue_ts: %" PRIu64, env->user_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);
}