xref: /freebsd/crypto/krb5/src/lib/krb5/krb/pac_sign.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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