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 plugin authorizes requested certificate SANs and EKUs by calling a
36 * service over IPC (Unix domain sockets on Linux/BSD/Illumos).
38 * The IPC protocol is request/response, with requests and responses sent as
42 * where the <length> is 4 bytes, unsigned binary in network byte order, and
43 * <string> is an array of <length> bytes and does NOT include a NUL
46 * Requests are of the form:
48 * check <princ> <exttype>=<extvalue> ...
50 * where <princ> is a URL-escaped principal name, <exttype> is one of:
59 * and <extvalue> is a URL-escaped string representation of the SAN or OID.
61 * OIDs are in the form 1.2.3.4.5.6.
63 * Only characters other than alphanumeric, '@', '.', '-', '_', and '/' are
66 * Responses are any of:
74 * C->S: check jane@TEST.H5L.SE san_dnsname=jane.foo.test.h5l.se eku=1.3.6.1.5.5.7.3.1
77 * Only digitalSignature and nonRepudiation key usages are allowed. Requested
78 * key usages are not sent to the CSR authorizer IPC server.
83 #include <sys/types.h>
97 #include <common_plugin.h>
98 #include <csr_authorizer_plugin.h>
101 * string_encode_sz() and string_encode() encode principal names and such to be
102 * safe for use in our IPC text messages. They function very much like URL
103 * encoders, but '~' also gets encoded, and '.' and '@' do not.
105 * An unescaper is not needed here.
108 string_encode_sz(const char *in
)
110 size_t sz
= strlen(in
);
132 string_encode(const char *in
)
134 size_t len
= strlen(in
);
135 size_t sz
= string_encode_sz(in
);
139 if ((s
= malloc(sz
+ 1)) == NULL
)
143 for (i
= k
= 0; i
< len
; i
++) {
144 unsigned char c
= ((const unsigned char *)in
)[i
];
159 s
[k
++] = "0123456789abcdef"[(c
&0xff)>>4];
160 s
[k
++] = "0123456789abcdef"[(c
&0x0f)];
168 cmd_append(struct rk_strpool
**cmd
, const char *s0
, ...)
174 if ((*cmd
= rk_strpoolprintf(*cmd
, "%s", s0
)) == NULL
)
178 while ((arg
= va_arg(ap
, const char *))) {
181 if ((s
= string_encode(arg
)) == NULL
) {
182 rk_strpoolfree(*cmd
);
187 *cmd
= rk_strpoolprintf(*cmd
, "%s", s
);
201 call_svc(krb5_context context
, heim_ipc ipc
, const char *cmd
)
203 heim_octet_string req
, resp
;
206 req
.data
= (void *)(uintptr_t)cmd
;
207 req
.length
= strlen(cmd
);
210 if ((ret
= heim_ipc_call(ipc
, &req
, &resp
, NULL
))) {
211 if (resp
.length
&& resp
.length
< INT_MAX
) {
212 krb5_set_error_message(context
, ret
, "CSR denied: %.*s",
213 (int)resp
.length
, (const char *)resp
.data
);
216 krb5_set_error_message(context
, EACCES
, "CSR denied because could "
217 "not reach CSR authorizer IPC service");
222 if (resp
.data
== NULL
|| resp
.length
== 0) {
224 krb5_set_error_message(context
, ret
, "CSR authorizer IPC service "
228 if (resp
.length
== sizeof("denied") - 1 &&
229 strncasecmp(resp
.data
, "denied", sizeof("denied") - 1) == 0) {
231 krb5_set_error_message(context
, ret
, "CSR authorizer rejected %s",
235 if (resp
.length
== sizeof("granted") - 1 &&
236 strncasecmp(resp
.data
, "granted", sizeof("granted") - 1) == 0) {
240 krb5_set_error_message(context
, ret
, "CSR authorizer failed %s: %.*s",
241 cmd
, resp
.length
< INT_MAX
? (int)resp
.length
: 0,
253 static krb5_error_code
254 mark_authorized(hx509_request csr
)
260 for (i
= 0; ret
== 0; i
++) {
261 ret
= hx509_request_get_eku(csr
, i
, &s
);
263 hx509_request_authorize_eku(csr
, i
);
266 if (ret
== HX509_NO_ITEM
)
269 for (i
= 0; ret
== 0; i
++) {
270 hx509_san_type san_type
;
271 ret
= hx509_request_get_san(csr
, i
, &san_type
, &s
);
273 hx509_request_authorize_san(csr
, i
);
276 return ret
== HX509_NO_ITEM
? 0 : ret
;
279 static KRB5_LIB_CALL krb5_error_code
281 krb5_context context
,
284 krb5_const_principal client
,
285 krb5_boolean
*result
)
287 struct rk_strpool
*cmd
= NULL
;
289 hx509_context hx509ctx
= NULL
;
298 if ((svc
= krb5_config_get_string(context
, NULL
, app
? app
: "kdc",
299 "ipc_csr_authorizer", "service", NULL
))
301 return KRB5_PLUGIN_NO_HANDLE
;
303 if ((ret
= heim_ipc_init_context(svc
, &ipc
))) {
304 krb5_set_error_message(context
, ret
, "Could not set up IPC client "
305 "end-point for service %s", svc
);
309 if ((ret
= hx509_context_init(&hx509ctx
)))
312 if ((ret
= krb5_unparse_name(context
, client
, &princ
)))
315 if ((ret
= cmd_append(&cmd
, "check ", princ
, NULL
)))
319 for (i
= 0; ret
== 0; i
++) {
320 hx509_san_type san_type
;
322 ret
= hx509_request_get_san(csr
, i
, &san_type
, &s
);
326 case HX509_SAN_TYPE_EMAIL
:
327 if ((ret
= cmd_append(&cmd
, " san_email=", s
, NULL
)))
331 case HX509_SAN_TYPE_DNSNAME
:
332 if ((ret
= cmd_append(&cmd
, " san_dnsname=", s
, NULL
)))
336 case HX509_SAN_TYPE_XMPP
:
337 if ((ret
= cmd_append(&cmd
, " san_xmpp=", s
, NULL
)))
341 case HX509_SAN_TYPE_PKINIT
:
342 if ((ret
= cmd_append(&cmd
, " san_pkinit=", s
, NULL
)))
346 case HX509_SAN_TYPE_MS_UPN
:
347 if ((ret
= cmd_append(&cmd
, " san_ms_upn=", s
, NULL
)))
352 if ((ret
= hx509_request_reject_san(csr
, i
)))
358 if (ret
== HX509_NO_ITEM
)
363 for (i
= 0; ret
== 0; i
++) {
364 ret
= hx509_request_get_eku(csr
, i
, &s
);
367 if ((ret
= cmd_append(&cmd
, " eku=", s
, NULL
)))
372 if (ret
== HX509_NO_ITEM
)
377 ku
= int2KeyUsage(0);
378 ku
.digitalSignature
= 1;
379 ku
.nonRepudiation
= 1;
380 hx509_request_authorize_ku(csr
, ku
);
383 if ((s
= rk_strpoolcollect(cmd
)) == NULL
)
386 if ((ret
= call_svc(context
, ipc
, s
)))
388 } /* else -> permit */
390 if ((ret
= mark_authorized(csr
)))
398 ret
= krb5_enomem(context
);
402 heim_ipc_free_context(ipc
);
403 hx509_context_free(&hx509ctx
);
411 static KRB5_LIB_CALL krb5_error_code
412 ipc_csr_authorizer_init(krb5_context context
, void **c
)
418 static KRB5_LIB_CALL
void
419 ipc_csr_authorizer_fini(void *c
)
423 static krb5plugin_csr_authorizer_ftable plug_desc
=
424 { 1, ipc_csr_authorizer_init
, ipc_csr_authorizer_fini
, authorize
};
426 static krb5plugin_csr_authorizer_ftable
*plugs
[] = { &plug_desc
};
429 ipc_csr_authorizer_get_instance(const char *libname
)
431 if (strcmp(libname
, "krb5") == 0)
432 return krb5_get_instance(libname
);
433 if (strcmp(libname
, "kdc") == 0)
434 return kdc_get_instance(libname
);
435 if (strcmp(libname
, "hx509") == 0)
436 return hx509_get_instance(libname
);
440 krb5_plugin_load_ft kdc_csr_authorizer_plugin_load
;
442 krb5_error_code KRB5_CALLCONV
443 kdc_csr_authorizer_plugin_load(heim_pcontext context
,
444 krb5_get_instance_func_t
*get_instance
,
446 krb5_plugin_common_ftable_cp
**plugins
)
448 *get_instance
= ipc_csr_authorizer_get_instance
;
449 *num_plugins
= sizeof(plugs
) / sizeof(plugs
[0]);
450 *plugins
= (krb5_plugin_common_ftable_cp
*)plugs
;