s4:torture: Adapt KDC canon test to Heimdal upstream changes
[samba.git] / third_party / heimdal / kdc / simple_csr_authorizer.c
1 /*
2  * Copyright (c) 2019 Kungliga Tekniska Högskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
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.
16  *
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.
20  *
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
31  * SUCH DAMAGE.
32  */
33
34 /*
35  * This plugin authorizes requested certificate SANs and EKUs by checking for
36  * existence of files of the form:
37  *
38  *
39  *      /<path>/<princ>/<ext>-<value>
40  *
41  * where <path> is the value of:
42  *
43  *      [kdc] simple_csr_authorizer_directory = PATH
44  *
45  * <princ> is a requesting client principal name with all characters other than
46  * alphanumeric, '-', '_', and non-leading '.' URL-encoded.
47  *
48  * <ext> is one of:
49  *
50  *  - pkinit        (SAN)
51  *  - xmpt          (SAN)
52  *  - emailt        (SAN)
53  *  - ms-upt        (SAN)
54  *  - dnsnamt       (SAN)
55  *  - eku           (EKU OID)
56  *
57  * and <value> is a display form of the SAN or EKU OID, with SANs URL-encoded
58  * just like principal names (see above).
59  *
60  * OIDs are of the form "1.2.3.4.5".
61  *
62  * Only digitalSignature and nonRepudiation key usage values are permitted.
63  */
64 #define _GNU_SOURCE 1
65
66 #include <sys/types.h>
67 #include <sys/stat.h>
68 #include <ctype.h>
69 #include <errno.h>
70 #include <stdlib.h>
71 #include <stdio.h>
72 #include <string.h>
73 #include <unistd.h>
74
75 #include <roken.h>
76 #include <krb5.h>
77 #include <hx509.h>
78 #include <kdc.h>
79 #include <common_plugin.h>
80 #include <csr_authorizer_plugin.h>
81
82 /*
83  * string_encode_sz() and string_encode() encode a string to be safe for use as
84  * a file name.  They function very much like URL encoders, but '~' also gets
85  * encoded, and '@', '-', '_', and non-leading '.' do not.
86  *
87  * A corresponding decoder is not needed.
88  */
89 static size_t
90 string_encode_sz(const char *in)
91 {
92     size_t sz = strlen(in);
93     int first = 1;
94
95     while (*in) {
96         char c = *(in++);
97
98         switch (c) {
99         case '@':
100         case '-':
101         case '_':
102             break;
103         case '.':
104             if (first)
105                 sz += 2;
106             break;
107         default:
108             if (!isalnum(c))
109                 sz += 2;
110         }
111         first = 0;
112     }
113     return sz;
114 }
115
116 static char *
117 string_encode(const char *in)
118 {
119     size_t len = strlen(in);
120     size_t sz = string_encode_sz(in);
121     size_t i, k;
122     char *s;
123     int first = 1;
124
125     if ((s = malloc(sz + 1)) == NULL)
126         return NULL;
127     s[sz] = '\0';
128
129     for (i = k = 0; i < len; i++, first = 0) {
130         unsigned char c = ((const unsigned char *)in)[i];
131
132         switch (c) {
133         case '@':
134         case '-':
135         case '_':
136             s[k++] = c;
137             break;
138         case '.':
139             if (first) {
140                 s[k++] = '%';
141                 s[k++] = "0123456789abcdef"[(c&0xff)>>4];
142                 s[k++] = "0123456789abcdef"[(c&0x0f)];
143             } else {
144                 s[k++] = c;
145             }
146             break;
147         default:
148             if (isalnum(c)) {
149                 s[k++] = c;
150             } else  {
151                 s[k++] = '%';
152                 s[k++] = "0123456789abcdef"[(c&0xff)>>4];
153                 s[k++] = "0123456789abcdef"[(c&0x0f)];
154             }
155         }
156     }
157     return s;
158 }
159
160 static KRB5_LIB_CALL krb5_error_code
161 authorize(void *ctx,
162           krb5_context context,
163           const char *app,
164           hx509_request csr,
165           krb5_const_principal client,
166           krb5_boolean *result)
167 {
168     krb5_error_code ret;
169     hx509_context hx509ctx = NULL;
170     KeyUsage ku;
171     const char *d;
172     size_t i;
173     char *princ = NULL;
174     char *s = NULL;
175
176     if ((d = krb5_config_get_string(context, NULL, app ? app : "kdc",
177                                     "simple_csr_authorizer_directory",
178                                     NULL)) == NULL)
179         return KRB5_PLUGIN_NO_HANDLE;
180
181     if ((ret = hx509_context_init(&hx509ctx)))
182         return ret;
183
184     if ((ret = krb5_unparse_name(context, client, &princ)))
185         goto out;
186
187     s = string_encode(princ);
188     free(princ);
189     princ = NULL;
190     if (s == NULL)
191         goto enomem;
192
193     princ = s;
194     s = NULL;
195
196     for (i = 0; ret == 0; i++) {
197         hx509_san_type san_type;
198         struct stat st;
199         const char *prefix;
200         char *san;
201         char *p;
202
203         ret = hx509_request_get_san(csr, i, &san_type, &s);
204         if (ret)
205             break;
206         switch (san_type) {
207         case HX509_SAN_TYPE_EMAIL:
208             prefix = "email";
209             break;
210         case HX509_SAN_TYPE_DNSNAME:
211             prefix = "dnsname";
212             break;
213         case HX509_SAN_TYPE_XMPP:
214             prefix = "xmpp";
215             break;
216         case HX509_SAN_TYPE_PKINIT:
217             prefix = "pkinit";
218             break;
219         case HX509_SAN_TYPE_MS_UPN:
220             prefix = "ms-upn";
221             break;
222         default:
223             ret = ENOTSUP;
224             break;
225         }
226         if (ret)
227             break;
228
229         if ((san = string_encode(s)) == NULL ||
230             asprintf(&p, "%s/%s/%s-%s", d, princ, prefix, san) == -1 ||
231             p == NULL)
232             goto enomem;
233         ret = stat(p, &st) == -1 ? errno : 0;
234         free(san);
235         free(p);
236         free(s);
237         s = NULL;
238         if (ret)
239             goto skip;
240         ret = hx509_request_authorize_san(csr, i);
241     }
242     if (ret == HX509_NO_ITEM)
243         ret = 0;
244     if (ret)
245         goto out;
246
247     for (i = 0; ret == 0; i++) {
248         struct stat st;
249         char *p;
250
251         ret = hx509_request_get_eku(csr, i, &s);
252         if (ret)
253             break;
254         if (asprintf(&p, "%s/%s/eku-%s", d, princ, s) == -1 || p == NULL) {
255             free(princ);
256             free(s);
257         }
258         ret = stat(p, &st) == -1 ? errno : 0;
259         free(p);
260         free(s);
261         s = NULL;
262         if (ret)
263             goto skip;
264         ret = hx509_request_authorize_eku(csr, i);
265     }
266     if (ret == HX509_NO_ITEM)
267         ret = 0;
268     if (ret)
269         goto out;
270
271     ku = int2KeyUsage(0);
272     ku.digitalSignature = 1;
273     ku.nonRepudiation = 1;
274     hx509_request_authorize_ku(csr, ku);
275
276     *result = TRUE;
277     ret = 0;
278     goto out;
279
280 skip:
281     /* Allow another plugin to get a crack at this */
282     ret = KRB5_PLUGIN_NO_HANDLE;
283     goto out;
284
285 enomem:
286     ret = krb5_enomem(context);
287     goto out;
288
289 out:
290     hx509_context_free(&hx509ctx);
291     free(princ);
292     free(s);
293     return ret;
294 }
295
296 static KRB5_LIB_CALL krb5_error_code
297 simple_csr_authorizer_init(krb5_context context, void **c)
298 {
299     *c = NULL;
300     return 0;
301 }
302
303 static KRB5_LIB_CALL void
304 simple_csr_authorizer_fini(void *c)
305 {
306 }
307
308 static krb5plugin_csr_authorizer_ftable plug_desc =
309     { 1, simple_csr_authorizer_init, simple_csr_authorizer_fini, authorize };
310
311 static krb5plugin_csr_authorizer_ftable *plugs[] = { &plug_desc };
312
313 static uintptr_t
314 simple_csr_authorizer_get_instance(const char *libname)
315 {
316     if (strcmp(libname, "krb5") == 0)
317         return krb5_get_instance(libname);
318     if (strcmp(libname, "kdc") == 0)
319         return kdc_get_instance(libname);
320     if (strcmp(libname, "hx509") == 0)
321         return hx509_get_instance(libname);
322     return 0;
323 }
324
325 krb5_plugin_load_ft kdc_csr_authorizer_plugin_load;
326
327 krb5_error_code KRB5_CALLCONV
328 kdc_csr_authorizer_plugin_load(heim_pcontext context,
329                                krb5_get_instance_func_t *get_instance,
330                                size_t *num_plugins,
331                                krb5_plugin_common_ftable_cp **plugins)
332 {
333     *get_instance = simple_csr_authorizer_get_instance;
334     *num_plugins = sizeof(plugs) / sizeof(plugs[0]);
335     *plugins = (krb5_plugin_common_ftable_cp *)plugs;
336     return 0;
337 }