1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/pac_sign.c */
3 /*
4 * Copyright 2008 by the Massachusetts Institute of Technology.
5 * All Rights Reserved.
6 *
7 * Export of this software from the United States of America may
8 * require a specific license from the United States Government.
9 * It is the responsibility of any person or organization contemplating
10 * export to obtain such a license before exporting.
11 *
12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13 * distribute this software and its documentation for any purpose and
14 * without fee is hereby granted, provided that the above copyright
15 * notice appear in all copies and that both that copyright notice and
16 * this permission notice appear in supporting documentation, and that
17 * the name of M.I.T. not be used in advertising or publicity pertaining
18 * to distribution of the software without specific, written prior
19 * permission. Furthermore if you modify this software you must label
20 * your software as modified software and not distribute it in such a
21 * fashion that it might be confused with the original M.I.T. software.
22 * M.I.T. makes no representations about the suitability of
23 * this software for any purpose. It is provided "as is" without express
24 * or implied warranty.
25 */
26
27 #include "k5-int.h"
28 #include "int-proto.h"
29 #include "authdata.h"
30
31 /* draft-brezak-win2k-krb-authz-00 */
32
33 static krb5_error_code
insert_client_info(krb5_context context,krb5_pac pac,krb5_timestamp authtime,krb5_const_principal principal,krb5_boolean with_realm)34 insert_client_info(krb5_context context, krb5_pac pac, krb5_timestamp authtime,
35 krb5_const_principal principal, krb5_boolean with_realm)
36 {
37 krb5_error_code ret;
38 krb5_data client_info;
39 char *princ_name_utf8 = NULL;
40 uint8_t *princ_name_utf16 = NULL, *p;
41 size_t princ_name_utf16_len = 0;
42 uint64_t nt_authtime;
43 int flags = 0;
44
45 /* If we already have a CLIENT_INFO buffer, then just validate it */
46 if (k5_pac_locate_buffer(context, pac, KRB5_PAC_CLIENT_INFO,
47 &client_info) == 0) {
48 return k5_pac_validate_client(context, pac, authtime, principal,
49 with_realm);
50 }
51
52 if (!with_realm) {
53 flags |= KRB5_PRINCIPAL_UNPARSE_NO_REALM;
54 } else if (principal->type == KRB5_NT_ENTERPRISE_PRINCIPAL) {
55 /* Avoid quoting the first @ sign for enterprise name with realm. */
56 flags |= KRB5_PRINCIPAL_UNPARSE_DISPLAY;
57 }
58
59 ret = krb5_unparse_name_flags(context, principal, flags, &princ_name_utf8);
60 if (ret)
61 goto cleanup;
62
63 ret = k5_utf8_to_utf16le(princ_name_utf8, &princ_name_utf16,
64 &princ_name_utf16_len);
65 if (ret)
66 goto cleanup;
67
68 client_info.length = PAC_CLIENT_INFO_LENGTH + princ_name_utf16_len;
69 client_info.data = NULL;
70
71 ret = k5_pac_add_buffer(context, pac, KRB5_PAC_CLIENT_INFO,
72 &client_info, TRUE, &client_info);
73 if (ret)
74 goto cleanup;
75
76 p = (uint8_t *)client_info.data;
77
78 /* copy in authtime converted to a 64-bit NT time */
79 k5_seconds_since_1970_to_time(authtime, &nt_authtime);
80 store_64_le(nt_authtime, p);
81 p += 8;
82
83 /* copy in number of UTF-16 bytes in principal name */
84 store_16_le(princ_name_utf16_len, p);
85 p += 2;
86
87 /* copy in principal name */
88 memcpy(p, princ_name_utf16, princ_name_utf16_len);
89
90 cleanup:
91 if (princ_name_utf16 != NULL)
92 free(princ_name_utf16);
93 krb5_free_unparsed_name(context, princ_name_utf8);
94
95 return ret;
96 }
97
98 static krb5_error_code
insert_checksum(krb5_context context,krb5_pac pac,krb5_ui_4 type,const krb5_keyblock * key,krb5_cksumtype * cksumtype)99 insert_checksum(krb5_context context, krb5_pac pac, krb5_ui_4 type,
100 const krb5_keyblock *key, krb5_cksumtype *cksumtype)
101 {
102 krb5_error_code ret;
103 size_t len;
104 krb5_data cksumdata;
105
106 ret = krb5int_c_mandatory_cksumtype(context, key->enctype, cksumtype);
107 if (ret)
108 return ret;
109
110 ret = krb5_c_checksum_length(context, *cksumtype, &len);
111 if (ret)
112 return ret;
113
114 ret = k5_pac_locate_buffer(context, pac, type, &cksumdata);
115 if (!ret) {
116 /*
117 * If we're resigning PAC, make sure we can fit checksum
118 * into existing buffer
119 */
120 if (cksumdata.length != PAC_SIGNATURE_DATA_LENGTH + len)
121 return ERANGE;
122
123 memset(cksumdata.data, 0, cksumdata.length);
124 } else {
125 /* Add a zero filled buffer */
126 cksumdata.length = PAC_SIGNATURE_DATA_LENGTH + len;
127 cksumdata.data = NULL;
128
129 ret = k5_pac_add_buffer(context, pac, type, &cksumdata, TRUE,
130 &cksumdata);
131 if (ret)
132 return ret;
133 }
134
135 /* Encode checksum type into buffer */
136 store_32_le((krb5_ui_4)*cksumtype, cksumdata.data);
137
138 return 0;
139 }
140
141 /* in-place encoding of PAC header */
142 static krb5_error_code
encode_header(krb5_context context,krb5_pac pac)143 encode_header(krb5_context context, krb5_pac pac)
144 {
145 size_t i;
146 unsigned char *p;
147 size_t header_len;
148
149 header_len = PACTYPE_LENGTH + (pac->nbuffers * PAC_INFO_BUFFER_LENGTH);
150 assert(pac->data.length >= header_len);
151
152 p = (uint8_t *)pac->data.data;
153
154 store_32_le(pac->nbuffers, p);
155 p += 4;
156 store_32_le(pac->version, p);
157 p += 4;
158
159 for (i = 0; i < pac->nbuffers; i++) {
160 struct k5_pac_buffer *buffer = &pac->buffers[i];
161
162 store_32_le(buffer->type, p);
163 p += 4;
164 store_32_le(buffer->size, p);
165 p += 4;
166 store_64_le(buffer->offset, p);
167 p += 8;
168
169 assert((buffer->offset % PAC_ALIGNMENT) == 0);
170 assert(buffer->size < pac->data.length);
171 assert(buffer->offset <= pac->data.length - buffer->size);
172 assert(buffer->offset >= header_len);
173
174 if (buffer->offset % PAC_ALIGNMENT ||
175 buffer->size > pac->data.length ||
176 buffer->offset > pac->data.length - buffer->size ||
177 buffer->offset < header_len)
178 return ERANGE;
179 }
180
181 return 0;
182 }
183
184 /* Find the buffer of type buftype in pac and write within it a checksum of
185 * type cksumtype over data. Set *cksum_out to the checksum. */
186 static krb5_error_code
compute_pac_checksum(krb5_context context,krb5_pac pac,uint32_t buftype,const krb5_keyblock * key,krb5_cksumtype cksumtype,const krb5_data * data,krb5_data * cksum_out)187 compute_pac_checksum(krb5_context context, krb5_pac pac, uint32_t buftype,
188 const krb5_keyblock *key, krb5_cksumtype cksumtype,
189 const krb5_data *data, krb5_data *cksum_out)
190 {
191 krb5_error_code ret;
192 krb5_data buf;
193 krb5_crypto_iov iov[2];
194
195 ret = k5_pac_locate_buffer(context, pac, buftype, &buf);
196 if (ret)
197 return ret;
198
199 assert(buf.length > PAC_SIGNATURE_DATA_LENGTH);
200 *cksum_out = make_data(buf.data + PAC_SIGNATURE_DATA_LENGTH,
201 buf.length - PAC_SIGNATURE_DATA_LENGTH);
202 iov[0].flags = KRB5_CRYPTO_TYPE_DATA;
203 iov[0].data = *data;
204 iov[1].flags = KRB5_CRYPTO_TYPE_CHECKSUM;
205 iov[1].data = *cksum_out;
206 return krb5_c_make_checksum_iov(context, cksumtype, key,
207 KRB5_KEYUSAGE_APP_DATA_CKSUM, iov, 2);
208 }
209
210 static krb5_error_code
sign_pac(krb5_context context,krb5_pac pac,krb5_timestamp authtime,krb5_const_principal principal,const krb5_keyblock * server_key,const krb5_keyblock * privsvr_key,krb5_boolean with_realm,krb5_boolean is_service_tkt,krb5_data * data)211 sign_pac(krb5_context context, krb5_pac pac, krb5_timestamp authtime,
212 krb5_const_principal principal, const krb5_keyblock *server_key,
213 const krb5_keyblock *privsvr_key, krb5_boolean with_realm,
214 krb5_boolean is_service_tkt, krb5_data *data)
215 {
216 krb5_error_code ret;
217 krb5_data full_cksum, server_cksum, privsvr_cksum;
218 krb5_cksumtype server_cksumtype, privsvr_cksumtype;
219
220 data->length = 0;
221 data->data = NULL;
222
223 if (principal != NULL) {
224 ret = insert_client_info(context, pac, authtime, principal,
225 with_realm);
226 if (ret)
227 return ret;
228 }
229
230 /* Create zeroed buffers for all checksums. */
231 ret = insert_checksum(context, pac, KRB5_PAC_SERVER_CHECKSUM, server_key,
232 &server_cksumtype);
233 if (ret)
234 return ret;
235 ret = insert_checksum(context, pac, KRB5_PAC_PRIVSVR_CHECKSUM, privsvr_key,
236 &privsvr_cksumtype);
237 if (ret)
238 return ret;
239 if (is_service_tkt) {
240 ret = insert_checksum(context, pac, KRB5_PAC_FULL_CHECKSUM,
241 privsvr_key, &privsvr_cksumtype);
242 if (ret)
243 return ret;
244 }
245
246 /* Encode the PAC header so that the checksums will include it. */
247 ret = encode_header(context, pac);
248 if (ret)
249 return ret;
250
251 if (is_service_tkt) {
252 /* Generate a full KDC checksum over the whole PAC. */
253 ret = compute_pac_checksum(context, pac, KRB5_PAC_FULL_CHECKSUM,
254 privsvr_key, privsvr_cksumtype,
255 &pac->data, &full_cksum);
256 if (ret)
257 return ret;
258 }
259
260 /* Generate the server checksum over the whole PAC, including the full KDC
261 * checksum if we added one. */
262 ret = compute_pac_checksum(context, pac, KRB5_PAC_SERVER_CHECKSUM,
263 server_key, server_cksumtype, &pac->data,
264 &server_cksum);
265 if (ret)
266 return ret;
267
268 /* Generate the privsvr checksum over the server checksum buffer. */
269 ret = compute_pac_checksum(context, pac, KRB5_PAC_PRIVSVR_CHECKSUM,
270 privsvr_key, privsvr_cksumtype, &server_cksum,
271 &privsvr_cksum);
272 if (ret)
273 return ret;
274
275 data->data = k5memdup(pac->data.data, pac->data.length, &ret);
276 if (data->data == NULL)
277 return ret;
278 data->length = pac->data.length;
279
280 memset(pac->data.data, 0,
281 PACTYPE_LENGTH + (pac->nbuffers * PAC_INFO_BUFFER_LENGTH));
282
283 return 0;
284 }
285
286 krb5_error_code KRB5_CALLCONV
krb5_pac_sign(krb5_context context,krb5_pac pac,krb5_timestamp authtime,krb5_const_principal principal,const krb5_keyblock * server_key,const krb5_keyblock * privsvr_key,krb5_data * data)287 krb5_pac_sign(krb5_context context, krb5_pac pac, krb5_timestamp authtime,
288 krb5_const_principal principal, const krb5_keyblock *server_key,
289 const krb5_keyblock *privsvr_key, krb5_data *data)
290 {
291 return sign_pac(context, pac, authtime, principal, server_key,
292 privsvr_key, FALSE, FALSE, data);
293 }
294
295 krb5_error_code KRB5_CALLCONV
krb5_pac_sign_ext(krb5_context context,krb5_pac pac,krb5_timestamp authtime,krb5_const_principal principal,const krb5_keyblock * server_key,const krb5_keyblock * privsvr_key,krb5_boolean with_realm,krb5_data * data)296 krb5_pac_sign_ext(krb5_context context, krb5_pac pac, krb5_timestamp authtime,
297 krb5_const_principal principal,
298 const krb5_keyblock *server_key,
299 const krb5_keyblock *privsvr_key, krb5_boolean with_realm,
300 krb5_data *data)
301 {
302 return sign_pac(context, pac, authtime, principal, server_key, privsvr_key,
303 with_realm, FALSE, data);
304 }
305
306 /* Add a signature over der_enc_tkt in privsvr to pac. der_enc_tkt should be
307 * encoded with a dummy PAC authdata element containing a single zero byte. */
308 static krb5_error_code
add_ticket_signature(krb5_context context,const krb5_pac pac,krb5_data * der_enc_tkt,const krb5_keyblock * privsvr)309 add_ticket_signature(krb5_context context, const krb5_pac pac,
310 krb5_data *der_enc_tkt, const krb5_keyblock *privsvr)
311 {
312 krb5_error_code ret;
313 krb5_data ticket_cksum;
314 krb5_cksumtype ticket_cksumtype;
315 krb5_crypto_iov iov[2];
316
317 /* Create zeroed buffer for checksum. */
318 ret = insert_checksum(context, pac, KRB5_PAC_TICKET_CHECKSUM, privsvr,
319 &ticket_cksumtype);
320 if (ret)
321 return ret;
322
323 ret = k5_pac_locate_buffer(context, pac, KRB5_PAC_TICKET_CHECKSUM,
324 &ticket_cksum);
325 if (ret)
326 return ret;
327
328 iov[0].flags = KRB5_CRYPTO_TYPE_DATA;
329 iov[0].data = *der_enc_tkt;
330 iov[1].flags = KRB5_CRYPTO_TYPE_CHECKSUM;
331 iov[1].data = make_data(ticket_cksum.data + PAC_SIGNATURE_DATA_LENGTH,
332 ticket_cksum.length - PAC_SIGNATURE_DATA_LENGTH);
333 ret = krb5_c_make_checksum_iov(context, ticket_cksumtype, privsvr,
334 KRB5_KEYUSAGE_APP_DATA_CKSUM, iov, 2);
335 if (ret)
336 return ret;
337
338 store_32_le(ticket_cksumtype, ticket_cksum.data);
339 return 0;
340 }
341
342 /* Set *out to an AD-IF-RELEVANT authdata element containing a PAC authdata
343 * element with contents pac_data. */
344 static krb5_error_code
encode_pac_ad(krb5_context context,krb5_data * pac_data,krb5_authdata ** out)345 encode_pac_ad(krb5_context context, krb5_data *pac_data, krb5_authdata **out)
346 {
347 krb5_error_code ret;
348 krb5_authdata *container[2], **encoded_container = NULL;
349 krb5_authdata pac_ad = { KV5M_AUTHDATA, KRB5_AUTHDATA_WIN2K_PAC };
350 uint8_t z = 0;
351
352 pac_ad.contents = (pac_data != NULL) ? (uint8_t *)pac_data->data : &z;
353 pac_ad.length = (pac_data != NULL) ? pac_data->length : 1;
354 container[0] = &pac_ad;
355 container[1] = NULL;
356
357 ret = krb5_encode_authdata_container(context, KRB5_AUTHDATA_IF_RELEVANT,
358 container, &encoded_container);
359 if (ret)
360 return ret;
361
362 *out = encoded_container[0];
363 free(encoded_container);
364 return 0;
365 }
366
367 krb5_error_code KRB5_CALLCONV
krb5_kdc_sign_ticket(krb5_context context,krb5_enc_tkt_part * enc_tkt,const krb5_pac pac,krb5_const_principal server_princ,krb5_const_principal client_princ,const krb5_keyblock * server,const krb5_keyblock * privsvr,krb5_boolean with_realm)368 krb5_kdc_sign_ticket(krb5_context context, krb5_enc_tkt_part *enc_tkt,
369 const krb5_pac pac, krb5_const_principal server_princ,
370 krb5_const_principal client_princ,
371 const krb5_keyblock *server, const krb5_keyblock *privsvr,
372 krb5_boolean with_realm)
373 {
374 krb5_error_code ret;
375 krb5_data *der_enc_tkt = NULL, pac_data = empty_data();
376 krb5_authdata **list, *pac_ad;
377 krb5_boolean is_service_tkt;
378 size_t count;
379
380 /* Reallocate space for another authdata element in enc_tkt. */
381 list = enc_tkt->authorization_data;
382 for (count = 0; list != NULL && list[count] != NULL; count++);
383 list = realloc(enc_tkt->authorization_data, (count + 2) * sizeof(*list));
384 if (list == NULL)
385 return ENOMEM;
386 list[count] = NULL;
387 enc_tkt->authorization_data = list;
388
389 /* Create a dummy PAC for ticket signing and make it the first element. */
390 ret = encode_pac_ad(context, NULL, &pac_ad);
391 if (ret)
392 goto cleanup;
393 memmove(list + 1, list, (count + 1) * sizeof(*list));
394 list[0] = pac_ad;
395
396 is_service_tkt = k5_pac_should_have_ticket_signature(server_princ);
397 if (is_service_tkt) {
398 ret = encode_krb5_enc_tkt_part(enc_tkt, &der_enc_tkt);
399 if (ret)
400 goto cleanup;
401
402 assert(privsvr != NULL);
403 ret = add_ticket_signature(context, pac, der_enc_tkt, privsvr);
404 if (ret)
405 goto cleanup;
406 }
407
408 ret = sign_pac(context, pac, enc_tkt->times.authtime, client_princ, server,
409 privsvr, with_realm, is_service_tkt, &pac_data);
410 if (ret)
411 goto cleanup;
412
413 /* Replace the dummy PAC with the signed real one. */
414 ret = encode_pac_ad(context, &pac_data, &pac_ad);
415 if (ret)
416 goto cleanup;
417 free(list[0]->contents);
418 free(list[0]);
419 list[0] = pac_ad;
420
421 cleanup:
422 krb5_free_data(context, der_enc_tkt);
423 krb5_free_data_contents(context, &pac_data);
424 return ret;
425 }
426