ctdb-scripts: Drop use of eval in CTDB callout handling
[samba.git] / third_party / heimdal / kdc / bx509d.c
blob064c424b7c29fa3e76997c0af737edc50871b0ca
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 file implements a RESTful HTTPS API to an online CA, as well as an
36 * HTTP/Negotiate token issuer.
38 * Users are authenticated with bearer tokens.
40 * This is essentially a RESTful online CA sharing code with the KDC's kx509
41 * online CA, and also a proxy for PKINIT and GSS-API (Negotiate).
43 * To get a key certified:
45 * GET /bx509?csr=<base64-encoded-PKCS#10-CSR>
47 * To get an HTTP/Negotiate token:
49 * GET /bnegotiate?target=<acceptor-principal>
51 * which, if authorized, produces a Negotiate token (base64-encoded, as
52 * expected, with the "Negotiate " prefix, ready to be put in an Authorization:
53 * header).
55 * TBD:
56 * - rewrite to not use libmicrohttpd but an alternative more appropriate to
57 * Heimdal's license (though libmicrohttpd will do)
58 * - /bx509 should include the certificate chain
59 * - /bx509 should support HTTP/Negotiate
60 * - there should be an end-point for fetching an issuer's chain
61 * - maybe add /bkrb5 which returns a KRB-CRED with the user's TGT
63 * NOTES:
64 * - We use krb5_error_code values as much as possible. Where we need to use
65 * MHD_NO because we got that from an mhd function and cannot respond with
66 * an HTTP response, we use (krb5_error_code)-1, and later map that to
67 * MHD_NO.
69 * (MHD_NO is an ENOMEM-cannot-even-make-a-static-503-response level event.)
72 #define _XOPEN_SOURCE_EXTENDED 1
73 #define _DEFAULT_SOURCE 1
74 #define _BSD_SOURCE 1
75 #define _GNU_SOURCE 1
77 #include <sys/socket.h>
78 #include <sys/types.h>
79 #include <sys/stat.h>
80 #include <sys/time.h>
81 #include <ctype.h>
82 #include <dlfcn.h>
83 #include <errno.h>
84 #include <fcntl.h>
85 #include <pthread.h>
86 #include <signal.h>
87 #include <stdarg.h>
88 #include <stddef.h>
89 #include <stdint.h>
90 #include <stdio.h>
91 #include <stdlib.h>
92 #include <string.h>
93 #include <time.h>
94 #include <unistd.h>
95 #include <netdb.h>
96 #include <netinet/in.h>
97 #include <netinet/ip.h>
99 #include <microhttpd.h>
100 #include "kdc_locl.h"
101 #include "token_validator_plugin.h"
102 #include <getarg.h>
103 #include <roken.h>
104 #include <krb5.h>
105 #include <gssapi/gssapi.h>
106 #include <gssapi/gssapi_krb5.h>
107 #include <hx509.h>
108 #include "../lib/hx509/hx_locl.h"
109 #include <hx509-private.h>
111 #define heim_pcontext krb5_context
112 #define heim_pconfig krb5_context
113 #include <heimbase-svc.h>
115 #if MHD_VERSION < 0x00097002 || defined(MHD_YES)
116 /* libmicrohttpd changed these from int valued macros to an enum in 0.9.71 */
117 #ifdef MHD_YES
118 #undef MHD_YES
119 #undef MHD_NO
120 #endif
121 enum MHD_Result { MHD_NO = 0, MHD_YES = 1 };
122 #define MHD_YES 1
123 #define MHD_NO 0
124 typedef int heim_mhd_result;
125 #else
126 typedef enum MHD_Result heim_mhd_result;
127 #endif
129 enum k5_creds_kind { K5_CREDS_EPHEMERAL, K5_CREDS_CACHED };
131 typedef struct bx509_request_desc {
132 HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS;
134 struct MHD_Connection *connection;
135 krb5_times token_times;
136 time_t req_life;
137 hx509_request req;
138 const char *for_cname;
139 const char *target;
140 const char *redir;
141 enum k5_creds_kind cckind;
142 char *pkix_store;
143 char *ccname;
144 char *freeme1;
145 krb5_addresses tgt_addresses; /* For /get-tgt */
146 char frombuf[128];
147 } *bx509_request_desc;
149 static void
150 audit_trail(bx509_request_desc r, krb5_error_code ret)
152 const char *retname = NULL;
154 /* Get a symbolic name for some error codes */
155 #define CASE(x) case x : retname = #x; break
156 switch (ret) {
157 CASE(ENOMEM);
158 CASE(EACCES);
159 CASE(HDB_ERR_NOT_FOUND_HERE);
160 CASE(HDB_ERR_WRONG_REALM);
161 CASE(HDB_ERR_EXISTS);
162 CASE(HDB_ERR_KVNO_NOT_FOUND);
163 CASE(HDB_ERR_NOENTRY);
164 CASE(HDB_ERR_NO_MKEY);
165 CASE(KRB5KDC_ERR_BADOPTION);
166 CASE(KRB5KDC_ERR_CANNOT_POSTDATE);
167 CASE(KRB5KDC_ERR_CLIENT_NOTYET);
168 CASE(KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN);
169 CASE(KRB5KDC_ERR_ETYPE_NOSUPP);
170 CASE(KRB5KDC_ERR_KEY_EXPIRED);
171 CASE(KRB5KDC_ERR_NAME_EXP);
172 CASE(KRB5KDC_ERR_NEVER_VALID);
173 CASE(KRB5KDC_ERR_NONE);
174 CASE(KRB5KDC_ERR_NULL_KEY);
175 CASE(KRB5KDC_ERR_PADATA_TYPE_NOSUPP);
176 CASE(KRB5KDC_ERR_POLICY);
177 CASE(KRB5KDC_ERR_PREAUTH_FAILED);
178 CASE(KRB5KDC_ERR_PREAUTH_REQUIRED);
179 CASE(KRB5KDC_ERR_SERVER_NOMATCH);
180 CASE(KRB5KDC_ERR_SERVICE_EXP);
181 CASE(KRB5KDC_ERR_SERVICE_NOTYET);
182 CASE(KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN);
183 CASE(KRB5KDC_ERR_TRTYPE_NOSUPP);
184 CASE(KRB5KRB_ERR_RESPONSE_TOO_BIG);
185 /* XXX Add relevant error codes */
186 case 0:
187 retname = "SUCCESS";
188 break;
189 default:
190 retname = NULL;
191 break;
194 /* Let's save a few bytes */
195 if (retname && strncmp("KRB5KDC_", retname, sizeof("KRB5KDC_") - 1) == 0)
196 retname += sizeof("KRB5KDC_") - 1;
197 #undef PREFIX
198 heim_audit_trail((heim_svc_req_desc)r, ret, retname);
201 static krb5_log_facility *logfac;
202 static pthread_key_t k5ctx;
204 static krb5_error_code
205 get_krb5_context(krb5_context *contextp)
207 krb5_error_code ret;
209 if ((*contextp = pthread_getspecific(k5ctx)))
210 return 0;
211 if ((ret = krb5_init_context(contextp)))
212 return *contextp = NULL, ret;
213 (void) pthread_setspecific(k5ctx, *contextp);
214 return *contextp ? 0 : ENOMEM;
217 static int port = -1;
218 static int help_flag;
219 static int daemonize;
220 static int daemon_child_fd = -1;
221 static int verbose_counter;
222 static int version_flag;
223 static int reverse_proxied_flag;
224 static int thread_per_client_flag;
225 struct getarg_strings audiences;
226 static const char *cert_file;
227 static const char *priv_key_file;
228 static const char *cache_dir;
229 static char *impersonation_key_fn;
231 static krb5_error_code resp(struct bx509_request_desc *, int,
232 enum MHD_ResponseMemoryMode, const char *,
233 const void *, size_t, const char *);
234 static krb5_error_code bad_req(struct bx509_request_desc *, krb5_error_code, int,
235 const char *, ...)
236 HEIMDAL_PRINTF_ATTRIBUTE((__printf__, 4, 5));
238 static krb5_error_code bad_enomem(struct bx509_request_desc *, krb5_error_code);
239 static krb5_error_code bad_400(struct bx509_request_desc *, krb5_error_code, char *);
240 static krb5_error_code bad_401(struct bx509_request_desc *, char *);
241 static krb5_error_code bad_403(struct bx509_request_desc *, krb5_error_code, char *);
242 static krb5_error_code bad_404(struct bx509_request_desc *, const char *);
243 static krb5_error_code bad_405(struct bx509_request_desc *, const char *);
244 static krb5_error_code bad_500(struct bx509_request_desc *, krb5_error_code, const char *);
245 static krb5_error_code bad_503(struct bx509_request_desc *, krb5_error_code, const char *);
247 static int
248 validate_token(struct bx509_request_desc *r)
250 krb5_error_code ret;
251 krb5_principal cprinc = NULL;
252 const char *token;
253 const char *host;
254 char token_type[64]; /* Plenty */
255 char *p;
256 krb5_data tok;
257 size_t host_len, brk, i;
259 memset(&r->token_times, 0, sizeof(r->token_times));
260 host = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
261 MHD_HTTP_HEADER_HOST);
262 if (host == NULL)
263 return bad_400(r, EINVAL, "Host header is missing");
265 /* Exclude port number here (IPv6-safe because of the below) */
266 host_len = ((p = strchr(host, ':'))) ? p - host : strlen(host);
268 token = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
269 MHD_HTTP_HEADER_AUTHORIZATION);
270 if (token == NULL)
271 return bad_401(r, "Authorization token is missing");
272 brk = strcspn(token, " \t");
273 if (token[brk] == '\0' || brk > sizeof(token_type) - 1)
274 return bad_401(r, "Authorization token is missing");
275 memcpy(token_type, token, brk);
276 token_type[brk] = '\0';
277 token += brk + 1;
278 tok.length = strlen(token);
279 tok.data = (void *)(uintptr_t)token;
281 for (i = 0; i < audiences.num_strings; i++)
282 if (strncasecmp(host, audiences.strings[i], host_len) == 0 &&
283 audiences.strings[i][host_len] == '\0')
284 break;
285 if (i == audiences.num_strings)
286 return bad_403(r, EINVAL, "Host: value is not accepted here");
288 r->sname = strdup(host); /* No need to check for ENOMEM here */
290 ret = kdc_validate_token(r->context, NULL /* realm */, token_type, &tok,
291 (const char **)&audiences.strings[i], 1,
292 &cprinc, &r->token_times);
293 if (ret)
294 return bad_403(r, ret, "Token validation failed");
295 if (cprinc == NULL)
296 return bad_403(r, ret, "Could not extract a principal name "
297 "from token");
298 ret = krb5_unparse_name(r->context, cprinc, &r->cname);
299 krb5_free_principal(r->context, cprinc);
300 if (ret)
301 return bad_503(r, ret, "Could not parse principal name");
302 return ret;
305 static void
306 generate_key(hx509_context context,
307 const char *key_name,
308 const char *gen_type,
309 unsigned long gen_bits,
310 char **fn)
312 struct hx509_generate_private_context *key_gen_ctx = NULL;
313 hx509_private_key key = NULL;
314 hx509_certs certs = NULL;
315 hx509_cert cert = NULL;
316 int ret;
318 if (strcmp(gen_type, "rsa") != 0)
319 errx(1, "Only RSA keys are supported at this time");
321 if (asprintf(fn, "PEM-FILE:%s/.%s_priv_key.pem",
322 cache_dir, key_name) == -1 ||
323 *fn == NULL)
324 err(1, "Could not set up private key for %s", key_name);
326 ret = _hx509_generate_private_key_init(context,
327 ASN1_OID_ID_PKCS1_RSAENCRYPTION,
328 &key_gen_ctx);
329 if (ret == 0)
330 ret = _hx509_generate_private_key_bits(context, key_gen_ctx, gen_bits);
331 if (ret == 0)
332 ret = _hx509_generate_private_key(context, key_gen_ctx, &key);
333 if (ret == 0)
334 cert = hx509_cert_init_private_key(context, key, NULL);
335 if (ret == 0)
336 ret = hx509_certs_init(context, *fn,
337 HX509_CERTS_CREATE | HX509_CERTS_UNPROTECT_ALL,
338 NULL, &certs);
339 if (ret == 0)
340 ret = hx509_certs_add(context, certs, cert);
341 if (ret == 0)
342 ret = hx509_certs_store(context, certs, 0, NULL);
343 if (ret)
344 hx509_err(context, 1, ret, "Could not generate and save private key "
345 "for %s", key_name);
347 _hx509_generate_private_key_free(&key_gen_ctx);
348 hx509_private_key_free(&key);
349 hx509_certs_free(&certs);
350 hx509_cert_free(cert);
353 static void
354 k5_free_context(void *ctx)
356 krb5_free_context(ctx);
359 #ifndef HAVE_UNLINKAT
360 static int
361 unlink1file(const char *dname, const char *name)
363 char p[PATH_MAX];
365 if (strlcpy(p, dname, sizeof(p)) < sizeof(p) &&
366 strlcat(p, "/", sizeof(p)) < sizeof(p) &&
367 strlcat(p, name, sizeof(p)) < sizeof(p))
368 return unlink(p);
369 return ERANGE;
371 #endif
373 static void
374 rm_cache_dir(void)
376 struct dirent *e;
377 DIR *d;
380 * This works, but not on Win32:
382 * (void) simple_execlp("rm", "rm", "-rf", cache_dir, NULL);
384 * We make no directories in `cache_dir', so we need not recurse.
386 if ((d = opendir(cache_dir)) == NULL)
387 return;
389 while ((e = readdir(d))) {
390 #ifdef HAVE_UNLINKAT
392 * Because unlinkat() takes a directory FD, implementing one for
393 * libroken is tricky at best. Instead we might want to implement an
394 * rm_dash_rf() function in lib/roken.
396 (void) unlinkat(dirfd(d), e->d_name, 0);
397 #else
398 (void) unlink1file(cache_dir, e->d_name);
399 #endif
401 (void) closedir(d);
402 (void) rmdir(cache_dir);
405 static krb5_error_code
406 mk_pkix_store(char **pkix_store)
408 char *s = NULL;
409 int ret = ENOMEM;
410 int fd;
412 *pkix_store = NULL;
413 if (asprintf(&s, "PEM-FILE:%s/pkix-XXXXXX", cache_dir) == -1 ||
414 s == NULL) {
415 free(s);
416 return ret;
419 * This way of using mkstemp() isn't safer than mktemp(), but we want to
420 * quiet the warning that we'd get if we used mktemp().
422 if ((fd = mkstemp(s + sizeof("PEM-FILE:") - 1)) == -1) {
423 free(s);
424 return errno;
426 (void) close(fd);
427 *pkix_store = s;
428 return 0;
432 * XXX Shouldn't be a body, but a status message. The body should be
433 * configurable to be from a file. MHD doesn't give us a way to set the
434 * response status message though, just the body.
436 static krb5_error_code
437 resp(struct bx509_request_desc *r,
438 int http_status_code,
439 enum MHD_ResponseMemoryMode rmmode,
440 const char *content_type,
441 const void *body,
442 size_t bodylen,
443 const char *token)
445 struct MHD_Response *response;
446 int mret = MHD_YES;
448 (void) gettimeofday(&r->tv_end, NULL);
449 if (http_status_code == MHD_HTTP_OK ||
450 http_status_code == MHD_HTTP_TEMPORARY_REDIRECT)
451 audit_trail(r, 0);
453 response = MHD_create_response_from_buffer(bodylen, rk_UNCONST(body),
454 rmmode);
455 if (response == NULL)
456 return -1;
457 mret = MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL,
458 "no-store, max-age=0");
459 if (mret == MHD_YES && http_status_code == MHD_HTTP_UNAUTHORIZED) {
460 mret = MHD_add_response_header(response,
461 MHD_HTTP_HEADER_WWW_AUTHENTICATE,
462 "Bearer");
463 if (mret == MHD_YES)
464 mret = MHD_add_response_header(response,
465 MHD_HTTP_HEADER_WWW_AUTHENTICATE,
466 "Negotiate");
467 } else if (mret == MHD_YES && http_status_code == MHD_HTTP_TEMPORARY_REDIRECT) {
468 const char *redir;
470 /* XXX Move this */
471 redir = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
472 "redirect");
473 mret = MHD_add_response_header(response, MHD_HTTP_HEADER_LOCATION,
474 redir);
475 if (mret != MHD_NO && token)
476 mret = MHD_add_response_header(response,
477 MHD_HTTP_HEADER_AUTHORIZATION,
478 token);
480 if (mret == MHD_YES && content_type) {
481 mret = MHD_add_response_header(response,
482 MHD_HTTP_HEADER_CONTENT_TYPE,
483 content_type);
485 if (mret == MHD_YES)
486 mret = MHD_queue_response(r->connection, http_status_code, response);
487 MHD_destroy_response(response);
488 return mret == MHD_NO ? -1 : 0;
491 static krb5_error_code
492 bad_reqv(struct bx509_request_desc *r,
493 krb5_error_code code,
494 int http_status_code,
495 const char *fmt,
496 va_list ap)
498 krb5_error_code ret;
499 krb5_context context = NULL;
500 const char *k5msg = NULL;
501 const char *emsg = NULL;
502 char *formatted = NULL;
503 char *msg = NULL;
505 heim_audit_setkv_number((heim_svc_req_desc)r, "http-status-code",
506 http_status_code);
507 (void) gettimeofday(&r->tv_end, NULL);
508 if (code == ENOMEM) {
509 if (r->context)
510 krb5_log_msg(r->context, logfac, 1, NULL, "Out of memory");
511 audit_trail(r, code);
512 return resp(r, http_status_code, MHD_RESPMEM_PERSISTENT,
513 NULL, fmt, strlen(fmt), NULL);
516 if (code) {
517 if (r->context)
518 emsg = k5msg = krb5_get_error_message(r->context, code);
519 else
520 emsg = strerror(code);
523 ret = vasprintf(&formatted, fmt, ap) == -1;
524 if (code) {
525 if (ret > -1 && formatted)
526 ret = asprintf(&msg, "%s: %s (%d)", formatted, emsg, (int)code);
527 } else {
528 msg = formatted;
529 formatted = NULL;
531 heim_audit_addreason((heim_svc_req_desc)r, "%s", msg);
532 audit_trail(r, code);
533 krb5_free_error_message(context, k5msg);
535 if (ret == -1 || msg == NULL) {
536 if (context)
537 krb5_log_msg(r->context, logfac, 1, NULL, "Out of memory");
538 return resp(r, MHD_HTTP_SERVICE_UNAVAILABLE, MHD_RESPMEM_PERSISTENT,
539 NULL, "Out of memory", sizeof("Out of memory") - 1, NULL);
542 ret = resp(r, http_status_code, MHD_RESPMEM_MUST_COPY,
543 NULL, msg, strlen(msg), NULL);
544 free(formatted);
545 free(msg);
546 return ret == -1 ? -1 : code;
549 static krb5_error_code
550 bad_req(struct bx509_request_desc *r,
551 krb5_error_code code,
552 int http_status_code,
553 const char *fmt,
554 ...)
556 krb5_error_code ret;
557 va_list ap;
559 va_start(ap, fmt);
560 ret = bad_reqv(r, code, http_status_code, fmt, ap);
561 va_end(ap);
562 return ret;
565 static krb5_error_code
566 bad_enomem(struct bx509_request_desc *r, krb5_error_code ret)
568 return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
569 "Out of memory");
572 static krb5_error_code
573 bad_400(struct bx509_request_desc *r, int ret, char *reason)
575 return bad_req(r, ret, MHD_HTTP_BAD_REQUEST, "%s", reason);
578 static krb5_error_code
579 bad_401(struct bx509_request_desc *r, char *reason)
581 return bad_req(r, EACCES, MHD_HTTP_UNAUTHORIZED, "%s", reason);
584 static krb5_error_code
585 bad_403(struct bx509_request_desc *r, krb5_error_code ret, char *reason)
587 return bad_req(r, ret, MHD_HTTP_FORBIDDEN, "%s", reason);
590 static krb5_error_code
591 bad_404(struct bx509_request_desc *r, const char *name)
593 return bad_req(r, ENOENT, MHD_HTTP_NOT_FOUND,
594 "Resource not found: %s", name);
597 static krb5_error_code
598 bad_405(struct bx509_request_desc *r, const char *method)
600 return bad_req(r, EPERM, MHD_HTTP_METHOD_NOT_ALLOWED,
601 "Method not supported: %s", method);
604 static krb5_error_code
605 bad_500(struct bx509_request_desc *r,
606 krb5_error_code ret,
607 const char *reason)
609 return bad_req(r, ret, MHD_HTTP_INTERNAL_SERVER_ERROR,
610 "Internal error: %s", reason);
613 static krb5_error_code
614 bad_503(struct bx509_request_desc *r,
615 krb5_error_code ret,
616 const char *reason)
618 return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
619 "Service unavailable: %s", reason);
622 static krb5_error_code
623 good_bx509(struct bx509_request_desc *r)
625 krb5_error_code ret;
626 const char *fn;
627 size_t bodylen;
628 void *body;
631 * This `fn' thing is just to quiet linters that think "hey, strchr() can
632 * return NULL so...", but here we've build `r->pkix_store' and know it has
633 * a ':'.
635 if (r->pkix_store == NULL)
636 return bad_503(r, EINVAL, "Internal error"); /* Quiet warnings */
637 fn = strchr(r->pkix_store, ':');
638 fn = fn ? fn + 1 : r->pkix_store;
639 ret = rk_undumpdata(fn, &body, &bodylen);
640 if (ret)
641 return bad_503(r, ret, "Could not recover issued certificate "
642 "from PKIX store");
644 (void) gettimeofday(&r->tv_end, NULL);
645 ret = resp(r, MHD_HTTP_OK, MHD_RESPMEM_MUST_COPY, "application/x-pem-file",
646 body, bodylen, NULL);
647 free(body);
648 return ret;
651 static heim_mhd_result
652 bx509_param_cb(void *d,
653 enum MHD_ValueKind kind,
654 const char *key,
655 const char *val)
657 struct bx509_request_desc *r = d;
658 heim_oid oid = { 0, 0 };
660 if (strcmp(key, "eku") == 0 && val) {
661 heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS, "requested_eku",
662 "%s", val);
663 r->error_code = der_parse_heim_oid(val, ".", &oid);
664 if (r->error_code == 0)
665 r->error_code = hx509_request_add_eku(r->context->hx509ctx, r->req, &oid);
666 der_free_oid(&oid);
667 } else if (strcmp(key, "dNSName") == 0 && val) {
668 heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
669 "requested_dNSName", "%s", val);
670 r->error_code = hx509_request_add_dns_name(r->context->hx509ctx, r->req, val);
671 } else if (strcmp(key, "rfc822Name") == 0 && val) {
672 heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
673 "requested_rfc822Name", "%s", val);
674 r->error_code = hx509_request_add_email(r->context->hx509ctx, r->req, val);
675 } else if (strcmp(key, "xMPPName") == 0 && val) {
676 heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
677 "requested_xMPPName", "%s", val);
678 r->error_code = hx509_request_add_xmpp_name(r->context->hx509ctx, r->req,
679 val);
680 } else if (strcmp(key, "krb5PrincipalName") == 0 && val) {
681 heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
682 "requested_krb5PrincipalName", "%s", val);
683 r->error_code = hx509_request_add_pkinit(r->context->hx509ctx, r->req,
684 val);
685 } else if (strcmp(key, "ms-upn") == 0 && val) {
686 heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
687 "requested_ms_upn", "%s", val);
688 r->error_code = hx509_request_add_ms_upn_name(r->context->hx509ctx, r->req,
689 val);
690 } else if (strcmp(key, "registeredID") == 0 && val) {
691 heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
692 "requested_registered_id", "%s", val);
693 r->error_code = der_parse_heim_oid(val, ".", &oid);
694 if (r->error_code == 0)
695 r->error_code = hx509_request_add_registered(r->context->hx509ctx, r->req,
696 &oid);
697 der_free_oid(&oid);
698 } else if (strcmp(key, "csr") == 0 && val) {
699 heim_audit_setkv_bool((heim_svc_req_desc)r, "requested_csr", TRUE);
700 r->error_code = 0; /* Handled upstairs */
701 } else if (strcmp(key, "lifetime") == 0 && val) {
702 r->req_life = parse_time(val, "day");
703 } else {
704 /* Produce error for unknown params */
705 heim_audit_setkv_bool((heim_svc_req_desc)r, "requested_unknown", TRUE);
706 krb5_set_error_message(r->context, r->error_code = ENOTSUP,
707 "Query parameter %s not supported", key);
709 return r->error_code == 0 ? MHD_YES : MHD_NO /* Stop iterating */;
712 static krb5_error_code
713 authorize_CSR(struct bx509_request_desc *r,
714 krb5_data *csr,
715 krb5_const_principal p)
717 krb5_error_code ret;
719 ret = hx509_request_parse_der(r->context->hx509ctx, csr, &r->req);
720 if (ret)
721 return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
722 "Could not parse CSR");
723 r->error_code = 0;
724 (void) MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
725 bx509_param_cb, r);
726 ret = r->error_code;
727 if (ret)
728 return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
729 "Could not handle query parameters");
731 ret = kdc_authorize_csr(r->context, "bx509", r->req, p);
732 if (ret)
733 return bad_403(r, ret, "Not authorized to requested certificate");
734 return ret;
738 * hx509_certs_iter_f() callback to assign a private key to the first cert in a
739 * store.
741 static int HX509_LIB_CALL
742 set_priv_key(hx509_context context, void *d, hx509_cert c)
744 (void) _hx509_cert_assign_key(c, (hx509_private_key)d);
745 return -1; /* stop iteration */
748 static krb5_error_code
749 store_certs(hx509_context context,
750 const char *store,
751 hx509_certs store_these,
752 hx509_private_key key)
754 krb5_error_code ret;
755 hx509_certs certs = NULL;
757 ret = hx509_certs_init(context, store, HX509_CERTS_CREATE, NULL,
758 &certs);
759 if (ret == 0) {
760 if (key)
761 (void) hx509_certs_iter_f(context, store_these, set_priv_key, key);
762 hx509_certs_merge(context, certs, store_these);
764 if (ret == 0)
765 hx509_certs_store(context, certs, 0, NULL);
766 hx509_certs_free(&certs);
767 return ret;
770 /* Setup a CSR for bx509() */
771 static krb5_error_code
772 do_CA(struct bx509_request_desc *r, const char *csr)
774 krb5_error_code ret = 0;
775 krb5_principal p;
776 hx509_certs certs = NULL;
777 krb5_data d;
778 ssize_t bytes;
779 char *csr2, *q;
782 * Work around bug where microhttpd decodes %2b to + then + to space. That
783 * bug does not affect other base64 special characters that get URI
784 * %-encoded.
786 if ((csr2 = strdup(csr)) == NULL)
787 return bad_enomem(r, ENOMEM);
788 for (q = strchr(csr2, ' '); q; q = strchr(q + 1, ' '))
789 *q = '+';
791 ret = krb5_parse_name(r->context, r->cname, &p);
792 if (ret) {
793 free(csr2);
794 return bad_req(r, ret, MHD_HTTP_SERVICE_UNAVAILABLE,
795 "Could not parse principal name");
798 /* Set CSR */
799 if ((d.data = malloc(strlen(csr2))) == NULL) {
800 krb5_free_principal(r->context, p);
801 free(csr2);
802 return bad_enomem(r, ENOMEM);
805 bytes = rk_base64_decode(csr2, d.data);
806 free(csr2);
807 if (bytes < 0)
808 ret = errno;
809 else
810 d.length = bytes;
811 if (ret) {
812 krb5_free_principal(r->context, p);
813 free(d.data);
814 return bad_500(r, ret, "Invalid base64 encoding of CSR");
818 * Parses and validates the CSR, adds external extension requests from
819 * query parameters, then checks authorization.
821 ret = authorize_CSR(r, &d, p);
822 free(d.data);
823 d.data = 0;
824 d.length = 0;
825 if (ret) {
826 krb5_free_principal(r->context, p);
827 return ret; /* authorize_CSR() calls bad_req() */
830 /* Issue the certificate */
831 ret = kdc_issue_certificate(r->context, "bx509", logfac, r->req, p,
832 &r->token_times, r->req_life,
833 1 /* send_chain */, &certs);
834 krb5_free_principal(r->context, p);
835 if (ret) {
836 if (ret == KRB5KDC_ERR_POLICY || ret == EACCES)
837 return bad_403(r, ret,
838 "Certificate request denied for policy reasons");
839 return bad_500(r, ret, "Certificate issuance failed");
842 /* Setup PKIX store */
843 if ((ret = mk_pkix_store(&r->pkix_store)))
844 return bad_500(r, ret,
845 "Could not create PEM store for issued certificate");
847 ret = store_certs(r->context->hx509ctx, r->pkix_store, certs, NULL);
848 hx509_certs_free(&certs);
849 if (ret)
850 return bad_500(r, ret, "Failed to convert issued"
851 " certificate and chain to PEM");
852 return 0;
855 /* Copied from kdc/connect.c */
856 static void
857 addr_to_string(krb5_context context,
858 struct sockaddr *addr,
859 char *str,
860 size_t len)
862 krb5_error_code ret;
863 krb5_address a;
865 ret = krb5_sockaddr2address(context, addr, &a);
866 if (ret == 0) {
867 ret = krb5_print_address(&a, str, len, &len);
868 krb5_free_address(context, &a);
870 if (ret)
871 snprintf(str, len, "<family=%d>", addr->sa_family);
874 static krb5_error_code
875 set_req_desc(struct MHD_Connection *connection,
876 const char *url,
877 struct bx509_request_desc *r)
879 const union MHD_ConnectionInfo *ci;
880 const char *token;
881 krb5_error_code ret;
883 memset(r, 0, sizeof(*r));
884 (void) gettimeofday(&r->tv_start, NULL);
886 ret = get_krb5_context(&r->context);
887 r->connection = connection;
888 r->request.data = "<HTTP-REQUEST>";
889 r->request.length = sizeof("<HTTP-REQUEST>");
890 r->from = r->frombuf;
891 r->tgt_addresses.len = 0;
892 r->tgt_addresses.val = 0;
893 r->hcontext = r->context ? r->context->hcontext : NULL;
894 r->config = NULL;
895 r->logf = logfac;
896 r->reqtype = url;
897 r->target = r->redir = NULL;
898 r->pkix_store = NULL;
899 r->for_cname = NULL;
900 r->freeme1 = NULL;
901 r->reason = NULL;
902 r->ccname = NULL;
903 r->reply = NULL;
904 r->sname = NULL;
905 r->cname = NULL;
906 r->addr = NULL;
907 r->req = NULL;
908 r->req_life = 0;
909 r->error_code = ret;
910 r->kv = heim_dict_create(10);
911 r->attributes = heim_dict_create(1);
912 if (ret == 0 && (r->kv == NULL || r->attributes == NULL))
913 r->error_code = ret = ENOMEM;
914 ci = MHD_get_connection_info(connection,
915 MHD_CONNECTION_INFO_CLIENT_ADDRESS);
916 if (ci) {
917 r->addr = ci->client_addr;
918 addr_to_string(r->context, r->addr, r->frombuf, sizeof(r->frombuf));
921 heim_audit_addkv((heim_svc_req_desc)r, 0, "method", "GET");
922 heim_audit_addkv((heim_svc_req_desc)r, 0, "endpoint", "%s", r->reqtype);
923 token = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
924 MHD_HTTP_HEADER_AUTHORIZATION);
925 if (token && r->kv) {
926 const char *token_end;
928 if ((token_end = strchr(token, ' ')) == NULL ||
929 (token_end - token) > INT_MAX || (token_end - token) < 2)
930 heim_audit_addkv((heim_svc_req_desc)r, 0, "auth", "<unknown>");
931 else
932 heim_audit_addkv((heim_svc_req_desc)r, 0, "auth", "%.*s",
933 (int)(token_end - token), token);
937 return ret;
940 static void
941 clean_req_desc(struct bx509_request_desc *r)
943 if (!r)
944 return;
945 if (r->pkix_store) {
946 const char *fn = strchr(r->pkix_store, ':');
949 * This `fn' thing is just to quiet linters that think "hey, strchr() can
950 * return NULL so...", but here we've build `r->pkix_store' and know it has
951 * a ':'.
953 fn = fn ? fn + 1 : r->pkix_store;
954 (void) unlink(fn);
956 krb5_free_addresses(r->context, &r->tgt_addresses);
957 hx509_request_free(&r->req);
958 heim_release(r->reason);
959 heim_release(r->kv);
960 if (r->ccname && r->cckind == K5_CREDS_EPHEMERAL) {
961 const char *fn = r->ccname;
963 if (strncmp(fn, "FILE:", sizeof("FILE:") - 1) == 0)
964 fn += sizeof("FILE:") - 1;
965 (void) unlink(fn);
967 free(r->pkix_store);
968 free(r->freeme1);
969 free(r->ccname);
970 free(r->cname);
971 free(r->sname);
974 /* Implements GETs of /bx509 */
975 static krb5_error_code
976 bx509(struct bx509_request_desc *r)
978 krb5_error_code ret;
979 const char *csr;
981 /* Get required inputs */
982 csr = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
983 "csr");
984 if (csr == NULL)
985 return bad_400(r, EINVAL, "CSR is missing");
987 if ((ret = validate_token(r)))
988 return ret; /* validate_token() calls bad_req() */
990 if (r->cname == NULL)
991 return bad_403(r, EINVAL,
992 "Could not extract principal name from token");
994 /* Parse CSR, add extensions from parameters, authorize, issue cert */
995 if ((ret = do_CA(r, csr)))
996 return ret;
998 /* Read and send the contents of the PKIX store */
999 krb5_log_msg(r->context, logfac, 1, NULL, "Issued certificate to %s",
1000 r->cname);
1001 return good_bx509(r);
1005 * princ_fs_encode_sz() and princ_fs_encode() encode a principal name to be
1006 * safe for use as a file name. They function very much like URL encoders, but
1007 * '~' and '.' also get encoded, and '@' does not.
1009 * A corresponding decoder is not needed.
1011 * XXX Maybe use krb5_cc_default_for()!
1013 static size_t
1014 princ_fs_encode_sz(const char *in)
1016 size_t sz = strlen(in);
1018 while (*in) {
1019 unsigned char c = *(const unsigned char *)(in++);
1021 if (isalnum(c))
1022 continue;
1023 switch (c) {
1024 case '@':
1025 case '-':
1026 case '_':
1027 continue;
1028 default:
1029 sz += 2;
1032 return sz;
1035 static char *
1036 princ_fs_encode(const char *in)
1038 size_t len = strlen(in);
1039 size_t sz = princ_fs_encode_sz(in);
1040 size_t i, k;
1041 char *s;
1043 if ((s = malloc(sz + 1)) == NULL)
1044 return NULL;
1045 s[sz] = '\0';
1047 for (i = k = 0; i < len; i++) {
1048 char c = in[i];
1050 switch (c) {
1051 case '@':
1052 case '-':
1053 case '_':
1054 s[k++] = c;
1055 break;
1056 default:
1057 if (isalnum(c)) {
1058 s[k++] = c;
1059 } else {
1060 s[k++] = '%';
1061 s[k++] = "0123456789abcdef"[(c&0xff)>>4];
1062 s[k++] = "0123456789abcdef"[(c&0x0f)];
1066 return s;
1071 * Find an existing, live ccache for `princ' in `cache_dir' or acquire Kerberos
1072 * creds for `princ' with PKINIT and put them in a ccache in `cache_dir'.
1074 static krb5_error_code
1075 find_ccache(krb5_context context, const char *princ, char **ccname)
1077 krb5_error_code ret = ENOMEM;
1078 krb5_ccache cc = NULL;
1079 time_t life;
1080 char *s = NULL;
1082 *ccname = NULL;
1085 * Name the ccache after the principal. The principal may have special
1086 * characters in it, such as / or \ (path component separarot), or shell
1087 * special characters, so princ_fs_encode() it to make a ccache name.
1089 if ((s = princ_fs_encode(princ)) == NULL ||
1090 asprintf(ccname, "FILE:%s/%s.cc", cache_dir, s) == -1 ||
1091 *ccname == NULL) {
1092 free(s);
1093 return ENOMEM;
1095 free(s);
1097 if ((ret = krb5_cc_resolve(context, *ccname, &cc))) {
1098 /* krb5_cc_resolve() suceeds even if the file doesn't exist */
1099 free(*ccname);
1100 *ccname = NULL;
1101 cc = NULL;
1104 /* Check if we have a good enough credential */
1105 if (ret == 0 &&
1106 (ret = krb5_cc_get_lifetime(context, cc, &life)) == 0 && life > 60) {
1107 krb5_cc_close(context, cc);
1108 return 0;
1110 if (cc)
1111 krb5_cc_close(context, cc);
1112 return ret ? ret : ENOENT;
1115 static krb5_error_code
1116 get_ccache(struct bx509_request_desc *r, krb5_ccache *cc, int *won)
1118 krb5_error_code ret = 0;
1119 char *temp_ccname = NULL;
1120 const char *fn = NULL;
1121 time_t life;
1122 int fd = -1;
1125 * Open and lock a .new ccache file. Use .new to avoid garbage files on
1126 * crash.
1128 * We can race with other threads to do this, so we loop until we
1129 * definitively win or definitely lose the race. We win when we have a) an
1130 * open FD that is b) flock'ed, and c) we observe with lstat() that the
1131 * file we opened and locked is the same as on disk after locking.
1133 * We don't close the FD until we're done.
1135 * If we had a proper anon MEMORY ccache, we could instead use that for a
1136 * temporary ccache, and then the initialization of and move to the final
1137 * FILE ccache would take care to mkstemp() and rename() into place.
1138 * fcc_open() basically does a similar thing.
1140 *cc = NULL;
1141 *won = -1;
1142 if (asprintf(&temp_ccname, "%s.ccnew", r->ccname) == -1 ||
1143 temp_ccname == NULL)
1144 ret = ENOMEM;
1145 if (ret == 0)
1146 fn = temp_ccname + sizeof("FILE:") - 1;
1147 if (ret == 0) do {
1148 struct stat st1, st2;
1150 * Open and flock the temp ccache file.
1152 * XXX We should really a) use _krb5_xlock(), or move that into
1153 * lib/roken anyways, b) abstract this loop into a utility function in
1154 * lib/roken.
1156 if (fd != -1) {
1157 (void) close(fd);
1158 fd = -1;
1160 errno = 0;
1161 memset(&st1, 0, sizeof(st1));
1162 memset(&st2, 0xff, sizeof(st2));
1163 if (ret == 0 &&
1164 ((fd = open(fn, O_RDWR | O_CREAT, 0600)) == -1 ||
1165 flock(fd, LOCK_EX) == -1 ||
1166 (lstat(fn, &st1) == -1 && errno != ENOENT) ||
1167 fstat(fd, &st2) == -1))
1168 ret = errno;
1169 if (ret == 0 && errno == 0 &&
1170 st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) {
1171 if (S_ISREG(st1.st_mode))
1172 break;
1173 if (unlink(fn) == -1)
1174 ret = errno;
1176 } while (ret == 0);
1178 /* Check if we lost any race to acquire Kerberos creds */
1179 if (ret == 0)
1180 ret = krb5_cc_resolve(r->context, temp_ccname, cc);
1181 if (ret == 0) {
1182 ret = krb5_cc_get_lifetime(r->context, *cc, &life);
1183 if (ret == 0 && life > 60)
1184 *won = 0; /* We lost the race, but we win: we get to do less work */
1185 *won = 1;
1186 ret = 0;
1188 free(temp_ccname);
1189 if (fd != -1)
1190 (void) close(fd); /* Drops the flock */
1191 return ret;
1195 * Acquire credentials for `princ' using PKINIT and the PKIX credentials in
1196 * `pkix_store', then place the result in the ccache named `ccname' (which will
1197 * be in our own private `cache_dir').
1199 * XXX This function could be rewritten using gss_acquire_cred_from() and
1200 * gss_store_cred_into() provided we add new generic cred store key/value pairs
1201 * for PKINIT.
1203 static krb5_error_code
1204 do_pkinit(struct bx509_request_desc *r, enum k5_creds_kind kind)
1206 krb5_get_init_creds_opt *opt = NULL;
1207 krb5_init_creds_context ctx = NULL;
1208 krb5_error_code ret = 0;
1209 krb5_ccache temp_cc = NULL;
1210 krb5_ccache cc = NULL;
1211 krb5_principal p = NULL;
1212 const char *crealm;
1213 const char *cname = r->for_cname ? r->for_cname : r->cname;
1215 if (kind == K5_CREDS_CACHED) {
1216 int won = -1;
1218 ret = get_ccache(r, &temp_cc, &won);
1219 if (ret || !won)
1220 goto out;
1222 * We won the race to do PKINIT. Setup to acquire Kerberos creds with
1223 * PKINIT.
1225 * We should really make sure that gss_acquire_cred_from() can do this
1226 * for us. We'd add generic cred store key/value pairs for PKIX cred
1227 * store, trust anchors, and so on, and acquire that way, then
1228 * gss_store_cred_into() to save it in a FILE ccache.
1230 } else {
1231 ret = krb5_cc_new_unique(r->context, "FILE", NULL, &temp_cc);
1234 if (ret == 0)
1235 ret = krb5_parse_name(r->context, cname, &p);
1236 if (ret == 0)
1237 crealm = krb5_principal_get_realm(r->context, p);
1238 if (ret == 0)
1239 ret = krb5_get_init_creds_opt_alloc(r->context, &opt);
1240 if (ret == 0)
1241 krb5_get_init_creds_opt_set_default_flags(r->context, "kinit", crealm,
1242 opt);
1243 if (ret == 0 && kind == K5_CREDS_EPHEMERAL &&
1244 !krb5_config_get_bool_default(r->context, NULL, TRUE,
1245 "get-tgt", "no_addresses", NULL)) {
1246 krb5_addresses addr;
1248 ret = _krb5_parse_address_no_lookup(r->context, r->frombuf, &addr);
1249 if (ret == 0)
1250 ret = krb5_append_addresses(r->context, &r->tgt_addresses,
1251 &addr);
1253 if (ret == 0 && r->tgt_addresses.len == 0)
1254 ret = krb5_get_init_creds_opt_set_addressless(r->context, opt, 1);
1255 else
1256 krb5_get_init_creds_opt_set_address_list(opt, &r->tgt_addresses);
1257 if (ret == 0)
1258 ret = krb5_get_init_creds_opt_set_pkinit(r->context, opt, p,
1259 r->pkix_store,
1260 NULL, /* pkinit_anchor */
1261 NULL, /* anchor_chain */
1262 NULL, /* pkinit_crl */
1263 0, /* flags */
1264 NULL, /* prompter */
1265 NULL, /* prompter data */
1266 NULL /* password */);
1267 if (ret == 0)
1268 ret = krb5_init_creds_init(r->context, p,
1269 NULL /* prompter */,
1270 NULL /* prompter data */,
1271 0 /* start_time */,
1272 opt, &ctx);
1275 * Finally, do the AS exchange w/ PKINIT, extract the new Kerberos creds
1276 * into temp_cc, and rename into place. Note that krb5_cc_move() closes
1277 * the source ccache, so we set temp_cc = NULL if it succeeds.
1279 if (ret == 0)
1280 ret = krb5_init_creds_get(r->context, ctx);
1281 if (ret == 0)
1282 ret = krb5_init_creds_store(r->context, ctx, temp_cc);
1283 if (kind == K5_CREDS_CACHED) {
1284 if (ret == 0)
1285 ret = krb5_cc_resolve(r->context, r->ccname, &cc);
1286 if (ret == 0)
1287 ret = krb5_cc_move(r->context, temp_cc, cc);
1288 if (ret == 0)
1289 temp_cc = NULL;
1290 } else if (ret == 0 && kind == K5_CREDS_EPHEMERAL) {
1291 ret = krb5_cc_get_full_name(r->context, temp_cc, &r->ccname);
1294 out:
1295 if (ctx)
1296 krb5_init_creds_free(r->context, ctx);
1297 krb5_get_init_creds_opt_free(r->context, opt);
1298 krb5_free_principal(r->context, p);
1299 krb5_cc_close(r->context, temp_cc);
1300 krb5_cc_close(r->context, cc);
1301 return ret;
1304 static krb5_error_code
1305 load_priv_key(krb5_context context, const char *fn, hx509_private_key *key)
1307 hx509_private_key *keys = NULL;
1308 krb5_error_code ret;
1309 hx509_certs certs = NULL;
1311 *key = NULL;
1312 ret = hx509_certs_init(context->hx509ctx, fn, 0, NULL, &certs);
1313 if (ret == ENOENT)
1314 return 0;
1315 if (ret == 0)
1316 ret = _hx509_certs_keys_get(context->hx509ctx, certs, &keys);
1317 if (ret == 0 && keys[0] == NULL)
1318 ret = ENOENT; /* XXX Better error please */
1319 if (ret == 0)
1320 *key = _hx509_private_key_ref(keys[0]);
1321 if (ret)
1322 krb5_set_error_message(context, ret, "Could not load private "
1323 "impersonation key from %s for PKINIT: %s", fn,
1324 hx509_get_error_string(context->hx509ctx, ret));
1325 _hx509_certs_keys_free(context->hx509ctx, keys);
1326 hx509_certs_free(&certs);
1327 return ret;
1330 static krb5_error_code
1331 k5_do_CA(struct bx509_request_desc *r)
1333 SubjectPublicKeyInfo spki;
1334 hx509_private_key key = NULL;
1335 krb5_error_code ret = 0;
1336 krb5_principal p = NULL;
1337 hx509_request req = NULL;
1338 hx509_certs certs = NULL;
1339 KeyUsage ku = int2KeyUsage(0);
1340 const char *cname = r->for_cname ? r->for_cname : r->cname;
1342 memset(&spki, 0, sizeof(spki));
1343 ku.digitalSignature = 1;
1345 /* Make a CSR (halfway -- we don't need to sign it here) */
1346 /* XXX Load impersonation key just once?? */
1347 ret = load_priv_key(r->context, impersonation_key_fn, &key);
1348 if (ret == 0)
1349 ret = hx509_request_init(r->context->hx509ctx, &req);
1350 if (ret == 0)
1351 ret = krb5_parse_name(r->context, cname, &p);
1352 if (ret == 0)
1353 ret = hx509_private_key2SPKI(r->context->hx509ctx, key, &spki);
1354 if (ret == 0)
1355 hx509_request_set_SubjectPublicKeyInfo(r->context->hx509ctx, req,
1356 &spki);
1357 free_SubjectPublicKeyInfo(&spki);
1358 if (ret == 0)
1359 ret = hx509_request_add_pkinit(r->context->hx509ctx, req, cname);
1360 if (ret == 0)
1361 ret = hx509_request_add_eku(r->context->hx509ctx, req,
1362 &asn1_oid_id_pkekuoid);
1364 /* Mark it authorized */
1365 if (ret == 0)
1366 ret = hx509_request_authorize_san(req, 0);
1367 if (ret == 0)
1368 ret = hx509_request_authorize_eku(req, 0);
1369 if (ret == 0)
1370 hx509_request_authorize_ku(req, ku);
1372 /* Issue the certificate */
1373 if (ret == 0)
1374 ret = kdc_issue_certificate(r->context, "get-tgt", logfac, req, p,
1375 &r->token_times, r->req_life,
1376 1 /* send_chain */, &certs);
1377 krb5_free_principal(r->context, p);
1378 hx509_request_free(&req);
1379 p = NULL;
1381 if (ret == KRB5KDC_ERR_POLICY || ret == EACCES) {
1382 hx509_private_key_free(&key);
1383 return bad_403(r, ret,
1384 "Certificate request denied for policy reasons");
1386 if (ret == ENOMEM) {
1387 hx509_private_key_free(&key);
1388 return bad_503(r, ret, "Certificate issuance failed");
1390 if (ret) {
1391 hx509_private_key_free(&key);
1392 return bad_500(r, ret, "Certificate issuance failed");
1395 /* Setup PKIX store and extract the certificate chain into it */
1396 ret = mk_pkix_store(&r->pkix_store);
1397 if (ret == 0)
1398 ret = store_certs(r->context->hx509ctx, r->pkix_store, certs, key);
1399 hx509_private_key_free(&key);
1400 hx509_certs_free(&certs);
1401 if (ret)
1402 return bad_500(r, ret,
1403 "Could not create PEM store for issued certificate");
1404 return 0;
1407 /* Get impersonated Kerberos credentials for `cprinc' */
1408 static krb5_error_code
1409 k5_get_creds(struct bx509_request_desc *r, enum k5_creds_kind kind)
1411 krb5_error_code ret;
1412 const char *cname = r->for_cname ? r->for_cname : r->cname;
1414 /* If we have a live ccache for `cprinc', we're done */
1415 r->cckind = kind;
1416 if (kind == K5_CREDS_CACHED &&
1417 (ret = find_ccache(r->context, cname, &r->ccname)) == 0)
1418 return ret; /* Success */
1421 * Else we have to acquire a credential for them using their bearer token
1422 * for authentication (and our keytab / initiator credentials perhaps).
1424 if ((ret = k5_do_CA(r)))
1425 return ret; /* k5_do_CA() calls bad_req() */
1427 if (ret == 0 && (ret = do_pkinit(r, kind)))
1428 ret = bad_403(r, ret,
1429 "Could not acquire Kerberos credentials using PKINIT");
1430 return ret;
1433 /* Accumulate strings */
1434 static void
1435 acc_str(char **acc, char *adds, size_t addslen)
1437 char *tmp;
1438 int l = addslen <= INT_MAX ? (int)addslen : INT_MAX;
1440 if (asprintf(&tmp, "%s%s%.*s",
1441 *acc ? *acc : "",
1442 *acc ? "; " : "", l, adds) > -1 &&
1443 tmp) {
1444 free(*acc);
1445 *acc = tmp;
1449 static char *
1450 fmt_gss_error(OM_uint32 code, gss_OID mech)
1452 gss_buffer_desc buf;
1453 OM_uint32 major, minor;
1454 OM_uint32 type = mech == GSS_C_NO_OID ? GSS_C_GSS_CODE: GSS_C_MECH_CODE;
1455 OM_uint32 more = 0;
1456 char *r = NULL;
1458 do {
1459 major = gss_display_status(&minor, code, type, mech, &more, &buf);
1460 if (!GSS_ERROR(major))
1461 acc_str(&r, (char *)buf.value, buf.length);
1462 gss_release_buffer(&minor, &buf);
1463 } while (!GSS_ERROR(major) && more);
1464 return r ? r : "Out of memory while formatting GSS-API error";
1467 static char *
1468 fmt_gss_errors(const char *r, OM_uint32 major, OM_uint32 minor, gss_OID mech)
1470 char *ma, *mi, *s;
1472 ma = fmt_gss_error(major, GSS_C_NO_OID);
1473 mi = mech == GSS_C_NO_OID ? NULL : fmt_gss_error(minor, mech);
1474 if (asprintf(&s, "%s: %s%s%s", r, ma, mi ? ": " : "", mi ? mi : "") > -1 &&
1475 s) {
1476 free(ma);
1477 free(mi);
1478 return s;
1480 free(mi);
1481 return ma;
1484 /* GSS-API error */
1485 static krb5_error_code
1486 bad_req_gss(struct bx509_request_desc *r,
1487 OM_uint32 major,
1488 OM_uint32 minor,
1489 gss_OID mech,
1490 int http_status_code,
1491 const char *reason)
1493 krb5_error_code ret;
1494 char *msg = fmt_gss_errors(reason, major, minor, mech);
1496 if (major == GSS_S_BAD_NAME || major == GSS_S_BAD_NAMETYPE)
1497 http_status_code = MHD_HTTP_BAD_REQUEST;
1499 ret = resp(r, http_status_code, MHD_RESPMEM_MUST_COPY, NULL,
1500 msg, strlen(msg), NULL);
1501 free(msg);
1502 return ret;
1505 /* Make an HTTP/Negotiate token */
1506 static krb5_error_code
1507 mk_nego_tok(struct bx509_request_desc *r,
1508 char **nego_tok,
1509 size_t *nego_toksz)
1511 gss_key_value_element_desc kv[1] = { { "ccache", r->ccname } };
1512 gss_key_value_set_desc store = { 1, kv };
1513 gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
1514 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
1515 gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
1516 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
1517 gss_name_t iname = GSS_C_NO_NAME;
1518 gss_name_t aname = GSS_C_NO_NAME;
1519 OM_uint32 major, minor, junk;
1520 krb5_error_code ret; /* More like a system error code here */
1521 const char *cname = r->for_cname ? r->for_cname : r->cname;
1522 char *token_b64 = NULL;
1524 *nego_tok = NULL;
1525 *nego_toksz = 0;
1527 /* Import initiator name */
1528 name.length = strlen(cname);
1529 name.value = rk_UNCONST(cname);
1530 major = gss_import_name(&minor, &name, GSS_KRB5_NT_PRINCIPAL_NAME, &iname);
1531 if (major != GSS_S_COMPLETE)
1532 return bad_req_gss(r, major, minor, GSS_C_NO_OID,
1533 MHD_HTTP_SERVICE_UNAVAILABLE,
1534 "Could not import cprinc parameter value as "
1535 "Kerberos principal name");
1537 /* Import target acceptor name */
1538 name.length = strlen(r->target);
1539 name.value = rk_UNCONST(r->target);
1540 major = gss_import_name(&minor, &name, GSS_C_NT_HOSTBASED_SERVICE, &aname);
1541 if (major != GSS_S_COMPLETE) {
1542 (void) gss_release_name(&junk, &iname);
1543 return bad_req_gss(r, major, minor, GSS_C_NO_OID,
1544 MHD_HTTP_SERVICE_UNAVAILABLE,
1545 "Could not import target parameter value as "
1546 "Kerberos principal name");
1549 /* Acquire a credential from the given ccache */
1550 major = gss_add_cred_from(&minor, cred, iname, GSS_KRB5_MECHANISM,
1551 GSS_C_INITIATE, GSS_C_INDEFINITE, 0, &store,
1552 &cred, NULL, NULL, NULL);
1553 (void) gss_release_name(&junk, &iname);
1554 if (major != GSS_S_COMPLETE) {
1555 (void) gss_release_name(&junk, &aname);
1556 return bad_req_gss(r, major, minor, GSS_KRB5_MECHANISM,
1557 MHD_HTTP_FORBIDDEN, "Could not acquire credentials "
1558 "for requested cprinc");
1561 major = gss_init_sec_context(&minor, cred, &ctx, aname,
1562 GSS_KRB5_MECHANISM, 0, GSS_C_INDEFINITE,
1563 NULL, GSS_C_NO_BUFFER, NULL, &token, NULL,
1564 NULL);
1565 (void) gss_delete_sec_context(&junk, &ctx, GSS_C_NO_BUFFER);
1566 (void) gss_release_name(&junk, &aname);
1567 (void) gss_release_cred(&junk, &cred);
1568 if (major != GSS_S_COMPLETE)
1569 return bad_req_gss(r, major, minor, GSS_KRB5_MECHANISM,
1570 MHD_HTTP_SERVICE_UNAVAILABLE, "Could not acquire "
1571 "Negotiate token for requested target");
1573 /* Encode token, output */
1574 ret = rk_base64_encode(token.value, token.length, &token_b64);
1575 (void) gss_release_buffer(&junk, &token);
1576 if (ret > 0)
1577 ret = asprintf(nego_tok, "Negotiate %s", token_b64);
1578 free(token_b64);
1579 if (ret < 0 || *nego_tok == NULL)
1580 return bad_req(r, errno, MHD_HTTP_SERVICE_UNAVAILABLE,
1581 "Could not allocate memory for encoding Negotiate "
1582 "token");
1583 *nego_toksz = ret;
1584 return 0;
1587 static krb5_error_code
1588 bnegotiate_get_target(struct bx509_request_desc *r)
1590 const char *target;
1591 const char *redir;
1592 const char *referer; /* misspelled on the wire, misspelled here, FYI */
1593 const char *authority;
1594 const char *local_part;
1595 char *s1 = NULL;
1596 char *s2 = NULL;
1598 target = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
1599 "target");
1600 redir = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
1601 "redirect");
1602 referer = MHD_lookup_connection_value(r->connection, MHD_HEADER_KIND,
1603 MHD_HTTP_HEADER_REFERER);
1604 if (target != NULL && redir == NULL) {
1605 r->target = target;
1606 return 0;
1608 if (target == NULL && redir == NULL)
1609 return bad_400(r, EINVAL,
1610 "Query missing 'target' or 'redirect' parameter value");
1611 if (target != NULL && redir != NULL)
1612 return bad_403(r, EACCES,
1613 "Only one of 'target' or 'redirect' parameter allowed");
1614 if (redir != NULL && referer == NULL)
1615 return bad_403(r, EACCES,
1616 "Redirect request without Referer header nor allowed");
1618 if (strncmp(referer, "https://", sizeof("https://") - 1) != 0 ||
1619 strncmp(redir, "https://", sizeof("https://") - 1) != 0)
1620 return bad_403(r, EACCES,
1621 "Redirect requests permitted only for https referrers");
1623 /* Parse out authority from each URI, redirect and referrer */
1624 authority = redir + sizeof("https://") - 1;
1625 if ((local_part = strchr(authority, '/')) == NULL)
1626 local_part = authority + strlen(authority);
1627 if ((s1 = strndup(authority, local_part - authority)) == NULL)
1628 return bad_enomem(r, ENOMEM);
1630 authority = referer + sizeof("https://") - 1;
1631 if ((local_part = strchr(authority, '/')) == NULL)
1632 local_part = authority + strlen(authority);
1633 if ((s2 = strndup(authority, local_part - authority)) == NULL) {
1634 free(s1);
1635 return bad_enomem(r, ENOMEM);
1638 /* Both must match */
1639 if (strcasecmp(s1, s2) != 0) {
1640 free(s2);
1641 free(s1);
1642 return bad_403(r, EACCES, "Redirect request does not match referer");
1644 free(s2);
1646 if (strchr(s1, '@')) {
1647 free(s1);
1648 return bad_403(r, EACCES,
1649 "Redirect request authority has login information");
1652 /* Extract hostname portion of authority and format GSS name */
1653 if (strchr(s1, ':'))
1654 *strchr(s1, ':') = '\0';
1655 if (asprintf(&r->freeme1, "HTTP@%s", s1) == -1 || r->freeme1 == NULL) {
1656 free(s1);
1657 return bad_enomem(r, ENOMEM);
1660 r->target = r->freeme1;
1661 r->redir = redir;
1662 free(s1);
1663 return 0;
1667 * Implements /bnegotiate end-point.
1669 * Query parameters (mutually exclusive):
1671 * - target=<name>
1672 * - redirect=<URL-encoded-URL>
1674 * If the redirect query parameter is set then the Referer: header must be as
1675 * well, and the authority of the redirect and Referer URIs must be the same.
1677 static krb5_error_code
1678 bnegotiate(struct bx509_request_desc *r)
1680 krb5_error_code ret;
1681 size_t nego_toksz = 0;
1682 char *nego_tok = NULL;
1684 ret = bnegotiate_get_target(r);
1685 if (ret == 0) {
1686 heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS, "target", "%s",
1687 r->target ? r->target : "<unknown>");
1688 heim_audit_setkv_bool((heim_svc_req_desc)r, "redir", !!r->redir);
1689 ret = validate_token(r);
1691 /* bnegotiate_get_target() and validate_token() call bad_req() */
1692 if (ret)
1693 return ret;
1696 * Make sure we have Kerberos credentials for cprinc. If we have them
1697 * cached from earlier, this will be fast (all local), else it will involve
1698 * taking a file lock and talking to the KDC using kx509 and PKINIT.
1700 * Perhaps we could use S4U instead, which would speed up the slow path a
1701 * bit.
1703 ret = k5_get_creds(r, K5_CREDS_CACHED);
1704 if (ret)
1705 return ret;
1707 /* Acquire the Negotiate token and output it */
1708 if (ret == 0 && r->ccname != NULL)
1709 ret = mk_nego_tok(r, &nego_tok, &nego_toksz);
1711 if (ret == 0) {
1712 /* Look ma', Negotiate as an OAuth-like token system! */
1713 if (r->redir)
1714 ret = resp(r, MHD_HTTP_TEMPORARY_REDIRECT, MHD_RESPMEM_PERSISTENT,
1715 NULL, "", 0, nego_tok);
1716 else
1717 ret = resp(r, MHD_HTTP_OK, MHD_RESPMEM_MUST_COPY,
1718 "application/x-negotiate-token", nego_tok, nego_toksz,
1719 NULL);
1722 free(nego_tok);
1723 return ret;
1726 static krb5_error_code
1727 authorize_TGT_REQ(struct bx509_request_desc *r)
1729 krb5_principal p = NULL;
1730 krb5_error_code ret;
1731 const char *for_cname = r->for_cname ? r->for_cname : r->cname;
1733 if (for_cname == r->cname || strcmp(r->cname, r->for_cname) == 0)
1734 return 0;
1736 ret = krb5_parse_name(r->context, r->cname, &p);
1737 if (ret == 0)
1738 ret = hx509_request_init(r->context->hx509ctx, &r->req);
1739 if (ret)
1740 return bad_500(r, ret, "Out of resources");
1741 heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
1742 "requested_krb5PrincipalName", "%s", for_cname);
1743 ret = hx509_request_add_eku(r->context->hx509ctx, r->req,
1744 ASN1_OID_ID_PKEKUOID);
1745 if (ret == 0)
1746 ret = hx509_request_add_pkinit(r->context->hx509ctx, r->req,
1747 for_cname);
1748 if (ret == 0)
1749 ret = kdc_authorize_csr(r->context, "get-tgt", r->req, p);
1750 krb5_free_principal(r->context, p);
1751 hx509_request_free(&r->req);
1752 if (ret)
1753 return bad_403(r, ret, "Not authorized to requested TGT");
1754 return ret;
1757 static heim_mhd_result
1758 get_tgt_param_cb(void *d,
1759 enum MHD_ValueKind kind,
1760 const char *key,
1761 const char *val)
1763 struct bx509_request_desc *r = d;
1765 if (strcmp(key, "address") == 0 && val) {
1766 if (!krb5_config_get_bool_default(r->context, NULL,
1767 FALSE,
1768 "get-tgt", "allow_addresses", NULL)) {
1769 krb5_set_error_message(r->context, r->error_code = ENOTSUP,
1770 "Query parameter %s not allowed", key);
1771 } else {
1772 krb5_addresses addresses;
1774 r->error_code = _krb5_parse_address_no_lookup(r->context, val,
1775 &addresses);
1776 if (r->error_code == 0)
1777 r->error_code = krb5_append_addresses(r->context, &r->tgt_addresses,
1778 &addresses);
1779 krb5_free_addresses(r->context, &addresses);
1781 } else if (strcmp(key, "cname") == 0) {
1782 /* Handled upstairs */
1784 } else if (strcmp(key, "lifetime") == 0 && val) {
1785 r->req_life = parse_time(val, "day");
1786 } else {
1787 /* Produce error for unknown params */
1788 heim_audit_setkv_bool((heim_svc_req_desc)r, "requested_unknown", TRUE);
1789 krb5_set_error_message(r->context, r->error_code = ENOTSUP,
1790 "Query parameter %s not supported", key);
1792 return r->error_code == 0 ? MHD_YES : MHD_NO /* Stop iterating */;
1796 * Implements /get-tgt end-point.
1798 * Query parameters (mutually exclusive):
1800 * - cname=<name> (client principal name, if not the same as the authenticated
1801 * name, then this will be impersonated if allowed)
1803 static krb5_error_code
1804 get_tgt(struct bx509_request_desc *r)
1806 krb5_error_code ret;
1807 size_t bodylen;
1808 const char *fn;
1809 void *body;
1811 r->for_cname = MHD_lookup_connection_value(r->connection,
1812 MHD_GET_ARGUMENT_KIND, "cname");
1813 if (r->for_cname && r->for_cname[0] == '\0')
1814 r->for_cname = NULL;
1815 ret = validate_token(r);
1816 if (ret == 0)
1817 ret = authorize_TGT_REQ(r);
1818 /* validate_token() and authorize_TGT_REQ() call bad_req() */
1819 if (ret)
1820 return ret;
1822 r->error_code = 0;
1823 (void) MHD_get_connection_values(r->connection, MHD_GET_ARGUMENT_KIND,
1824 get_tgt_param_cb, r);
1825 ret = r->error_code;
1827 /* k5_get_creds() calls bad_req() */
1828 if (ret == 0)
1829 ret = k5_get_creds(r, K5_CREDS_EPHEMERAL);
1830 if (ret)
1831 return ret;
1833 fn = strchr(r->ccname, ':');
1834 if (fn == NULL)
1835 return bad_500(r, ret, "Impossible error");
1836 fn++;
1837 if ((errno = rk_undumpdata(fn, &body, &bodylen)))
1838 return bad_503(r, ret, "Could not get TGT");
1840 ret = resp(r, MHD_HTTP_OK, MHD_RESPMEM_MUST_COPY,
1841 "application/x-krb5-ccache", body, bodylen, NULL);
1842 free(body);
1843 return ret;
1846 static krb5_error_code
1847 health(const char *method, struct bx509_request_desc *r)
1849 if (strcmp(method, "HEAD") == 0)
1850 return resp(r, MHD_HTTP_OK, MHD_RESPMEM_PERSISTENT, NULL, "", 0, NULL);
1851 return resp(r, MHD_HTTP_OK, MHD_RESPMEM_PERSISTENT, NULL,
1852 "To determine the health of the service, use the /bx509 "
1853 "end-point.\n",
1854 sizeof("To determine the health of the service, use the "
1855 "/bx509 end-point.\n") - 1, NULL);
1859 /* Implements the entirety of this REST service */
1860 static heim_mhd_result
1861 route(void *cls,
1862 struct MHD_Connection *connection,
1863 const char *url,
1864 const char *method,
1865 const char *version,
1866 const char *upload_data,
1867 size_t *upload_data_size,
1868 void **ctx)
1870 static int aptr = 0;
1871 struct bx509_request_desc r;
1872 int ret;
1874 if (*ctx == NULL) {
1876 * This is the first call, right after headers were read.
1878 * We must return quickly so that any 100-Continue might be sent with
1879 * celerity.
1881 * We'll get called again to really do the processing. If we handled
1882 * POSTs then we'd also get called with upload_data != NULL between the
1883 * first and last calls. We need to keep no state between the first
1884 * and last calls, but we do need to distinguish first and last call,
1885 * so we use the ctx argument for this.
1887 *ctx = &aptr;
1888 return MHD_YES;
1891 if ((ret = set_req_desc(connection, url, &r)))
1892 return bad_503(&r, ret, "Could not initialize request state");
1893 if ((strcmp(method, "HEAD") == 0 || strcmp(method, "GET") == 0) &&
1894 (strcmp(url, "/health") == 0 || strcmp(url, "/") == 0))
1895 ret = health(method, &r);
1896 else if (strcmp(method, "GET") != 0)
1897 ret = bad_405(&r, method);
1898 else if (strcmp(url, "/get-cert") == 0 ||
1899 strcmp(url, "/bx509") == 0) /* old name */
1900 ret = bx509(&r);
1901 else if (strcmp(url, "/get-negotiate-token") == 0 ||
1902 strcmp(url, "/bnegotiate") == 0) /* old name */
1903 ret = bnegotiate(&r);
1904 else if (strcmp(url, "/get-tgt") == 0)
1905 ret = get_tgt(&r);
1906 else
1907 ret = bad_404(&r, url);
1909 clean_req_desc(&r);
1910 return ret == -1 ? MHD_NO : MHD_YES;
1913 static struct getargs args[] = {
1914 { "help", 'h', arg_flag, &help_flag, "Print usage message", NULL },
1915 { "version", '\0', arg_flag, &version_flag, "Print version", NULL },
1916 { NULL, 'H', arg_strings, &audiences,
1917 "expected token audience(s) of bx509 service", "HOSTNAME" },
1918 { "daemon", 'd', arg_flag, &daemonize, "daemonize", "daemonize" },
1919 { "daemon-child", 0, arg_flag, &daemon_child_fd, NULL, NULL }, /* priv */
1920 { "reverse-proxied", 0, arg_flag, &reverse_proxied_flag,
1921 "reverse proxied", "listen on 127.0.0.1 and do not use TLS" },
1922 { NULL, 'p', arg_integer, &port, "PORT", "port number (default: 443)" },
1923 { "cache-dir", 0, arg_string, &cache_dir,
1924 "cache directory", "DIRECTORY" },
1925 { "cert", 0, arg_string, &cert_file,
1926 "certificate file path (PEM)", "HX509-STORE" },
1927 { "private-key", 0, arg_string, &priv_key_file,
1928 "private key file path (PEM)", "HX509-STORE" },
1929 { "thread-per-client", 't', arg_flag, &thread_per_client_flag,
1930 "thread per-client", "use thread per-client" },
1931 { "verbose", 'v', arg_counter, &verbose_counter, "verbose", "run verbosely" }
1934 static int
1935 usage(int e)
1937 arg_printusage(args, sizeof(args) / sizeof(args[0]), "bx509",
1938 "\nServes RESTful GETs of /bx509 and /bnegotiate,\n"
1939 "performing corresponding kx509 and, possibly, PKINIT requests\n"
1940 "to the KDCs of the requested realms (or just the given REALM).\n");
1941 exit(e);
1944 static int sigpipe[2] = { -1, -1 };
1946 static void
1947 sighandler(int sig)
1949 char c = sig;
1950 while (write(sigpipe[1], &c, sizeof(c)) == -1 && errno == EINTR)
1954 static void
1955 bx509_openlog(krb5_context context,
1956 const char *svc,
1957 krb5_log_facility **fac)
1959 char **s = NULL, **p;
1961 krb5_initlog(context, "bx509d", fac);
1962 s = krb5_config_get_strings(context, NULL, svc, "logging", NULL);
1963 if (s == NULL)
1964 s = krb5_config_get_strings(context, NULL, "logging", svc, NULL);
1965 if (s) {
1966 for(p = s; *p; p++)
1967 krb5_addlog_dest(context, *fac, *p);
1968 krb5_config_free_strings(s);
1969 } else {
1970 char *ss;
1971 if (asprintf(&ss, "0-1/FILE:%s/%s", hdb_db_dir(context),
1972 KDC_LOG_FILE) < 0)
1973 err(1, "out of memory");
1974 krb5_addlog_dest(context, *fac, ss);
1975 free(ss);
1977 krb5_set_warn_dest(context, *fac);
1980 static const char *sysplugin_dirs[] = {
1981 #ifdef _WIN32
1982 "$ORIGIN",
1983 #else
1984 "$ORIGIN/../lib/plugin/kdc",
1985 #endif
1986 #ifdef __APPLE__
1987 LIBDIR "/plugin/kdc",
1988 #endif
1989 NULL
1992 static void
1993 load_plugins(krb5_context context)
1995 const char * const *dirs = sysplugin_dirs;
1996 #ifndef _WIN32
1997 char **cfdirs;
1999 cfdirs = krb5_config_get_strings(context, NULL, "kdc", "plugin_dir", NULL);
2000 if (cfdirs)
2001 dirs = (const char * const *)cfdirs;
2002 #endif
2004 /* XXX kdc? */
2005 _krb5_load_plugins(context, "kdc", (const char **)dirs);
2007 #ifndef _WIN32
2008 krb5_config_free_strings(cfdirs);
2009 #endif
2013 main(int argc, char **argv)
2015 unsigned int flags = MHD_USE_THREAD_PER_CONNECTION; /* XXX */
2016 struct sockaddr_in sin;
2017 struct MHD_Daemon *previous = NULL;
2018 struct MHD_Daemon *current = NULL;
2019 struct sigaction sa;
2020 krb5_context context = NULL;
2021 MHD_socket sock = MHD_INVALID_SOCKET;
2022 char *priv_key_pem = NULL;
2023 char *cert_pem = NULL;
2024 char sig;
2025 int optidx = 0;
2026 int ret;
2028 setprogname("bx509d");
2029 if (getarg(args, sizeof(args) / sizeof(args[0]), argc, argv, &optidx))
2030 usage(1);
2031 if (help_flag)
2032 usage(0);
2033 if (version_flag) {
2034 print_version(NULL);
2035 exit(0);
2037 if (argc > optidx) /* Add option to set a URI local part prefix? */
2038 usage(1);
2039 if (port < 0)
2040 errx(1, "Port number must be given");
2042 if (audiences.num_strings == 0) {
2043 char localhost[MAXHOSTNAMELEN];
2045 ret = gethostname(localhost, sizeof(localhost));
2046 if (ret == -1)
2047 errx(1, "Could not determine local hostname; use --audience");
2049 if ((audiences.strings =
2050 calloc(1, sizeof(audiences.strings[0]))) == NULL ||
2051 (audiences.strings[0] = strdup(localhost)) == NULL)
2052 err(1, "Out of memory");
2053 audiences.num_strings = 1;
2056 if (daemonize && daemon_child_fd == -1)
2057 daemon_child_fd = roken_detach_prep(argc, argv, "--daemon-child");
2058 daemonize = 0;
2060 argc -= optidx;
2061 argv += optidx;
2062 if (argc != 0)
2063 usage(1);
2065 if ((errno = pthread_key_create(&k5ctx, k5_free_context)))
2066 err(1, "Could not create thread-specific storage");
2068 if ((errno = get_krb5_context(&context)))
2069 err(1, "Could not init krb5 context");
2071 bx509_openlog(context, "bx509d", &logfac);
2072 load_plugins(context);
2074 if (cache_dir == NULL) {
2075 char *s = NULL;
2077 if (asprintf(&s, "%s/bx509d-XXXXXX",
2078 getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp") == -1 ||
2079 s == NULL ||
2080 (cache_dir = mkdtemp(s)) == NULL)
2081 err(1, "could not create temporary cache directory");
2082 if (verbose_counter)
2083 fprintf(stderr, "Note: using %s as cache directory\n", cache_dir);
2084 atexit(rm_cache_dir);
2085 setenv("TMPDIR", cache_dir, 1);
2088 generate_key(context->hx509ctx, "impersonation", "rsa", 2048, &impersonation_key_fn);
2090 again:
2091 if (cert_file && !priv_key_file)
2092 priv_key_file = cert_file;
2094 if (cert_file) {
2095 hx509_cursor cursor = NULL;
2096 hx509_certs certs = NULL;
2097 hx509_cert cert = NULL;
2098 time_t min_cert_life = 0;
2099 size_t len;
2100 void *s;
2102 ret = hx509_certs_init(context->hx509ctx, cert_file, 0, NULL, &certs);
2103 if (ret == 0)
2104 ret = hx509_certs_start_seq(context->hx509ctx, certs, &cursor);
2105 while (ret == 0 &&
2106 (ret = hx509_certs_next_cert(context->hx509ctx, certs,
2107 cursor, &cert)) == 0 && cert) {
2108 time_t notAfter = 0;
2110 if (!hx509_cert_have_private_key_only(cert) &&
2111 (notAfter = hx509_cert_get_notAfter(cert)) <= time(NULL) + 30)
2112 errx(1, "One or more certificates in %s are expired",
2113 cert_file);
2114 if (notAfter) {
2115 notAfter -= time(NULL);
2116 if (notAfter < 600)
2117 warnx("One or more certificates in %s expire soon",
2118 cert_file);
2119 /* Reload 5 minutes prior to expiration */
2120 if (notAfter < min_cert_life || min_cert_life < 1)
2121 min_cert_life = notAfter;
2123 hx509_cert_free(cert);
2125 if (certs)
2126 (void) hx509_certs_end_seq(context->hx509ctx, certs, cursor);
2127 if (min_cert_life > 4)
2128 alarm(min_cert_life >> 1);
2129 hx509_certs_free(&certs);
2130 if (ret)
2131 hx509_err(context->hx509ctx, 1, ret,
2132 "could not read certificate from %s", cert_file);
2134 if ((errno = rk_undumpdata(cert_file, &s, &len)) ||
2135 (cert_pem = strndup(s, len)) == NULL)
2136 err(1, "could not read certificate from %s", cert_file);
2137 if (strlen(cert_pem) != len)
2138 err(1, "NULs in certificate file contents: %s", cert_file);
2139 free(s);
2142 if (priv_key_file) {
2143 size_t len;
2144 void *s;
2146 if ((errno = rk_undumpdata(priv_key_file, &s, &len)) ||
2147 (priv_key_pem = strndup(s, len)) == NULL)
2148 err(1, "could not read private key from %s", priv_key_file);
2149 if (strlen(priv_key_pem) != len)
2150 err(1, "NULs in private key file contents: %s", priv_key_file);
2151 free(s);
2154 if (verbose_counter > 1)
2155 flags |= MHD_USE_DEBUG;
2156 if (thread_per_client_flag)
2157 flags |= MHD_USE_THREAD_PER_CONNECTION;
2160 if (pipe(sigpipe) == -1)
2161 err(1, "Could not set up key/cert reloading");
2162 memset(&sa, 0, sizeof(sa));
2163 sa.sa_handler = sighandler;
2164 if (reverse_proxied_flag) {
2166 * We won't use TLS in the reverse proxy case, so no need to reload
2167 * certs. But we'll still read them if given, and alarm() will get
2168 * called.
2170 (void) signal(SIGHUP, SIG_IGN);
2171 (void) signal(SIGUSR1, SIG_IGN);
2172 (void) signal(SIGALRM, SIG_IGN);
2173 } else {
2174 (void) sigaction(SIGHUP, &sa, NULL); /* Reload key & cert */
2175 (void) sigaction(SIGUSR1, &sa, NULL); /* Reload key & cert */
2176 (void) sigaction(SIGALRM, &sa, NULL); /* Reload key & cert */
2178 (void) sigaction(SIGINT, &sa, NULL); /* Graceful shutdown */
2179 (void) sigaction(SIGTERM, &sa, NULL); /* Graceful shutdown */
2180 (void) signal(SIGPIPE, SIG_IGN);
2182 if (previous)
2183 sock = MHD_quiesce_daemon(previous);
2185 if (reverse_proxied_flag) {
2187 * XXX IPv6 too. Create the sockets and tell MHD_start_daemon() about
2188 * them.
2190 sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
2191 sin.sin_family = AF_INET;
2192 sin.sin_port = htons(port);
2193 current = MHD_start_daemon(flags, port,
2194 NULL, NULL,
2195 route, (char *)NULL,
2196 MHD_OPTION_SOCK_ADDR, &sin,
2197 MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
2198 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
2199 MHD_OPTION_END);
2200 } else if (sock != MHD_INVALID_SOCKET) {
2202 * Certificate/key rollover: reuse the listen socket returned by
2203 * MHD_quiesce_daemon().
2205 current = MHD_start_daemon(flags | MHD_USE_SSL, port,
2206 NULL, NULL,
2207 route, (char *)NULL,
2208 MHD_OPTION_HTTPS_MEM_KEY, priv_key_pem,
2209 MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
2210 MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
2211 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
2212 MHD_OPTION_LISTEN_SOCKET, sock,
2213 MHD_OPTION_END);
2214 sock = MHD_INVALID_SOCKET;
2215 } else {
2216 current = MHD_start_daemon(flags | MHD_USE_SSL, port,
2217 NULL, NULL,
2218 route, (char *)NULL,
2219 MHD_OPTION_HTTPS_MEM_KEY, priv_key_pem,
2220 MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
2221 MHD_OPTION_CONNECTION_LIMIT, (unsigned int)200,
2222 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)10,
2223 MHD_OPTION_END);
2225 if (current == NULL)
2226 err(1, "Could not start bx509 REST service");
2228 if (previous) {
2229 MHD_stop_daemon(previous);
2230 previous = NULL;
2233 if (verbose_counter)
2234 fprintf(stderr, "Ready!\n");
2235 if (daemon_child_fd != -1)
2236 roken_detach_finish(NULL, daemon_child_fd);
2238 /* Wait for signal, possibly SIGALRM, to reload certs and/or exit */
2239 while ((ret = read(sigpipe[0], &sig, sizeof(sig))) == -1 &&
2240 errno == EINTR)
2243 free(priv_key_pem);
2244 free(cert_pem);
2245 priv_key_pem = NULL;
2246 cert_pem = NULL;
2248 if (ret == 1 && (sig == SIGHUP || sig == SIGUSR1 || sig == SIGALRM)) {
2249 /* Reload certs and restart service gracefully */
2250 previous = current;
2251 current = NULL;
2252 goto again;
2255 MHD_stop_daemon(current);
2256 _krb5_unload_plugins(context, "kdc");
2257 pthread_key_delete(k5ctx);
2258 return 0;