tests: Use here-doc kadmin in Java test
[heimdal.git] / kdc / ipc_csr_authorizer.c
blobf2ce03c879cefec577a41cca05d410b39387094c
1 /*
2 * Copyright (c) 2019 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
35 * This plugin authorizes requested certificate SANs and EKUs by calling a
36 * service over IPC (Unix domain sockets on Linux/BSD/Illumos).
38 * The IPC protocol is request/response, with requests and responses sent as
40 * <length><string>
42 * where the <length> is 4 bytes, unsigned binary in network byte order, and
43 * <string> is an array of <length> bytes and does NOT include a NUL
44 * terminator.
46 * Requests are of the form:
48 * check <princ> <exttype>=<extvalue> ...
50 * where <princ> is a URL-escaped principal name, <exttype> is one of:
52 * - san_pkinit
53 * - san_xmpp
54 * - san_email
55 * - san_ms_upn
56 * - san_dnsname
57 * - eku
59 * and <extvalue> is a URL-escaped string representation of the SAN or OID.
61 * OIDs are in the form 1.2.3.4.5.6.
63 * Only characters other than alphanumeric, '@', '.', '-', '_', and '/' are
64 * URL-encoded.
66 * Responses are any of:
68 * - granted
69 * - denied
70 * - error message
72 * Example:
74 * C->S: check jane@TEST.H5L.SE san_dnsname=jane.foo.test.h5l.se eku=1.3.6.1.5.5.7.3.1
75 * S->C: granted
77 * Only digitalSignature and nonRepudiation key usages are allowed. Requested
78 * key usages are not sent to the CSR authorizer IPC server.
81 #define _GNU_SOURCE 1
83 #include <sys/types.h>
84 #include <sys/stat.h>
85 #include <ctype.h>
86 #include <errno.h>
87 #include <stdlib.h>
88 #include <stdio.h>
89 #include <string.h>
90 #include <unistd.h>
92 #include <roken.h>
93 #include <heim-ipc.h>
94 #include <krb5.h>
95 #include <hx509.h>
96 #include <kdc.h>
97 #include <common_plugin.h>
98 #include <csr_authorizer_plugin.h>
101 * string_encode_sz() and string_encode() encode principal names and such to be
102 * safe for use in our IPC text messages. They function very much like URL
103 * encoders, but '~' also gets encoded, and '.' and '@' do not.
105 * An unescaper is not needed here.
107 static size_t
108 string_encode_sz(const char *in)
110 size_t sz = strlen(in);
112 while (*in) {
113 char c = *(in++);
115 switch (c) {
116 case '@':
117 case '.':
118 case '-':
119 case '_':
120 case '/':
121 continue;
122 default:
123 if (isalnum((unsigned char)c))
124 continue;
125 sz += 2;
128 return sz;
131 static char *
132 string_encode(const char *in)
134 size_t len = strlen(in);
135 size_t sz = string_encode_sz(in);
136 size_t i, k;
137 char *s;
139 if ((s = malloc(sz + 1)) == NULL)
140 return NULL;
141 s[sz] = '\0';
143 for (i = k = 0; i < len; i++) {
144 unsigned char c = ((const unsigned char *)in)[i];
146 switch (c) {
147 case '@':
148 case '.':
149 case '-':
150 case '_':
151 case '/':
152 s[k++] = c;
153 break;
154 default:
155 if (isalnum(c)) {
156 s[k++] = c;
157 } else {
158 s[k++] = '%';
159 s[k++] = "0123456789abcdef"[(c&0xff)>>4];
160 s[k++] = "0123456789abcdef"[(c&0x0f)];
164 return s;
167 static int
168 cmd_append(struct rk_strpool **cmd, const char *s0, ...)
170 va_list ap;
171 const char *arg;
172 int ret = 0;
174 if ((*cmd = rk_strpoolprintf(*cmd, "%s", s0)) == NULL)
175 return ENOMEM;
177 va_start(ap, s0);
178 while ((arg = va_arg(ap, const char *))) {
179 char *s;
181 if ((s = string_encode(arg)) == NULL) {
182 rk_strpoolfree(*cmd);
183 *cmd = NULL;
184 ret = ENOMEM;
185 goto out;
187 *cmd = rk_strpoolprintf(*cmd, "%s", s);
188 free(s);
189 if (*cmd == NULL) {
190 ret = ENOMEM;
191 goto out;
195 out:
196 va_end(ap);
197 return ret;
200 /* Like strpbrk(), but from the end of the string */
201 static char *
202 strrpbrk(char *s, const char *accept)
204 char *last = NULL;
205 char *p = s;
207 do {
208 p = strpbrk(p, accept);
209 if (p != NULL) {
210 last = p;
211 p++;
213 } while (p != NULL);
214 return last;
218 * For /get-tgts we need to support partial authorization of requests. The
219 * hx509_request APIs support that.
221 * Here we just step through the IPC server's response and mark the
222 * corresponding request elements authorized so that /get-tgts can issue or not
223 * issue TGTs according to which requested principals are authorized and which
224 * are not.
226 static int
227 mark_piecemeal_authorized(krb5_context context,
228 hx509_request csr,
229 heim_octet_string *rep)
231 size_t san_idx = 0;
232 size_t eku_idx = 0;
233 char *s, *p, *rep2, *tok, *next = NULL;
234 int slow_path = 0;
235 int partial = 0;
236 int ret = 0;
238 /* We have a data, but we want a C string */
239 if ((rep2 = strndup(rep->data, rep->length)) == NULL)
240 return krb5_enomem(context);
242 /* The first token should be "denied"; skip it */
243 if ((s = strchr(rep2, ' ')) == NULL) {
244 free(rep2);
245 return EACCES;
247 s++;
249 while ((tok = strtok_r(s, ",", &next))) {
250 hx509_san_type san_type, san_type2;
251 char *s2 = NULL;
253 s = NULL; /* for strtok_r() */
255 if (strncmp(tok, "eku=", sizeof("eku=") -1) == 0) {
257 * Very simplistic handling of partial authz for EKUs:
259 * - denial of an EKU -> deny the whole request
260 * - else below mark all EKUs approved
262 if (strstr(tok, ":denied")) {
263 krb5_set_error_message(context, EACCES, "CSR denied because "
264 "EKU denied: %s", tok);
265 ret = EACCES;
266 break;
268 continue;
272 * For SANs we check that the nth SAN in the response matches the nth
273 * SAN in the hx509_request.
276 if (strncmp(tok, "san_pkinit=", sizeof("san_pkinit=") - 1) == 0) {
277 tok += sizeof("san_pkinit=") - 1;
278 san_type = HX509_SAN_TYPE_PKINIT;
279 } else if (strncmp(tok, "san_dnsname=", sizeof("san_dnsname=") -1) == 0) {
280 tok += sizeof("san_dnsname=") - 1;
281 san_type = HX509_SAN_TYPE_DNSNAME;
282 } else if (strncmp(tok, "san_email=", sizeof("san_email=") -1) == 0) {
283 tok += sizeof("san_email=") - 1;
284 san_type = HX509_SAN_TYPE_EMAIL;
285 } else if (strncmp(tok, "san_xmpp=", sizeof("san_xmpp=") -1) == 0) {
286 tok += sizeof("san_xmpp=") - 1;
287 san_type = HX509_SAN_TYPE_XMPP;
288 } else if (strncmp(tok, "san_ms_upn=", sizeof("san_ms_upn=") -1) == 0) {
289 tok += sizeof("san_ms_upn=") - 1;
290 san_type = HX509_SAN_TYPE_MS_UPN;
291 } else {
292 krb5_set_error_message(context, EACCES, "CSR denied because could "
293 "not parse token in response: %s", tok);
294 ret = EACCES;
295 break;
299 * This token has to end in ":granted" or ":denied". Using our
300 * `strrpbrk()' means we can deal with principals names that have ':'
301 * in them.
303 if ((p = strrpbrk(tok, ":")) == NULL) {
304 san_idx++;
305 continue;
307 *(p++) = '\0';
309 /* Now we get the nth SAN from the authorization */
310 ret = hx509_request_get_san(csr, san_idx, &san_type2, &s2);
311 if (ret == HX509_NO_ITEM) {
312 /* See below */
313 slow_path = 1;
314 break;
317 /* And we check that it matches the SAN in this token */
318 if (ret == 0) {
319 if (san_type != san_type2 ||
320 strcmp(tok, s2) != 0) {
322 * We expect the tokens in the reply to be in the same order as
323 * in the request. If not, we must take a slow path where we
324 * have to sort requests and responses then iterate them in
325 * order.
327 slow_path = 1;
328 hx509_xfree(s2);
329 break;
331 hx509_xfree(s2);
333 if (strcmp(p, "granted") == 0) {
334 ret = hx509_request_authorize_san(csr, san_idx);
335 } else {
336 partial = 1;
337 ret = hx509_request_reject_san(csr, san_idx);
339 if (ret)
340 break;
342 san_idx++;
345 if (slow_path) {
347 * FIXME? Implement the slow path?
349 * Basically, we'd get all the SANs from the request into an array of
350 * {SAN, index} and sort that array, then all the SANs from the
351 * response into an array and sort it, then step a cursor through both,
352 * using the index from the first to mark SANs in the request
353 * authorized or rejected.
355 krb5_set_error_message(context, EACCES, "CSR denied because "
356 "authorizer service did not include all "
357 "piecemeal grants/denials in order");
358 ret = EACCES;
361 /* Mark all the EKUs authorized */
362 for (eku_idx = 0; ret == 0; eku_idx++)
363 ret = hx509_request_authorize_eku(csr, eku_idx);
364 if (ret == HX509_NO_ITEM)
365 ret = 0;
366 if (ret == 0 && partial) {
367 krb5_set_error_message(context, EACCES, "CSR partially authorized");
368 ret = EACCES;
371 free(rep2);
372 return ret;
375 static krb5_error_code mark_authorized(hx509_request);
377 static int
378 call_svc(krb5_context context,
379 heim_ipc ipc,
380 hx509_request csr,
381 const char *cmd,
382 int piecemeal_check_ok)
384 heim_octet_string req, resp;
385 int ret;
387 req.data = (void *)(uintptr_t)cmd;
388 req.length = strlen(cmd);
389 resp.length = 0;
390 resp.data = NULL;
391 ret = heim_ipc_call(ipc, &req, &resp, NULL);
393 /* Check for all granted case */
394 if (ret == 0 &&
395 resp.length == sizeof("granted") - 1 &&
396 strncasecmp(resp.data, "granted", sizeof("granted") - 1) == 0) {
397 free(resp.data);
398 return mark_authorized(csr); /* Full approval */
401 /* Check for "denied ..." piecemeal authorization case */
402 if ((ret == 0 || ret == EACCES || ret == KRB5_PLUGIN_NO_HANDLE) &&
403 piecemeal_check_ok &&
404 resp.length > sizeof("denied") - 1 &&
405 strncasecmp(resp.data, "denied", sizeof("denied") - 1) == 0) {
406 /* Piecemeal authorization */
407 ret = mark_piecemeal_authorized(context, csr, &resp);
409 /* mark_piecemeal_authorized() should return EACCES; just in case: */
410 if (ret == 0)
411 ret = EACCES;
412 free(resp.data);
413 return ret;
416 /* All other failure cases */
418 if (resp.data == NULL || resp.length == 0) {
419 krb5_set_error_message(context, ret, "CSR authorizer IPC service "
420 "failed silently");
421 free(resp.data);
422 return EACCES;
425 if (resp.length == sizeof("ignore") - 1 &&
426 strncasecmp(resp.data, "ignore", sizeof("ignore") - 1) == 0) {
428 * In this case the server is saying "I can't handle this request, try
429 * some other authorizer plugin".
431 free(resp.data);
432 return KRB5_PLUGIN_NO_HANDLE;
435 if (resp.length == sizeof("denied") - 1 &&
436 strncasecmp(resp.data, "denied", sizeof("denied") - 1) == 0) {
437 krb5_set_error_message(context, ret, "CSR authorizer rejected %s",
438 cmd);
439 free(resp.data);
440 return EACCES;
443 if (resp.length > INT_MAX)
444 krb5_set_error_message(context, ret, "CSR authorizer rejected %s", cmd);
445 else
446 krb5_set_error_message(context, ret, "CSR authorizer rejected %s: %.*s",
447 cmd, resp.length, resp.data);
449 free(resp.data);
450 return ret;
453 static void
454 frees(char **s)
456 free(*s);
457 *s = NULL;
460 static krb5_error_code
461 mark_authorized(hx509_request csr)
463 size_t i;
464 char *s;
465 int ret = 0;
467 for (i = 0; ret == 0; i++) {
468 ret = hx509_request_get_eku(csr, i, &s);
469 if (ret == 0)
470 hx509_request_authorize_eku(csr, i);
471 frees(&s);
473 if (ret == HX509_NO_ITEM)
474 ret = 0;
476 for (i = 0; ret == 0; i++) {
477 hx509_san_type san_type;
478 ret = hx509_request_get_san(csr, i, &san_type, &s);
479 if (ret == 0)
480 hx509_request_authorize_san(csr, i);
481 frees(&s);
483 return ret == HX509_NO_ITEM ? 0 : ret;
486 static KRB5_LIB_CALL krb5_error_code
487 authorize(void *ctx,
488 krb5_context context,
489 const char *app,
490 hx509_request csr,
491 krb5_const_principal client,
492 krb5_boolean *result)
494 struct rk_strpool *cmd = NULL;
495 krb5_error_code ret;
496 hx509_context hx509ctx = NULL;
497 heim_ipc ipc = NULL;
498 const char *svc;
499 KeyUsage ku;
500 size_t i;
501 char *princ = NULL;
502 char *s = NULL;
503 int do_check = 0;
504 int piecemeal_check_ok = 1;
506 if ((svc = krb5_config_get_string_default(context, NULL,
507 "ANY:org.h5l.csr_authorizer",
508 app ? app : "kdc",
509 "ipc_csr_authorizer", "service",
510 NULL)) == NULL)
511 return KRB5_PLUGIN_NO_HANDLE;
513 if ((ret = heim_ipc_init_context(svc, &ipc))) {
515 * If the IPC authorizer is optional, then fallback on whatever is
516 * next.
518 if (krb5_config_get_bool_default(context, NULL, FALSE,
519 app ? app : "kdc",
520 "ipc_csr_authorizer", "optional",
521 NULL))
522 return KRB5_PLUGIN_NO_HANDLE;
523 krb5_set_error_message(context, ret, "Could not set up IPC client "
524 "end-point for service %s", svc);
525 return ret;
528 if ((ret = hx509_context_init(&hx509ctx)))
529 goto out;
531 if ((ret = krb5_unparse_name(context, client, &princ)))
532 goto out;
534 if ((ret = cmd_append(&cmd, "check ", princ, NULL)))
535 goto enomem;
536 frees(&princ);
538 for (i = 0; ret == 0; i++) {
539 hx509_san_type san_type;
540 size_t p;
542 ret = hx509_request_get_san(csr, i, &san_type, &s);
543 if (ret)
544 break;
547 * We cannot do a piecemeal check if any of the SANs could make the
548 * response ambiguous.
550 p = strcspn(s, ",= ");
551 if (s[p] != '\0')
552 piecemeal_check_ok = 0;
553 if (piecemeal_check_ok && strstr(s, ":granted") != NULL)
554 piecemeal_check_ok = 0;
556 switch (san_type) {
557 case HX509_SAN_TYPE_EMAIL:
558 if ((ret = cmd_append(&cmd, " san_email=", s, NULL)))
559 goto enomem;
560 do_check = 1;
561 break;
562 case HX509_SAN_TYPE_DNSNAME:
563 if ((ret = cmd_append(&cmd, " san_dnsname=", s, NULL)))
564 goto enomem;
565 do_check = 1;
566 break;
567 case HX509_SAN_TYPE_XMPP:
568 if ((ret = cmd_append(&cmd, " san_xmpp=", s, NULL)))
569 goto enomem;
570 do_check = 1;
571 break;
572 case HX509_SAN_TYPE_PKINIT:
573 if ((ret = cmd_append(&cmd, " san_pkinit=", s, NULL)))
574 goto enomem;
575 do_check = 1;
576 break;
577 case HX509_SAN_TYPE_MS_UPN:
578 if ((ret = cmd_append(&cmd, " san_ms_upn=", s, NULL)))
579 goto enomem;
580 do_check = 1;
581 break;
582 default:
583 if ((ret = hx509_request_reject_san(csr, i)))
584 goto out;
585 break;
587 frees(&s);
589 if (ret == HX509_NO_ITEM)
590 ret = 0;
591 if (ret)
592 goto out;
594 for (i = 0; ret == 0; i++) {
595 ret = hx509_request_get_eku(csr, i, &s);
596 if (ret)
597 break;
598 if ((ret = cmd_append(&cmd, " eku=", s, NULL)))
599 goto enomem;
600 do_check = 1;
601 frees(&s);
603 if (ret == HX509_NO_ITEM)
604 ret = 0;
605 if (ret)
606 goto out;
608 ku = int2KeyUsage(0);
609 ku.digitalSignature = 1;
610 ku.nonRepudiation = 1;
611 hx509_request_authorize_ku(csr, ku);
613 if (do_check) {
614 s = rk_strpoolcollect(cmd);
615 cmd = NULL;
616 if (s == NULL)
617 goto enomem;
618 if ((ret = call_svc(context, ipc, csr, s, piecemeal_check_ok)))
619 goto out;
620 } /* else there was nothing to check -> permit */
622 *result = TRUE;
623 ret = 0;
624 goto out;
626 enomem:
627 ret = krb5_enomem(context);
628 goto out;
630 out:
631 heim_ipc_free_context(ipc);
632 hx509_context_free(&hx509ctx);
633 if (cmd)
634 rk_strpoolfree(cmd);
635 free(princ);
636 free(s);
637 return ret;
640 static KRB5_LIB_CALL krb5_error_code
641 ipc_csr_authorizer_init(krb5_context context, void **c)
643 *c = NULL;
644 return 0;
647 static KRB5_LIB_CALL void
648 ipc_csr_authorizer_fini(void *c)
652 static krb5plugin_csr_authorizer_ftable plug_desc =
653 { 1, ipc_csr_authorizer_init, ipc_csr_authorizer_fini, authorize };
655 static krb5plugin_csr_authorizer_ftable *plugs[] = { &plug_desc };
657 static uintptr_t
658 ipc_csr_authorizer_get_instance(const char *libname)
660 if (strcmp(libname, "krb5") == 0)
661 return krb5_get_instance(libname);
662 if (strcmp(libname, "kdc") == 0)
663 return kdc_get_instance(libname);
664 if (strcmp(libname, "hx509") == 0)
665 return hx509_get_instance(libname);
666 return 0;
669 krb5_plugin_load_ft kdc_csr_authorizer_plugin_load;
671 krb5_error_code KRB5_CALLCONV
672 kdc_csr_authorizer_plugin_load(heim_pcontext context,
673 krb5_get_instance_func_t *get_instance,
674 size_t *num_plugins,
675 krb5_plugin_common_ftable_cp **plugins)
677 *get_instance = ipc_csr_authorizer_get_instance;
678 *num_plugins = sizeof(plugs) / sizeof(plugs[0]);
679 *plugins = (krb5_plugin_common_ftable_cp *)plugs;
680 return 0;