2 * Copyright (c) 2019 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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
35 * This file implements a RESTful HTTPS API to an online CA, as well as an
36 * HTTP/Negotiate token issuer, as well as a way to get TGTs.
38 * Users are authenticated with Negotiate and/or Bearer.
40 * This is essentially a RESTful online CA sharing some code with the KDC's
41 * kx509 online CA, and also a proxy for PKINIT and GSS-API (Negotiate).
43 * See the manual page for HTTP API details.
46 * - rewrite to not use libmicrohttpd but an alternative more appropriate to
47 * Heimdal's license (though libmicrohttpd will do)
48 * - there should be an end-point for fetching an issuer's chain
51 * - We use krb5_error_code values as much as possible. Where we need to use
52 * MHD_NO because we got that from an mhd function and cannot respond with
53 * an HTTP response, we use (krb5_error_code)-1, and later map that to
56 * (MHD_NO is an ENOMEM-cannot-even-make-a-static-503-response level event.)
60 * Theory of operation:
62 * - We use libmicrohttpd (MHD) for the HTTP(S) implementation.
64 * - MHD has an online request processing model:
66 * - all requests are handled via the `dh' and `dh_cls' closure arguments
67 * of `MHD_start_daemon()'; ours is called `route()'
69 * - `dh' is called N+1 times:
70 * - once to allocate a request context
71 * - once for every N chunks of request body
72 * - once to process the request and produce a response
74 * - the response cannot begin to be produced before consuming the whole
75 * request body (for requests that have a body)
76 * (this seems like a bug in MHD)
78 * - the response body can be produced over multiple calls (i.e., in an
81 * - Our `route()' processes any POST request body form data / multipart by
82 * treating all the key/value pairs as if they had been additional URI query
85 * - Then `route()' calls a handler appropriate to the URI local-part with the
86 * request context, and the handler produces a response in one call.
88 * I.e., we turn the online MHD request processing into not-online. Our
89 * handlers are presented with complete requests and must produce complete
90 * responses in one call.
92 * - `route()' also does any authentication and CSRF protection so that the
93 * request handlers don't have to.
95 * This non-online request handling approach works for most everything we want
96 * to do. However, for /get-tgts with very large numbers of principals, we
97 * might have to revisit this, using MHD_create_response_from_callback() or
98 * MHD_create_response_from_pipe() (and a thread to do the actual work of
99 * producing the body) instead of MHD_create_response_from_buffer().
102 #define _XOPEN_SOURCE_EXTENDED 1
103 #define _DEFAULT_SOURCE 1
104 #define _BSD_SOURCE 1
105 #define _GNU_SOURCE 1
107 #include <sys/socket.h>
108 #include <sys/types.h>
109 #include <sys/stat.h>
110 #include <sys/time.h>
126 #include <netinet/in.h>
127 #include <netinet/ip.h>
129 #include <microhttpd.h>
130 #include "kdc_locl.h"
131 #include "token_validator_plugin.h"
135 #include <gssapi/gssapi.h>
136 #include <gssapi/gssapi_krb5.h>
138 #include "../lib/hx509/hx_locl.h"
139 #include <hx509-private.h>
141 #define heim_pcontext krb5_context
142 #define heim_pconfig krb5_context
143 #include <heimbase-svc.h>
145 #if MHD_VERSION < 0x00097002 || defined(MHD_YES)
146 /* libmicrohttpd changed these from int valued macros to an enum in 0.9.71 */
151 enum MHD_Result
{ MHD_NO
= 0, MHD_YES
= 1 };
154 typedef int heim_mhd_result
;
156 typedef enum MHD_Result heim_mhd_result
;
159 enum k5_creds_kind
{ K5_CREDS_EPHEMERAL
, K5_CREDS_CACHED
};
162 * This is to keep track of memory we need to free, mainly because we had to
163 * duplicate data from the MHD POST form data processor.
165 struct free_tend_list
{
168 struct free_tend_list
*next
;
171 /* Per-request context data structure */
172 typedef struct bx509_request_desc
{
173 /* Common elements for Heimdal request/response services */
174 HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS
;
176 struct MHD_Connection
*connection
;
177 struct MHD_PostProcessor
*pp
;
178 struct MHD_Response
*response
;
179 krb5_times token_times
;
182 struct free_tend_list
*free_list
;
183 const char *for_cname
;
187 size_t post_data_size
;
188 size_t san_idx
; /* For /get-tgts */
189 enum k5_creds_kind cckind
;
196 krb5_addresses tgt_addresses
; /* For /get-tgt */
198 } *bx509_request_desc
;
201 audit_trail(bx509_request_desc r
, krb5_error_code ret
)
203 const char *retname
= NULL
;
205 /* Get a symbolic name for some error codes */
206 #define CASE(x) case x : retname = #x; break
210 CASE(HDB_ERR_NOT_FOUND_HERE
);
211 CASE(HDB_ERR_WRONG_REALM
);
212 CASE(HDB_ERR_EXISTS
);
213 CASE(HDB_ERR_KVNO_NOT_FOUND
);
214 CASE(HDB_ERR_NOENTRY
);
215 CASE(HDB_ERR_NO_MKEY
);
216 CASE(KRB5KDC_ERR_BADOPTION
);
217 CASE(KRB5KDC_ERR_CANNOT_POSTDATE
);
218 CASE(KRB5KDC_ERR_CLIENT_NOTYET
);
219 CASE(KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN
);
220 CASE(KRB5KDC_ERR_ETYPE_NOSUPP
);
221 CASE(KRB5KDC_ERR_KEY_EXPIRED
);
222 CASE(KRB5KDC_ERR_NAME_EXP
);
223 CASE(KRB5KDC_ERR_NEVER_VALID
);
224 CASE(KRB5KDC_ERR_NONE
);
225 CASE(KRB5KDC_ERR_NULL_KEY
);
226 CASE(KRB5KDC_ERR_PADATA_TYPE_NOSUPP
);
227 CASE(KRB5KDC_ERR_POLICY
);
228 CASE(KRB5KDC_ERR_PREAUTH_FAILED
);
229 CASE(KRB5KDC_ERR_PREAUTH_REQUIRED
);
230 CASE(KRB5KDC_ERR_SERVER_NOMATCH
);
231 CASE(KRB5KDC_ERR_SERVICE_EXP
);
232 CASE(KRB5KDC_ERR_SERVICE_NOTYET
);
233 CASE(KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN
);
234 CASE(KRB5KDC_ERR_TRTYPE_NOSUPP
);
235 CASE(KRB5KRB_ERR_RESPONSE_TOO_BIG
);
236 /* XXX Add relevant error codes */
245 /* Let's save a few bytes */
246 if (retname
&& strncmp("KRB5KDC_", retname
, sizeof("KRB5KDC_") - 1) == 0)
247 retname
+= sizeof("KRB5KDC_") - 1;
249 heim_audit_trail((heim_svc_req_desc
)r
, ret
, retname
);
252 static krb5_log_facility
*logfac
;
253 static pthread_key_t k5ctx
;
255 static krb5_error_code
256 get_krb5_context(krb5_context
*contextp
)
260 if ((*contextp
= pthread_getspecific(k5ctx
)))
262 if ((ret
= krb5_init_context(contextp
)))
263 return *contextp
= NULL
, ret
;
265 krb5_set_log_dest(*contextp
, logfac
);
266 (void) pthread_setspecific(k5ctx
, *contextp
);
267 return *contextp
? 0 : ENOMEM
;
271 CSRF_PROT_UNSPEC
= 0,
272 CSRF_PROT_GET_WITH_HEADER
= 1,
273 CSRF_PROT_GET_WITH_TOKEN
= 2,
274 CSRF_PROT_POST_WITH_HEADER
= 8,
275 CSRF_PROT_POST_WITH_TOKEN
= 16,
276 } csrf_protection_type
;
278 static csrf_protection_type csrf_prot_type
= CSRF_PROT_UNSPEC
;
279 static int port
= -1;
280 static int allow_GET_flag
= -1;
281 static int help_flag
;
282 static int daemonize
;
283 static int daemon_child_fd
= -1;
284 static int verbose_counter
;
285 static int version_flag
;
286 static int reverse_proxied_flag
;
287 static int thread_per_client_flag
;
288 struct getarg_strings audiences
;
289 static getarg_strings csrf_prot_type_strs
;
290 static const char *csrf_header
= "X-CSRF";
291 static const char *cert_file
;
292 static const char *priv_key_file
;
293 static const char *cache_dir
;
294 static const char *csrf_key_file
;
295 static char *impersonation_key_fn
;
297 static char csrf_key
[16];
299 static krb5_error_code
resp(struct bx509_request_desc
*, int,
300 enum MHD_ResponseMemoryMode
, const char *,
301 const void *, size_t, const char *);
302 static krb5_error_code
bad_req(struct bx509_request_desc
*, krb5_error_code
, int,
304 HEIMDAL_PRINTF_ATTRIBUTE((__printf__
, 4, 5));
306 static krb5_error_code
bad_enomem(struct bx509_request_desc
*, krb5_error_code
);
307 static krb5_error_code
bad_400(struct bx509_request_desc
*, krb5_error_code
, char *);
308 static krb5_error_code
bad_401(struct bx509_request_desc
*, char *);
309 static krb5_error_code
bad_403(struct bx509_request_desc
*, krb5_error_code
, char *);
310 static krb5_error_code
bad_404(struct bx509_request_desc
*, const char *);
311 static krb5_error_code
bad_405(struct bx509_request_desc
*, const char *);
312 static krb5_error_code
bad_500(struct bx509_request_desc
*, krb5_error_code
, const char *);
313 static krb5_error_code
bad_503(struct bx509_request_desc
*, krb5_error_code
, const char *);
314 static heim_mhd_result
validate_csrf_token(struct bx509_request_desc
*r
);
317 validate_token(struct bx509_request_desc
*r
)
320 krb5_principal cprinc
= NULL
;
323 char token_type
[64]; /* Plenty */
326 size_t host_len
, brk
, i
;
328 memset(&r
->token_times
, 0, sizeof(r
->token_times
));
329 host
= MHD_lookup_connection_value(r
->connection
, MHD_HEADER_KIND
,
330 MHD_HTTP_HEADER_HOST
);
332 return bad_400(r
, EINVAL
, "Host header is missing");
334 /* Exclude port number here (IPv6-safe because of the below) */
335 host_len
= ((p
= strchr(host
, ':'))) ? p
- host
: strlen(host
);
337 token
= MHD_lookup_connection_value(r
->connection
, MHD_HEADER_KIND
,
338 MHD_HTTP_HEADER_AUTHORIZATION
);
340 return bad_401(r
, "Authorization token is missing");
341 brk
= strcspn(token
, " \t");
342 if (token
[brk
] == '\0' || brk
> sizeof(token_type
) - 1)
343 return bad_401(r
, "Authorization token is missing");
344 memcpy(token_type
, token
, brk
);
345 token_type
[brk
] = '\0';
347 tok
.length
= strlen(token
);
348 tok
.data
= (void *)(uintptr_t)token
;
350 for (i
= 0; i
< audiences
.num_strings
; i
++)
351 if (strncasecmp(host
, audiences
.strings
[i
], host_len
) == 0 &&
352 audiences
.strings
[i
][host_len
] == '\0')
354 if (i
== audiences
.num_strings
)
355 return bad_403(r
, EINVAL
, "Host: value is not accepted here");
357 r
->sname
= strdup(host
); /* No need to check for ENOMEM here */
359 ret
= kdc_validate_token(r
->context
, NULL
/* realm */, token_type
, &tok
,
360 (const char **)&audiences
.strings
[i
], 1,
361 &cprinc
, &r
->token_times
);
363 return bad_403(r
, ret
, "Token validation failed");
365 return bad_403(r
, ret
, "Could not extract a principal name "
367 ret
= krb5_unparse_name(r
->context
, cprinc
, &r
->cname
);
368 krb5_free_principal(r
->context
, cprinc
);
370 return bad_503(r
, ret
, "Could not parse principal name");
375 generate_key(hx509_context context
,
376 const char *key_name
,
377 const char *gen_type
,
378 unsigned long gen_bits
,
381 struct hx509_generate_private_context
*key_gen_ctx
= NULL
;
382 hx509_private_key key
= NULL
;
383 hx509_certs certs
= NULL
;
384 hx509_cert cert
= NULL
;
387 if (strcmp(gen_type
, "rsa") != 0)
388 errx(1, "Only RSA keys are supported at this time");
390 if (asprintf(fn
, "PEM-FILE:%s/.%s_priv_key.pem",
391 cache_dir
, key_name
) == -1 ||
393 err(1, "Could not set up private key for %s", key_name
);
395 ret
= _hx509_generate_private_key_init(context
,
396 ASN1_OID_ID_PKCS1_RSAENCRYPTION
,
399 ret
= _hx509_generate_private_key_bits(context
, key_gen_ctx
, gen_bits
);
401 ret
= _hx509_generate_private_key(context
, key_gen_ctx
, &key
);
403 cert
= hx509_cert_init_private_key(context
, key
, NULL
);
405 ret
= hx509_certs_init(context
, *fn
,
406 HX509_CERTS_CREATE
| HX509_CERTS_UNPROTECT_ALL
,
409 ret
= hx509_certs_add(context
, certs
, cert
);
411 ret
= hx509_certs_store(context
, certs
, 0, NULL
);
413 hx509_err(context
, 1, ret
, "Could not generate and save private key "
416 _hx509_generate_private_key_free(&key_gen_ctx
);
417 hx509_private_key_free(&key
);
418 hx509_certs_free(&certs
);
419 hx509_cert_free(cert
);
423 k5_free_context(void *ctx
)
425 krb5_free_context(ctx
);
428 #ifndef HAVE_UNLINKAT
430 unlink1file(const char *dname
, const char *name
)
434 if (strlcpy(p
, dname
, sizeof(p
)) < sizeof(p
) &&
435 strlcat(p
, "/", sizeof(p
)) < sizeof(p
) &&
436 strlcat(p
, name
, sizeof(p
)) < sizeof(p
))
449 * This works, but not on Win32:
451 * (void) simple_execlp("rm", "rm", "-rf", cache_dir, NULL);
453 * We make no directories in `cache_dir', so we need not recurse.
455 if ((d
= opendir(cache_dir
)) == NULL
)
458 while ((e
= readdir(d
))) {
461 * Because unlinkat() takes a directory FD, implementing one for
462 * libroken is tricky at best. Instead we might want to implement an
463 * rm_dash_rf() function in lib/roken.
465 (void) unlinkat(dirfd(d
), e
->d_name
, 0);
467 (void) unlink1file(cache_dir
, e
->d_name
);
471 (void) rmdir(cache_dir
);
474 static krb5_error_code
475 mk_pkix_store(char **pkix_store
)
482 const char *fn
= strchr(*pkix_store
, ':');
484 fn
= fn
? fn
+ 1 : *pkix_store
;
490 if (asprintf(&s
, "PEM-FILE:%s/pkix-XXXXXX", cache_dir
) == -1 ||
495 if ((fd
= mkstemp(s
+ sizeof("PEM-FILE:") - 1)) == -1) {
504 static krb5_error_code
505 resp(struct bx509_request_desc
*r
,
506 int http_status_code
,
507 enum MHD_ResponseMemoryMode rmmode
,
508 const char *content_type
,
518 (void) gettimeofday(&r
->tv_end
, NULL
);
519 if (http_status_code
== MHD_HTTP_OK
||
520 http_status_code
== MHD_HTTP_TEMPORARY_REDIRECT
)
523 r
->response
= MHD_create_response_from_buffer(bodylen
, rk_UNCONST(body
),
525 if (r
->response
== NULL
)
528 mret
= MHD_add_response_header(r
->response
, "X-CSRF-Token", r
->csrf_token
);
530 mret
= MHD_add_response_header(r
->response
, MHD_HTTP_HEADER_CACHE_CONTROL
,
531 "no-store, max-age=0");
532 if (mret
== MHD_YES
&& http_status_code
== MHD_HTTP_UNAUTHORIZED
) {
533 mret
= MHD_add_response_header(r
->response
,
534 MHD_HTTP_HEADER_WWW_AUTHENTICATE
,
537 mret
= MHD_add_response_header(r
->response
,
538 MHD_HTTP_HEADER_WWW_AUTHENTICATE
,
540 } else if (mret
== MHD_YES
&& http_status_code
== MHD_HTTP_TEMPORARY_REDIRECT
) {
544 redir
= MHD_lookup_connection_value(r
->connection
, MHD_GET_ARGUMENT_KIND
,
546 mret
= MHD_add_response_header(r
->response
, MHD_HTTP_HEADER_LOCATION
,
548 if (mret
!= MHD_NO
&& token
)
549 mret
= MHD_add_response_header(r
->response
,
550 MHD_HTTP_HEADER_AUTHORIZATION
,
553 if (mret
== MHD_YES
&& content_type
) {
554 mret
= MHD_add_response_header(r
->response
,
555 MHD_HTTP_HEADER_CONTENT_TYPE
,
559 mret
= MHD_queue_response(r
->connection
, http_status_code
, r
->response
);
560 MHD_destroy_response(r
->response
);
561 return mret
== MHD_NO
? -1 : 0;
564 static krb5_error_code
565 bad_reqv(struct bx509_request_desc
*r
,
566 krb5_error_code code
,
567 int http_status_code
,
572 const char *k5msg
= NULL
;
573 const char *emsg
= NULL
;
574 char *formatted
= NULL
;
577 heim_audit_setkv_number((heim_svc_req_desc
)r
, "http-status-code",
579 (void) gettimeofday(&r
->tv_end
, NULL
);
580 if (code
== ENOMEM
) {
582 krb5_log_msg(r
->context
, logfac
, 1, NULL
, "Out of memory");
583 audit_trail(r
, code
);
584 return resp(r
, http_status_code
, MHD_RESPMEM_PERSISTENT
,
585 NULL
, fmt
, strlen(fmt
), NULL
);
590 emsg
= k5msg
= krb5_get_error_message(r
->context
, code
);
592 emsg
= strerror(code
);
594 emsg
= "Unknown error";
597 ret
= vasprintf(&formatted
, fmt
, ap
);
599 if (ret
> -1 && formatted
)
600 ret
= asprintf(&msg
, "%s: %s (%d)", formatted
, emsg
, (int)code
);
605 heim_audit_addreason((heim_svc_req_desc
)r
, "%s", msg
);
606 audit_trail(r
, code
);
608 krb5_free_error_message(r
->context
, k5msg
);
610 if (ret
== -1 || msg
== NULL
) {
612 krb5_log_msg(r
->context
, logfac
, 1, NULL
, "Out of memory");
613 return resp(r
, MHD_HTTP_SERVICE_UNAVAILABLE
, MHD_RESPMEM_PERSISTENT
,
614 NULL
, "Out of memory", sizeof("Out of memory") - 1, NULL
);
617 ret
= resp(r
, http_status_code
, MHD_RESPMEM_MUST_COPY
,
618 NULL
, msg
, strlen(msg
), NULL
);
621 return ret
== -1 ? -1 : code
;
624 static krb5_error_code
625 bad_req(struct bx509_request_desc
*r
,
626 krb5_error_code code
,
627 int http_status_code
,
635 ret
= bad_reqv(r
, code
, http_status_code
, fmt
, ap
);
640 static krb5_error_code
641 bad_enomem(struct bx509_request_desc
*r
, krb5_error_code ret
)
643 return bad_req(r
, ret
, MHD_HTTP_SERVICE_UNAVAILABLE
,
647 static krb5_error_code
648 bad_400(struct bx509_request_desc
*r
, int ret
, char *reason
)
650 return bad_req(r
, ret
, MHD_HTTP_BAD_REQUEST
, "%s", reason
);
653 static krb5_error_code
654 bad_401(struct bx509_request_desc
*r
, char *reason
)
656 return bad_req(r
, EACCES
, MHD_HTTP_UNAUTHORIZED
, "%s", reason
);
659 static krb5_error_code
660 bad_403(struct bx509_request_desc
*r
, krb5_error_code ret
, char *reason
)
662 return bad_req(r
, ret
, MHD_HTTP_FORBIDDEN
, "%s", reason
);
665 static krb5_error_code
666 bad_404(struct bx509_request_desc
*r
, const char *name
)
668 return bad_req(r
, ENOENT
, MHD_HTTP_NOT_FOUND
,
669 "Resource not found: %s", name
);
672 static krb5_error_code
673 bad_405(struct bx509_request_desc
*r
, const char *method
)
675 return bad_req(r
, EPERM
, MHD_HTTP_METHOD_NOT_ALLOWED
,
676 "Method not supported: %s", method
);
679 static krb5_error_code
680 bad_413(struct bx509_request_desc
*r
)
682 return bad_req(r
, E2BIG
, MHD_HTTP_METHOD_NOT_ALLOWED
,
683 "POST request body too large");
686 static krb5_error_code
687 bad_500(struct bx509_request_desc
*r
,
691 return bad_req(r
, ret
, MHD_HTTP_INTERNAL_SERVER_ERROR
,
692 "Internal error: %s", reason
);
695 static krb5_error_code
696 bad_503(struct bx509_request_desc
*r
,
700 return bad_req(r
, ret
, MHD_HTTP_SERVICE_UNAVAILABLE
,
701 "Service unavailable: %s", reason
);
704 static krb5_error_code
705 good_bx509(struct bx509_request_desc
*r
)
713 * This `fn' thing is just to quiet linters that think "hey, strchr() can
714 * return NULL so...", but here we've build `r->pkix_store' and know it has
717 if (r
->pkix_store
== NULL
)
718 return bad_503(r
, EINVAL
, "Internal error"); /* Quiet warnings */
719 fn
= strchr(r
->pkix_store
, ':');
720 fn
= fn
? fn
+ 1 : r
->pkix_store
;
721 ret
= rk_undumpdata(fn
, &body
, &bodylen
);
723 return bad_503(r
, ret
, "Could not recover issued certificate "
726 (void) gettimeofday(&r
->tv_end
, NULL
);
727 ret
= resp(r
, MHD_HTTP_OK
, MHD_RESPMEM_MUST_COPY
, "application/x-pem-file",
728 body
, bodylen
, NULL
);
733 static heim_mhd_result
734 bx509_param_cb(void *d
,
735 enum MHD_ValueKind kind
,
739 struct bx509_request_desc
*r
= d
;
740 heim_oid oid
= { 0, 0 };
742 if (strcmp(key
, "eku") == 0 && val
) {
743 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
, "requested_eku",
745 r
->error_code
= der_parse_heim_oid(val
, ".", &oid
);
746 if (r
->error_code
== 0)
747 r
->error_code
= hx509_request_add_eku(r
->context
->hx509ctx
, r
->req
, &oid
);
749 } else if (strcmp(key
, "dNSName") == 0 && val
) {
750 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
751 "requested_dNSName", "%s", val
);
752 r
->error_code
= hx509_request_add_dns_name(r
->context
->hx509ctx
, r
->req
, val
);
753 } else if (strcmp(key
, "rfc822Name") == 0 && val
) {
754 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
755 "requested_rfc822Name", "%s", val
);
756 r
->error_code
= hx509_request_add_email(r
->context
->hx509ctx
, r
->req
, val
);
757 } else if (strcmp(key
, "xMPPName") == 0 && val
) {
758 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
759 "requested_xMPPName", "%s", val
);
760 r
->error_code
= hx509_request_add_xmpp_name(r
->context
->hx509ctx
, r
->req
,
762 } else if (strcmp(key
, "krb5PrincipalName") == 0 && val
) {
763 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
764 "requested_krb5PrincipalName", "%s", val
);
765 r
->error_code
= hx509_request_add_pkinit(r
->context
->hx509ctx
, r
->req
,
767 } else if (strcmp(key
, "ms-upn") == 0 && val
) {
768 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
769 "requested_ms_upn", "%s", val
);
770 r
->error_code
= hx509_request_add_ms_upn_name(r
->context
->hx509ctx
, r
->req
,
772 } else if (strcmp(key
, "registeredID") == 0 && val
) {
773 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
774 "requested_registered_id", "%s", val
);
775 r
->error_code
= der_parse_heim_oid(val
, ".", &oid
);
776 if (r
->error_code
== 0)
777 r
->error_code
= hx509_request_add_registered(r
->context
->hx509ctx
, r
->req
,
780 } else if (strcmp(key
, "csr") == 0 && val
) {
781 heim_audit_setkv_bool((heim_svc_req_desc
)r
, "requested_csr", TRUE
);
782 r
->error_code
= 0; /* Handled upstairs */
783 } else if (strcmp(key
, "lifetime") == 0 && val
) {
784 r
->req_life
= parse_time(val
, "day");
786 /* Produce error for unknown params */
787 heim_audit_setkv_bool((heim_svc_req_desc
)r
, "requested_unknown", TRUE
);
788 krb5_set_error_message(r
->context
, r
->error_code
= ENOTSUP
,
789 "Query parameter %s not supported", key
);
791 return r
->error_code
== 0 ? MHD_YES
: MHD_NO
/* Stop iterating */;
794 static krb5_error_code
795 authorize_CSR(struct bx509_request_desc
*r
,
797 krb5_const_principal p
)
801 ret
= hx509_request_parse_der(r
->context
->hx509ctx
, csr
, &r
->req
);
803 return bad_req(r
, ret
, MHD_HTTP_SERVICE_UNAVAILABLE
,
804 "Could not parse CSR");
806 (void) MHD_get_connection_values(r
->connection
, MHD_GET_ARGUMENT_KIND
,
810 return bad_req(r
, ret
, MHD_HTTP_SERVICE_UNAVAILABLE
,
811 "Could not handle query parameters");
813 ret
= kdc_authorize_csr(r
->context
, "bx509", r
->req
, p
);
815 return bad_403(r
, ret
, "Not authorized to requested certificate");
820 * hx509_certs_iter_f() callback to assign a private key to the first cert in a
823 static int HX509_LIB_CALL
824 set_priv_key(hx509_context context
, void *d
, hx509_cert c
)
826 (void) _hx509_cert_assign_key(c
, (hx509_private_key
)d
);
827 return -1; /* stop iteration */
830 static krb5_error_code
831 store_certs(hx509_context context
,
833 hx509_certs store_these
,
834 hx509_private_key key
)
837 hx509_certs certs
= NULL
;
839 ret
= hx509_certs_init(context
, store
, HX509_CERTS_CREATE
, NULL
,
843 (void) hx509_certs_iter_f(context
, store_these
, set_priv_key
, key
);
844 hx509_certs_merge(context
, certs
, store_these
);
847 hx509_certs_store(context
, certs
, 0, NULL
);
848 hx509_certs_free(&certs
);
852 /* Setup a CSR for bx509() */
853 static krb5_error_code
854 do_CA(struct bx509_request_desc
*r
, const char *csr
)
856 krb5_error_code ret
= 0;
858 hx509_certs certs
= NULL
;
864 * Work around bug where microhttpd decodes %2b to + then + to space. That
865 * bug does not affect other base64 special characters that get URI
868 if ((csr2
= strdup(csr
)) == NULL
)
869 return bad_enomem(r
, ENOMEM
);
870 for (q
= strchr(csr2
, ' '); q
; q
= strchr(q
+ 1, ' '))
873 ret
= krb5_parse_name(r
->context
, r
->cname
, &p
);
876 return bad_req(r
, ret
, MHD_HTTP_SERVICE_UNAVAILABLE
,
877 "Could not parse principal name");
881 if ((d
.data
= malloc(strlen(csr2
))) == NULL
) {
882 krb5_free_principal(r
->context
, p
);
884 return bad_enomem(r
, ENOMEM
);
887 bytes
= rk_base64_decode(csr2
, d
.data
);
890 ret
= errno
? errno
: EINVAL
;
894 krb5_free_principal(r
->context
, p
);
896 return bad_500(r
, ret
, "Invalid base64 encoding of CSR");
900 * Parses and validates the CSR, adds external extension requests from
901 * query parameters, then checks authorization.
903 ret
= authorize_CSR(r
, &d
, p
);
908 krb5_free_principal(r
->context
, p
);
909 return ret
; /* authorize_CSR() calls bad_req() */
912 /* Issue the certificate */
913 ret
= kdc_issue_certificate(r
->context
, "bx509", logfac
, r
->req
, p
,
914 &r
->token_times
, r
->req_life
,
915 1 /* send_chain */, &certs
);
916 krb5_free_principal(r
->context
, p
);
918 if (ret
== KRB5KDC_ERR_POLICY
|| ret
== EACCES
)
919 return bad_403(r
, ret
,
920 "Certificate request denied for policy reasons");
921 return bad_500(r
, ret
, "Certificate issuance failed");
924 /* Setup PKIX store */
925 if ((ret
= mk_pkix_store(&r
->pkix_store
)))
926 return bad_500(r
, ret
,
927 "Could not create PEM store for issued certificate");
929 ret
= store_certs(r
->context
->hx509ctx
, r
->pkix_store
, certs
, NULL
);
930 hx509_certs_free(&certs
);
932 return bad_500(r
, ret
, "Failed to convert issued"
933 " certificate and chain to PEM");
937 /* Copied from kdc/connect.c */
939 addr_to_string(krb5_context context
,
940 struct sockaddr
*addr
,
947 ret
= krb5_sockaddr2address(context
, addr
, &a
);
949 ret
= krb5_print_address(&a
, str
, len
, &len
);
950 krb5_free_address(context
, &a
);
953 snprintf(str
, len
, "<family=%d>", addr
->sa_family
);
956 static void clean_req_desc(struct bx509_request_desc
*);
958 static krb5_error_code
959 set_req_desc(struct MHD_Connection
*connection
,
962 struct bx509_request_desc
**rp
)
964 struct bx509_request_desc
*r
;
965 const union MHD_ConnectionInfo
*ci
;
970 if ((r
= calloc(1, sizeof(*r
))) == NULL
)
972 (void) gettimeofday(&r
->tv_start
, NULL
);
974 ret
= get_krb5_context(&r
->context
);
975 r
->connection
= connection
;
978 r
->request
.data
= "<HTTP-REQUEST>";
979 r
->request
.length
= sizeof("<HTTP-REQUEST>");
980 r
->from
= r
->frombuf
;
981 r
->tgt_addresses
.len
= 0;
982 r
->tgt_addresses
.val
= 0;
983 r
->hcontext
= r
->context
? r
->context
->hcontext
: NULL
;
986 r
->csrf_token
= NULL
;
990 r
->target
= r
->redir
= NULL
;
991 r
->pkix_store
= NULL
;
995 r
->tgts_filename
= NULL
;
1004 r
->error_code
= ret
;
1005 r
->kv
= heim_dict_create(10);
1006 r
->attributes
= heim_dict_create(1);
1007 if (ret
== 0 && (r
->kv
== NULL
|| r
->attributes
== NULL
))
1008 r
->error_code
= ret
= ENOMEM
;
1009 ci
= MHD_get_connection_info(connection
,
1010 MHD_CONNECTION_INFO_CLIENT_ADDRESS
);
1012 r
->addr
= ci
->client_addr
;
1013 addr_to_string(r
->context
, r
->addr
, r
->frombuf
, sizeof(r
->frombuf
));
1016 heim_audit_addkv((heim_svc_req_desc
)r
, 0, "method", "GET");
1017 heim_audit_addkv((heim_svc_req_desc
)r
, 0, "endpoint", "%s", r
->reqtype
);
1018 token
= MHD_lookup_connection_value(r
->connection
, MHD_HEADER_KIND
,
1019 MHD_HTTP_HEADER_AUTHORIZATION
);
1020 if (token
&& r
->kv
) {
1021 const char *token_end
;
1023 if ((token_end
= strchr(token
, ' ')) == NULL
||
1024 (token_end
- token
) > INT_MAX
|| (token_end
- token
) < 2)
1025 heim_audit_addkv((heim_svc_req_desc
)r
, 0, "auth", "<unknown>");
1027 heim_audit_addkv((heim_svc_req_desc
)r
, 0, "auth", "%.*s",
1028 (int)(token_end
- token
), token
);
1040 clean_req_desc(struct bx509_request_desc
*r
)
1044 while (r
->free_list
) {
1045 struct free_tend_list
*ftl
= r
->free_list
;
1046 r
->free_list
= r
->free_list
->next
;
1051 if (r
->pkix_store
) {
1052 const char *fn
= strchr(r
->pkix_store
, ':');
1055 * This `fn' thing is just to quiet linters that think "hey, strchr() can
1056 * return NULL so...", but here we've build `r->pkix_store' and know it has
1059 fn
= fn
? fn
+ 1 : r
->pkix_store
;
1062 krb5_free_addresses(r
->context
, &r
->tgt_addresses
);
1063 hx509_request_free(&r
->req
);
1064 heim_release(r
->attributes
);
1065 heim_release(r
->reason
);
1066 heim_release(r
->kv
);
1067 if (r
->ccname
&& r
->cckind
== K5_CREDS_EPHEMERAL
) {
1068 const char *fn
= r
->ccname
;
1070 if (strncmp(fn
, "FILE:", sizeof("FILE:") - 1) == 0)
1071 fn
+= sizeof("FILE:") - 1;
1075 (void) fclose(r
->tgts
);
1076 if (r
->tgts_filename
) {
1077 (void) unlink(r
->tgts_filename
);
1078 free(r
->tgts_filename
);
1080 /* No need to destroy r->response */
1082 MHD_destroy_post_processor(r
->pp
);
1083 free(r
->csrf_token
);
1084 free(r
->pkix_store
);
1092 /* Implements GETs of /bx509 */
1093 static krb5_error_code
1094 bx509(struct bx509_request_desc
*r
)
1096 krb5_error_code ret
;
1099 /* Get required inputs */
1100 csr
= MHD_lookup_connection_value(r
->connection
, MHD_GET_ARGUMENT_KIND
,
1103 return bad_400(r
, EINVAL
, "CSR is missing");
1105 if (r
->cname
== NULL
)
1106 return bad_403(r
, EINVAL
,
1107 "Could not extract principal name from token");
1109 /* Parse CSR, add extensions from parameters, authorize, issue cert */
1110 if ((ret
= do_CA(r
, csr
)))
1113 /* Read and send the contents of the PKIX store */
1114 krb5_log_msg(r
->context
, logfac
, 1, NULL
, "Issued certificate to %s",
1116 return good_bx509(r
);
1120 * princ_fs_encode_sz() and princ_fs_encode() encode a principal name to be
1121 * safe for use as a file name. They function very much like URL encoders, but
1122 * '~' and '.' also get encoded, and '@' does not.
1124 * A corresponding decoder is not needed.
1126 * XXX Maybe use krb5_cc_default_for()!
1129 princ_fs_encode_sz(const char *in
)
1131 size_t sz
= strlen(in
);
1134 unsigned char c
= *(const unsigned char *)(in
++);
1151 princ_fs_encode(const char *in
)
1153 size_t len
= strlen(in
);
1154 size_t sz
= princ_fs_encode_sz(in
);
1158 if ((s
= malloc(sz
+ 1)) == NULL
)
1162 for (i
= k
= 0; i
< len
; i
++) {
1172 if (isalnum((unsigned char)c
)) {
1176 s
[k
++] = "0123456789abcdef"[(c
&0xff)>>4];
1177 s
[k
++] = "0123456789abcdef"[(c
&0x0f)];
1186 * Find an existing, live ccache for `princ' in `cache_dir' or acquire Kerberos
1187 * creds for `princ' with PKINIT and put them in a ccache in `cache_dir'.
1189 static krb5_error_code
1190 find_ccache(krb5_context context
, const char *princ
, char **ccname
)
1192 krb5_error_code ret
= ENOMEM
;
1193 krb5_ccache cc
= NULL
;
1200 * Name the ccache after the principal. The principal may have special
1201 * characters in it, such as / or \ (path component separarot), or shell
1202 * special characters, so princ_fs_encode() it to make a ccache name.
1204 if ((s
= princ_fs_encode(princ
)) == NULL
||
1205 asprintf(ccname
, "FILE:%s/%s.cc", cache_dir
, s
) == -1 ||
1212 if ((ret
= krb5_cc_resolve(context
, *ccname
, &cc
))) {
1213 /* krb5_cc_resolve() suceeds even if the file doesn't exist */
1219 /* Check if we have a good enough credential */
1221 (ret
= krb5_cc_get_lifetime(context
, cc
, &life
)) == 0 && life
> 60) {
1222 krb5_cc_close(context
, cc
);
1226 krb5_cc_close(context
, cc
);
1227 return ret
? ret
: ENOENT
;
1230 static krb5_error_code
1231 get_ccache(struct bx509_request_desc
*r
, krb5_ccache
*cc
, int *won
)
1233 krb5_error_code ret
= 0;
1234 char *temp_ccname
= NULL
;
1235 const char *fn
= NULL
;
1240 * Open and lock a .new ccache file. Use .new to avoid garbage files on
1243 * We can race with other threads to do this, so we loop until we
1244 * definitively win or definitely lose the race. We win when we have a) an
1245 * open FD that is b) flock'ed, and c) we observe with lstat() that the
1246 * file we opened and locked is the same as on disk after locking.
1248 * We don't close the FD until we're done.
1250 * If we had a proper anon MEMORY ccache, we could instead use that for a
1251 * temporary ccache, and then the initialization of and move to the final
1252 * FILE ccache would take care to mkstemp() and rename() into place.
1253 * fcc_open() basically does a similar thing.
1257 if (asprintf(&temp_ccname
, "%s.ccnew", r
->ccname
) == -1 ||
1258 temp_ccname
== NULL
)
1261 fn
= temp_ccname
+ sizeof("FILE:") - 1;
1263 struct stat st1
, st2
;
1265 * Open and flock the temp ccache file.
1267 * XXX We should really a) use _krb5_xlock(), or move that into
1268 * lib/roken anyways, b) abstract this loop into a utility function in
1276 memset(&st1
, 0, sizeof(st1
));
1277 memset(&st2
, 0xff, sizeof(st2
));
1279 ((fd
= open(fn
, O_RDWR
| O_CREAT
, 0600)) == -1 ||
1280 flock(fd
, LOCK_EX
) == -1 ||
1281 (lstat(fn
, &st1
) == -1 && errno
!= ENOENT
) ||
1282 fstat(fd
, &st2
) == -1))
1284 if (ret
== 0 && errno
== 0 &&
1285 st1
.st_dev
== st2
.st_dev
&& st1
.st_ino
== st2
.st_ino
) {
1286 if (S_ISREG(st1
.st_mode
))
1288 if (unlink(fn
) == -1)
1293 /* Check if we lost any race to acquire Kerberos creds */
1295 ret
= krb5_cc_resolve(r
->context
, temp_ccname
, cc
);
1297 ret
= krb5_cc_get_lifetime(r
->context
, *cc
, &life
);
1298 if (ret
== 0 && life
> 60)
1299 *won
= 0; /* We lost the race, but we win: we get to do less work */
1305 (void) close(fd
); /* Drops the flock */
1310 * Acquire credentials for `princ' using PKINIT and the PKIX credentials in
1311 * `pkix_store', then place the result in the ccache named `ccname' (which will
1312 * be in our own private `cache_dir').
1314 * XXX This function could be rewritten using gss_acquire_cred_from() and
1315 * gss_store_cred_into() provided we add new generic cred store key/value pairs
1318 static krb5_error_code
1319 do_pkinit(struct bx509_request_desc
*r
, enum k5_creds_kind kind
)
1321 krb5_get_init_creds_opt
*opt
= NULL
;
1322 krb5_init_creds_context ctx
= NULL
;
1323 krb5_error_code ret
= 0;
1324 krb5_ccache temp_cc
= NULL
;
1325 krb5_ccache cc
= NULL
;
1326 krb5_principal p
= NULL
;
1328 const char *cname
= r
->for_cname
? r
->for_cname
: r
->cname
;
1330 if (kind
== K5_CREDS_CACHED
) {
1333 ret
= get_ccache(r
, &temp_cc
, &won
);
1337 * We won the race to do PKINIT. Setup to acquire Kerberos creds with
1340 * We should really make sure that gss_acquire_cred_from() can do this
1341 * for us. We'd add generic cred store key/value pairs for PKIX cred
1342 * store, trust anchors, and so on, and acquire that way, then
1343 * gss_store_cred_into() to save it in a FILE ccache.
1346 ret
= krb5_cc_new_unique(r
->context
, "FILE", NULL
, &temp_cc
);
1350 ret
= krb5_parse_name(r
->context
, cname
, &p
);
1352 crealm
= krb5_principal_get_realm(r
->context
, p
);
1354 ret
= krb5_get_init_creds_opt_alloc(r
->context
, &opt
);
1356 krb5_get_init_creds_opt_set_default_flags(r
->context
, "kinit", crealm
,
1358 if (ret
== 0 && kind
== K5_CREDS_EPHEMERAL
&&
1359 !krb5_config_get_bool_default(r
->context
, NULL
, TRUE
,
1360 "get-tgt", "no_addresses", NULL
)) {
1361 krb5_addresses addr
;
1363 ret
= _krb5_parse_address_no_lookup(r
->context
, r
->frombuf
, &addr
);
1365 ret
= krb5_append_addresses(r
->context
, &r
->tgt_addresses
,
1369 if (r
->tgt_addresses
.len
== 0)
1370 ret
= krb5_get_init_creds_opt_set_addressless(r
->context
, opt
, 1);
1372 krb5_get_init_creds_opt_set_address_list(opt
, &r
->tgt_addresses
);
1375 ret
= krb5_get_init_creds_opt_set_pkinit(r
->context
, opt
, p
,
1377 NULL
, /* pkinit_anchor */
1378 NULL
, /* anchor_chain */
1379 NULL
, /* pkinit_crl */
1381 NULL
, /* prompter */
1382 NULL
, /* prompter data */
1383 NULL
/* password */);
1385 ret
= krb5_init_creds_init(r
->context
, p
,
1386 NULL
/* prompter */,
1387 NULL
/* prompter data */,
1392 * Finally, do the AS exchange w/ PKINIT, extract the new Kerberos creds
1393 * into temp_cc, and rename into place. Note that krb5_cc_move() closes
1394 * the source ccache, so we set temp_cc = NULL if it succeeds.
1397 ret
= krb5_init_creds_get(r
->context
, ctx
);
1399 ret
= krb5_init_creds_store(r
->context
, ctx
, temp_cc
);
1400 if (kind
== K5_CREDS_CACHED
) {
1402 ret
= krb5_cc_resolve(r
->context
, r
->ccname
, &cc
);
1404 ret
= krb5_cc_move(r
->context
, temp_cc
, cc
);
1407 } else if (ret
== 0 && kind
== K5_CREDS_EPHEMERAL
) {
1408 ret
= krb5_cc_get_full_name(r
->context
, temp_cc
, &r
->ccname
);
1413 krb5_init_creds_free(r
->context
, ctx
);
1414 krb5_get_init_creds_opt_free(r
->context
, opt
);
1415 krb5_free_principal(r
->context
, p
);
1416 krb5_cc_close(r
->context
, temp_cc
);
1417 krb5_cc_close(r
->context
, cc
);
1421 static krb5_error_code
1422 load_priv_key(krb5_context context
, const char *fn
, hx509_private_key
*key
)
1424 hx509_private_key
*keys
= NULL
;
1425 krb5_error_code ret
;
1426 hx509_certs certs
= NULL
;
1429 ret
= hx509_certs_init(context
->hx509ctx
, fn
, 0, NULL
, &certs
);
1433 ret
= _hx509_certs_keys_get(context
->hx509ctx
, certs
, &keys
);
1434 if (ret
== 0 && keys
[0] == NULL
)
1435 ret
= ENOENT
; /* XXX Better error please */
1437 *key
= _hx509_private_key_ref(keys
[0]);
1439 krb5_set_error_message(context
, ret
, "Could not load private "
1440 "impersonation key from %s for PKINIT: %s", fn
,
1441 hx509_get_error_string(context
->hx509ctx
, ret
));
1442 _hx509_certs_keys_free(context
->hx509ctx
, keys
);
1443 hx509_certs_free(&certs
);
1447 static krb5_error_code
1448 k5_do_CA(struct bx509_request_desc
*r
)
1450 SubjectPublicKeyInfo spki
;
1451 hx509_private_key key
= NULL
;
1452 krb5_error_code ret
= 0;
1453 krb5_principal p
= NULL
;
1454 hx509_request req
= NULL
;
1455 hx509_certs certs
= NULL
;
1456 KeyUsage ku
= int2KeyUsage(0);
1457 const char *cname
= r
->for_cname
? r
->for_cname
: r
->cname
;
1459 memset(&spki
, 0, sizeof(spki
));
1460 ku
.digitalSignature
= 1;
1462 /* Make a CSR (halfway -- we don't need to sign it here) */
1463 /* XXX Load impersonation key just once?? */
1464 ret
= load_priv_key(r
->context
, impersonation_key_fn
, &key
);
1466 ret
= hx509_request_init(r
->context
->hx509ctx
, &req
);
1468 ret
= krb5_parse_name(r
->context
, cname
, &p
);
1470 ret
= hx509_private_key2SPKI(r
->context
->hx509ctx
, key
, &spki
);
1472 hx509_request_set_SubjectPublicKeyInfo(r
->context
->hx509ctx
, req
,
1474 free_SubjectPublicKeyInfo(&spki
);
1476 ret
= hx509_request_add_pkinit(r
->context
->hx509ctx
, req
, cname
);
1478 ret
= hx509_request_add_eku(r
->context
->hx509ctx
, req
,
1479 &asn1_oid_id_pkekuoid
);
1481 /* Mark it authorized */
1483 ret
= hx509_request_authorize_san(req
, 0);
1485 ret
= hx509_request_authorize_eku(req
, 0);
1487 hx509_request_authorize_ku(req
, ku
);
1489 /* Issue the certificate */
1491 ret
= kdc_issue_certificate(r
->context
, "get-tgt", logfac
, req
, p
,
1492 &r
->token_times
, r
->req_life
,
1493 1 /* send_chain */, &certs
);
1494 krb5_free_principal(r
->context
, p
);
1495 hx509_request_free(&req
);
1498 if (ret
== KRB5KDC_ERR_POLICY
|| ret
== EACCES
) {
1499 hx509_private_key_free(&key
);
1500 return bad_403(r
, ret
,
1501 "Certificate request denied for policy reasons");
1503 if (ret
== ENOMEM
) {
1504 hx509_private_key_free(&key
);
1505 return bad_503(r
, ret
, "Certificate issuance failed");
1508 hx509_private_key_free(&key
);
1509 return bad_500(r
, ret
, "Certificate issuance failed");
1512 /* Setup PKIX store and extract the certificate chain into it */
1513 ret
= mk_pkix_store(&r
->pkix_store
);
1515 ret
= store_certs(r
->context
->hx509ctx
, r
->pkix_store
, certs
, key
);
1516 hx509_private_key_free(&key
);
1517 hx509_certs_free(&certs
);
1519 return bad_500(r
, ret
,
1520 "Could not create PEM store for issued certificate");
1524 /* Get impersonated Kerberos credentials for `cprinc' */
1525 static krb5_error_code
1526 k5_get_creds(struct bx509_request_desc
*r
, enum k5_creds_kind kind
)
1528 krb5_error_code ret
;
1529 const char *cname
= r
->for_cname
? r
->for_cname
: r
->cname
;
1531 /* If we have a live ccache for `cprinc', we're done */
1533 if (kind
== K5_CREDS_CACHED
&&
1534 (ret
= find_ccache(r
->context
, cname
, &r
->ccname
)) == 0)
1535 return ret
; /* Success */
1538 * Else we have to acquire a credential for them using their bearer token
1539 * for authentication (and our keytab / initiator credentials perhaps).
1541 if ((ret
= k5_do_CA(r
)))
1542 return ret
; /* k5_do_CA() calls bad_req() */
1545 ret
= do_pkinit(r
, kind
);
1549 /* Accumulate strings */
1551 acc_str(char **acc
, char *adds
, size_t addslen
)
1554 int l
= addslen
<= INT_MAX
? (int)addslen
: INT_MAX
;
1556 if (asprintf(&tmp
, "%s%s%.*s",
1558 *acc
? "; " : "", l
, adds
) > -1 &&
1566 fmt_gss_error(OM_uint32 code
, gss_OID mech
)
1568 gss_buffer_desc buf
;
1569 OM_uint32 major
, minor
;
1570 OM_uint32 type
= mech
== GSS_C_NO_OID
? GSS_C_GSS_CODE
: GSS_C_MECH_CODE
;
1575 major
= gss_display_status(&minor
, code
, type
, mech
, &more
, &buf
);
1576 if (!GSS_ERROR(major
))
1577 acc_str(&r
, (char *)buf
.value
, buf
.length
);
1578 gss_release_buffer(&minor
, &buf
);
1579 } while (!GSS_ERROR(major
) && more
);
1584 fmt_gss_errors(const char *r
, OM_uint32 major
, OM_uint32 minor
, gss_OID mech
)
1588 ma
= fmt_gss_error(major
, GSS_C_NO_OID
);
1589 mi
= mech
== GSS_C_NO_OID
? NULL
: fmt_gss_error(minor
, mech
);
1590 if (asprintf(&s
, "%s: %s%s%s", r
,
1591 ma
? ma
: "Out of memory",
1593 mi
? mi
: "") > -1 &&
1604 static krb5_error_code
1605 bad_req_gss(struct bx509_request_desc
*r
,
1609 int http_status_code
,
1612 krb5_error_code ret
;
1613 char *msg
= fmt_gss_errors(reason
, major
, minor
, mech
);
1615 if (major
== GSS_S_BAD_NAME
|| major
== GSS_S_BAD_NAMETYPE
)
1616 http_status_code
= MHD_HTTP_BAD_REQUEST
;
1619 ret
= resp(r
, http_status_code
, MHD_RESPMEM_MUST_COPY
, NULL
,
1620 msg
, strlen(msg
), NULL
);
1622 ret
= resp(r
, http_status_code
, MHD_RESPMEM_MUST_COPY
, NULL
,
1623 "Out of memory while formatting GSS error message",
1624 sizeof("Out of memory while formatting GSS error message") - 1, NULL
);
1629 /* Make an HTTP/Negotiate token */
1630 static krb5_error_code
1631 mk_nego_tok(struct bx509_request_desc
*r
,
1635 gss_key_value_element_desc kv
[1] = { { "ccache", r
->ccname
} };
1636 gss_key_value_set_desc store
= { 1, kv
};
1637 gss_buffer_desc token
= GSS_C_EMPTY_BUFFER
;
1638 gss_buffer_desc name
= GSS_C_EMPTY_BUFFER
;
1639 gss_cred_id_t cred
= GSS_C_NO_CREDENTIAL
;
1640 gss_ctx_id_t ctx
= GSS_C_NO_CONTEXT
;
1641 gss_name_t iname
= GSS_C_NO_NAME
;
1642 gss_name_t aname
= GSS_C_NO_NAME
;
1643 OM_uint32 major
, minor
, junk
;
1644 krb5_error_code ret
; /* More like a system error code here */
1645 const char *cname
= r
->for_cname
? r
->for_cname
: r
->cname
;
1646 char *token_b64
= NULL
;
1651 /* Import initiator name */
1652 name
.length
= strlen(cname
);
1653 name
.value
= rk_UNCONST(cname
);
1654 major
= gss_import_name(&minor
, &name
, GSS_KRB5_NT_PRINCIPAL_NAME
, &iname
);
1655 if (major
!= GSS_S_COMPLETE
)
1656 return bad_req_gss(r
, major
, minor
, GSS_C_NO_OID
,
1657 MHD_HTTP_SERVICE_UNAVAILABLE
,
1658 "Could not import cprinc parameter value as "
1659 "Kerberos principal name");
1661 /* Import target acceptor name */
1662 name
.length
= strlen(r
->target
);
1663 name
.value
= rk_UNCONST(r
->target
);
1664 major
= gss_import_name(&minor
, &name
, GSS_C_NT_HOSTBASED_SERVICE
, &aname
);
1665 if (major
!= GSS_S_COMPLETE
) {
1666 (void) gss_release_name(&junk
, &iname
);
1667 return bad_req_gss(r
, major
, minor
, GSS_C_NO_OID
,
1668 MHD_HTTP_SERVICE_UNAVAILABLE
,
1669 "Could not import target parameter value as "
1670 "Kerberos principal name");
1673 /* Acquire a credential from the given ccache */
1674 major
= gss_add_cred_from(&minor
, cred
, iname
, GSS_KRB5_MECHANISM
,
1675 GSS_C_INITIATE
, GSS_C_INDEFINITE
, 0, &store
,
1676 &cred
, NULL
, NULL
, NULL
);
1677 (void) gss_release_name(&junk
, &iname
);
1678 if (major
!= GSS_S_COMPLETE
) {
1679 (void) gss_release_name(&junk
, &aname
);
1680 return bad_req_gss(r
, major
, minor
, GSS_KRB5_MECHANISM
,
1681 MHD_HTTP_FORBIDDEN
, "Could not acquire credentials "
1682 "for requested cprinc");
1685 major
= gss_init_sec_context(&minor
, cred
, &ctx
, aname
,
1686 GSS_KRB5_MECHANISM
, 0, GSS_C_INDEFINITE
,
1687 NULL
, GSS_C_NO_BUFFER
, NULL
, &token
, NULL
,
1689 (void) gss_delete_sec_context(&junk
, &ctx
, GSS_C_NO_BUFFER
);
1690 (void) gss_release_name(&junk
, &aname
);
1691 (void) gss_release_cred(&junk
, &cred
);
1692 if (major
!= GSS_S_COMPLETE
)
1693 return bad_req_gss(r
, major
, minor
, GSS_KRB5_MECHANISM
,
1694 MHD_HTTP_SERVICE_UNAVAILABLE
, "Could not acquire "
1695 "Negotiate token for requested target");
1697 /* Encode token, output */
1698 ret
= rk_base64_encode(token
.value
, token
.length
, &token_b64
);
1699 (void) gss_release_buffer(&junk
, &token
);
1701 ret
= asprintf(nego_tok
, "Negotiate %s", token_b64
);
1703 if (ret
< 0 || *nego_tok
== NULL
)
1704 return bad_req(r
, errno
, MHD_HTTP_SERVICE_UNAVAILABLE
,
1705 "Could not allocate memory for encoding Negotiate "
1711 static krb5_error_code
1712 bnegotiate_get_target(struct bx509_request_desc
*r
)
1716 const char *referer
; /* misspelled on the wire, misspelled here, FYI */
1717 const char *authority
;
1718 const char *local_part
;
1722 target
= MHD_lookup_connection_value(r
->connection
, MHD_GET_ARGUMENT_KIND
,
1724 redir
= MHD_lookup_connection_value(r
->connection
, MHD_GET_ARGUMENT_KIND
,
1726 referer
= MHD_lookup_connection_value(r
->connection
, MHD_HEADER_KIND
,
1727 MHD_HTTP_HEADER_REFERER
);
1728 if (target
!= NULL
&& redir
== NULL
) {
1732 if (target
== NULL
&& redir
== NULL
)
1733 return bad_400(r
, EINVAL
,
1734 "Query missing 'target' or 'redirect' parameter value");
1735 if (target
!= NULL
&& redir
!= NULL
)
1736 return bad_403(r
, EACCES
,
1737 "Only one of 'target' or 'redirect' parameter allowed");
1738 if (redir
!= NULL
&& referer
== NULL
)
1739 return bad_403(r
, EACCES
,
1740 "Redirect request without Referer header nor allowed");
1742 if (strncmp(referer
, "https://", sizeof("https://") - 1) != 0 ||
1743 strncmp(redir
, "https://", sizeof("https://") - 1) != 0)
1744 return bad_403(r
, EACCES
,
1745 "Redirect requests permitted only for https referrers");
1747 /* Parse out authority from each URI, redirect and referrer */
1748 authority
= redir
+ sizeof("https://") - 1;
1749 if ((local_part
= strchr(authority
, '/')) == NULL
)
1750 local_part
= authority
+ strlen(authority
);
1751 if ((s1
= strndup(authority
, local_part
- authority
)) == NULL
)
1752 return bad_enomem(r
, ENOMEM
);
1754 authority
= referer
+ sizeof("https://") - 1;
1755 if ((local_part
= strchr(authority
, '/')) == NULL
)
1756 local_part
= authority
+ strlen(authority
);
1757 if ((s2
= strndup(authority
, local_part
- authority
)) == NULL
) {
1759 return bad_enomem(r
, ENOMEM
);
1762 /* Both must match */
1763 if (strcasecmp(s1
, s2
) != 0) {
1766 return bad_403(r
, EACCES
, "Redirect request does not match referer");
1770 if (strchr(s1
, '@')) {
1772 return bad_403(r
, EACCES
,
1773 "Redirect request authority has login information");
1776 /* Extract hostname portion of authority and format GSS name */
1777 if (strchr(s1
, ':'))
1778 *strchr(s1
, ':') = '\0';
1779 if (asprintf(&r
->freeme1
, "HTTP@%s", s1
) == -1 || r
->freeme1
== NULL
) {
1781 return bad_enomem(r
, ENOMEM
);
1784 r
->target
= r
->freeme1
;
1791 * Implements /bnegotiate end-point.
1793 * Query parameters (mutually exclusive):
1796 * - redirect=<URL-encoded-URL>
1798 * If the redirect query parameter is set then the Referer: header must be as
1799 * well, and the authority of the redirect and Referer URIs must be the same.
1801 static krb5_error_code
1802 bnegotiate(struct bx509_request_desc
*r
)
1804 krb5_error_code ret
;
1805 size_t nego_toksz
= 0;
1806 char *nego_tok
= NULL
;
1808 ret
= bnegotiate_get_target(r
);
1810 return ret
; /* bnegotiate_get_target() calls bad_req() */
1811 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
, "target", "%s",
1812 r
->target
? r
->target
: "<unknown>");
1813 heim_audit_setkv_bool((heim_svc_req_desc
)r
, "redir", !!r
->redir
);
1816 * Make sure we have Kerberos credentials for cprinc. If we have them
1817 * cached from earlier, this will be fast (all local), else it will involve
1818 * taking a file lock and talking to the KDC using kx509 and PKINIT.
1820 * Perhaps we could use S4U instead, which would speed up the slow path a
1823 ret
= k5_get_creds(r
, K5_CREDS_CACHED
);
1825 return bad_403(r
, ret
,
1826 "Could not acquire Kerberos credentials using PKINIT");
1828 /* Acquire the Negotiate token and output it */
1829 if (ret
== 0 && r
->ccname
!= NULL
)
1830 ret
= mk_nego_tok(r
, &nego_tok
, &nego_toksz
);
1833 /* Look ma', Negotiate as an OAuth-like token system! */
1835 ret
= resp(r
, MHD_HTTP_TEMPORARY_REDIRECT
, MHD_RESPMEM_PERSISTENT
,
1836 NULL
, "", 0, nego_tok
);
1838 ret
= resp(r
, MHD_HTTP_OK
, MHD_RESPMEM_MUST_COPY
,
1839 "application/x-negotiate-token", nego_tok
, nego_toksz
,
1847 static krb5_error_code
1848 authorize_TGT_REQ(struct bx509_request_desc
*r
)
1850 krb5_principal p
= NULL
;
1851 krb5_error_code ret
;
1852 const char *for_cname
= r
->for_cname
? r
->for_cname
: r
->cname
;
1854 if (for_cname
== r
->cname
|| strcmp(r
->cname
, r
->for_cname
) == 0)
1857 ret
= hx509_request_init(r
->context
->hx509ctx
, &r
->req
);
1859 return bad_500(r
, ret
, "Out of resources");
1860 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
1861 "requested_krb5PrincipalName", "%s", for_cname
);
1862 ret
= hx509_request_add_eku(r
->context
->hx509ctx
, r
->req
,
1863 ASN1_OID_ID_PKEKUOID
);
1865 ret
= hx509_request_add_pkinit(r
->context
->hx509ctx
, r
->req
,
1868 ret
= krb5_parse_name(r
->context
, r
->cname
, &p
);
1870 ret
= kdc_authorize_csr(r
->context
, "get-tgt", r
->req
, p
);
1871 krb5_free_principal(r
->context
, p
);
1872 hx509_request_free(&r
->req
);
1875 return bad_403(r
, ret
, "Not authorized to requested TGT");
1879 static heim_mhd_result
1880 get_tgt_param_cb(void *d
,
1881 enum MHD_ValueKind kind
,
1885 struct bx509_request_desc
*r
= d
;
1887 if (strcmp(key
, "address") == 0 && val
) {
1888 if (!krb5_config_get_bool_default(r
->context
, NULL
,
1890 "get-tgt", "allow_addresses", NULL
)) {
1891 krb5_set_error_message(r
->context
, r
->error_code
= ENOTSUP
,
1892 "Query parameter %s not allowed", key
);
1894 krb5_addresses addresses
;
1896 r
->error_code
= _krb5_parse_address_no_lookup(r
->context
, val
,
1898 if (r
->error_code
== 0)
1899 r
->error_code
= krb5_append_addresses(r
->context
, &r
->tgt_addresses
,
1901 krb5_free_addresses(r
->context
, &addresses
);
1903 } else if (strcmp(key
, "cname") == 0) {
1904 /* Handled upstairs */
1906 } else if (strcmp(key
, "lifetime") == 0 && val
) {
1907 r
->req_life
= parse_time(val
, "day");
1909 /* Produce error for unknown params */
1910 heim_audit_setkv_bool((heim_svc_req_desc
)r
, "requested_unknown", TRUE
);
1911 krb5_set_error_message(r
->context
, r
->error_code
= ENOTSUP
,
1912 "Query parameter %s not supported", key
);
1914 return r
->error_code
== 0 ? MHD_YES
: MHD_NO
/* Stop iterating */;
1918 * Implements /get-tgt end-point.
1922 * - cname=<name> (client principal name, if not the same as the authenticated
1923 * name, then this will be impersonated if allowed; may be
1926 * - address=<IP> (IP address to add as a ticket address; may be given
1929 * - lifetime=<time> (requested lifetime for the ticket; may be given only
1932 static krb5_error_code
1933 get_tgt(struct bx509_request_desc
*r
)
1935 krb5_error_code ret
;
1940 r
->for_cname
= MHD_lookup_connection_value(r
->connection
,
1941 MHD_GET_ARGUMENT_KIND
, "cname");
1942 if (r
->for_cname
&& r
->for_cname
[0] == '\0')
1943 r
->for_cname
= NULL
;
1944 ret
= authorize_TGT_REQ(r
);
1946 return ret
; /* authorize_TGT_REQ() calls bad_req() */
1949 (void) MHD_get_connection_values(r
->connection
, MHD_GET_ARGUMENT_KIND
,
1950 get_tgt_param_cb
, r
);
1951 ret
= r
->error_code
;
1953 /* k5_get_creds() calls bad_req() */
1955 ret
= k5_get_creds(r
, K5_CREDS_EPHEMERAL
);
1957 return bad_403(r
, ret
,
1958 "Could not acquire Kerberos credentials using PKINIT");
1960 fn
= strchr(r
->ccname
, ':');
1962 return bad_500(r
, ret
, "Impossible error");
1964 if ((errno
= rk_undumpdata(fn
, &body
, &bodylen
)))
1965 return bad_503(r
, ret
, "Could not get TGT");
1967 ret
= resp(r
, MHD_HTTP_OK
, MHD_RESPMEM_MUST_COPY
,
1968 "application/x-krb5-ccache", body
, bodylen
, NULL
);
1974 get_tgts_accumulate_ccache_write_json(struct bx509_request_desc
*r
,
1975 krb5_error_code code
,
1981 heim_error_t e
= NULL
;
1985 o
= heim_dict_create(9);
1986 k
= heim_string_create("name");
1987 v
= heim_string_create(r
->for_cname
);
1989 ret
= heim_dict_set_value(o
, k
, v
);
1996 k
= heim_string_create("error_code");
1997 v
= heim_number_create(code
);
1999 ret
= heim_dict_set_value(o
, k
, v
);
2001 if (ret
== 0 && data
!= NULL
) {
2004 k
= heim_string_create("ccache");
2005 v
= heim_data_create(data
, datalen
);
2007 ret
= heim_dict_set_value(o
, k
, v
);
2009 if (ret
== 0 && code
!= 0) {
2010 const char *s
= krb5_get_error_message(r
->context
, code
);
2014 k
= heim_string_create("error");
2015 v
= heim_string_create(s
? s
: "Out of memory");
2016 krb5_free_error_message(r
->context
, s
);
2018 ret
= heim_dict_set_value(o
, k
, v
);
2024 return bad_503(r
, errno
, "Out of memory");
2027 text
= heim_json_copy_serialize(o
,
2028 HEIM_JSON_F_NO_DATA_DICT
|
2029 HEIM_JSON_F_ONE_LINE
,
2032 const char *s
= heim_string_get_utf8(text
);
2034 (void) fwrite(s
, strlen(s
), 1, r
->tgts
);
2036 const char *s
= NULL
;
2037 v
= heim_error_copy_string(e
);
2039 s
= heim_string_get_utf8(v
);
2041 s
= "<unknown encoder error>";
2042 krb5_log_msg(r
->context
, logfac
, 1, NULL
, "Failed to encode JSON text with ccache or error for %s: %s",
2051 /* Writes one ccache to a response file, as JSON */
2053 get_tgts_accumulate_ccache(struct bx509_request_desc
*r
, krb5_error_code ret
)
2060 if (r
->tgts
== NULL
) {
2063 if (asprintf(&r
->tgts_filename
,
2064 "%s/tgts-json-XXXXXX", cache_dir
) == -1 ||
2065 r
->tgts_filename
== NULL
) {
2066 free(r
->tgts_filename
);
2067 r
->tgts_filename
= NULL
;
2069 return bad_enomem(r
, r
->error_code
= ENOMEM
);
2071 if ((fd
= mkstemp(r
->tgts_filename
)) == -1)
2072 return bad_req(r
, errno
, MHD_HTTP_SERVICE_UNAVAILABLE
,
2073 "%s", strerror(r
->error_code
= errno
));
2074 if ((r
->tgts
= fdopen(fd
, "w+")) == NULL
) {
2076 return bad_req(r
, errno
, MHD_HTTP_SERVICE_UNAVAILABLE
,
2077 "%s", strerror(r
->error_code
= errno
));
2082 fn
= strchr(r
->ccname
, ':');
2084 return bad_req(r
, errno
, MHD_HTTP_SERVICE_UNAVAILABLE
,
2085 "Internal error (invalid credentials cache name)");
2087 if ((r
->error_code
= rk_undumpdata(fn
, &body
, &bodylen
)))
2088 return bad_req(r
, errno
, MHD_HTTP_SERVICE_UNAVAILABLE
,
2089 "%s", strerror(r
->error_code
));
2093 if (bodylen
> INT_MAX
>> 4) {
2095 return bad_req(r
, errno
, MHD_HTTP_SERVICE_UNAVAILABLE
,
2096 "Credentials cache too large!");
2100 res
= get_tgts_accumulate_ccache_write_json(r
, ret
, body
, bodylen
);
2105 static heim_mhd_result
2106 get_tgts_param_authorize_cb(void *d
,
2107 enum MHD_ValueKind kind
,
2111 struct bx509_request_desc
*r
= d
;
2112 krb5_error_code ret
= 0;
2114 if (strcmp(key
, "cname") != 0 || val
== NULL
)
2117 if (r
->req
== NULL
) {
2118 ret
= hx509_request_init(r
->context
->hx509ctx
, &r
->req
);
2120 ret
= hx509_request_add_eku(r
->context
->hx509ctx
, r
->req
,
2121 ASN1_OID_ID_PKEKUOID
);
2123 return bad_500(r
, ret
, "Out of resources");
2125 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
2126 "requested_krb5PrincipalName", "%s", val
);
2127 ret
= hx509_request_add_pkinit(r
->context
->hx509ctx
, r
->req
,
2130 return bad_403(r
, ret
, "Not authorized to requested TGT");
2134 /* For each requested principal, produce a ccache */
2135 static heim_mhd_result
2136 get_tgts_param_execute_cb(void *d
,
2137 enum MHD_ValueKind kind
,
2141 struct bx509_request_desc
*r
= d
;
2142 hx509_san_type san_type
;
2143 krb5_error_code ret
;
2144 size_t san_idx
= r
->san_idx
++;
2145 const char *save_for_cname
= r
->for_cname
;
2149 /* We expect only cname=principal q-params here */
2150 if (strcmp(key
, "cname") != 0 || val
== NULL
)
2154 * We expect the `san_idx'th SAN in the `r->req' request checked by
2155 * kdc_authorize_csr() to be the same as this cname. This happens
2156 * naturally because we add these SANs to `r->req' in the same order as we
2157 * visit them here (unless our HTTP library somehow went crazy).
2159 * Still, we check that it's the same SAN.
2161 ret
= hx509_request_get_san(r
->req
, san_idx
, &san_type
, &s
);
2162 if (ret
== HX509_NO_ITEM
||
2163 san_type
!= HX509_SAN_TYPE_PKINIT
||
2164 strcmp(s
, val
) != 0) {
2166 * If the cname and SAN don't match, it's some weird internal error
2169 krb5_set_error_message(r
->context
, r
->error_code
= EACCES
,
2170 "PKINIT SAN not granted: %s (internal error)",
2176 * We're going to pretend to be this SAN for the purpose of acquring a TGT
2177 * for it. So we "push" `r->for_cname'.
2183 * Our authorizer supports partial authorization where the whole request is
2184 * rejected but some features of it are permitted.
2186 * (In most end-points we don't want partial authorization, but in
2187 * /get-tgts we very much do.)
2189 if (ret
== 0 && !hx509_request_san_authorized_p(r
->req
, san_idx
)) {
2190 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
2191 "REJECT_krb5PrincipalName", "%s", val
);
2192 krb5_set_error_message(r
->context
, r
->error_code
= EACCES
,
2193 "PKINIT SAN denied: %s", val
);
2197 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
2198 "ACCEPT_krb5PrincipalName", "%s", val
);
2199 ret
= k5_get_creds(r
, K5_CREDS_EPHEMERAL
);
2201 heim_audit_addkv((heim_svc_req_desc
)r
, KDC_AUDIT_VIS
,
2202 "ISSUE_krb5PrincipalName", "%s", val
);
2206 * If ret == 0 this will gather the TGT we acquired, else it will acquire
2209 res
= get_tgts_accumulate_ccache(r
, ret
);
2211 /* Now we "pop" `r->for_cname' */
2212 r
->for_cname
= save_for_cname
;
2219 * Implements /get-tgts end-point.
2223 * - cname=<name> (client principal name, if not the same as the authenticated
2224 * name, then this will be impersonated if allowed; may be
2225 * given multiple times)
2227 static krb5_error_code
2228 get_tgts(struct bx509_request_desc
*r
)
2230 krb5_error_code ret
;
2231 krb5_principal p
= NULL
;
2236 /* Prep to authorize */
2237 ret
= krb5_parse_name(r
->context
, r
->cname
, &p
);
2239 return bad_403(r
, ret
, "Could not parse caller principal name");
2241 /* Extract q-params other than `cname' */
2243 res
= MHD_get_connection_values(r
->connection
, MHD_GET_ARGUMENT_KIND
,
2244 get_tgt_param_cb
, r
);
2245 if (r
->response
|| res
== MHD_NO
) {
2246 krb5_free_principal(r
->context
, p
);
2250 ret
= r
->error_code
;
2254 * Check authorization of the authenticated client to the requested
2255 * client principal names (calls bad_req()).
2258 res
= MHD_get_connection_values(r
->connection
, MHD_GET_ARGUMENT_KIND
,
2259 get_tgts_param_authorize_cb
, r
);
2260 if (r
->response
|| res
== MHD_NO
) {
2261 krb5_free_principal(r
->context
, p
);
2265 ret
= r
->error_code
;
2267 /* Use the same configuration as /get-tgt (or should we?) */
2268 ret
= kdc_authorize_csr(r
->context
, "get-tgt", r
->req
, p
);
2271 * We tolerate EACCES because we support partial approval.
2273 * (KRB5_PLUGIN_NO_HANDLE means no plugin handled the authorization
2276 if (ret
== EACCES
|| ret
== KRB5_PLUGIN_NO_HANDLE
)
2279 krb5_free_principal(r
->context
, p
);
2280 return bad_403(r
, ret
, "Permission denied");
2286 * Get the actual TGTs that were authorized.
2288 * get_tgts_param_execute_cb() calls bad_req()
2291 res
= MHD_get_connection_values(r
->connection
, MHD_GET_ARGUMENT_KIND
,
2292 get_tgts_param_execute_cb
, r
);
2293 if (r
->response
|| res
== MHD_NO
) {
2294 krb5_free_principal(r
->context
, p
);
2297 ret
= r
->error_code
;
2299 krb5_free_principal(r
->context
, p
);
2300 hx509_request_free(&r
->req
);
2304 * get_tgts_param_execute_cb() will write its JSON response to the file
2305 * named by r->ccname.
2307 if (fflush(r
->tgts
) != 0)
2308 return bad_503(r
, ret
, "Could not get TGT");
2309 if ((errno
= rk_undumpdata(r
->tgts_filename
, &body
, &bodylen
)))
2310 return bad_503(r
, ret
, "Could not get TGT");
2312 ret
= resp(r
, MHD_HTTP_OK
, MHD_RESPMEM_MUST_COPY
,
2313 "application/x-krb5-ccaches-json", body
, bodylen
, NULL
);
2318 static krb5_error_code
2319 health(const char *method
, struct bx509_request_desc
*r
)
2321 if (strcmp(method
, "HEAD") == 0)
2322 return resp(r
, MHD_HTTP_OK
, MHD_RESPMEM_PERSISTENT
, NULL
, "", 0, NULL
);
2323 return resp(r
, MHD_HTTP_OK
, MHD_RESPMEM_PERSISTENT
, NULL
,
2324 "To determine the health of the service, use the /bx509 "
2326 sizeof("To determine the health of the service, use the "
2327 "/bx509 end-point.\n") - 1, NULL
);
2331 static krb5_error_code
2332 mac_csrf_token(struct bx509_request_desc
*r
, krb5_storage
*sp
)
2334 krb5_error_code ret
;
2336 char mac
[EVP_MAX_MD_SIZE
];
2337 unsigned int maclen
= sizeof(mac
);
2338 HMAC_CTX
*ctx
= NULL
;
2340 ret
= krb5_storage_to_data(sp
, &data
);
2341 if (ret
== 0 && (ctx
= HMAC_CTX_new()) == NULL
)
2342 ret
= krb5_enomem(r
->context
);
2343 /* HMAC the token body and the client principal name */
2345 if (HMAC_Init_ex(ctx
, csrf_key
, sizeof(csrf_key
),
2348 HMAC_CTX_cleanup(ctx
);
2349 ret
= krb5_enomem(r
->context
);
2351 HMAC_Update(ctx
, data
.data
, data
.length
);
2353 HMAC_Update(ctx
, r
->cname
, strlen(r
->cname
));
2354 HMAC_Final(ctx
, mac
, &maclen
);
2355 HMAC_CTX_cleanup(ctx
);
2356 krb5_data_free(&data
);
2357 data
.length
= maclen
;
2359 if (krb5_storage_write(sp
, mac
, maclen
) != maclen
)
2360 ret
= krb5_enomem(r
->context
);
2369 * Make a CSRF token. If one is also given, make one with the same body
2370 * content so we can check the HMAC.
2372 * Outputs the token and its age. Do not use either if the token does not
2373 * equal the given token.
2375 static krb5_error_code
2376 make_csrf_token(struct bx509_request_desc
*r
,
2381 krb5_error_code ret
= 0;
2382 unsigned char given_decoded
[128];
2383 krb5_storage
*sp
= NULL
;
2394 size_t len
= strlen(given
);
2396 /* Extract issue time and nonce from token */
2397 if (len
>= sizeof(given_decoded
))
2399 if (ret
== 0 && (dlen
= rk_base64_decode(given
, &given_decoded
)) <= 0)
2402 (sp
= krb5_storage_from_mem(given_decoded
, dlen
)) == NULL
)
2403 ret
= krb5_enomem(r
->context
);
2405 ret
= krb5_ret_int64(sp
, &t
);
2407 ret
= krb5_ret_uint64(sp
, &nonce
);
2408 krb5_storage_free(sp
);
2411 *age
= time(NULL
) - t
;
2414 krb5_generate_random_block((void *)&nonce
, sizeof(nonce
));
2417 if (ret
== 0 && (sp
= krb5_storage_emem()) == NULL
)
2418 ret
= krb5_enomem(r
->context
);
2420 ret
= krb5_store_int64(sp
, t
);
2422 ret
= krb5_store_uint64(sp
, nonce
);
2424 ret
= mac_csrf_token(r
, sp
);
2426 ret
= krb5_storage_to_data(sp
, &data
);
2427 if (ret
== 0 && data
.length
> INT_MAX
)
2430 rk_base64_encode(data
.data
, data
.length
, token
) < 0)
2432 krb5_storage_free(sp
);
2433 krb5_data_free(&data
);
2437 static heim_mhd_result
2438 validate_csrf_token(struct bx509_request_desc
*r
)
2442 krb5_error_code ret
;
2444 if ((((csrf_prot_type
& CSRF_PROT_GET_WITH_HEADER
) &&
2445 strcmp(r
->method
, "GET") == 0) ||
2446 ((csrf_prot_type
& CSRF_PROT_POST_WITH_HEADER
) &&
2447 strcmp(r
->method
, "POST") == 0)) &&
2448 MHD_lookup_connection_value(r
->connection
, MHD_HEADER_KIND
,
2449 csrf_header
) == NULL
) {
2450 ret
= bad_req(r
, EACCES
, MHD_HTTP_FORBIDDEN
,
2451 "Request must have header \"%s\"", csrf_header
);
2452 return ret
== -1 ? MHD_NO
: MHD_YES
;
2455 if (strcmp(r
->method
, "GET") == 0 &&
2456 !(csrf_prot_type
& CSRF_PROT_GET_WITH_TOKEN
))
2458 if (strcmp(r
->method
, "POST") == 0 &&
2459 !(csrf_prot_type
& CSRF_PROT_POST_WITH_TOKEN
))
2462 given
= MHD_lookup_connection_value(r
->connection
, MHD_HEADER_KIND
,
2464 ret
= make_csrf_token(r
, given
, &r
->csrf_token
, &age
);
2466 return bad_503(r
, ret
, "Could not make or validate CSRF token");
2468 return bad_req(r
, EACCES
, MHD_HTTP_FORBIDDEN
,
2469 "CSRF token needed; copy the X-CSRF-Token: response "
2470 "header to your next POST");
2471 if (strlen(given
) != strlen(r
->csrf_token
) ||
2472 strcmp(given
, r
->csrf_token
) != 0)
2473 return bad_403(r
, EACCES
, "Invalid CSRF token");
2475 return bad_403(r
, EACCES
, "CSRF token expired");
2480 * MHD callback to free the request context when MHD is done sending the
2484 cleanup_req(void *cls
,
2485 struct MHD_Connection
*connection
,
2487 enum MHD_RequestTerminationCode toe
)
2489 struct bx509_request_desc
*r
= *con_cls
;
2498 /* Callback for MHD POST form data processing */
2499 static heim_mhd_result
2501 enum MHD_ValueKind kind
,
2503 const char *content_name
,
2504 const char *content_type
,
2505 const char *transfer_encoding
,
2510 struct bx509_request_desc
*r
= cls
;
2511 struct free_tend_list
*ftl
= calloc(1, sizeof(*ftl
));
2512 char *keydup
= strdup(key
);
2513 char *valdup
= strndup(val
, size
);
2515 (void)content_name
; /* MIME attachment name */
2516 (void)content_type
; /* Don't care -- MHD liked it */
2517 (void)transfer_encoding
;
2518 (void)off
; /* Offset in POST data */
2521 * We're going to MHD_set_connection_value(), but we need copies because
2522 * the MHD POST processor quite naturally keeps none of the chunks
2525 if (ftl
== NULL
|| keydup
== NULL
|| valdup
== NULL
) {
2531 ftl
->freeme1
= keydup
;
2532 ftl
->freeme2
= valdup
;
2533 ftl
->next
= r
->free_list
;
2536 return MHD_set_connection_value(r
->connection
, MHD_GET_ARGUMENT_KIND
,
2540 typedef krb5_error_code (*handler
)(struct bx509_request_desc
*);
2543 const char *local_part
;
2545 unsigned int referer_ok
:1;
2547 { "/get-cert", bx509
, 0 },
2548 { "/get-negotiate-token", bnegotiate
, 1 },
2549 { "/get-tgt", get_tgt
, 0 },
2550 { "/get-tgts", get_tgts
, 0 },
2551 /* Lousy old names to be removed eventually */
2552 { "/bnegotiate", bnegotiate
, 1 },
2553 { "/bx509", bx509
, 0 },
2557 * We should commonalize all of:
2559 * - route() and related infrastructure
2560 * - including the CSRF functions
2561 * - and Negotiate/Bearer authentication
2563 * so that we end up with a simple framework that our daemons can invoke to
2564 * serve simple functions that take a fully-consumed request and send a
2569 * - split out the CA and non-CA bits into separate daemons using that common
2571 * - make httpkadmind use that common code,
2572 * - abstract out all the MHD stuff.
2575 /* Routes requests */
2576 static heim_mhd_result
2578 struct MHD_Connection
*connection
,
2581 const char *version
,
2582 const char *upload_data
,
2583 size_t *upload_data_size
,
2586 struct bx509_request_desc
*r
= *ctx
;
2592 * This is the first call, right after headers were read.
2594 * We must return quickly so that any 100-Continue might be sent with
2595 * celerity. We want to make sure to send any 401s early, so we check
2596 * WWW-Authenticate now, not later.
2598 * We'll get called again to really do the processing. If we're
2599 * handling a POST then we'll also get called with upload_data != NULL,
2600 * possibly multiple times.
2602 if ((ret
= set_req_desc(connection
, method
, url
, &r
)))
2606 /* All requests other than /health require authentication */
2607 if (strcmp(url
, "/health") == 0)
2611 * Authenticate and do CSRF protection.
2613 * If the Referer: header is set in the request, we don't want CSRF
2614 * protection as only /get-negotiate-token will accept a Referer:
2615 * header (see routes[] and below), so we'll call validate_csrf_token()
2616 * for the other routes or reject the request for having Referer: set.
2618 ret
= validate_token(r
);
2620 MHD_lookup_connection_value(r
->connection
, MHD_HEADER_KIND
, "Referer") == NULL
)
2621 ret
= validate_csrf_token(r
);
2624 * As this is the initial call to this handler, we must return now.
2626 * If authentication or CSRF protection failed then we'll already have
2627 * enqueued a 401, 403, or 5xx response and then we're done.
2629 * If both authentication and CSRF protection succeeded then no
2630 * response has been queued up and we'll get called again to finally
2631 * process the request, then this entire if block will not be executed.
2633 return ret
== -1 ? MHD_NO
: MHD_YES
;
2636 /* Validate HTTP method */
2637 if (strcmp(method
, "GET") != 0 &&
2638 strcmp(method
, "POST") != 0 &&
2639 strcmp(method
, "HEAD") != 0) {
2640 return bad_405(r
, method
) == -1 ? MHD_NO
: MHD_YES
;
2643 if ((strcmp(method
, "HEAD") == 0 || strcmp(method
, "GET") == 0) &&
2644 (strcmp(url
, "/health") == 0 || strcmp(url
, "/") == 0)) {
2645 /* /health end-point -- no authentication, no CSRF, no nothing */
2646 return health(method
, r
) == -1 ? MHD_NO
: MHD_YES
;
2649 if (r
->cname
== NULL
)
2650 return bad_401(r
, "Authorization token is missing");
2652 if (strcmp(method
, "POST") == 0 && *upload_data_size
!= 0) {
2654 * Consume all the POST body and set form data as MHD_GET_ARGUMENT_KIND
2655 * (as if they had been URI query parameters).
2657 * We have to do this before we can MHD_queue_response() as MHD will
2658 * not consume the rest of the request body on its own, so it's an
2659 * error to MHD_queue_response() before we've done this, and if we do
2660 * then MHD just closes the connection.
2662 * 4KB should be more than enough buffer space for all the keys we
2666 r
->pp
= MHD_create_post_processor(connection
, 4096, ip
, r
);
2667 if (r
->pp
== NULL
) {
2668 ret
= bad_503(r
, errno
? errno
: ENOMEM
,
2669 "Could not consume POST data");
2670 return ret
== -1 ? MHD_NO
: MHD_YES
;
2672 if (r
->post_data_size
+ *upload_data_size
> 1UL<<17) {
2673 return bad_413(r
) == -1 ? MHD_NO
: MHD_YES
;
2675 r
->post_data_size
+= *upload_data_size
;
2676 if (MHD_post_process(r
->pp
, upload_data
,
2677 *upload_data_size
) == MHD_NO
) {
2678 ret
= bad_503(r
, errno
? errno
: ENOMEM
,
2679 "Could not consume POST data");
2680 return ret
== -1 ? MHD_NO
: MHD_YES
;
2682 *upload_data_size
= 0;
2687 * Either this is a HEAD, a GET, or a POST whose request body has now been
2688 * received completely and processed.
2692 if (strcmp(method
, "GET") == 0 && !allow_GET_flag
) {
2694 return bad_405(r
, method
) == -1 ? MHD_NO
: MHD_YES
;
2697 for (i
= 0; i
< sizeof(routes
)/sizeof(routes
[0]); i
++) {
2698 if (strcmp(url
, routes
[i
].local_part
) != 0)
2700 if (!routes
[i
].referer_ok
&&
2701 MHD_lookup_connection_value(r
->connection
,
2703 "Referer") != NULL
) {
2704 ret
= bad_req(r
, EACCES
, MHD_HTTP_FORBIDDEN
,
2705 "GET from browser not allowed");
2706 return ret
== -1 ? MHD_NO
: MHD_YES
;
2708 if (strcmp(method
, "HEAD") == 0)
2709 ret
= resp(r
, MHD_HTTP_OK
, MHD_RESPMEM_PERSISTENT
, NULL
, "", 0,
2712 ret
= routes
[i
].h(r
);
2713 return ret
== -1 ? MHD_NO
: MHD_YES
;
2716 ret
= bad_404(r
, url
);
2717 return ret
== -1 ? MHD_NO
: MHD_YES
;
2720 static struct getargs args
[] = {
2721 { "help", 'h', arg_flag
, &help_flag
, "Print usage message", NULL
},
2722 { "version", '\0', arg_flag
, &version_flag
, "Print version", NULL
},
2723 { NULL
, 'H', arg_strings
, &audiences
,
2724 "expected token audience(s)", "HOSTNAME" },
2725 { "daemon", 'd', arg_flag
, &daemonize
, "daemonize", "daemonize" },
2726 { "daemon-child", 0, arg_flag
, &daemon_child_fd
, NULL
, NULL
}, /* priv */
2727 { "reverse-proxied", 0, arg_flag
, &reverse_proxied_flag
,
2728 "reverse proxied", "listen on 127.0.0.1 and do not use TLS" },
2729 { "port", 'p', arg_integer
, &port
, "port number (default: 443)", "PORT" },
2730 { "cache-dir", 0, arg_string
, &cache_dir
,
2731 "cache directory", "DIRECTORY" },
2732 { "allow-GET", 0, arg_negative_flag
, &allow_GET_flag
, NULL
, NULL
},
2733 { "csrf-header", 0, arg_flag
,
2734 &csrf_header
, "required request header", "HEADER-NAME" },
2735 { "csrf-protection-type", 0, arg_strings
, &csrf_prot_type_strs
,
2736 "Anti-CSRF protection type", "TYPE" },
2737 { "csrf-key-file", 0, arg_string
, &csrf_key_file
,
2738 "CSRF MAC key", "FILE" },
2739 { "cert", 0, arg_string
, &cert_file
,
2740 "certificate file path (PEM)", "HX509-STORE" },
2741 { "private-key", 0, arg_string
, &priv_key_file
,
2742 "private key file path (PEM)", "HX509-STORE" },
2743 { "thread-per-client", 't', arg_flag
, &thread_per_client_flag
,
2744 "thread per-client", "use thread per-client" },
2745 { "verbose", 'v', arg_counter
, &verbose_counter
, "verbose", "run verbosely" }
2751 arg_printusage(args
, sizeof(args
) / sizeof(args
[0]), "bx509",
2752 "\nServes RESTful GETs of /get-cert, /get-tgt, /get-tgts, and\n"
2753 "/get-negotiate-toke, performing corresponding kx509 and, \n"
2754 "possibly, PKINIT requests to the KDCs of the requested \n"
2755 "realms (or just the given REALM).\n");
2759 static int sigpipe
[2] = { -1, -1 };
2765 while (write(sigpipe
[1], &c
, sizeof(c
)) == -1 && errno
== EINTR
)
2770 bx509_openlog(krb5_context context
,
2772 krb5_log_facility
**fac
)
2774 char **s
= NULL
, **p
;
2776 krb5_initlog(context
, "bx509d", fac
);
2777 s
= krb5_config_get_strings(context
, NULL
, svc
, "logging", NULL
);
2779 s
= krb5_config_get_strings(context
, NULL
, "logging", svc
, NULL
);
2782 krb5_addlog_dest(context
, *fac
, *p
);
2783 krb5_config_free_strings(s
);
2786 if (asprintf(&ss
, "0-1/FILE:%s/%s", hdb_db_dir(context
),
2788 err(1, "out of memory");
2789 krb5_addlog_dest(context
, *fac
, ss
);
2792 krb5_set_warn_dest(context
, *fac
);
2795 static const char *sysplugin_dirs
[] = {
2799 "$ORIGIN/../lib/plugin/kdc",
2802 LIBDIR
"/plugin/kdc",
2808 load_plugins(krb5_context context
)
2810 const char * const *dirs
= sysplugin_dirs
;
2814 cfdirs
= krb5_config_get_strings(context
, NULL
, "kdc", "plugin_dir", NULL
);
2816 dirs
= (const char * const *)cfdirs
;
2820 _krb5_load_plugins(context
, "kdc", (const char **)dirs
);
2823 krb5_config_free_strings(cfdirs
);
2828 get_csrf_prot_type(krb5_context context
)
2830 char * const *strs
= csrf_prot_type_strs
.strings
;
2831 size_t n
= csrf_prot_type_strs
.num_strings
;
2833 char **freeme
= NULL
;
2835 if (csrf_header
== NULL
)
2836 csrf_header
= krb5_config_get_string(context
, NULL
, "bx509d",
2837 "csrf_protection_csrf_header",
2843 strs
= freeme
= krb5_config_get_strings(context
, NULL
, "bx509d",
2844 "csrf_protection_type", NULL
);
2845 for (p
= strs
; p
&& p
; p
++)
2849 for (i
= 0; i
< n
; i
++) {
2850 if (strcmp(strs
[i
], "GET-with-header") == 0)
2851 csrf_prot_type
|= CSRF_PROT_GET_WITH_HEADER
;
2852 else if (strcmp(strs
[i
], "GET-with-token") == 0)
2853 csrf_prot_type
|= CSRF_PROT_GET_WITH_TOKEN
;
2854 else if (strcmp(strs
[i
], "POST-with-header") == 0)
2855 csrf_prot_type
|= CSRF_PROT_POST_WITH_HEADER
;
2856 else if (strcmp(strs
[i
], "POST-with-token") == 0)
2857 csrf_prot_type
|= CSRF_PROT_POST_WITH_TOKEN
;
2862 * For GETs we default to no CSRF protection as our GETable resources are
2863 * safe and idempotent and we count on the browser not to make the
2864 * responses available to cross-site requests.
2866 * But, really, we don't want browsers even making these requests since, if
2867 * the browsers behave correctly, then there's no point, and if they don't
2868 * behave correctly then that could be catastrophic. Of course, there's no
2869 * guarantee that a browser won't have other catastrophic bugs, but still,
2870 * we should probably change this default in the future:
2872 * if (!(csrf_prot_type & CSRF_PROT_GET_WITH_HEADER) &&
2873 * !(csrf_prot_type & CSRF_PROT_GET_WITH_TOKEN))
2874 * csrf_prot_type |= <whatever-the-new-default-should-be>;
2878 * For POSTs we default to CSRF protection with anti-CSRF tokens even
2879 * though out POSTable resources are safe and idempotent when POSTed and we
2880 * could count on the browser not to make the responses available to
2881 * cross-site requests.
2883 if (!(csrf_prot_type
& CSRF_PROT_POST_WITH_HEADER
) &&
2884 !(csrf_prot_type
& CSRF_PROT_POST_WITH_TOKEN
))
2885 csrf_prot_type
|= CSRF_PROT_POST_WITH_TOKEN
;
2889 main(int argc
, char **argv
)
2891 unsigned int flags
= MHD_USE_THREAD_PER_CONNECTION
; /* XXX */
2892 struct sockaddr_in sin
;
2893 struct MHD_Daemon
*previous
= NULL
;
2894 struct MHD_Daemon
*current
= NULL
;
2895 struct sigaction sa
;
2896 krb5_context context
= NULL
;
2897 MHD_socket sock
= MHD_INVALID_SOCKET
;
2898 char *priv_key_pem
= NULL
;
2899 char *cert_pem
= NULL
;
2904 setprogname("bx509d");
2905 if (getarg(args
, sizeof(args
) / sizeof(args
[0]), argc
, argv
, &optidx
))
2910 print_version(NULL
);
2913 if (argc
> optidx
) /* Add option to set a URI local part prefix? */
2916 errx(1, "Port number must be given");
2918 if ((errno
= pthread_key_create(&k5ctx
, k5_free_context
)))
2919 err(1, "Could not create thread-specific storage");
2921 if ((errno
= get_krb5_context(&context
)))
2922 err(1, "Could not init krb5 context");
2924 bx509_openlog(context
, "bx509d", &logfac
);
2925 krb5_set_log_dest(context
, logfac
);
2926 load_plugins(context
);
2928 if (allow_GET_flag
== -1)
2929 warnx("It is safer to use --no-allow-GET");
2931 get_csrf_prot_type(context
);
2933 krb5_generate_random_block((void *)&csrf_key
, sizeof(csrf_key
));
2934 if (csrf_key_file
== NULL
)
2935 csrf_key_file
= krb5_config_get_string(context
, NULL
, "bx509d",
2936 "csrf_key_file", NULL
);
2937 if (csrf_key_file
) {
2941 fd
= open(csrf_key_file
, O_RDONLY
);
2943 err(1, "CSRF key file missing %s", csrf_key_file
);
2944 bytes
= read(fd
, csrf_key
, sizeof(csrf_key
));
2946 err(1, "Could not read CSRF key file %s", csrf_key_file
);
2947 if (bytes
!= sizeof(csrf_key
))
2948 errx(1, "CSRF key file too small (should be %lu) %s",
2949 (unsigned long)sizeof(csrf_key
), csrf_key_file
);
2952 if (audiences
.num_strings
== 0) {
2953 char localhost
[MAXHOSTNAMELEN
];
2955 ret
= gethostname(localhost
, sizeof(localhost
));
2957 errx(1, "Could not determine local hostname; use --audience");
2959 if ((audiences
.strings
=
2960 calloc(1, sizeof(audiences
.strings
[0]))) == NULL
||
2961 (audiences
.strings
[0] = strdup(localhost
)) == NULL
)
2962 err(1, "Out of memory");
2963 audiences
.num_strings
= 1;
2966 if (daemonize
&& daemon_child_fd
== -1)
2967 daemon_child_fd
= roken_detach_prep(argc
, argv
, "--daemon-child");
2975 if (cache_dir
== NULL
) {
2978 if (asprintf(&s
, "%s/bx509d-XXXXXX",
2979 getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp") == -1 ||
2981 (cache_dir
= mkdtemp(s
)) == NULL
)
2982 err(1, "could not create temporary cache directory");
2983 if (verbose_counter
)
2984 fprintf(stderr
, "Note: using %s as cache directory\n", cache_dir
);
2985 atexit(rm_cache_dir
);
2986 setenv("TMPDIR", cache_dir
, 1);
2989 generate_key(context
->hx509ctx
, "impersonation", "rsa", 2048, &impersonation_key_fn
);
2992 if (cert_file
&& !priv_key_file
)
2993 priv_key_file
= cert_file
;
2996 hx509_cursor cursor
= NULL
;
2997 hx509_certs certs
= NULL
;
2998 hx509_cert cert
= NULL
;
2999 time_t min_cert_life
= 0;
3003 ret
= hx509_certs_init(context
->hx509ctx
, cert_file
, 0, NULL
, &certs
);
3005 ret
= hx509_certs_start_seq(context
->hx509ctx
, certs
, &cursor
);
3007 (ret
= hx509_certs_next_cert(context
->hx509ctx
, certs
,
3008 cursor
, &cert
)) == 0 && cert
) {
3009 time_t notAfter
= 0;
3011 if (!hx509_cert_have_private_key_only(cert
) &&
3012 (notAfter
= hx509_cert_get_notAfter(cert
)) <= time(NULL
) + 30)
3013 errx(1, "One or more certificates in %s are expired",
3016 notAfter
-= time(NULL
);
3018 warnx("One or more certificates in %s expire soon",
3020 /* Reload 5 minutes prior to expiration */
3021 if (notAfter
< min_cert_life
|| min_cert_life
< 1)
3022 min_cert_life
= notAfter
;
3024 hx509_cert_free(cert
);
3027 (void) hx509_certs_end_seq(context
->hx509ctx
, certs
, cursor
);
3028 if (min_cert_life
> 4)
3029 alarm(min_cert_life
>> 1);
3030 hx509_certs_free(&certs
);
3032 hx509_err(context
->hx509ctx
, 1, ret
,
3033 "could not read certificate from %s", cert_file
);
3035 if ((errno
= rk_undumpdata(cert_file
, &s
, &len
)) ||
3036 (cert_pem
= strndup(s
, len
)) == NULL
)
3037 err(1, "could not read certificate from %s", cert_file
);
3038 if (strlen(cert_pem
) != len
)
3039 err(1, "NULs in certificate file contents: %s", cert_file
);
3043 if (priv_key_file
) {
3047 if ((errno
= rk_undumpdata(priv_key_file
, &s
, &len
)) ||
3048 (priv_key_pem
= strndup(s
, len
)) == NULL
)
3049 err(1, "could not read private key from %s", priv_key_file
);
3050 if (strlen(priv_key_pem
) != len
)
3051 err(1, "NULs in private key file contents: %s", priv_key_file
);
3055 if (verbose_counter
> 1)
3056 flags
|= MHD_USE_DEBUG
;
3057 if (thread_per_client_flag
)
3058 flags
|= MHD_USE_THREAD_PER_CONNECTION
;
3061 if (pipe(sigpipe
) == -1)
3062 err(1, "Could not set up key/cert reloading");
3063 memset(&sa
, 0, sizeof(sa
));
3064 sa
.sa_handler
= sighandler
;
3065 if (reverse_proxied_flag
) {
3067 * We won't use TLS in the reverse proxy case, so no need to reload
3068 * certs. But we'll still read them if given, and alarm() will get
3071 (void) signal(SIGHUP
, SIG_IGN
);
3072 (void) signal(SIGUSR1
, SIG_IGN
);
3073 (void) signal(SIGALRM
, SIG_IGN
);
3075 (void) sigaction(SIGHUP
, &sa
, NULL
); /* Reload key & cert */
3076 (void) sigaction(SIGUSR1
, &sa
, NULL
); /* Reload key & cert */
3077 (void) sigaction(SIGALRM
, &sa
, NULL
); /* Reload key & cert */
3079 (void) sigaction(SIGINT
, &sa
, NULL
); /* Graceful shutdown */
3080 (void) sigaction(SIGTERM
, &sa
, NULL
); /* Graceful shutdown */
3081 (void) signal(SIGPIPE
, SIG_IGN
);
3084 sock
= MHD_quiesce_daemon(previous
);
3086 if (reverse_proxied_flag
) {
3088 * XXX IPv6 too. Create the sockets and tell MHD_start_daemon() about
3091 sin
.sin_addr
.s_addr
= htonl(INADDR_LOOPBACK
);
3092 sin
.sin_family
= AF_INET
;
3093 sin
.sin_port
= htons(port
);
3094 current
= MHD_start_daemon(flags
, port
,
3096 * This is a connection access callback. We
3100 /* This is our request handler */
3101 route
, (char *)NULL
,
3102 MHD_OPTION_SOCK_ADDR
, &sin
,
3103 MHD_OPTION_CONNECTION_LIMIT
, (unsigned int)200,
3104 MHD_OPTION_CONNECTION_TIMEOUT
, (unsigned int)10,
3105 /* This is our request cleanup handler */
3106 MHD_OPTION_NOTIFY_COMPLETED
, cleanup_req
, NULL
,
3108 } else if (sock
!= MHD_INVALID_SOCKET
) {
3110 * Restart following a possible certificate/key rollover, reusing the
3111 * listen socket returned by MHD_quiesce_daemon().
3113 current
= MHD_start_daemon(flags
| MHD_USE_SSL
, port
,
3115 route
, (char *)NULL
,
3116 MHD_OPTION_HTTPS_MEM_KEY
, priv_key_pem
,
3117 MHD_OPTION_HTTPS_MEM_CERT
, cert_pem
,
3118 MHD_OPTION_CONNECTION_LIMIT
, (unsigned int)200,
3119 MHD_OPTION_CONNECTION_TIMEOUT
, (unsigned int)10,
3120 MHD_OPTION_NOTIFY_COMPLETED
, cleanup_req
, NULL
,
3121 MHD_OPTION_LISTEN_SOCKET
, sock
,
3123 sock
= MHD_INVALID_SOCKET
;
3126 * Initial MHD_start_daemon(), with TLS.
3128 * Subsequently we'll restart reusing the listen socket this creates.
3131 current
= MHD_start_daemon(flags
| MHD_USE_SSL
, port
,
3133 route
, (char *)NULL
,
3134 MHD_OPTION_HTTPS_MEM_KEY
, priv_key_pem
,
3135 MHD_OPTION_HTTPS_MEM_CERT
, cert_pem
,
3136 MHD_OPTION_CONNECTION_LIMIT
, (unsigned int)200,
3137 MHD_OPTION_CONNECTION_TIMEOUT
, (unsigned int)10,
3138 MHD_OPTION_NOTIFY_COMPLETED
, cleanup_req
, NULL
,
3141 if (current
== NULL
)
3142 err(1, "Could not start bx509 REST service");
3145 MHD_stop_daemon(previous
);
3149 if (verbose_counter
)
3150 fprintf(stderr
, "Ready!\n");
3151 if (daemon_child_fd
!= -1)
3152 roken_detach_finish(NULL
, daemon_child_fd
);
3154 /* Wait for signal, possibly SIGALRM, to reload certs and/or exit */
3155 while ((ret
= read(sigpipe
[0], &sig
, sizeof(sig
))) == -1 &&
3161 priv_key_pem
= NULL
;
3164 if (ret
== 1 && (sig
== SIGHUP
|| sig
== SIGUSR1
|| sig
== SIGALRM
)) {
3165 /* Reload certs and restart service gracefully */
3171 MHD_stop_daemon(current
);
3172 _krb5_unload_plugins(context
, "kdc");
3173 pthread_key_delete(k5ctx
);