kdc: cmd_append do not forget va_end()
[heimdal.git] / kdc / ipc_csr_authorizer.c
blob03df59529ac3fbd10504005614981eebcdebb594
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(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 ret = rk_strpoolfree(*cmd), *cmd = NULL, ENOMEM;
183 goto out;
185 *cmd = rk_strpoolprintf(*cmd, "%s", s);
186 free(s);
187 if (*cmd == NULL) {
188 ret = ENOMEM;
189 goto out;
193 out:
194 va_end(ap);
195 return ret;
198 static int
199 call_svc(krb5_context context, heim_ipc ipc, const char *cmd)
201 heim_octet_string req, resp;
202 int ret;
204 req.data = (void *)(uintptr_t)cmd;
205 req.length = strlen(cmd);
206 resp.length = 0;
207 resp.data = NULL;
208 if ((ret = heim_ipc_call(ipc, &req, &resp, NULL))) {
209 if (resp.length && resp.length < INT_MAX) {
210 krb5_set_error_message(context, ret, "CSR denied: %.*s",
211 (int)resp.length, (const char *)resp.data);
212 ret = EACCES;
213 } else {
214 krb5_set_error_message(context, EACCES, "CSR denied because could "
215 "not reach CSR authorizer IPC service");
216 ret = EACCES;
218 return ret;
220 if (resp.data == NULL || resp.length == 0) {
221 free(resp.data);
222 krb5_set_error_message(context, ret, "CSR authorizer IPC service "
223 "failed silently");
224 return EACCES;
226 if (resp.length == sizeof("denied") - 1 &&
227 strncasecmp(resp.data, "denied", sizeof("denied") - 1) == 0) {
228 free(resp.data);
229 krb5_set_error_message(context, ret, "CSR authorizer rejected %s",
230 cmd);
231 return EACCES;
233 if (resp.length == sizeof("granted") - 1 &&
234 strncasecmp(resp.data, "granted", sizeof("granted") - 1) == 0) {
235 free(resp.data);
236 return 0;
238 krb5_set_error_message(context, ret, "CSR authorizer failed %s: %.*s",
239 cmd, resp.length < INT_MAX ? (int)resp.length : 0,
240 resp.data);
241 return EACCES;
244 static void
245 frees(char **s)
247 free(*s);
248 *s = NULL;
251 static krb5_error_code
252 mark_authorized(hx509_request csr)
254 size_t i;
255 char *s;
256 int ret = 0;
258 for (i = 0; ret == 0; i++) {
259 ret = hx509_request_get_eku(csr, i, &s);
260 if (ret == 0)
261 hx509_request_authorize_eku(csr, i);
262 frees(&s);
264 if (ret == HX509_NO_ITEM)
265 ret = 0;
267 for (i = 0; ret == 0; i++) {
268 hx509_san_type san_type;
269 ret = hx509_request_get_san(csr, i, &san_type, &s);
270 if (ret == 0)
271 hx509_request_authorize_san(csr, i);
272 frees(&s);
274 return ret == HX509_NO_ITEM ? 0 : ret;
277 static KRB5_LIB_CALL krb5_error_code
278 authorize(void *ctx,
279 krb5_context context,
280 const char *app,
281 hx509_request csr,
282 krb5_const_principal client,
283 krb5_boolean *result)
285 struct rk_strpool *cmd = NULL;
286 krb5_error_code ret;
287 hx509_context hx509ctx = NULL;
288 heim_ipc ipc = NULL;
289 const char *svc;
290 KeyUsage ku;
291 size_t i;
292 char *princ = NULL;
293 char *s = NULL;
294 int do_check = 0;
296 if ((svc = krb5_config_get_string(context, NULL, app ? app : "kdc",
297 "ipc_csr_authorizer", "service", NULL))
298 == NULL)
299 return KRB5_PLUGIN_NO_HANDLE;
301 if ((ret = heim_ipc_init_context(svc, &ipc))) {
302 krb5_set_error_message(context, ret, "Could not set up IPC client "
303 "end-point for service %s", svc);
304 return ret;
307 if ((ret = hx509_context_init(&hx509ctx)))
308 goto out;
310 if ((ret = krb5_unparse_name(context, client, &princ)))
311 goto out;
313 if ((ret = cmd_append(&cmd, "check ", princ, NULL)))
314 goto enomem;
315 frees(&princ);
317 for (i = 0; ret == 0; i++) {
318 hx509_san_type san_type;
320 ret = hx509_request_get_san(csr, i, &san_type, &s);
321 if (ret)
322 break;
323 switch (san_type) {
324 case HX509_SAN_TYPE_EMAIL:
325 if ((ret = cmd_append(&cmd, " san_email=", s, NULL)))
326 goto enomem;
327 do_check = 1;
328 break;
329 case HX509_SAN_TYPE_DNSNAME:
330 if ((ret = cmd_append(&cmd, " san_dnsname=", s, NULL)))
331 goto enomem;
332 do_check = 1;
333 break;
334 case HX509_SAN_TYPE_XMPP:
335 if ((ret = cmd_append(&cmd, " san_xmpp=", s, NULL)))
336 goto enomem;
337 do_check = 1;
338 break;
339 case HX509_SAN_TYPE_PKINIT:
340 if ((ret = cmd_append(&cmd, " san_pkinit=", s, NULL)))
341 goto enomem;
342 do_check = 1;
343 break;
344 case HX509_SAN_TYPE_MS_UPN:
345 if ((ret = cmd_append(&cmd, " san_ms_upn=", s, NULL)))
346 goto enomem;
347 do_check = 1;
348 break;
349 default:
350 if ((ret = hx509_request_reject_san(csr, i)))
351 goto out;
352 break;
354 frees(&s);
356 if (ret == HX509_NO_ITEM)
357 ret = 0;
358 if (ret)
359 goto out;
361 for (i = 0; ret == 0; i++) {
362 ret = hx509_request_get_eku(csr, i, &s);
363 if (ret)
364 break;
365 if ((ret = cmd_append(&cmd, " eku=", s, NULL)))
366 goto enomem;
367 do_check = 1;
368 frees(&s);
370 if (ret == HX509_NO_ITEM)
371 ret = 0;
372 if (ret)
373 goto out;
375 ku = int2KeyUsage(0);
376 ku.digitalSignature = 1;
377 ku.nonRepudiation = 1;
378 hx509_request_authorize_ku(csr, ku);
380 if (do_check) {
381 if ((s = rk_strpoolcollect(cmd)) == NULL)
382 goto enomem;
383 cmd = NULL;
384 if ((ret = call_svc(context, ipc, s)))
385 goto out;
386 } /* else -> permit */
388 if ((ret = mark_authorized(csr)))
389 goto out;
391 *result = TRUE;
392 ret = 0;
393 goto out;
395 enomem:
396 ret = krb5_enomem(context);
397 goto out;
399 out:
400 heim_ipc_free_context(ipc);
401 hx509_context_free(&hx509ctx);
402 if (cmd)
403 rk_strpoolfree(cmd);
404 free(princ);
405 free(s);
406 return ret;
409 static KRB5_LIB_CALL krb5_error_code
410 ipc_csr_authorizer_init(krb5_context context, void **c)
412 *c = NULL;
413 return 0;
416 static KRB5_LIB_CALL void
417 ipc_csr_authorizer_fini(void *c)
421 static krb5plugin_csr_authorizer_ftable plug_desc =
422 { 1, ipc_csr_authorizer_init, ipc_csr_authorizer_fini, authorize };
424 static krb5plugin_csr_authorizer_ftable *plugs[] = { &plug_desc };
426 static uintptr_t
427 ipc_csr_authorizer_get_instance(const char *libname)
429 if (strcmp(libname, "krb5") == 0)
430 return krb5_get_instance(libname);
431 if (strcmp(libname, "kdc") == 0)
432 return kdc_get_instance(libname);
433 if (strcmp(libname, "hx509") == 0)
434 return hx509_get_instance(libname);
435 return 0;
438 krb5_plugin_load_ft kdc_csr_authorizer_plugin_load;
440 krb5_error_code KRB5_CALLCONV
441 kdc_csr_authorizer_plugin_load(heim_pcontext context,
442 krb5_get_instance_func_t *get_instance,
443 size_t *num_plugins,
444 krb5_plugin_common_ftable_cp **plugins)
446 *get_instance = ipc_csr_authorizer_get_instance;
447 *num_plugins = sizeof(plugs) / sizeof(plugs[0]);
448 *plugins = (krb5_plugin_common_ftable_cp *)plugs;
449 return 0;