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.
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:
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
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
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
77 #include <sys/socket.h>
78 #include <sys/types.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"
105 #include <gssapi/gssapi.h>
106 #include <gssapi/gssapi_krb5.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 */
121 enum MHD_Result
{ MHD_NO
= 0, MHD_YES
= 1 };
124 typedef int heim_mhd_result
;
126 typedef enum MHD_Result heim_mhd_result
;
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
;
138 const char *for_cname
;
141 enum k5_creds_kind cckind
;
145 krb5_addresses tgt_addresses
; /* For /get-tgt */
147 } *bx509_request_desc
;
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
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 */
194 /* Let's save a few bytes */
195 if (retname
&& strncmp("KRB5KDC_", retname
, sizeof("KRB5KDC_") - 1) == 0)
196 retname
+= sizeof("KRB5KDC_") - 1;
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
)
209 if ((*contextp
= pthread_getspecific(k5ctx
)))
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,
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 *);
248 validate_token(struct bx509_request_desc
*r
)
251 krb5_principal cprinc
= NULL
;
254 char token_type
[64]; /* Plenty */
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
);
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
);
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';
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')
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
);
294 return bad_403(r
, ret
, "Token validation failed");
296 return bad_403(r
, ret
, "Could not extract a principal name "
298 ret
= krb5_unparse_name(r
->context
, cprinc
, &r
->cname
);
299 krb5_free_principal(r
->context
, cprinc
);
301 return bad_503(r
, ret
, "Could not parse principal name");
306 generate_key(hx509_context context
,
307 const char *key_name
,
308 const char *gen_type
,
309 unsigned long gen_bits
,
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
;
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 ||
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
,
330 ret
= _hx509_generate_private_key_bits(context
, key_gen_ctx
, gen_bits
);
332 ret
= _hx509_generate_private_key(context
, key_gen_ctx
, &key
);
334 cert
= hx509_cert_init_private_key(context
, key
, NULL
);
336 ret
= hx509_certs_init(context
, *fn
,
337 HX509_CERTS_CREATE
| HX509_CERTS_UNPROTECT_ALL
,
340 ret
= hx509_certs_add(context
, certs
, cert
);
342 ret
= hx509_certs_store(context
, certs
, 0, NULL
);
344 hx509_err(context
, 1, ret
, "Could not generate and save private key "
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
);
354 k5_free_context(void *ctx
)
356 krb5_free_context(ctx
);
359 #ifndef HAVE_UNLINKAT
361 unlink1file(const char *dname
, const char *name
)
365 if (strlcpy(p
, dname
, sizeof(p
)) < sizeof(p
) &&
366 strlcat(p
, "/", sizeof(p
)) < sizeof(p
) &&
367 strlcat(p
, name
, sizeof(p
)) < sizeof(p
))
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
)
389 while ((e
= readdir(d
))) {
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);
398 (void) unlink1file(cache_dir
, e
->d_name
);
402 (void) rmdir(cache_dir
);
405 static krb5_error_code
406 mk_pkix_store(char **pkix_store
)
413 if (asprintf(&s
, "PEM-FILE:%s/pkix-XXXXXX", cache_dir
) == -1 ||
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) {
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
,
445 struct MHD_Response
*response
;
448 (void) gettimeofday(&r
->tv_end
, NULL
);
449 if (http_status_code
== MHD_HTTP_OK
||
450 http_status_code
== MHD_HTTP_TEMPORARY_REDIRECT
)
453 response
= MHD_create_response_from_buffer(bodylen
, rk_UNCONST(body
),
455 if (response
== NULL
)
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
,
464 mret
= MHD_add_response_header(response
,
465 MHD_HTTP_HEADER_WWW_AUTHENTICATE
,
467 } else if (mret
== MHD_YES
&& http_status_code
== MHD_HTTP_TEMPORARY_REDIRECT
) {
471 redir
= MHD_lookup_connection_value(r
->connection
, MHD_GET_ARGUMENT_KIND
,
473 mret
= MHD_add_response_header(response
, MHD_HTTP_HEADER_LOCATION
,
475 if (mret
!= MHD_NO
&& token
)
476 mret
= MHD_add_response_header(response
,
477 MHD_HTTP_HEADER_AUTHORIZATION
,
480 if (mret
== MHD_YES
&& content_type
) {
481 mret
= MHD_add_response_header(response
,
482 MHD_HTTP_HEADER_CONTENT_TYPE
,
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
,
499 krb5_context context
= NULL
;
500 const char *k5msg
= NULL
;
501 const char *emsg
= NULL
;
502 char *formatted
= NULL
;
505 heim_audit_setkv_number((heim_svc_req_desc
)r
, "http-status-code",
507 (void) gettimeofday(&r
->tv_end
, NULL
);
508 if (code
== ENOMEM
) {
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
);
518 emsg
= k5msg
= krb5_get_error_message(r
->context
, code
);
520 emsg
= strerror(code
);
523 ret
= vasprintf(&formatted
, fmt
, ap
) == -1;
525 if (ret
> -1 && formatted
)
526 ret
= asprintf(&msg
, "%s: %s (%d)", formatted
, emsg
, (int)code
);
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
) {
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
);
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
,
560 ret
= bad_reqv(r
, code
, http_status_code
, fmt
, ap
);
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
,
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
,
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
,
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
)
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
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
);
641 return bad_503(r
, ret
, "Could not recover issued certificate "
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
);
651 static heim_mhd_result
652 bx509_param_cb(void *d
,
653 enum MHD_ValueKind kind
,
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",
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
);
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
,
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
,
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
,
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
,
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");
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
,
715 krb5_const_principal p
)
719 ret
= hx509_request_parse_der(r
->context
->hx509ctx
, csr
, &r
->req
);
721 return bad_req(r
, ret
, MHD_HTTP_SERVICE_UNAVAILABLE
,
722 "Could not parse CSR");
724 (void) MHD_get_connection_values(r
->connection
, MHD_GET_ARGUMENT_KIND
,
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
);
733 return bad_403(r
, ret
, "Not authorized to requested certificate");
738 * hx509_certs_iter_f() callback to assign a private key to the first cert in a
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
,
751 hx509_certs store_these
,
752 hx509_private_key key
)
755 hx509_certs certs
= NULL
;
757 ret
= hx509_certs_init(context
, store
, HX509_CERTS_CREATE
, NULL
,
761 (void) hx509_certs_iter_f(context
, store_these
, set_priv_key
, key
);
762 hx509_certs_merge(context
, certs
, store_these
);
765 hx509_certs_store(context
, certs
, 0, NULL
);
766 hx509_certs_free(&certs
);
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;
776 hx509_certs certs
= NULL
;
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
786 if ((csr2
= strdup(csr
)) == NULL
)
787 return bad_enomem(r
, ENOMEM
);
788 for (q
= strchr(csr2
, ' '); q
; q
= strchr(q
+ 1, ' '))
791 ret
= krb5_parse_name(r
->context
, r
->cname
, &p
);
794 return bad_req(r
, ret
, MHD_HTTP_SERVICE_UNAVAILABLE
,
795 "Could not parse principal name");
799 if ((d
.data
= malloc(strlen(csr2
))) == NULL
) {
800 krb5_free_principal(r
->context
, p
);
802 return bad_enomem(r
, ENOMEM
);
805 bytes
= rk_base64_decode(csr2
, d
.data
);
812 krb5_free_principal(r
->context
, p
);
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
);
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
);
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
);
850 return bad_500(r
, ret
, "Failed to convert issued"
851 " certificate and chain to PEM");
855 /* Copied from kdc/connect.c */
857 addr_to_string(krb5_context context
,
858 struct sockaddr
*addr
,
865 ret
= krb5_sockaddr2address(context
, addr
, &a
);
867 ret
= krb5_print_address(&a
, str
, len
, &len
);
868 krb5_free_address(context
, &a
);
871 snprintf(str
, len
, "<family=%d>", addr
->sa_family
);
874 static krb5_error_code
875 set_req_desc(struct MHD_Connection
*connection
,
877 struct bx509_request_desc
*r
)
879 const union MHD_ConnectionInfo
*ci
;
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
;
897 r
->target
= r
->redir
= NULL
;
898 r
->pkix_store
= NULL
;
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
);
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>");
932 heim_audit_addkv((heim_svc_req_desc
)r
, 0, "auth", "%.*s",
933 (int)(token_end
- token
), token
);
941 clean_req_desc(struct bx509_request_desc
*r
)
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
953 fn
= fn
? fn
+ 1 : r
->pkix_store
;
956 krb5_free_addresses(r
->context
, &r
->tgt_addresses
);
957 hx509_request_free(&r
->req
);
958 heim_release(r
->reason
);
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;
974 /* Implements GETs of /bx509 */
975 static krb5_error_code
976 bx509(struct bx509_request_desc
*r
)
981 /* Get required inputs */
982 csr
= MHD_lookup_connection_value(r
->connection
, MHD_GET_ARGUMENT_KIND
,
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
)))
998 /* Read and send the contents of the PKIX store */
999 krb5_log_msg(r
->context
, logfac
, 1, NULL
, "Issued certificate to %s",
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()!
1014 princ_fs_encode_sz(const char *in
)
1016 size_t sz
= strlen(in
);
1019 unsigned char c
= *(const unsigned char *)(in
++);
1036 princ_fs_encode(const char *in
)
1038 size_t len
= strlen(in
);
1039 size_t sz
= princ_fs_encode_sz(in
);
1043 if ((s
= malloc(sz
+ 1)) == NULL
)
1047 for (i
= k
= 0; i
< len
; i
++) {
1061 s
[k
++] = "0123456789abcdef"[(c
&0xff)>>4];
1062 s
[k
++] = "0123456789abcdef"[(c
&0x0f)];
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
;
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 ||
1097 if ((ret
= krb5_cc_resolve(context
, *ccname
, &cc
))) {
1098 /* krb5_cc_resolve() suceeds even if the file doesn't exist */
1104 /* Check if we have a good enough credential */
1106 (ret
= krb5_cc_get_lifetime(context
, cc
, &life
)) == 0 && life
> 60) {
1107 krb5_cc_close(context
, 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
;
1125 * Open and lock a .new ccache file. Use .new to avoid garbage files on
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.
1142 if (asprintf(&temp_ccname
, "%s.ccnew", r
->ccname
) == -1 ||
1143 temp_ccname
== NULL
)
1146 fn
= temp_ccname
+ sizeof("FILE:") - 1;
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
1161 memset(&st1
, 0, sizeof(st1
));
1162 memset(&st2
, 0xff, sizeof(st2
));
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))
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
))
1173 if (unlink(fn
) == -1)
1178 /* Check if we lost any race to acquire Kerberos creds */
1180 ret
= krb5_cc_resolve(r
->context
, temp_ccname
, cc
);
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 */
1190 (void) close(fd
); /* Drops the flock */
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
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
;
1213 const char *cname
= r
->for_cname
? r
->for_cname
: r
->cname
;
1215 if (kind
== K5_CREDS_CACHED
) {
1218 ret
= get_ccache(r
, &temp_cc
, &won
);
1222 * We won the race to do PKINIT. Setup to acquire Kerberos creds with
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.
1231 ret
= krb5_cc_new_unique(r
->context
, "FILE", NULL
, &temp_cc
);
1235 ret
= krb5_parse_name(r
->context
, cname
, &p
);
1237 crealm
= krb5_principal_get_realm(r
->context
, p
);
1239 ret
= krb5_get_init_creds_opt_alloc(r
->context
, &opt
);
1241 krb5_get_init_creds_opt_set_default_flags(r
->context
, "kinit", crealm
,
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
);
1250 ret
= krb5_append_addresses(r
->context
, &r
->tgt_addresses
,
1253 if (ret
== 0 && r
->tgt_addresses
.len
== 0)
1254 ret
= krb5_get_init_creds_opt_set_addressless(r
->context
, opt
, 1);
1256 krb5_get_init_creds_opt_set_address_list(opt
, &r
->tgt_addresses
);
1258 ret
= krb5_get_init_creds_opt_set_pkinit(r
->context
, opt
, p
,
1260 NULL
, /* pkinit_anchor */
1261 NULL
, /* anchor_chain */
1262 NULL
, /* pkinit_crl */
1264 NULL
, /* prompter */
1265 NULL
, /* prompter data */
1266 NULL
/* password */);
1268 ret
= krb5_init_creds_init(r
->context
, p
,
1269 NULL
/* prompter */,
1270 NULL
/* prompter data */,
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.
1280 ret
= krb5_init_creds_get(r
->context
, ctx
);
1282 ret
= krb5_init_creds_store(r
->context
, ctx
, temp_cc
);
1283 if (kind
== K5_CREDS_CACHED
) {
1285 ret
= krb5_cc_resolve(r
->context
, r
->ccname
, &cc
);
1287 ret
= krb5_cc_move(r
->context
, temp_cc
, cc
);
1290 } else if (ret
== 0 && kind
== K5_CREDS_EPHEMERAL
) {
1291 ret
= krb5_cc_get_full_name(r
->context
, temp_cc
, &r
->ccname
);
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
);
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
;
1312 ret
= hx509_certs_init(context
->hx509ctx
, fn
, 0, NULL
, &certs
);
1316 ret
= _hx509_certs_keys_get(context
->hx509ctx
, certs
, &keys
);
1317 if (ret
== 0 && keys
[0] == NULL
)
1318 ret
= ENOENT
; /* XXX Better error please */
1320 *key
= _hx509_private_key_ref(keys
[0]);
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
);
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
);
1349 ret
= hx509_request_init(r
->context
->hx509ctx
, &req
);
1351 ret
= krb5_parse_name(r
->context
, cname
, &p
);
1353 ret
= hx509_private_key2SPKI(r
->context
->hx509ctx
, key
, &spki
);
1355 hx509_request_set_SubjectPublicKeyInfo(r
->context
->hx509ctx
, req
,
1357 free_SubjectPublicKeyInfo(&spki
);
1359 ret
= hx509_request_add_pkinit(r
->context
->hx509ctx
, req
, cname
);
1361 ret
= hx509_request_add_eku(r
->context
->hx509ctx
, req
,
1362 &asn1_oid_id_pkekuoid
);
1364 /* Mark it authorized */
1366 ret
= hx509_request_authorize_san(req
, 0);
1368 ret
= hx509_request_authorize_eku(req
, 0);
1370 hx509_request_authorize_ku(req
, ku
);
1372 /* Issue the certificate */
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
);
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");
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
);
1398 ret
= store_certs(r
->context
->hx509ctx
, r
->pkix_store
, certs
, key
);
1399 hx509_private_key_free(&key
);
1400 hx509_certs_free(&certs
);
1402 return bad_500(r
, ret
,
1403 "Could not create PEM store for issued certificate");
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 */
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");
1433 /* Accumulate strings */
1435 acc_str(char **acc
, char *adds
, size_t addslen
)
1438 int l
= addslen
<= INT_MAX
? (int)addslen
: INT_MAX
;
1440 if (asprintf(&tmp
, "%s%s%.*s",
1442 *acc
? "; " : "", l
, adds
) > -1 &&
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
;
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";
1468 fmt_gss_errors(const char *r
, OM_uint32 major
, OM_uint32 minor
, gss_OID mech
)
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 &&
1485 static krb5_error_code
1486 bad_req_gss(struct bx509_request_desc
*r
,
1490 int http_status_code
,
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
);
1505 /* Make an HTTP/Negotiate token */
1506 static krb5_error_code
1507 mk_nego_tok(struct bx509_request_desc
*r
,
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
;
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
,
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
);
1577 ret
= asprintf(nego_tok
, "Negotiate %s", 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 "
1587 static krb5_error_code
1588 bnegotiate_get_target(struct bx509_request_desc
*r
)
1592 const char *referer
; /* misspelled on the wire, misspelled here, FYI */
1593 const char *authority
;
1594 const char *local_part
;
1598 target
= MHD_lookup_connection_value(r
->connection
, MHD_GET_ARGUMENT_KIND
,
1600 redir
= MHD_lookup_connection_value(r
->connection
, MHD_GET_ARGUMENT_KIND
,
1602 referer
= MHD_lookup_connection_value(r
->connection
, MHD_HEADER_KIND
,
1603 MHD_HTTP_HEADER_REFERER
);
1604 if (target
!= NULL
&& redir
== NULL
) {
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
) {
1635 return bad_enomem(r
, ENOMEM
);
1638 /* Both must match */
1639 if (strcasecmp(s1
, s2
) != 0) {
1642 return bad_403(r
, EACCES
, "Redirect request does not match referer");
1646 if (strchr(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
) {
1657 return bad_enomem(r
, ENOMEM
);
1660 r
->target
= r
->freeme1
;
1667 * Implements /bnegotiate end-point.
1669 * Query parameters (mutually exclusive):
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
);
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() */
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
1703 ret
= k5_get_creds(r
, K5_CREDS_CACHED
);
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
);
1712 /* Look ma', Negotiate as an OAuth-like token system! */
1714 ret
= resp(r
, MHD_HTTP_TEMPORARY_REDIRECT
, MHD_RESPMEM_PERSISTENT
,
1715 NULL
, "", 0, nego_tok
);
1717 ret
= resp(r
, MHD_HTTP_OK
, MHD_RESPMEM_MUST_COPY
,
1718 "application/x-negotiate-token", nego_tok
, nego_toksz
,
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)
1736 ret
= krb5_parse_name(r
->context
, r
->cname
, &p
);
1738 ret
= hx509_request_init(r
->context
->hx509ctx
, &r
->req
);
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
);
1746 ret
= hx509_request_add_pkinit(r
->context
->hx509ctx
, r
->req
,
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
);
1753 return bad_403(r
, ret
, "Not authorized to requested TGT");
1757 static heim_mhd_result
1758 get_tgt_param_cb(void *d
,
1759 enum MHD_ValueKind kind
,
1763 struct bx509_request_desc
*r
= d
;
1765 if (strcmp(key
, "address") == 0 && val
) {
1766 if (!krb5_config_get_bool_default(r
->context
, NULL
,
1768 "get-tgt", "allow_addresses", NULL
)) {
1769 krb5_set_error_message(r
->context
, r
->error_code
= ENOTSUP
,
1770 "Query parameter %s not allowed", key
);
1772 krb5_addresses addresses
;
1774 r
->error_code
= _krb5_parse_address_no_lookup(r
->context
, val
,
1776 if (r
->error_code
== 0)
1777 r
->error_code
= krb5_append_addresses(r
->context
, &r
->tgt_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");
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
;
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
);
1817 ret
= authorize_TGT_REQ(r
);
1818 /* validate_token() and authorize_TGT_REQ() call bad_req() */
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() */
1829 ret
= k5_get_creds(r
, K5_CREDS_EPHEMERAL
);
1833 fn
= strchr(r
->ccname
, ':');
1835 return bad_500(r
, ret
, "Impossible error");
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
);
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 "
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
1862 struct MHD_Connection
*connection
,
1865 const char *version
,
1866 const char *upload_data
,
1867 size_t *upload_data_size
,
1870 static int aptr
= 0;
1871 struct bx509_request_desc r
;
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
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.
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 */
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)
1907 ret
= bad_404(&r
, url
);
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" }
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");
1944 static int sigpipe
[2] = { -1, -1 };
1950 while (write(sigpipe
[1], &c
, sizeof(c
)) == -1 && errno
== EINTR
)
1955 bx509_openlog(krb5_context context
,
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
);
1964 s
= krb5_config_get_strings(context
, NULL
, "logging", svc
, NULL
);
1967 krb5_addlog_dest(context
, *fac
, *p
);
1968 krb5_config_free_strings(s
);
1971 if (asprintf(&ss
, "0-1/FILE:%s/%s", hdb_db_dir(context
),
1973 err(1, "out of memory");
1974 krb5_addlog_dest(context
, *fac
, ss
);
1977 krb5_set_warn_dest(context
, *fac
);
1980 static const char *sysplugin_dirs
[] = {
1984 "$ORIGIN/../lib/plugin/kdc",
1987 LIBDIR
"/plugin/kdc",
1993 load_plugins(krb5_context context
)
1995 const char * const *dirs
= sysplugin_dirs
;
1999 cfdirs
= krb5_config_get_strings(context
, NULL
, "kdc", "plugin_dir", NULL
);
2001 dirs
= (const char * const *)cfdirs
;
2005 _krb5_load_plugins(context
, "kdc", (const char **)dirs
);
2008 krb5_config_free_strings(cfdirs
);
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
;
2028 setprogname("bx509d");
2029 if (getarg(args
, sizeof(args
) / sizeof(args
[0]), argc
, argv
, &optidx
))
2034 print_version(NULL
);
2037 if (argc
> optidx
) /* Add option to set a URI local part prefix? */
2040 errx(1, "Port number must be given");
2042 if (audiences
.num_strings
== 0) {
2043 char localhost
[MAXHOSTNAMELEN
];
2045 ret
= gethostname(localhost
, sizeof(localhost
));
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");
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
) {
2077 if (asprintf(&s
, "%s/bx509d-XXXXXX",
2078 getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp") == -1 ||
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
);
2091 if (cert_file
&& !priv_key_file
)
2092 priv_key_file
= cert_file
;
2095 hx509_cursor cursor
= NULL
;
2096 hx509_certs certs
= NULL
;
2097 hx509_cert cert
= NULL
;
2098 time_t min_cert_life
= 0;
2102 ret
= hx509_certs_init(context
->hx509ctx
, cert_file
, 0, NULL
, &certs
);
2104 ret
= hx509_certs_start_seq(context
->hx509ctx
, certs
, &cursor
);
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",
2115 notAfter
-= time(NULL
);
2117 warnx("One or more certificates in %s expire soon",
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
);
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
);
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
);
2142 if (priv_key_file
) {
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
);
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
2170 (void) signal(SIGHUP
, SIG_IGN
);
2171 (void) signal(SIGUSR1
, SIG_IGN
);
2172 (void) signal(SIGALRM
, SIG_IGN
);
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
);
2183 sock
= MHD_quiesce_daemon(previous
);
2185 if (reverse_proxied_flag
) {
2187 * XXX IPv6 too. Create the sockets and tell MHD_start_daemon() about
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
,
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,
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
,
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
,
2214 sock
= MHD_INVALID_SOCKET
;
2216 current
= MHD_start_daemon(flags
| MHD_USE_SSL
, port
,
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,
2225 if (current
== NULL
)
2226 err(1, "Could not start bx509 REST service");
2229 MHD_stop_daemon(previous
);
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 &&
2245 priv_key_pem
= NULL
;
2248 if (ret
== 1 && (sig
== SIGHUP
|| sig
== SIGUSR1
|| sig
== SIGALRM
)) {
2249 /* Reload certs and restart service gracefully */
2255 MHD_stop_daemon(current
);
2256 _krb5_unload_plugins(context
, "kdc");
2257 pthread_key_delete(k5ctx
);