xref: /freebsd/crypto/krb5/src/kdc/ndr.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* kdc/ndr.c - NDR encoding and decoding functions */
3 /*
4  * Copyright (C) 2021 by Red Hat, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  *   notice, this list of conditions and the following disclaimer.
13  *
14  * * Redistributions in binary form must reproduce the above copyright
15  *   notice, this list of conditions and the following disclaimer in
16  *   the documentation and/or other materials provided with the
17  *   distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30  * OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include "k5-int.h"
34 #include "k5-input.h"
35 #include "k5-buf.h"
36 #include "k5-utf8.h"
37 #include "kdc_util.h"
38 
39 struct encoded_wchars {
40     uint16_t bytes_len;
41     uint16_t num_wchars;
42     uint8_t *encoded;
43 };
44 
45 /*
46  * MS-DTYP 2.3.10:
47  *
48  * typedef struct _RPC_UNICODE_STRING {
49  *     unsigned short Length;
50  *     unsigned short MaximumLength;
51  *     [size_is(MaximumLength/2), length_is(Length/2)] WCHAR* Buffer;
52  * } RPC_UNICODE_STRING, *PRPC_UNICODE_STRING;
53  *
54  * Note that Buffer is not a String - there's no termination.
55  *
56  * We don't actually decode Length and MaximumLength here - this is a
57  * conformant-varying array, which means that (per DCE-1.1-RPC 14.3.7.2) where
58  * those actually appear in the serialized data is variable depending on
59  * whether the string is at top level of the struct or not.  (This also
60  * affects where the pointer identifier appears.)
61  *
62  * See MS-RPCE 4.7 for what an RPC_UNICODE_STRING looks like when not at
63  * top-level.
64  */
65 static krb5_error_code
dec_wchar_pointer(struct k5input * in,char ** out)66 dec_wchar_pointer(struct k5input *in, char **out)
67 {
68     const uint8_t *bytes;
69     uint32_t actual_count;
70 
71     /* Maximum count. */
72     (void)k5_input_get_uint32_le(in);
73     /* Offset - all zeroes, "should" not be checked. */
74     (void)k5_input_get_uint32_le(in);
75 
76     actual_count = k5_input_get_uint32_le(in);
77     if (actual_count > UINT32_MAX / 2)
78         return ERANGE;
79 
80     bytes = k5_input_get_bytes(in, actual_count * 2);
81     if (bytes == NULL || k5_utf16le_to_utf8(bytes, actual_count * 2, out) != 0)
82         return EINVAL;
83 
84     /* Always align on 4. */
85     if (actual_count % 2 == 1)
86         (void)k5_input_get_uint16_le(in);
87 
88     return 0;
89 }
90 
91 static krb5_error_code
enc_wchar_pointer(const char * utf8,struct encoded_wchars * encoded_out)92 enc_wchar_pointer(const char *utf8, struct encoded_wchars *encoded_out)
93 {
94     krb5_error_code ret;
95     struct k5buf b;
96     size_t utf16len, num_wchars;
97     uint8_t *utf16;
98 
99     ret = k5_utf8_to_utf16le(utf8, &utf16, &utf16len);
100     if (ret)
101         return ret;
102 
103     num_wchars = utf16len / 2;
104 
105     k5_buf_init_dynamic(&b);
106     k5_buf_add_uint32_le(&b, num_wchars + 1);
107     k5_buf_add_uint32_le(&b, 0);
108     k5_buf_add_uint32_le(&b, num_wchars);
109     k5_buf_add_len(&b, utf16, utf16len);
110 
111     free(utf16);
112 
113     if (num_wchars % 2 == 1)
114         k5_buf_add_uint16_le(&b, 0);
115 
116     ret = k5_buf_status(&b);
117     if (ret)
118         return ret;
119 
120     encoded_out->bytes_len = b.len;
121     encoded_out->num_wchars = num_wchars;
122     encoded_out->encoded = b.data;
123     return 0;
124 }
125 
126 /*
127  * Decode a delegation info structure, leaving room to add an additional
128  * service.
129  *
130  * MS-PAC 2.9:
131  *
132  * typedef struct _S4U_DELEGATION_INFO {
133  *     RPC_UNICODE_STRING S4U2proxyTarget;
134  *     ULONG TransitedListSize;
135  *     [size_is(TransitedListSize)] PRPC_UNICODE_STRING S4UTransitedServices;
136  * } S4U_DELEGATION_INFO, *PS4U_DELEGATION_INFO;
137  */
138 krb5_error_code
ndr_dec_delegation_info(krb5_data * data,struct pac_s4u_delegation_info ** out)139 ndr_dec_delegation_info(krb5_data *data, struct pac_s4u_delegation_info **out)
140 {
141     krb5_error_code ret;
142     struct pac_s4u_delegation_info *di = NULL;
143     struct k5input in;
144     uint32_t i, object_buffer_length, nservices;
145     uint8_t version, endianness, common_header_length;
146 
147     *out = NULL;
148 
149     di = k5alloc(sizeof(*di), &ret);
150     if (di == NULL)
151         return ret;
152 
153     k5_input_init(&in, data->data, data->length);
154 
155     /* Common Type Header - MS-RPCE 2.2.6.1 */
156     version = k5_input_get_byte(&in);
157     endianness = k5_input_get_byte(&in);
158     common_header_length = k5_input_get_uint16_le(&in);
159     (void)k5_input_get_uint32_le(&in); /* Filler - 0xcccccccc. */
160     if (version != 1 || endianness != 0x10 || common_header_length != 8) {
161         ret = EINVAL;
162         goto error;
163     }
164 
165     /* Private Header for Constructed Type - MS-RPCE 2.2.6.2 */
166     object_buffer_length = k5_input_get_uint32_le(&in);
167     if (data->length < 16 || object_buffer_length != data->length - 16) {
168         ret = EINVAL;
169         goto error;
170     }
171 
172     (void)k5_input_get_uint32_le(&in); /* Filler - 0. */
173 
174     /* This code doesn't handle re-used pointers, which could come into play in
175      * the unlikely case of a delegation loop. */
176 
177     /* Pointer.  Microsoft always starts at 00 00 02 00 */
178     (void)k5_input_get_uint32_le(&in);
179     /* Length of proxy target - 2 */
180     (void)k5_input_get_uint16_le(&in);
181     /* Length of proxy target */
182     (void)k5_input_get_uint16_le(&in);
183     /* Another pointer - 04 00 02 00.  Microsoft increments by 4 (le). */
184     (void)k5_input_get_uint32_le(&in);
185 
186     /* Transited services length - header version. */
187     (void)k5_input_get_uint32_le(&in);
188 
189     /* More pointer: 08 00 02 00 */
190     (void)k5_input_get_uint32_le(&in);
191 
192     ret = dec_wchar_pointer(&in, &di->proxy_target);
193     if (ret)
194         goto error;
195     nservices = k5_input_get_uint32_le(&in);
196 
197     /* Here, we have encoded 2 bytes of length, 2 bytes of (length + 2), and 4
198      * bytes of pointer, for each element (deferred pointers). */
199     if (nservices > data->length / 8) {
200         ret = ERANGE;
201         goto error;
202     }
203     (void)k5_input_get_bytes(&in, 8 * nservices);
204 
205     /* Since we're likely to add another entry, leave a blank at the end. */
206     di->transited_services = k5calloc(nservices + 1, sizeof(char *), &ret);
207     if (di->transited_services == NULL)
208         goto error;
209 
210     for (i = 0; i < nservices; i++) {
211         ret = dec_wchar_pointer(&in, &di->transited_services[i]);
212         if (ret)
213             goto error;
214         di->transited_services_length++;
215     }
216 
217     ret = in.status;
218     if (ret)
219         goto error;
220 
221     *out = di;
222     return 0;
223 
224 error:
225     ndr_free_delegation_info(di);
226     return ret;
227 }
228 
229 /* Empirically, Microsoft starts pointers at 00 00 02 00, and if treated little
230  * endian, they increase by 4. */
231 static inline void
write_ptr(struct k5buf * buf,uint32_t * pointer)232 write_ptr(struct k5buf *buf, uint32_t *pointer)
233 {
234     if (*pointer == 0)
235         *pointer = 0x00020000;
236     k5_buf_add_uint32_le(buf, *pointer);
237     *pointer += 4;
238 }
239 
240 krb5_error_code
ndr_enc_delegation_info(struct pac_s4u_delegation_info * in,krb5_data * out)241 ndr_enc_delegation_info(struct pac_s4u_delegation_info *in, krb5_data *out)
242 {
243     krb5_error_code ret;
244     size_t i;
245     struct k5buf b;
246     struct encoded_wchars pt_encoded = { 0 }, *tss_encoded = NULL;
247     uint32_t pointer = 0;
248 
249     /* Encode ahead of time since we need the lengths. */
250     ret = enc_wchar_pointer(in->proxy_target, &pt_encoded);
251     if (ret)
252         goto cleanup;
253 
254     tss_encoded = k5calloc(in->transited_services_length, sizeof(*tss_encoded),
255                            &ret);
256     if (tss_encoded == NULL)
257         goto cleanup;
258 
259     k5_buf_init_dynamic(&b);
260 
261     /* Common Type Header - MS-RPCE 2.2.6.1 */
262     k5_buf_add_len(&b, "\x01\x10\x08\x00", 4);
263     k5_buf_add_uint32_le(&b, 0xcccccccc);
264 
265     /* Private Header for Constructed Type - MS-RPCE 2.2.6.2 */
266     k5_buf_add_uint32_le(&b, 0); /* Skip over where payload length goes. */
267     k5_buf_add_uint32_le(&b, 0); /* Filler - all zeroes. */
268 
269     write_ptr(&b, &pointer);
270     k5_buf_add_uint16_le(&b, 2 * pt_encoded.num_wchars);
271     k5_buf_add_uint16_le(&b, 2 * (pt_encoded.num_wchars + 1));
272     write_ptr(&b, &pointer);
273 
274     k5_buf_add_uint32_le(&b, in->transited_services_length);
275     write_ptr(&b, &pointer);
276 
277     k5_buf_add_len(&b, pt_encoded.encoded, pt_encoded.bytes_len);
278 
279     k5_buf_add_uint32_le(&b, in->transited_services_length);
280 
281     /* Deferred pointers. */
282     for (i = 0; i < in->transited_services_length; i++) {
283         ret = enc_wchar_pointer(in->transited_services[i], &tss_encoded[i]);
284         if (ret)
285             goto cleanup;
286 
287         k5_buf_add_uint16_le(&b, 2 * tss_encoded[i].num_wchars);
288         k5_buf_add_uint16_le(&b, 2 * (tss_encoded[i].num_wchars + 1));
289         write_ptr(&b, &pointer);
290     }
291 
292     for (i = 0; i < in->transited_services_length; i++)
293         k5_buf_add_len(&b, tss_encoded[i].encoded, tss_encoded[i].bytes_len);
294 
295     /* Now, pad to 8 bytes.  RPC_UNICODE_STRING is aligned on 4 bytes. */
296     if (b.len % 8 != 0)
297         k5_buf_add_uint32_le(&b, 0);
298 
299     /* Record the payload length where we skipped over it previously. */
300     if (b.data != NULL)
301         store_32_le(b.len - 0x10, ((uint8_t *)b.data) + 8);
302 
303     ret = k5_buf_status(&b);
304     if (ret)
305         goto cleanup;
306 
307     *out = make_data(b.data, b.len);
308     b.data = NULL;
309 
310 cleanup:
311     free(b.data);
312     free(pt_encoded.encoded);
313     for (i = 0; tss_encoded != NULL && i < in->transited_services_length; i++)
314         free(tss_encoded[i].encoded);
315     free(tss_encoded);
316     return ret;
317 }
318 
319 void
ndr_free_delegation_info(struct pac_s4u_delegation_info * di)320 ndr_free_delegation_info(struct pac_s4u_delegation_info *di)
321 {
322     uint32_t i;
323 
324     if (di == NULL)
325         return;
326     free(di->proxy_target);
327     for (i = 0; i < di->transited_services_length; i++)
328         free(di->transited_services[i]);
329     free(di->transited_services);
330     free(di);
331 }
332