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
, ...)
173 if ((*cmd
= rk_strpoolprintf(*cmd
, "%s", s0
)) == NULL
)
177 while ((arg
= va_arg(ap
, const char *))) {
180 if ((s
= string_encode(arg
)) == NULL
)
181 return rk_strpoolfree(*cmd
), *cmd
= NULL
, ENOMEM
;
182 *cmd
= rk_strpoolprintf(*cmd
, "%s", s
);
191 call_svc(krb5_context context
, heim_ipc ipc
, const char *cmd
)
193 heim_octet_string req
, resp
;
196 req
.data
= (void *)(uintptr_t)cmd
;
197 req
.length
= strlen(cmd
);
200 if ((ret
= heim_ipc_call(ipc
, &req
, &resp
, NULL
))) {
201 if (resp
.length
&& resp
.length
< INT_MAX
) {
202 krb5_set_error_message(context
, ret
, "CSR denied: %.*s",
203 (int)resp
.length
, (const char *)resp
.data
);
206 krb5_set_error_message(context
, EACCES
, "CSR denied because could "
207 "not reach CSR authorizer IPC service");
212 if (resp
.data
== NULL
|| resp
.length
== 0) {
214 krb5_set_error_message(context
, ret
, "CSR authorizer IPC service "
218 if (resp
.length
== sizeof("denied") - 1 &&
219 strncasecmp(resp
.data
, "denied", sizeof("denied") - 1) == 0) {
221 krb5_set_error_message(context
, ret
, "CSR authorizer rejected %s",
225 if (resp
.length
== sizeof("granted") - 1 &&
226 strncasecmp(resp
.data
, "granted", sizeof("granted") - 1) == 0) {
230 krb5_set_error_message(context
, ret
, "CSR authorizer failed %s: %.*s",
231 cmd
, resp
.length
< INT_MAX
? (int)resp
.length
: 0,
243 static krb5_error_code
244 mark_authorized(hx509_request csr
)
250 for (i
= 0; ret
== 0; i
++) {
251 ret
= hx509_request_get_eku(csr
, i
, &s
);
253 hx509_request_authorize_eku(csr
, i
);
256 if (ret
== HX509_NO_ITEM
)
259 for (i
= 0; ret
== 0; i
++) {
260 hx509_san_type san_type
;
261 ret
= hx509_request_get_san(csr
, i
, &san_type
, &s
);
263 hx509_request_authorize_san(csr
, i
);
266 return ret
== HX509_NO_ITEM
? 0 : ret
;
269 static KRB5_LIB_CALL krb5_error_code
271 krb5_context context
,
274 krb5_const_principal client
,
275 krb5_boolean
*result
)
277 struct rk_strpool
*cmd
= NULL
;
279 hx509_context hx509ctx
= NULL
;
288 if ((svc
= krb5_config_get_string(context
, NULL
, app
? app
: "kdc",
289 "ipc_csr_authorizer", "service", NULL
))
291 return KRB5_PLUGIN_NO_HANDLE
;
293 if ((ret
= heim_ipc_init_context(svc
, &ipc
))) {
294 krb5_set_error_message(context
, ret
, "Could not set up IPC client "
295 "end-point for service %s", svc
);
299 if ((ret
= hx509_context_init(&hx509ctx
)))
302 if ((ret
= krb5_unparse_name(context
, client
, &princ
)))
305 if ((ret
= cmd_append(&cmd
, "check ", princ
, NULL
)))
309 for (i
= 0; ret
== 0; i
++) {
310 hx509_san_type san_type
;
312 ret
= hx509_request_get_san(csr
, i
, &san_type
, &s
);
316 case HX509_SAN_TYPE_EMAIL
:
317 if ((ret
= cmd_append(&cmd
, " san_email=", s
, NULL
)))
321 case HX509_SAN_TYPE_DNSNAME
:
322 if ((ret
= cmd_append(&cmd
, " san_dnsname=", s
, NULL
)))
326 case HX509_SAN_TYPE_XMPP
:
327 if ((ret
= cmd_append(&cmd
, " san_xmpp=", s
, NULL
)))
331 case HX509_SAN_TYPE_PKINIT
:
332 if ((ret
= cmd_append(&cmd
, " san_pkinit=", s
, NULL
)))
336 case HX509_SAN_TYPE_MS_UPN
:
337 if ((ret
= cmd_append(&cmd
, " san_ms_upn=", s
, NULL
)))
342 if ((ret
= hx509_request_reject_san(csr
, i
)))
348 if (ret
== HX509_NO_ITEM
)
353 for (i
= 0; ret
== 0; i
++) {
354 ret
= hx509_request_get_eku(csr
, i
, &s
);
357 if ((ret
= cmd_append(&cmd
, " eku=", s
, NULL
)))
362 if (ret
== HX509_NO_ITEM
)
367 ku
= int2KeyUsage(0);
368 ku
.digitalSignature
= 1;
369 ku
.nonRepudiation
= 1;
370 hx509_request_authorize_ku(csr
, ku
);
373 if ((s
= rk_strpoolcollect(cmd
)) == NULL
)
376 if ((ret
= call_svc(context
, ipc
, s
)))
378 } /* else -> permit */
380 if ((ret
= mark_authorized(csr
)))
388 ret
= krb5_enomem(context
);
392 heim_ipc_free_context(ipc
);
393 hx509_context_free(&hx509ctx
);
401 static KRB5_LIB_CALL krb5_error_code
402 ipc_csr_authorizer_init(krb5_context context
, void **c
)
408 static KRB5_LIB_CALL
void
409 ipc_csr_authorizer_fini(void *c
)
413 static krb5plugin_csr_authorizer_ftable plug_desc
=
414 { 1, ipc_csr_authorizer_init
, ipc_csr_authorizer_fini
, authorize
};
416 static krb5plugin_csr_authorizer_ftable
*plugs
[] = { &plug_desc
};
419 ipc_csr_authorizer_get_instance(const char *libname
)
421 if (strcmp(libname
, "krb5") == 0)
422 return krb5_get_instance(libname
);
423 if (strcmp(libname
, "kdc") == 0)
424 return kdc_get_instance(libname
);
425 if (strcmp(libname
, "hx509") == 0)
426 return hx509_get_instance(libname
);
430 krb5_plugin_load_ft kdc_csr_authorizer_plugin_load
;
432 krb5_error_code KRB5_CALLCONV
433 kdc_csr_authorizer_plugin_load(heim_pcontext context
,
434 krb5_get_instance_func_t
*get_instance
,
436 krb5_plugin_common_ftable_cp
**plugins
)
438 *get_instance
= ipc_csr_authorizer_get_instance
;
439 *num_plugins
= sizeof(plugs
) / sizeof(plugs
[0]);
440 *plugins
= (krb5_plugin_common_ftable_cp
*)plugs
;