xref: /freebsd/crypto/krb5/src/lib/krb5/krb/pac.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/pac.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 #include "k5-input.h"
31 
32 #define MAX_BUFFERS 4096
33 
34 /*
35  * Add a buffer containing data to pac's metadata and encoding.  If zerofill is
36  * true, data->data must be NULL and the buffer will be zero-filled with length
37  * data->length.
38  */
39 krb5_error_code
k5_pac_add_buffer(krb5_context context,krb5_pac pac,uint32_t type,const krb5_data * data,krb5_boolean zerofill,krb5_data * data_out)40 k5_pac_add_buffer(krb5_context context, krb5_pac pac, uint32_t type,
41                   const krb5_data *data, krb5_boolean zerofill,
42                   krb5_data *data_out)
43 {
44     struct k5_pac_buffer *nbufs;
45     size_t header_len, i, pad = 0;
46     char *ndata, *bufdata;
47 
48     assert((data->data == NULL) == zerofill);
49 
50     /* Check for an existing buffer of this type. */
51     if (k5_pac_locate_buffer(context, pac, type, NULL) == 0)
52         return EEXIST;
53 
54     if (pac->nbuffers >= MAX_BUFFERS)
55         return ERANGE;
56     nbufs = realloc(pac->buffers, (pac->nbuffers + 1) * sizeof(*pac->buffers));
57     if (nbufs == NULL)
58         return ENOMEM;
59     pac->buffers = nbufs;
60 
61     header_len = PACTYPE_LENGTH + pac->nbuffers * PAC_INFO_BUFFER_LENGTH;
62 
63     if (data->length % PAC_ALIGNMENT)
64         pad = PAC_ALIGNMENT - (data->length % PAC_ALIGNMENT);
65     ndata = realloc(pac->data.data,
66                     pac->data.length + PAC_INFO_BUFFER_LENGTH +
67                     data->length + pad);
68     if (ndata == NULL)
69         return ENOMEM;
70     pac->data.data = ndata;
71 
72     /* Update the offsets of existing buffers. */
73     for (i = 0; i < pac->nbuffers; i++)
74         pac->buffers[i].offset += PAC_INFO_BUFFER_LENGTH;
75 
76     /* Make room for the new buffer's metadata. */
77     memmove(pac->data.data + header_len + PAC_INFO_BUFFER_LENGTH,
78             pac->data.data + header_len,
79             pac->data.length - header_len);
80     memset(pac->data.data + header_len, 0, PAC_INFO_BUFFER_LENGTH);
81 
82     /* Initialize the new buffer. */
83     pac->buffers[i].type = type;
84     pac->buffers[i].size = data->length;
85     pac->buffers[i].offset = pac->data.length + PAC_INFO_BUFFER_LENGTH;
86     assert((pac->buffers[i].offset % PAC_ALIGNMENT) == 0);
87 
88     /* Copy in new PAC data and zero padding bytes. */
89     bufdata = pac->data.data + pac->buffers[i].offset;
90     if (zerofill)
91         memset(bufdata, 0, data->length);
92     else
93         memcpy(bufdata, data->data, data->length);
94     memset(bufdata + data->length, 0, pad);
95 
96     pac->nbuffers++;
97     pac->data.length += PAC_INFO_BUFFER_LENGTH + data->length + pad;
98 
99     if (data_out != NULL)
100         *data_out = make_data(bufdata, data->length);
101 
102     pac->verified = FALSE;
103 
104     return 0;
105 }
106 
107 krb5_error_code KRB5_CALLCONV
krb5_pac_add_buffer(krb5_context context,krb5_pac pac,uint32_t type,const krb5_data * data)108 krb5_pac_add_buffer(krb5_context context, krb5_pac pac, uint32_t type,
109                     const krb5_data *data)
110 {
111     return k5_pac_add_buffer(context, pac, type, data, FALSE, NULL);
112 }
113 
114 /*
115  * Free a PAC
116  */
117 void KRB5_CALLCONV
krb5_pac_free(krb5_context context,krb5_pac pac)118 krb5_pac_free(krb5_context context, krb5_pac pac)
119 {
120     if (pac != NULL) {
121         zapfree(pac->data.data, pac->data.length);
122         free(pac->buffers);
123         zapfree(pac, sizeof(*pac));
124     }
125 }
126 
127 krb5_error_code
k5_pac_locate_buffer(krb5_context context,const krb5_pac pac,uint32_t type,krb5_data * data_out)128 k5_pac_locate_buffer(krb5_context context, const krb5_pac pac, uint32_t type,
129                      krb5_data *data_out)
130 {
131     struct k5_pac_buffer *buffer = NULL;
132     size_t i;
133 
134     if (pac == NULL)
135         return EINVAL;
136 
137     for (i = 0; i < pac->nbuffers; i++) {
138         if (pac->buffers[i].type == type) {
139             if (buffer == NULL)
140                 buffer = &pac->buffers[i];
141             else
142                 return EINVAL;
143         }
144     }
145 
146     if (buffer == NULL)
147         return ENOENT;
148 
149     assert(buffer->offset <= pac->data.length);
150     assert(buffer->size <= pac->data.length - buffer->offset);
151 
152     if (data_out != NULL)
153         *data_out = make_data(pac->data.data + buffer->offset, buffer->size);
154 
155     return 0;
156 }
157 
158 /*
159  * Find a buffer and copy data into output
160  */
161 krb5_error_code KRB5_CALLCONV
krb5_pac_get_buffer(krb5_context context,krb5_pac pac,uint32_t type,krb5_data * data_out)162 krb5_pac_get_buffer(krb5_context context, krb5_pac pac, uint32_t type,
163                     krb5_data *data_out)
164 {
165     krb5_data d;
166     krb5_error_code ret;
167 
168     ret = k5_pac_locate_buffer(context, pac, type, &d);
169     if (ret)
170         return ret;
171 
172     data_out->data = k5memdup(d.data, d.length, &ret);
173     if (data_out->data == NULL)
174         return ret;
175     data_out->length = d.length;
176     return 0;
177 }
178 
179 /*
180  * Return an array of the types of data in the PAC
181  */
182 krb5_error_code KRB5_CALLCONV
krb5_pac_get_types(krb5_context context,krb5_pac pac,size_t * len_out,uint32_t ** types_out)183 krb5_pac_get_types(krb5_context context, krb5_pac pac, size_t *len_out,
184                    uint32_t **types_out)
185 {
186     size_t i;
187 
188     *types_out = calloc(pac->nbuffers, sizeof(*types_out));
189     if (*types_out == NULL)
190         return ENOMEM;
191 
192     *len_out = pac->nbuffers;
193 
194     for (i = 0; i < pac->nbuffers; i++)
195         (*types_out)[i] = pac->buffers[i].type;
196 
197     return 0;
198 }
199 
200 krb5_error_code KRB5_CALLCONV
krb5_pac_init(krb5_context context,krb5_pac * pac_out)201 krb5_pac_init(krb5_context context, krb5_pac *pac_out)
202 {
203     krb5_error_code ret;
204     krb5_pac pac;
205 
206     *pac_out = NULL;
207 
208     pac = malloc(sizeof(*pac));
209     if (pac == NULL)
210         return ENOMEM;
211 
212     pac->nbuffers = 0;
213     pac->buffers = NULL;
214     pac->version = 0;
215 
216     pac->data.length = PACTYPE_LENGTH;
217     pac->data.data = k5alloc(pac->data.length, &ret);
218     if (pac->data.data == NULL) {
219         free(pac);
220         return ret;
221     }
222 
223     pac->verified = FALSE;
224 
225     *pac_out = pac;
226     return 0;
227 }
228 
229 static krb5_error_code
copy_pac(krb5_context context,krb5_pac src,krb5_pac * dst_out)230 copy_pac(krb5_context context, krb5_pac src, krb5_pac *dst_out)
231 {
232     krb5_error_code ret;
233     krb5_pac pac;
234 
235     *dst_out = NULL;
236 
237     pac = k5alloc(sizeof(*pac), &ret);
238     if (pac == NULL)
239         goto fail;
240 
241     pac->buffers = k5memdup(src->buffers,
242                             src->nbuffers * sizeof(*pac->buffers), &ret);
243     if (pac->buffers == NULL)
244         goto fail;
245 
246     ret = krb5int_copy_data_contents(context, &src->data, &pac->data);
247     if (ret)
248         goto fail;
249 
250     pac->nbuffers = src->nbuffers;
251     pac->version = src->version;
252     pac->verified = src->verified;
253 
254     *dst_out = pac;
255     return 0;
256 
257 fail:
258     krb5_pac_free(context, pac);
259     return ret;
260 }
261 
262 /* Parse the supplied data into an allocated PAC. */
263 krb5_error_code KRB5_CALLCONV
krb5_pac_parse(krb5_context context,const void * ptr,size_t len,krb5_pac * ppac)264 krb5_pac_parse(krb5_context context, const void *ptr, size_t len,
265                krb5_pac *ppac)
266 {
267     krb5_error_code ret;
268     size_t i;
269     krb5_pac pac;
270     size_t header_len;
271     uint32_t nbuffers, version;
272     struct k5input in;
273     char *ndata;
274 
275     *ppac = NULL;
276 
277     k5_input_init(&in, ptr, len);
278 
279     nbuffers = k5_input_get_uint32_le(&in);
280     version = k5_input_get_uint32_le(&in);
281     if (in.status || version != 0)
282         return EINVAL;
283 
284     if (nbuffers < 1 || nbuffers > MAX_BUFFERS)
285         return ERANGE;
286 
287     header_len = PACTYPE_LENGTH + (nbuffers * PAC_INFO_BUFFER_LENGTH);
288     if (len < header_len)
289         return ERANGE;
290 
291     ret = krb5_pac_init(context, &pac);
292     if (ret)
293         return ret;
294 
295     pac->buffers = k5calloc(nbuffers, sizeof(*pac->buffers), &ret);
296     if (ret)
297         goto fail;
298 
299     pac->nbuffers = nbuffers;
300     pac->version = version;
301 
302     for (i = 0; i < nbuffers; i++) {
303         struct k5_pac_buffer *buffer = &pac->buffers[i];
304 
305         buffer->type = k5_input_get_uint32_le(&in);
306         buffer->size = k5_input_get_uint32_le(&in);
307         buffer->offset = k5_input_get_uint64_le(&in);
308 
309         if (in.status || buffer->offset % PAC_ALIGNMENT) {
310             ret = EINVAL;
311             goto fail;
312         }
313         if (buffer->offset < header_len || buffer->offset > len ||
314             buffer->size > len - buffer->offset) {
315             ret = ERANGE;
316             goto fail;
317         }
318     }
319 
320     ndata = realloc(pac->data.data, len);
321     if (ndata == NULL) {
322         krb5_pac_free(context, pac);
323         return ENOMEM;
324     }
325     pac->data.data = ndata;
326     memcpy(ndata, ptr, len);
327 
328     pac->data.length = len;
329 
330     *ppac = pac;
331 
332     return 0;
333 
334 fail:
335     krb5_pac_free(context, pac);
336     return ret;
337 }
338 
339 static krb5_error_code
k5_time_to_seconds_since_1970(uint64_t ntTime,krb5_timestamp * elapsedSeconds)340 k5_time_to_seconds_since_1970(uint64_t ntTime, krb5_timestamp *elapsedSeconds)
341 {
342     uint64_t abstime = ntTime / 10000000 - NT_TIME_EPOCH;
343 
344     if (abstime > UINT32_MAX)
345         return ERANGE;
346     *elapsedSeconds = abstime;
347     return 0;
348 }
349 
350 krb5_error_code
k5_seconds_since_1970_to_time(krb5_timestamp elapsedSeconds,uint64_t * ntTime)351 k5_seconds_since_1970_to_time(krb5_timestamp elapsedSeconds, uint64_t *ntTime)
352 {
353     *ntTime = (uint32_t)elapsedSeconds;
354     *ntTime += NT_TIME_EPOCH;
355     *ntTime *= 10000000;
356     return 0;
357 }
358 
359 krb5_error_code KRB5_CALLCONV
krb5_pac_get_client_info(krb5_context context,const krb5_pac pac,krb5_timestamp * authtime_out,char ** princname_out)360 krb5_pac_get_client_info(krb5_context context, const krb5_pac pac,
361                          krb5_timestamp *authtime_out, char **princname_out)
362 {
363     krb5_error_code ret;
364     krb5_data client_info;
365     char *pac_princname;
366     unsigned char *p;
367     krb5_timestamp pac_authtime;
368     uint16_t pac_princname_length;
369     uint64_t pac_nt_authtime;
370 
371     if (authtime_out != NULL)
372         *authtime_out = 0;
373     *princname_out = NULL;
374 
375     ret = k5_pac_locate_buffer(context, pac, KRB5_PAC_CLIENT_INFO,
376                                &client_info);
377     if (ret)
378         return ret;
379 
380     if (client_info.length < PAC_CLIENT_INFO_LENGTH)
381         return ERANGE;
382 
383     p = (unsigned char *)client_info.data;
384     pac_nt_authtime = load_64_le(p);
385     p += 8;
386     pac_princname_length = load_16_le(p);
387     p += 2;
388 
389     ret = k5_time_to_seconds_since_1970(pac_nt_authtime, &pac_authtime);
390     if (ret)
391         return ret;
392 
393     if (client_info.length < PAC_CLIENT_INFO_LENGTH + pac_princname_length ||
394         pac_princname_length % 2)
395         return ERANGE;
396 
397     ret = k5_utf16le_to_utf8(p, pac_princname_length, &pac_princname);
398     if (ret)
399         return ret;
400 
401     if (authtime_out != NULL)
402         *authtime_out = pac_authtime;
403     *princname_out = pac_princname;
404 
405     return 0;
406 }
407 
408 krb5_error_code
k5_pac_validate_client(krb5_context context,const krb5_pac pac,krb5_timestamp authtime,krb5_const_principal principal,krb5_boolean with_realm)409 k5_pac_validate_client(krb5_context context, const krb5_pac pac,
410                        krb5_timestamp authtime, krb5_const_principal principal,
411                        krb5_boolean with_realm)
412 {
413     krb5_error_code ret;
414     char *pac_princname, *princname;
415     krb5_timestamp pac_authtime;
416     int flags = 0;
417 
418     ret = krb5_pac_get_client_info(context, pac, &pac_authtime,
419                                    &pac_princname);
420     if (ret)
421         return ret;
422 
423     flags = KRB5_PRINCIPAL_UNPARSE_DISPLAY;
424     if (!with_realm)
425         flags |= KRB5_PRINCIPAL_UNPARSE_NO_REALM;
426 
427     ret = krb5_unparse_name_flags(context, principal, flags, &princname);
428     if (ret) {
429         free(pac_princname);
430         return ret;
431     }
432 
433     if (pac_authtime != authtime || strcmp(pac_princname, princname) != 0)
434         ret = KRB5KRB_AP_WRONG_PRINC;
435 
436     free(pac_princname);
437     krb5_free_unparsed_name(context, princname);
438 
439     return ret;
440 }
441 
442 /* Zero out the signature in a copy of the PAC data. */
443 static krb5_error_code
zero_signature(krb5_context context,const krb5_pac pac,uint32_t type,krb5_data * data)444 zero_signature(krb5_context context, const krb5_pac pac, uint32_t type,
445                krb5_data *data)
446 {
447     struct k5_pac_buffer *buffer = NULL;
448     size_t i;
449 
450     assert(type == KRB5_PAC_SERVER_CHECKSUM ||
451            type == KRB5_PAC_PRIVSVR_CHECKSUM ||
452            type == KRB5_PAC_FULL_CHECKSUM);
453     assert(data->length >= pac->data.length);
454 
455     for (i = 0; i < pac->nbuffers; i++) {
456         if (pac->buffers[i].type == type) {
457             buffer = &pac->buffers[i];
458             break;
459         }
460     }
461 
462     if (buffer == NULL)
463         return ENOENT;
464 
465     if (buffer->size < PAC_SIGNATURE_DATA_LENGTH)
466         return KRB5_BAD_MSIZE;
467     if (buffer->size > pac->data.length ||
468         buffer->offset > pac->data.length - buffer->size)
469         return ERANGE;
470 
471     /* Within the copy, zero out the data portion of the checksum only. */
472     memset(data->data + buffer->offset + PAC_SIGNATURE_DATA_LENGTH, 0,
473            buffer->size - PAC_SIGNATURE_DATA_LENGTH);
474 
475     return 0;
476 }
477 
478 static krb5_error_code
verify_checksum(krb5_context context,const krb5_pac pac,uint32_t buffer_type,const krb5_keyblock * key,krb5_keyusage usage,const krb5_data * data)479 verify_checksum(krb5_context context, const krb5_pac pac, uint32_t buffer_type,
480                 const krb5_keyblock *key, krb5_keyusage usage,
481                 const krb5_data *data)
482 {
483     krb5_error_code ret;
484     krb5_data buffer;
485     krb5_cksumtype cksumtype;
486     krb5_checksum checksum;
487     krb5_boolean valid;
488     size_t cksumlen;
489 
490     ret = k5_pac_locate_buffer(context, pac, buffer_type, &buffer);
491     if (ret)
492         return ret;
493     if (buffer.length < PAC_SIGNATURE_DATA_LENGTH)
494         return KRB5_BAD_MSIZE;
495 
496     cksumtype = load_32_le(buffer.data);
497     if (buffer_type == KRB5_PAC_SERVER_CHECKSUM && cksumtype == CKSUMTYPE_SHA1)
498         return KRB5KDC_ERR_SUMTYPE_NOSUPP;
499     if (!krb5_c_is_keyed_cksum(cksumtype))
500         return KRB5KRB_ERR_GENERIC;
501 
502     /* There may be an RODCIdentifier trailer (see [MS-PAC] 2.8), so look up
503      * the length of the checksum by its type. */
504     ret = krb5_c_checksum_length(context, cksumtype, &cksumlen);
505     if (ret)
506         return ret;
507     if (cksumlen > buffer.length - PAC_SIGNATURE_DATA_LENGTH)
508         return KRB5_BAD_MSIZE;
509     checksum.checksum_type = cksumtype;
510     checksum.length = cksumlen;
511     checksum.contents = (uint8_t *)buffer.data + PAC_SIGNATURE_DATA_LENGTH;
512 
513     ret = krb5_c_verify_checksum(context, key, usage, data, &checksum, &valid);
514     return ret ? ret : (valid ? 0 : KRB5KRB_AP_ERR_MODIFIED);
515 }
516 
517 static krb5_error_code
verify_pac_checksums(krb5_context context,const krb5_pac pac,krb5_boolean expect_full_checksum,const krb5_keyblock * server,const krb5_keyblock * privsvr)518 verify_pac_checksums(krb5_context context, const krb5_pac pac,
519                      krb5_boolean expect_full_checksum,
520                      const krb5_keyblock *server, const krb5_keyblock *privsvr)
521 {
522     krb5_error_code ret;
523     krb5_data copy, server_checksum;
524 
525     /* Make a copy of the PAC with zeroed out server and privsvr checksums. */
526     ret = krb5int_copy_data_contents(context, &pac->data, &copy);
527     if (ret)
528         return ret;
529 
530     ret = zero_signature(context, pac, KRB5_PAC_SERVER_CHECKSUM, &copy);
531     if (ret)
532         goto cleanup;
533     ret = zero_signature(context, pac, KRB5_PAC_PRIVSVR_CHECKSUM, &copy);
534     if (ret)
535         goto cleanup;
536 
537     if (server != NULL) {
538         /* Verify the server checksum over the PAC copy. */
539         ret = verify_checksum(context, pac, KRB5_PAC_SERVER_CHECKSUM, server,
540                               KRB5_KEYUSAGE_APP_DATA_CKSUM, &copy);
541     }
542 
543     if (privsvr != NULL && expect_full_checksum) {
544         /* Zero the full checksum buffer in the copy and verify the full
545          * checksum over the copy with all three checksums zeroed. */
546         ret = zero_signature(context, pac, KRB5_PAC_FULL_CHECKSUM, &copy);
547         if (ret)
548             goto cleanup;
549         ret = verify_checksum(context, pac, KRB5_PAC_FULL_CHECKSUM, privsvr,
550                               KRB5_KEYUSAGE_APP_DATA_CKSUM, &copy);
551         if (ret)
552             goto cleanup;
553     }
554 
555     if (privsvr != NULL) {
556         /* Verify the privsvr checksum over the server checksum. */
557         ret = k5_pac_locate_buffer(context, pac, KRB5_PAC_SERVER_CHECKSUM,
558                                    &server_checksum);
559         if (ret)
560             goto cleanup;
561         if (server_checksum.length < PAC_SIGNATURE_DATA_LENGTH) {
562             ret = KRB5_BAD_MSIZE;
563             goto cleanup;
564         }
565         server_checksum.data += PAC_SIGNATURE_DATA_LENGTH;
566         server_checksum.length -= PAC_SIGNATURE_DATA_LENGTH;
567 
568         ret = verify_checksum(context, pac, KRB5_PAC_PRIVSVR_CHECKSUM, privsvr,
569                               KRB5_KEYUSAGE_APP_DATA_CKSUM, &server_checksum);
570         if (ret)
571             goto cleanup;
572     }
573 
574     pac->verified = TRUE;
575 
576 cleanup:
577     free(copy.data);
578     return ret;
579 }
580 
581 /* Per MS-PAC 2.8.3, tickets encrypted to TGS and password change principals
582  * should not have ticket signatures. */
583 krb5_boolean
k5_pac_should_have_ticket_signature(krb5_const_principal sprinc)584 k5_pac_should_have_ticket_signature(krb5_const_principal sprinc)
585 {
586     if (IS_TGS_PRINC(sprinc))
587         return FALSE;
588     if (sprinc->length == 2 && data_eq_string(sprinc->data[0], "kadmin") &&
589         data_eq_string(sprinc->data[1], "changepw"))
590         return FALSE;
591     return TRUE;
592 }
593 
594 krb5_error_code KRB5_CALLCONV
krb5_kdc_verify_ticket(krb5_context context,const krb5_enc_tkt_part * enc_tkt,krb5_const_principal server_princ,const krb5_keyblock * server,const krb5_keyblock * privsvr,krb5_pac * pac_out)595 krb5_kdc_verify_ticket(krb5_context context, const krb5_enc_tkt_part *enc_tkt,
596                        krb5_const_principal server_princ,
597                        const krb5_keyblock *server,
598                        const krb5_keyblock *privsvr, krb5_pac *pac_out)
599 {
600     krb5_error_code ret;
601     krb5_pac pac = NULL;
602     krb5_data *recoded_tkt = NULL;
603     krb5_authdata **authdata = enc_tkt->authorization_data;
604     krb5_authdata *orig, **ifrel = NULL, **recoded_ifrel = NULL;
605     uint8_t z = 0;
606     krb5_authdata zpac = { KV5M_AUTHDATA, KRB5_AUTHDATA_WIN2K_PAC, 1, &z };
607     krb5_boolean is_service_tkt;
608     size_t i, j;
609 
610     *pac_out = NULL;
611 
612     if (authdata == NULL)
613         return 0;
614 
615     /*
616      * Find the position of the PAC in the ticket authdata.  ifrel will be the
617      * decoded AD-IF-RELEVANT container at position i containing a PAC, and j
618      * will be the offset within the container.
619      */
620     for (i = 0; authdata[i] != NULL; i++) {
621         if (authdata[i]->ad_type != KRB5_AUTHDATA_IF_RELEVANT)
622             continue;
623 
624         ret = krb5_decode_authdata_container(context,
625                                              KRB5_AUTHDATA_IF_RELEVANT,
626                                              authdata[i], &ifrel);
627         if (ret)
628             goto cleanup;
629 
630         for (j = 0; ifrel[j] != NULL; j++) {
631             if (ifrel[j]->ad_type == KRB5_AUTHDATA_WIN2K_PAC)
632                 break;
633         }
634         if (ifrel[j] != NULL)
635             break;
636 
637         krb5_free_authdata(context, ifrel);
638         ifrel = NULL;
639     }
640 
641     /* Stop and return successfully if we didn't find a PAC. */
642     if (authdata[i] == NULL) {
643         ret = 0;
644         goto cleanup;
645     }
646 
647     ret = krb5_pac_parse(context, ifrel[j]->contents, ifrel[j]->length, &pac);
648     if (ret)
649         goto cleanup;
650 
651     is_service_tkt = k5_pac_should_have_ticket_signature(server_princ);
652     if (privsvr != NULL && is_service_tkt) {
653         /* To check the PAC ticket signatures, re-encode the ticket with the
654          * PAC contents replaced by a single zero. */
655         orig = ifrel[j];
656         ifrel[j] = &zpac;
657         ret = krb5_encode_authdata_container(context,
658                                              KRB5_AUTHDATA_IF_RELEVANT,
659                                              ifrel, &recoded_ifrel);
660         ifrel[j] = orig;
661         if (ret)
662             goto cleanup;
663         orig = authdata[i];
664         authdata[i] = recoded_ifrel[0];
665         ret = encode_krb5_enc_tkt_part(enc_tkt, &recoded_tkt);
666         authdata[i] = orig;
667         if (ret)
668             goto cleanup;
669 
670         ret = verify_checksum(context, pac, KRB5_PAC_TICKET_CHECKSUM, privsvr,
671                               KRB5_KEYUSAGE_APP_DATA_CKSUM, recoded_tkt);
672         if (ret)
673             goto cleanup;
674     }
675 
676     ret = verify_pac_checksums(context, pac, is_service_tkt, server, privsvr);
677     if (ret)
678         goto cleanup;
679 
680     *pac_out = pac;
681     pac = NULL;
682 
683 cleanup:
684     krb5_pac_free(context, pac);
685     krb5_free_data(context, recoded_tkt);
686     krb5_free_authdata(context, ifrel);
687     krb5_free_authdata(context, recoded_ifrel);
688     return ret;
689 }
690 
691 krb5_error_code KRB5_CALLCONV
krb5_pac_verify(krb5_context context,const krb5_pac pac,krb5_timestamp authtime,krb5_const_principal principal,const krb5_keyblock * server,const krb5_keyblock * privsvr)692 krb5_pac_verify(krb5_context context,
693                 const krb5_pac pac,
694                 krb5_timestamp authtime,
695                 krb5_const_principal principal,
696                 const krb5_keyblock *server,
697                 const krb5_keyblock *privsvr)
698 {
699     return krb5_pac_verify_ext(context, pac, authtime, principal, server,
700                                privsvr, FALSE);
701 }
702 
703 krb5_error_code KRB5_CALLCONV
krb5_pac_verify_ext(krb5_context context,const krb5_pac pac,krb5_timestamp authtime,krb5_const_principal principal,const krb5_keyblock * server,const krb5_keyblock * privsvr,krb5_boolean with_realm)704 krb5_pac_verify_ext(krb5_context context,
705                     const krb5_pac pac,
706                     krb5_timestamp authtime,
707                     krb5_const_principal principal,
708                     const krb5_keyblock *server,
709                     const krb5_keyblock *privsvr,
710                     krb5_boolean with_realm)
711 {
712     krb5_error_code ret;
713 
714     if (server != NULL || privsvr != NULL) {
715         ret = verify_pac_checksums(context, pac, FALSE, server, privsvr);
716         if (ret)
717             return ret;
718     }
719 
720     if (principal != NULL) {
721         ret = k5_pac_validate_client(context, pac, authtime,
722                                      principal, with_realm);
723         if (ret)
724             return ret;
725     }
726 
727     return 0;
728 }
729 
730 /*
731  * PAC auth data attribute backend
732  */
733 struct mspac_context {
734     krb5_pac pac;
735 };
736 
737 static krb5_error_code
mspac_init(krb5_context context,void ** plugin_context)738 mspac_init(krb5_context context, void **plugin_context)
739 {
740     *plugin_context = NULL;
741     return 0;
742 }
743 
744 static void
mspac_flags(krb5_context context,void * plugin_context,krb5_authdatatype ad_type,krb5_flags * flags)745 mspac_flags(krb5_context context, void *plugin_context,
746             krb5_authdatatype ad_type, krb5_flags *flags)
747 {
748     *flags = AD_USAGE_TGS_REQ;
749 }
750 
751 static void
mspac_fini(krb5_context context,void * plugin_context)752 mspac_fini(krb5_context context, void *plugin_context)
753 {
754     return;
755 }
756 
757 static krb5_error_code
mspac_request_init(krb5_context context,krb5_authdata_context actx,void * plugin_context,void ** request_context)758 mspac_request_init(krb5_context context, krb5_authdata_context actx,
759                    void *plugin_context, void **request_context)
760 {
761     struct mspac_context *pacctx;
762 
763     pacctx = malloc(sizeof(*pacctx));
764     if (pacctx == NULL)
765         return ENOMEM;
766 
767     pacctx->pac = NULL;
768 
769     *request_context = pacctx;
770 
771     return 0;
772 }
773 
774 static krb5_error_code
mspac_import_authdata(krb5_context context,krb5_authdata_context actx,void * plugin_context,void * request_context,krb5_authdata ** authdata,krb5_boolean kdc_issued,krb5_const_principal kdc_issuer)775 mspac_import_authdata(krb5_context context, krb5_authdata_context actx,
776                       void *plugin_context, void *request_context,
777                       krb5_authdata **authdata, krb5_boolean kdc_issued,
778                       krb5_const_principal kdc_issuer)
779 {
780     struct mspac_context *pacctx = (struct mspac_context *)request_context;
781 
782     if (kdc_issued)
783         return EINVAL;
784 
785     if (pacctx->pac != NULL) {
786         krb5_pac_free(context, pacctx->pac);
787         pacctx->pac = NULL;
788     }
789 
790     assert(authdata[0] != NULL);
791     assert((authdata[0]->ad_type & AD_TYPE_FIELD_TYPE_MASK) ==
792            KRB5_AUTHDATA_WIN2K_PAC);
793 
794     return krb5_pac_parse(context, authdata[0]->contents, authdata[0]->length,
795                           &pacctx->pac);
796 }
797 
798 static krb5_error_code
mspac_export_authdata(krb5_context context,krb5_authdata_context actx,void * plugin_context,void * request_context,krb5_flags usage,krb5_authdata *** authdata_out)799 mspac_export_authdata(krb5_context context, krb5_authdata_context actx,
800                       void *plugin_context, void *request_context,
801                       krb5_flags usage, krb5_authdata ***authdata_out)
802 {
803     struct mspac_context *pacctx = (struct mspac_context *)request_context;
804     krb5_error_code code;
805     krb5_authdata **authdata;
806     krb5_data data;
807 
808     if (pacctx->pac == NULL)
809         return 0;
810 
811     authdata = calloc(2, sizeof(krb5_authdata *));
812     if (authdata == NULL)
813         return ENOMEM;
814 
815     authdata[0] = calloc(1, sizeof(krb5_authdata));
816     if (authdata[0] == NULL) {
817         free(authdata);
818         return ENOMEM;
819     }
820     authdata[1] = NULL;
821 
822     code = krb5int_copy_data_contents(context, &pacctx->pac->data, &data);
823     if (code != 0) {
824         krb5_free_authdata(context, authdata);
825         return code;
826     }
827 
828     authdata[0]->magic = KV5M_AUTHDATA;
829     authdata[0]->ad_type = KRB5_AUTHDATA_WIN2K_PAC;
830     authdata[0]->length = data.length;
831     authdata[0]->contents = (krb5_octet *)data.data;
832 
833     authdata[1] = NULL;
834 
835     *authdata_out = authdata;
836 
837     return 0;
838 }
839 
840 static krb5_error_code
mspac_verify(krb5_context context,krb5_authdata_context actx,void * plugin_context,void * request_context,const krb5_auth_context * auth_context,const krb5_keyblock * key,const krb5_ap_req * req)841 mspac_verify(krb5_context context, krb5_authdata_context actx,
842              void *plugin_context, void *request_context,
843              const krb5_auth_context *auth_context, const krb5_keyblock *key,
844              const krb5_ap_req *req)
845 {
846     krb5_error_code ret;
847     struct mspac_context *pacctx = (struct mspac_context *)request_context;
848 
849     if (pacctx->pac == NULL)
850         return EINVAL;
851 
852     ret = krb5_pac_verify(context, pacctx->pac,
853                           req->ticket->enc_part2->times.authtime,
854                           req->ticket->enc_part2->client, key, NULL);
855     if (ret)
856         TRACE_MSPAC_VERIFY_FAIL(context, ret);
857 
858     /*
859      * If the above verification failed, don't fail the whole authentication,
860      * just don't mark the PAC as verified.  A checksum mismatch can occur if
861      * the PAC was copied from a cross-realm TGT by an ignorant KDC, and Apple
862      * macOS Server Open Directory (as of 10.6) generates PACs with no server
863      * checksum at all.
864      */
865     return 0;
866 }
867 
868 static void
mspac_request_fini(krb5_context context,krb5_authdata_context actx,void * plugin_context,void * request_context)869 mspac_request_fini(krb5_context context, krb5_authdata_context actx,
870                    void *plugin_context, void *request_context)
871 {
872     struct mspac_context *pacctx = (struct mspac_context *)request_context;
873 
874     if (pacctx != NULL) {
875         krb5_pac_free(context, pacctx->pac);
876         free(pacctx);
877     }
878 }
879 
880 #define STRLENOF(x) (sizeof((x)) - 1)
881 
882 static struct {
883     uint32_t type;
884     krb5_data attribute;
885 } mspac_attribute_types[] = {
886     { (uint32_t)-1,             { KV5M_DATA, STRLENOF("urn:mspac:"),
887                                   "urn:mspac:" } },
888     { KRB5_PAC_LOGON_INFO,       { KV5M_DATA,
889                                    STRLENOF("urn:mspac:logon-info"),
890                                    "urn:mspac:logon-info" } },
891     { KRB5_PAC_CREDENTIALS_INFO, { KV5M_DATA,
892                                    STRLENOF("urn:mspac:credentials-info"),
893                                    "urn:mspac:credentials-info" } },
894     { KRB5_PAC_SERVER_CHECKSUM,  { KV5M_DATA,
895                                    STRLENOF("urn:mspac:server-checksum"),
896                                    "urn:mspac:server-checksum" } },
897     { KRB5_PAC_PRIVSVR_CHECKSUM, { KV5M_DATA,
898                                    STRLENOF("urn:mspac:privsvr-checksum"),
899                                    "urn:mspac:privsvr-checksum" } },
900     { KRB5_PAC_CLIENT_INFO,      { KV5M_DATA,
901                                    STRLENOF("urn:mspac:client-info"),
902                                    "urn:mspac:client-info" } },
903     { KRB5_PAC_DELEGATION_INFO,  { KV5M_DATA,
904                                    STRLENOF("urn:mspac:delegation-info"),
905                                    "urn:mspac:delegation-info" } },
906     { KRB5_PAC_UPN_DNS_INFO,     { KV5M_DATA,
907                                    STRLENOF("urn:mspac:upn-dns-info"),
908                                    "urn:mspac:upn-dns-info" } },
909 };
910 
911 #define MSPAC_ATTRIBUTE_COUNT   (sizeof(mspac_attribute_types)/sizeof(mspac_attribute_types[0]))
912 
913 static krb5_error_code
mspac_type2attr(uint32_t type,krb5_data * attr)914 mspac_type2attr(uint32_t type, krb5_data *attr)
915 {
916     unsigned int i;
917 
918     for (i = 0; i < MSPAC_ATTRIBUTE_COUNT; i++) {
919         if (mspac_attribute_types[i].type == type) {
920             *attr = mspac_attribute_types[i].attribute;
921             return 0;
922         }
923     }
924 
925     return ENOENT;
926 }
927 
928 static krb5_error_code
mspac_attr2type(const krb5_data * attr,uint32_t * type)929 mspac_attr2type(const krb5_data *attr, uint32_t *type)
930 {
931     unsigned int i;
932 
933     for (i = 0; i < MSPAC_ATTRIBUTE_COUNT; i++) {
934         if (attr->length == mspac_attribute_types[i].attribute.length &&
935             strncasecmp(attr->data, mspac_attribute_types[i].attribute.data, attr->length) == 0) {
936             *type = mspac_attribute_types[i].type;
937             return 0;
938         }
939     }
940 
941     if (attr->length > STRLENOF("urn:mspac:") &&
942         strncasecmp(attr->data, "urn:mspac:", STRLENOF("urn:mspac:")) == 0)
943     {
944         char *p = &attr->data[STRLENOF("urn:mspac:")];
945         char *endptr;
946 
947         *type = strtoul(p, &endptr, 10);
948         if (*type != 0 && *endptr == '\0')
949             return 0;
950     }
951 
952     return ENOENT;
953 }
954 
955 static krb5_error_code
mspac_get_attribute_types(krb5_context context,krb5_authdata_context actx,void * plugin_context,void * request_context,krb5_data ** attrs_out)956 mspac_get_attribute_types(krb5_context context, krb5_authdata_context actx,
957                           void *plugin_context, void *request_context,
958                           krb5_data **attrs_out)
959 {
960     struct mspac_context *pacctx = (struct mspac_context *)request_context;
961     size_t i, j;
962     krb5_data *attrs;
963     krb5_error_code ret;
964 
965     if (pacctx->pac == NULL)
966         return ENOENT;
967 
968     attrs = calloc(1 + pacctx->pac->nbuffers + 1, sizeof(krb5_data));
969     if (attrs == NULL)
970         return ENOMEM;
971 
972     j = 0;
973 
974     /* The entire PAC */
975     ret = krb5int_copy_data_contents(context,
976                                      &mspac_attribute_types[0].attribute,
977                                      &attrs[j++]);
978     if (ret)
979         goto fail;
980 
981     /* PAC buffers */
982     for (i = 0; i < pacctx->pac->nbuffers; i++) {
983         krb5_data attr;
984 
985         ret = mspac_type2attr(pacctx->pac->buffers[i].type, &attr);
986         if (!ret) {
987             ret = krb5int_copy_data_contents(context, &attr, &attrs[j++]);
988             if (ret)
989                 goto fail;
990         } else {
991             int length;
992 
993             length = asprintf(&attrs[j].data, "urn:mspac:%d",
994                               pacctx->pac->buffers[i].type);
995             if (length < 0) {
996                 ret = ENOMEM;
997                 goto fail;
998             }
999             attrs[j++].length = length;
1000         }
1001     }
1002     attrs[j].data = NULL;
1003     attrs[j].length = 0;
1004 
1005     *attrs_out = attrs;
1006 
1007     return 0;
1008 
1009 fail:
1010     krb5int_free_data_list(context, attrs);
1011     return ret;
1012 }
1013 
1014 static krb5_error_code
mspac_get_attribute(krb5_context context,krb5_authdata_context actx,void * plugin_context,void * request_context,const krb5_data * attribute,krb5_boolean * authenticated,krb5_boolean * complete,krb5_data * value,krb5_data * display_value,int * more)1015 mspac_get_attribute(krb5_context context, krb5_authdata_context actx,
1016                     void *plugin_context, void *request_context,
1017                     const krb5_data *attribute, krb5_boolean *authenticated,
1018                     krb5_boolean *complete, krb5_data *value,
1019                     krb5_data *display_value, int *more)
1020 {
1021     struct mspac_context *pacctx = (struct mspac_context *)request_context;
1022     krb5_error_code ret;
1023     uint32_t type;
1024 
1025     if (display_value != NULL) {
1026         display_value->data = NULL;
1027         display_value->length = 0;
1028     }
1029 
1030     if (*more != -1 || pacctx->pac == NULL)
1031         return ENOENT;
1032 
1033     /* If it didn't verify, pretend it didn't exist. */
1034     if (!pacctx->pac->verified) {
1035         TRACE_MSPAC_DISCARD_UNVERF(context);
1036         return ENOENT;
1037     }
1038 
1039     ret = mspac_attr2type(attribute, &type);
1040     if (ret)
1041         return ret;
1042 
1043     /* -1 is a magic type that refers to the entire PAC */
1044     if (type == (uint32_t)-1) {
1045         if (value != NULL)
1046             ret = krb5int_copy_data_contents(context, &pacctx->pac->data,
1047                                              value);
1048         else
1049             ret = 0;
1050     } else {
1051         if (value != NULL)
1052             ret = krb5_pac_get_buffer(context, pacctx->pac, type, value);
1053         else
1054             ret = k5_pac_locate_buffer(context, pacctx->pac, type, NULL);
1055     }
1056     if (!ret) {
1057         *authenticated = pacctx->pac->verified;
1058         *complete = TRUE;
1059     }
1060 
1061     *more = 0;
1062 
1063     return ret;
1064 }
1065 
1066 static krb5_error_code
mspac_set_attribute(krb5_context context,krb5_authdata_context actx,void * plugin_context,void * request_context,krb5_boolean complete,const krb5_data * attribute,const krb5_data * value)1067 mspac_set_attribute(krb5_context context, krb5_authdata_context actx,
1068                     void *plugin_context, void *request_context,
1069                     krb5_boolean complete, const krb5_data *attribute,
1070                     const krb5_data *value)
1071 {
1072     struct mspac_context *pacctx = (struct mspac_context *)request_context;
1073     krb5_error_code ret;
1074     uint32_t type;
1075 
1076     if (pacctx->pac == NULL)
1077         return ENOENT;
1078 
1079     ret = mspac_attr2type(attribute, &type);
1080     if (ret)
1081         return ret;
1082 
1083     /* -1 is a magic type that refers to the entire PAC */
1084     if (type == (uint32_t)-1) {
1085         krb5_pac newpac;
1086 
1087         ret = krb5_pac_parse(context, value->data, value->length, &newpac);
1088         if (ret)
1089             return ret;
1090 
1091         krb5_pac_free(context, pacctx->pac);
1092         pacctx->pac = newpac;
1093     } else {
1094         ret = krb5_pac_add_buffer(context, pacctx->pac, type, value);
1095     }
1096 
1097     return ret;
1098 }
1099 
1100 static krb5_error_code
mspac_export_internal(krb5_context context,krb5_authdata_context actx,void * plugin_context,void * request_context,krb5_boolean restrict_authenticated,void ** ptr)1101 mspac_export_internal(krb5_context context, krb5_authdata_context actx,
1102                       void *plugin_context, void *request_context,
1103                       krb5_boolean restrict_authenticated, void **ptr)
1104 {
1105     struct mspac_context *pacctx = (struct mspac_context *)request_context;
1106     krb5_error_code ret;
1107     krb5_pac pac;
1108 
1109     *ptr = NULL;
1110 
1111     if (pacctx->pac == NULL)
1112         return ENOENT;
1113 
1114     if (restrict_authenticated && (pacctx->pac->verified) == FALSE)
1115         return ENOENT;
1116 
1117     ret = krb5_pac_parse(context, pacctx->pac->data.data,
1118                          pacctx->pac->data.length, &pac);
1119     if (!ret) {
1120         pac->verified = pacctx->pac->verified;
1121         *ptr = pac;
1122     }
1123 
1124     return ret;
1125 }
1126 
1127 static void
mspac_free_internal(krb5_context context,krb5_authdata_context actx,void * plugin_context,void * request_context,void * ptr)1128 mspac_free_internal(krb5_context context, krb5_authdata_context actx,
1129                     void *plugin_context, void *request_context, void *ptr)
1130 {
1131     if (ptr != NULL)
1132         krb5_pac_free(context, (krb5_pac)ptr);
1133 
1134     return;
1135 }
1136 
1137 static krb5_error_code
mspac_size(krb5_context context,krb5_authdata_context actx,void * plugin_context,void * request_context,size_t * sizep)1138 mspac_size(krb5_context context, krb5_authdata_context actx,
1139            void *plugin_context, void *request_context, size_t *sizep)
1140 {
1141     struct mspac_context *pacctx = (struct mspac_context *)request_context;
1142 
1143     *sizep += sizeof(int32_t);
1144 
1145     if (pacctx->pac != NULL)
1146         *sizep += pacctx->pac->data.length;
1147 
1148     *sizep += sizeof(int32_t);
1149 
1150     return 0;
1151 }
1152 
1153 static krb5_error_code
mspac_externalize(krb5_context context,krb5_authdata_context actx,void * plugin_context,void * request_context,krb5_octet ** buffer,size_t * lenremain)1154 mspac_externalize(krb5_context context, krb5_authdata_context actx,
1155                   void *plugin_context, void *request_context,
1156                   krb5_octet **buffer, size_t *lenremain)
1157 {
1158     krb5_error_code ret = 0;
1159     struct mspac_context *pacctx = (struct mspac_context *)request_context;
1160     size_t required = 0;
1161     krb5_octet *bp;
1162     size_t remain;
1163 
1164     bp = *buffer;
1165     remain = *lenremain;
1166 
1167     if (pacctx->pac != NULL) {
1168         mspac_size(context, actx, plugin_context, request_context, &required);
1169 
1170         if (required <= remain) {
1171             krb5_ser_pack_int32(pacctx->pac->data.length, &bp, &remain);
1172             krb5_ser_pack_bytes((krb5_octet *)pacctx->pac->data.data,
1173                                 (size_t)pacctx->pac->data.length,
1174                                 &bp, &remain);
1175             krb5_ser_pack_int32(pacctx->pac->verified, &bp, &remain);
1176         } else {
1177             ret = ENOMEM;
1178         }
1179     } else {
1180         krb5_ser_pack_int32(0, &bp, &remain); /* length */
1181         krb5_ser_pack_int32(0, &bp, &remain); /* verified */
1182     }
1183 
1184     *buffer = bp;
1185     *lenremain = remain;
1186 
1187     return ret;
1188 }
1189 
1190 static krb5_error_code
mspac_internalize(krb5_context context,krb5_authdata_context actx,void * plugin_context,void * request_context,krb5_octet ** buffer,size_t * lenremain)1191 mspac_internalize(krb5_context context, krb5_authdata_context actx,
1192                   void *plugin_context, void *request_context,
1193                   krb5_octet **buffer, size_t *lenremain)
1194 {
1195     struct mspac_context *pacctx = (struct mspac_context *)request_context;
1196     krb5_error_code ret;
1197     int32_t ibuf;
1198     uint8_t *bp;
1199     size_t remain;
1200     krb5_pac pac = NULL;
1201 
1202     bp = *buffer;
1203     remain = *lenremain;
1204 
1205     /* length */
1206     ret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
1207     if (ret)
1208         return ret;
1209 
1210     if (ibuf != 0) {
1211         ret = krb5_pac_parse(context, bp, ibuf, &pac);
1212         if (ret)
1213             return ret;
1214 
1215         bp += ibuf;
1216         remain -= ibuf;
1217     }
1218 
1219     /* verified */
1220     ret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
1221     if (ret) {
1222         krb5_pac_free(context, pac);
1223         return ret;
1224     }
1225 
1226     if (pac != NULL)
1227         pac->verified = (ibuf != 0);
1228 
1229     if (pacctx->pac != NULL)
1230         krb5_pac_free(context, pacctx->pac);
1231 
1232     pacctx->pac = pac;
1233 
1234     *buffer = bp;
1235     *lenremain = remain;
1236 
1237     return 0;
1238 }
1239 
1240 static krb5_error_code
mspac_copy(krb5_context context,krb5_authdata_context actx,void * plugin_context,void * request_context,void * dst_plugin_context,void * dst_request_context)1241 mspac_copy(krb5_context context, krb5_authdata_context actx,
1242            void *plugin_context, void *request_context,
1243            void *dst_plugin_context, void *dst_request_context)
1244 {
1245     struct mspac_context *srcctx = (struct mspac_context *)request_context;
1246     struct mspac_context *dstctx = (struct mspac_context *)dst_request_context;
1247     krb5_error_code ret = 0;
1248 
1249     assert(dstctx != NULL);
1250     assert(dstctx->pac == NULL);
1251 
1252     if (srcctx->pac != NULL)
1253         ret = copy_pac(context, srcctx->pac, &dstctx->pac);
1254 
1255     return ret;
1256 }
1257 
1258 static krb5_authdatatype mspac_ad_types[] = { KRB5_AUTHDATA_WIN2K_PAC, 0 };
1259 
1260 krb5plugin_authdata_client_ftable_v0 k5_mspac_ad_client_ftable = {
1261     "mspac",
1262     mspac_ad_types,
1263     mspac_init,
1264     mspac_fini,
1265     mspac_flags,
1266     mspac_request_init,
1267     mspac_request_fini,
1268     mspac_get_attribute_types,
1269     mspac_get_attribute,
1270     mspac_set_attribute,
1271     NULL, /* delete_attribute_proc */
1272     mspac_export_authdata,
1273     mspac_import_authdata,
1274     mspac_export_internal,
1275     mspac_free_internal,
1276     mspac_verify,
1277     mspac_size,
1278     mspac_externalize,
1279     mspac_internalize,
1280     mspac_copy
1281 };
1282