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 ret
= rk_strpoolfree(*cmd
), *cmd
= NULL
, ENOMEM
;
185 *cmd
= rk_strpoolprintf(*cmd
, "%s", s
);
199 call_svc(krb5_context context
, heim_ipc ipc
, const char *cmd
)
201 heim_octet_string req
, resp
;
204 req
.data
= (void *)(uintptr_t)cmd
;
205 req
.length
= strlen(cmd
);
208 if ((ret
= heim_ipc_call(ipc
, &req
, &resp
, NULL
))) {
209 if (resp
.length
&& resp
.length
< INT_MAX
) {
210 krb5_set_error_message(context
, ret
, "CSR denied: %.*s",
211 (int)resp
.length
, (const char *)resp
.data
);
214 krb5_set_error_message(context
, EACCES
, "CSR denied because could "
215 "not reach CSR authorizer IPC service");
220 if (resp
.data
== NULL
|| resp
.length
== 0) {
222 krb5_set_error_message(context
, ret
, "CSR authorizer IPC service "
226 if (resp
.length
== sizeof("denied") - 1 &&
227 strncasecmp(resp
.data
, "denied", sizeof("denied") - 1) == 0) {
229 krb5_set_error_message(context
, ret
, "CSR authorizer rejected %s",
233 if (resp
.length
== sizeof("granted") - 1 &&
234 strncasecmp(resp
.data
, "granted", sizeof("granted") - 1) == 0) {
238 krb5_set_error_message(context
, ret
, "CSR authorizer failed %s: %.*s",
239 cmd
, resp
.length
< INT_MAX
? (int)resp
.length
: 0,
251 static krb5_error_code
252 mark_authorized(hx509_request csr
)
258 for (i
= 0; ret
== 0; i
++) {
259 ret
= hx509_request_get_eku(csr
, i
, &s
);
261 hx509_request_authorize_eku(csr
, i
);
264 if (ret
== HX509_NO_ITEM
)
267 for (i
= 0; ret
== 0; i
++) {
268 hx509_san_type san_type
;
269 ret
= hx509_request_get_san(csr
, i
, &san_type
, &s
);
271 hx509_request_authorize_san(csr
, i
);
274 return ret
== HX509_NO_ITEM
? 0 : ret
;
277 static KRB5_LIB_CALL krb5_error_code
279 krb5_context context
,
282 krb5_const_principal client
,
283 krb5_boolean
*result
)
285 struct rk_strpool
*cmd
= NULL
;
287 hx509_context hx509ctx
= NULL
;
296 if ((svc
= krb5_config_get_string(context
, NULL
, app
? app
: "kdc",
297 "ipc_csr_authorizer", "service", NULL
))
299 return KRB5_PLUGIN_NO_HANDLE
;
301 if ((ret
= heim_ipc_init_context(svc
, &ipc
))) {
302 krb5_set_error_message(context
, ret
, "Could not set up IPC client "
303 "end-point for service %s", svc
);
307 if ((ret
= hx509_context_init(&hx509ctx
)))
310 if ((ret
= krb5_unparse_name(context
, client
, &princ
)))
313 if ((ret
= cmd_append(&cmd
, "check ", princ
, NULL
)))
317 for (i
= 0; ret
== 0; i
++) {
318 hx509_san_type san_type
;
320 ret
= hx509_request_get_san(csr
, i
, &san_type
, &s
);
324 case HX509_SAN_TYPE_EMAIL
:
325 if ((ret
= cmd_append(&cmd
, " san_email=", s
, NULL
)))
329 case HX509_SAN_TYPE_DNSNAME
:
330 if ((ret
= cmd_append(&cmd
, " san_dnsname=", s
, NULL
)))
334 case HX509_SAN_TYPE_XMPP
:
335 if ((ret
= cmd_append(&cmd
, " san_xmpp=", s
, NULL
)))
339 case HX509_SAN_TYPE_PKINIT
:
340 if ((ret
= cmd_append(&cmd
, " san_pkinit=", s
, NULL
)))
344 case HX509_SAN_TYPE_MS_UPN
:
345 if ((ret
= cmd_append(&cmd
, " san_ms_upn=", s
, NULL
)))
350 if ((ret
= hx509_request_reject_san(csr
, i
)))
356 if (ret
== HX509_NO_ITEM
)
361 for (i
= 0; ret
== 0; i
++) {
362 ret
= hx509_request_get_eku(csr
, i
, &s
);
365 if ((ret
= cmd_append(&cmd
, " eku=", s
, NULL
)))
370 if (ret
== HX509_NO_ITEM
)
375 ku
= int2KeyUsage(0);
376 ku
.digitalSignature
= 1;
377 ku
.nonRepudiation
= 1;
378 hx509_request_authorize_ku(csr
, ku
);
381 if ((s
= rk_strpoolcollect(cmd
)) == NULL
)
384 if ((ret
= call_svc(context
, ipc
, s
)))
386 } /* else -> permit */
388 if ((ret
= mark_authorized(csr
)))
396 ret
= krb5_enomem(context
);
400 heim_ipc_free_context(ipc
);
401 hx509_context_free(&hx509ctx
);
409 static KRB5_LIB_CALL krb5_error_code
410 ipc_csr_authorizer_init(krb5_context context
, void **c
)
416 static KRB5_LIB_CALL
void
417 ipc_csr_authorizer_fini(void *c
)
421 static krb5plugin_csr_authorizer_ftable plug_desc
=
422 { 1, ipc_csr_authorizer_init
, ipc_csr_authorizer_fini
, authorize
};
424 static krb5plugin_csr_authorizer_ftable
*plugs
[] = { &plug_desc
};
427 ipc_csr_authorizer_get_instance(const char *libname
)
429 if (strcmp(libname
, "krb5") == 0)
430 return krb5_get_instance(libname
);
431 if (strcmp(libname
, "kdc") == 0)
432 return kdc_get_instance(libname
);
433 if (strcmp(libname
, "hx509") == 0)
434 return hx509_get_instance(libname
);
438 krb5_plugin_load_ft kdc_csr_authorizer_plugin_load
;
440 krb5_error_code KRB5_CALLCONV
441 kdc_csr_authorizer_plugin_load(heim_pcontext context
,
442 krb5_get_instance_func_t
*get_instance
,
444 krb5_plugin_common_ftable_cp
**plugins
)
446 *get_instance
= ipc_csr_authorizer_get_instance
;
447 *num_plugins
= sizeof(plugs
) / sizeof(plugs
[0]);
448 *plugins
= (krb5_plugin_common_ftable_cp
*)plugs
;