OpenSolaris_b135/lib/gss_mechs/mech_krb5/krb5/krb/pac.c

Compare this file to the similar file:
Show the results in this format:

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/*
 * lib/krb5/krb/pac.c
 *
 * Copyright 2008 by the Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * Export of this software from the United States of America may
 *   require a specific license from the United States Government.
 *   It is the responsibility of any person or organization contemplating
 *   export to obtain such a license before exporting.
 * 
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of M.I.T. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  Furthermore if you modify this software you must label
 * your software as modified software and not distribute it in such a
 * fashion that it might be confused with the original M.I.T. software.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 *
 */

#include "k5-int.h"
#include "k5-utf8.h"

/* draft-brezak-win2k-krb-authz-00 */

/*
 * A PAC consists of a sequence of PAC_INFO_BUFFERs, preceeded by
 * a PACTYPE header. Decoding the contents of the buffers is left
 * to the application (notwithstanding signature verification).
 */

/*
 * SUNW17PACresync
 * These should eventually go to k5-platform.h or equiv.
 */
static inline unsigned short
load_16_le (const void *cvp)
{
    const unsigned char *p = cvp;
#if defined(__GNUC__) && defined(K5_LE)
    return GET(16,p);
#elif defined(__GNUC__) && defined(K5_BE) && defined(SWAP16)
    return GETSWAPPED(16,p);
#else
    return (p[0] | (p[1] << 8));
#endif
}

static inline unsigned int
load_32_le (const void *cvp)
{
    const unsigned char *p = cvp;
#if defined(__GNUC__) && defined(K5_LE)
    return GET(32,p);
#elif defined(__GNUC__) && defined(K5_BE) && defined(SWAP32)
    return GETSWAPPED(32,p);
#else
    return (p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24));
#endif
}
static inline UINT64_TYPE
load_64_le (const void *cvp)
{
    const unsigned char *p = cvp;
#if defined(__GNUC__) && defined(K5_LE)
    return GET(64,p);
#elif defined(__GNUC__) && defined(K5_BE) && defined(SWAP64)
    return GETSWAPPED(64,p);
#else
    return ((UINT64_TYPE)load_32_le(p+4) << 32) | load_32_le(p);
#endif
}

static inline void
store_16_le (unsigned int val, void *vp)
{
    unsigned char *p = vp;
#if defined(__GNUC__) && defined(K5_LE)
    PUT(16,p,val);
#elif defined(__GNUC__) && defined(K5_BE) && defined(SWAP16)
    PUTSWAPPED(16,p,val);
#else
    p[1] = (val >>  8) & 0xff;
    p[0] = (val      ) & 0xff;
#endif
}

static inline void
store_32_le (unsigned int val, void *vp)
{
    unsigned char *p = vp;
#if defined(__GNUC__) && defined(K5_LE)
    PUT(32,p,val);
#elif defined(__GNUC__) && defined(K5_BE) && defined(SWAP32)
    PUTSWAPPED(32,p,val);
#else
    p[3] = (val >> 24) & 0xff;
    p[2] = (val >> 16) & 0xff;
    p[1] = (val >>  8) & 0xff;
    p[0] = (val      ) & 0xff;
#endif
}
static inline void
store_64_le (UINT64_TYPE val, void *vp)
{
    unsigned char *p = vp;
#if defined(__GNUC__) && defined(K5_LE)
    PUT(64,p,val);
#elif defined(__GNUC__) && defined(K5_BE) && defined(SWAP64)
    PUTSWAPPED(64,p,val);
#else
    p[7] = (unsigned char)((val >> 56) & 0xff);
    p[6] = (unsigned char)((val >> 48) & 0xff);
    p[5] = (unsigned char)((val >> 40) & 0xff);
    p[4] = (unsigned char)((val >> 32) & 0xff);
    p[3] = (unsigned char)((val >> 24) & 0xff);
    p[2] = (unsigned char)((val >> 16) & 0xff);
    p[1] = (unsigned char)((val >>  8) & 0xff);
    p[0] = (unsigned char)((val      ) & 0xff);
#endif
}


typedef struct _PAC_INFO_BUFFER {
    krb5_ui_4 ulType;
    krb5_ui_4 cbBufferSize;
    krb5_ui_8 Offset;
} PAC_INFO_BUFFER;

#define PAC_INFO_BUFFER_LENGTH	16

/* ulType */
#define PAC_LOGON_INFO		1
#define PAC_SERVER_CHECKSUM	6
#define PAC_PRIVSVR_CHECKSUM	7
#define PAC_CLIENT_INFO		10

typedef struct _PACTYPE {
    krb5_ui_4 cBuffers;
    krb5_ui_4 Version;
    PAC_INFO_BUFFER Buffers[1];
} PACTYPE;

#define PAC_ALIGNMENT		    8
#define PACTYPE_LENGTH		    8U
#define PAC_SIGNATURE_DATA_LENGTH   4U
#define PAC_CLIENT_INFO_LENGTH	    10U

#define NT_TIME_EPOCH		    11644473600LL

struct krb5_pac_data {
    PACTYPE *pac;	/* PAC header + info buffer array */
    krb5_data data;	/* PAC data (including uninitialised header) */
};

static krb5_error_code
k5_pac_locate_buffer(krb5_context context,
		     const krb5_pac pac,
		     krb5_ui_4 type,
		     krb5_data *data);

/*
 * Add a buffer to the provided PAC and update header.
 */
static krb5_error_code
k5_pac_add_buffer(krb5_context context,
		  krb5_pac pac,
		  krb5_ui_4 type,
		  const krb5_data *data,
		  krb5_boolean zerofill,
		  krb5_data *out_data)
{
    PACTYPE *header;
    size_t header_len, i, pad = 0;
    char *pac_data;

    assert((data->data == NULL) == zerofill);

    /* Check there isn't already a buffer of this type */
    if (k5_pac_locate_buffer(context, pac, type, NULL) == 0) {
	return EINVAL;
    }

    header = (PACTYPE *)realloc(pac->pac,
				sizeof(PACTYPE) +
				(pac->pac->cBuffers * sizeof(PAC_INFO_BUFFER)));
    if (header == NULL) {
	return ENOMEM;
    }
    pac->pac = header;

    header_len = PACTYPE_LENGTH + (pac->pac->cBuffers * PAC_INFO_BUFFER_LENGTH);

    if (data->length % PAC_ALIGNMENT)
	pad = PAC_ALIGNMENT - (data->length % PAC_ALIGNMENT);

    pac_data = realloc(pac->data.data,
		       pac->data.length + PAC_INFO_BUFFER_LENGTH + data->length + pad);
    if (pac_data == NULL) {
	return ENOMEM;
    }
    pac->data.data = pac_data;

    /* Update offsets of existing buffers */
    for (i = 0; i < pac->pac->cBuffers; i++)
	pac->pac->Buffers[i].Offset += PAC_INFO_BUFFER_LENGTH;

    /* Make room for new PAC_INFO_BUFFER */
    memmove(pac->data.data + header_len + PAC_INFO_BUFFER_LENGTH,
	    pac->data.data + header_len,
	    pac->data.length - header_len);
    memset(pac->data.data + header_len, 0, PAC_INFO_BUFFER_LENGTH);

    /* Initialise new PAC_INFO_BUFFER */
    pac->pac->Buffers[i].ulType = type;
    pac->pac->Buffers[i].cbBufferSize = data->length;
    pac->pac->Buffers[i].Offset = pac->data.length + PAC_INFO_BUFFER_LENGTH;
    assert((pac->pac->Buffers[i].Offset % PAC_ALIGNMENT) == 0);

    /* Copy in new PAC data and zero padding bytes */
    if (zerofill)
	memset(pac->data.data + pac->pac->Buffers[i].Offset, 0, data->length);
    else
	memcpy(pac->data.data + pac->pac->Buffers[i].Offset, data->data, data->length);

    memset(pac->data.data + pac->pac->Buffers[i].Offset + data->length, 0, pad);

    pac->pac->cBuffers++;
    pac->data.length += PAC_INFO_BUFFER_LENGTH + data->length + pad;

    if (out_data != NULL) {
	out_data->data = pac->data.data + pac->pac->Buffers[i].Offset;
	out_data->length = data->length;
    }

    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_pac_add_buffer(krb5_context context,
		    krb5_pac pac,
		    krb5_ui_4 type,
		    const krb5_data *data)
{
    return k5_pac_add_buffer(context, pac, type, data, FALSE, NULL);
}

/*
 * Free a PAC
 */
void KRB5_CALLCONV
krb5_pac_free(krb5_context context,
	      krb5_pac pac)
{
    if (pac != NULL) {
	if (pac->data.data != NULL) {
	    memset(pac->data.data, 0, pac->data.length);
	    free(pac->data.data);
	}
	if (pac->pac != NULL)
	    free(pac->pac);
	memset(pac, 0, sizeof(*pac));
	free(pac);
    }
}

static krb5_error_code
k5_pac_locate_buffer(krb5_context context,
		     const krb5_pac pac,
		     krb5_ui_4 type,
		     krb5_data *data)
{
    PAC_INFO_BUFFER *buffer = NULL;
    size_t i;

    if (pac == NULL)
	return EINVAL;

    for (i = 0; i < pac->pac->cBuffers; i++) {
	if (pac->pac->Buffers[i].ulType == type) {
	    if (buffer == NULL)
		buffer = &pac->pac->Buffers[i];
	    else
		return EINVAL;
	}
    }

    if (buffer == NULL)
	return ENOENT;

    assert(buffer->Offset + buffer->cbBufferSize <= pac->data.length);

    if (data != NULL) {
	data->length = buffer->cbBufferSize;
	data->data = pac->data.data + buffer->Offset;
    }

    return 0;
}

/*
 * Find a buffer and copy data into output
 */
krb5_error_code KRB5_CALLCONV
krb5_pac_get_buffer(krb5_context context,
		    krb5_pac pac,
		    krb5_ui_4 type,
		    krb5_data *data)
{
    krb5_data d;
    krb5_error_code ret;

    ret = k5_pac_locate_buffer(context, pac, type, &d);
    if (ret != 0)
	return ret;
 
    data->data = malloc(d.length);
    if (data->data == NULL)
	return ENOMEM;

    data->length = d.length;
    memcpy(data->data, d.data, d.length);

    return 0;
}

/*
 * Return an array of the types of data in the PAC
 */
krb5_error_code KRB5_CALLCONV
krb5_pac_get_types(krb5_context context,
		   krb5_pac pac,
		   size_t *len,
		   krb5_ui_4 **types)
{
    size_t i;

    *types = (krb5_ui_4 *)malloc(pac->pac->cBuffers * sizeof(krb5_ui_4));
    if (*types == NULL)
	return ENOMEM;

    *len = pac->pac->cBuffers;

    for (i = 0; i < pac->pac->cBuffers; i++)
	(*types)[i] = pac->pac->Buffers[i].ulType;

    return 0;
}

/*
 * Initialize PAC
 */
krb5_error_code KRB5_CALLCONV
krb5_pac_init(krb5_context context,
	      krb5_pac *ppac)
{
    krb5_pac pac;

    pac = (krb5_pac)malloc(sizeof(*pac));
    if (pac == NULL)
	return ENOMEM;

    pac->pac = (PACTYPE *)malloc(sizeof(PACTYPE));
    if (pac->pac == NULL) {
	free( pac);
	return ENOMEM;
    }

    pac->pac->cBuffers = 0;
    pac->pac->Version = 0;

    pac->data.length = PACTYPE_LENGTH;
    pac->data.data = calloc(1, pac->data.length);
    if (pac->data.data == NULL) {
	krb5_pac_free(context, pac);
	return ENOMEM;
    }

    *ppac = pac;

    return 0;
}

/*
 * Parse the supplied data into the PAC allocated by this function
 */
krb5_error_code KRB5_CALLCONV
krb5_pac_parse(krb5_context context,
	       const void *ptr,
	       size_t len,
	       krb5_pac *ppac)
{
    krb5_error_code ret;
    size_t i;
    const unsigned char *p = (const unsigned char *)ptr;
    krb5_pac pac;
    size_t header_len;
    krb5_ui_4 cbuffers, version;

    *ppac = NULL;

    if (len < PACTYPE_LENGTH)
	return ERANGE;

    cbuffers = load_32_le(p);
    p += 4;
    version = load_32_le(p);
    p += 4;

    if (version != 0)
	return EINVAL;

    header_len = PACTYPE_LENGTH + (cbuffers * PAC_INFO_BUFFER_LENGTH);
    if (len < header_len)
	return ERANGE;

    ret = krb5_pac_init(context, &pac);
    if (ret != 0)
	return ret;

    pac->pac = (PACTYPE *)realloc(pac->pac,
	sizeof(PACTYPE) + ((cbuffers - 1) * sizeof(PAC_INFO_BUFFER)));
    if (pac->pac == NULL) {
	krb5_pac_free(context, pac);
	return ENOMEM;
    }

    pac->pac->cBuffers = cbuffers;
    pac->pac->Version = version;

    for (i = 0; i < pac->pac->cBuffers; i++) {
	PAC_INFO_BUFFER *buffer = &pac->pac->Buffers[i];

	buffer->ulType = load_32_le(p);
	p += 4;
	buffer->cbBufferSize = load_32_le(p);
	p += 4;
	buffer->Offset = load_64_le(p);
	p += 8;

	if (buffer->Offset % PAC_ALIGNMENT) {
	    krb5_pac_free(context, pac);
	    return EINVAL;
	}
	if (buffer->Offset < header_len ||
	    buffer->Offset + buffer->cbBufferSize > len) {
	    krb5_pac_free(context, pac);
	    return ERANGE;
	}
    }

    pac->data.data = realloc(pac->data.data, len);
    if (pac->data.data == NULL) {
	krb5_pac_free(context, pac);
	return ENOMEM;
    }
    memcpy(pac->data.data, ptr, len);

    pac->data.length = len;

    *ppac = pac;

    return 0;
}

static krb5_error_code
k5_time_to_seconds_since_1970(krb5_int64 ntTime, krb5_timestamp *elapsedSeconds)
{
    krb5_ui_8 abstime;

    ntTime /= 10000000;

    abstime = ntTime > 0 ? ntTime - NT_TIME_EPOCH : -ntTime;

    if (abstime > KRB5_INT32_MAX)
	return ERANGE;

    *elapsedSeconds = abstime;

    return 0;
}    

static krb5_error_code
k5_seconds_since_1970_to_time(krb5_timestamp elapsedSeconds, krb5_ui_8 *ntTime)
{
    *ntTime = elapsedSeconds;

    if (elapsedSeconds > 0)
	*ntTime += NT_TIME_EPOCH;

    *ntTime *= 10000000;
   
    return 0;
}

static krb5_error_code
k5_pac_validate_client(krb5_context context,
		       const krb5_pac pac,
		       krb5_timestamp authtime,
		       krb5_const_principal principal)
{
    krb5_error_code ret;
    krb5_data client_info;
    char *pac_princname;
    unsigned char *p;
    krb5_timestamp pac_authtime;
    krb5_ui_2 pac_princname_length;
    krb5_int64 pac_nt_authtime;
    krb5_principal pac_principal;

    ret = k5_pac_locate_buffer(context, pac, PAC_CLIENT_INFO, &client_info);
    if (ret != 0)
	return ret;

    if (client_info.length < PAC_CLIENT_INFO_LENGTH)
	return ERANGE;

    p = (unsigned char *)client_info.data;
    pac_nt_authtime = load_64_le(p);
    p += 8;
    pac_princname_length = load_16_le(p);
    p += 2;

    ret = k5_time_to_seconds_since_1970(pac_nt_authtime, &pac_authtime);
    if (ret != 0)
	return ret;

    if (client_info.length < PAC_CLIENT_INFO_LENGTH + pac_princname_length ||
        pac_princname_length % 2)
	return ERANGE;

    ret = krb5int_ucs2lecs_to_utf8s(p, (size_t)pac_princname_length / 2, &pac_princname, NULL);
    if (ret != 0)
	return ret;

    ret = krb5_parse_name_flags(context, pac_princname, 0, &pac_principal);
    if (ret != 0) {
	free(pac_princname);
	return ret;
    }

    free(pac_princname);

    if (pac_authtime != authtime ||
	krb5_principal_compare(context, pac_principal, principal) == FALSE)
	ret = KRB5KRB_AP_WRONG_PRINC;

    krb5_free_principal(context, pac_principal);

    return ret;
}

static krb5_error_code
k5_pac_zero_signature(krb5_context context,
		      const krb5_pac pac,
		      krb5_ui_4 type,
		      krb5_data *data)
{
    PAC_INFO_BUFFER *buffer = NULL;
    size_t i;

    assert(type == PAC_SERVER_CHECKSUM || type == PAC_PRIVSVR_CHECKSUM);
    assert(data->length >= pac->data.length);

    for (i = 0; i < pac->pac->cBuffers; i++) {
	if (pac->pac->Buffers[i].ulType == type) {
	    buffer = &pac->pac->Buffers[i];
	    break;
	}
    }

    if (buffer == NULL)
	return ENOENT;

    if (buffer->Offset + buffer->cbBufferSize > pac->data.length)
	return ERANGE;

    if (buffer->cbBufferSize < PAC_SIGNATURE_DATA_LENGTH)
	return KRB5_BAD_MSIZE;

    /* Zero out the data portion of the checksum only */
    memset(data->data + buffer->Offset + PAC_SIGNATURE_DATA_LENGTH,
	   0,
	   buffer->cbBufferSize - PAC_SIGNATURE_DATA_LENGTH);

    return 0;
}

static krb5_error_code
k5_pac_verify_server_checksum(krb5_context context,
			      const krb5_pac pac,
			      const krb5_keyblock *server)
{
    krb5_error_code ret;
    krb5_data pac_data; /* PAC with zeroed checksums */
    krb5_checksum checksum;
    krb5_data checksum_data;
    krb5_boolean valid;
    krb5_octet *p;

    ret = k5_pac_locate_buffer(context, pac, PAC_SERVER_CHECKSUM, &checksum_data);
    if (ret != 0)
	return ret;

    if (checksum_data.length < PAC_SIGNATURE_DATA_LENGTH)
	return KRB5_BAD_MSIZE;

    p = (krb5_octet *)checksum_data.data;
    checksum.checksum_type = load_32_le(p);
    checksum.length = checksum_data.length - PAC_SIGNATURE_DATA_LENGTH;
    checksum.contents = p + PAC_SIGNATURE_DATA_LENGTH;

    pac_data.length = pac->data.length;
    pac_data.data = malloc(pac->data.length);
    if (pac_data.data == NULL)
	return ENOMEM;

    memcpy(pac_data.data, pac->data.data, pac->data.length);

    /* Zero out both checksum buffers */
    ret = k5_pac_zero_signature(context, pac, PAC_SERVER_CHECKSUM, &pac_data);
    if (ret != 0) {
	free(pac_data.data);
	return ret;
    }

    ret = k5_pac_zero_signature(context, pac, PAC_PRIVSVR_CHECKSUM, &pac_data);
    if (ret != 0) {
	free(pac_data.data);
	return ret;
    }

    ret = krb5_c_verify_checksum(context, server, KRB5_KEYUSAGE_APP_DATA_CKSUM,
				 &pac_data, &checksum, &valid);
    if (ret != 0) {
        free(pac_data.data);
	return ret;
    }

    if (valid == FALSE)
	ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;

    free(pac_data.data); /* SUNW17PACresync - mem leak fix */
    return ret;
}

static krb5_error_code
k5_pac_verify_kdc_checksum(krb5_context context,
			   const krb5_pac pac,
			   const krb5_keyblock *privsvr)
{
    krb5_error_code ret;
    krb5_data server_checksum, privsvr_checksum;
    krb5_checksum checksum;
    krb5_boolean valid;
    krb5_octet *p;

    ret = k5_pac_locate_buffer(context, pac, PAC_PRIVSVR_CHECKSUM, &privsvr_checksum);
    if (ret != 0)
	return ret;

    if (privsvr_checksum.length < PAC_SIGNATURE_DATA_LENGTH)
	return KRB5_BAD_MSIZE;

    ret = k5_pac_locate_buffer(context, pac, PAC_SERVER_CHECKSUM, &server_checksum);
    if (ret != 0)
	return ret;

    if (server_checksum.length < PAC_SIGNATURE_DATA_LENGTH)
	return KRB5_BAD_MSIZE;

    p = (krb5_octet *)privsvr_checksum.data;
    checksum.checksum_type = load_32_le(p);
    checksum.length = privsvr_checksum.length - PAC_SIGNATURE_DATA_LENGTH;
    checksum.contents = p + PAC_SIGNATURE_DATA_LENGTH;

    server_checksum.data += PAC_SIGNATURE_DATA_LENGTH;
    server_checksum.length -= PAC_SIGNATURE_DATA_LENGTH;

    ret = krb5_c_verify_checksum(context, privsvr, KRB5_KEYUSAGE_APP_DATA_CKSUM,
				 &server_checksum, &checksum, &valid);
    if (ret != 0)
	return ret;

    if (valid == FALSE)
	ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;

    return ret;
}

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)
{
    krb5_error_code ret;

    if (server == NULL)
	return EINVAL;

    ret = k5_pac_verify_server_checksum(context, pac, server);
    if (ret != 0)
	return ret;

    if (privsvr != NULL) {
	ret = k5_pac_verify_kdc_checksum(context, pac, privsvr);
	if (ret != 0)
	    return ret;
    }

    if (principal != NULL) {
	ret = k5_pac_validate_client(context, pac, authtime, principal);
	if (ret != 0)
	    return ret;
    }

    return 0;
}

static krb5_error_code
k5_insert_client_info(krb5_context context,
		      krb5_pac pac,
		      krb5_timestamp authtime,
		      krb5_const_principal principal)
{
    krb5_error_code ret;
    krb5_data client_info;
    char *princ_name_utf8 = NULL;
    unsigned char *princ_name_ucs2 = NULL, *p;
    size_t princ_name_ucs2_len = 0;
    krb5_ui_8 nt_authtime;

    /* If we already have a CLIENT_INFO buffer, then just validate it */
    if (k5_pac_locate_buffer(context, pac, PAC_CLIENT_INFO, &client_info) == 0) {
	return k5_pac_validate_client(context, pac, authtime, principal);
    }

    ret = krb5_unparse_name_flags(context, principal,
				  KRB5_PRINCIPAL_UNPARSE_NO_REALM, &princ_name_utf8);
    if (ret != 0)
	goto cleanup;

    ret = krb5int_utf8s_to_ucs2les(princ_name_utf8,
				   &princ_name_ucs2,
				   &princ_name_ucs2_len);
    if (ret != 0)
	goto cleanup;

    client_info.length = PAC_CLIENT_INFO_LENGTH + princ_name_ucs2_len;
    client_info.data = NULL;

    ret = k5_pac_add_buffer(context, pac, PAC_CLIENT_INFO, &client_info, TRUE, &client_info);
    if (ret != 0)
	goto cleanup;

    p = (unsigned char *)client_info.data;

    /* copy in authtime converted to a 64-bit NT time */
    k5_seconds_since_1970_to_time(authtime, &nt_authtime);
    store_64_le(nt_authtime, p);
    p += 8;

    /* copy in number of UCS-2 characters in principal name */
    store_16_le(princ_name_ucs2_len, p);
    p += 2;

    /* copy in principal name */
    memcpy(p, princ_name_ucs2, princ_name_ucs2_len);
 
cleanup:
    if (princ_name_utf8 != NULL)
	free(princ_name_utf8);
    if (princ_name_ucs2 != NULL)
	free(princ_name_ucs2);

    return ret;
}

static krb5_error_code
k5_insert_checksum(krb5_context context,
		   krb5_pac pac,
		   krb5_ui_4 type,
		   const krb5_keyblock *key,
		   krb5_cksumtype *cksumtype)
{
    krb5_error_code ret;
    size_t len;
    krb5_data cksumdata;

    ret = krb5int_c_mandatory_cksumtype(context, key->enctype, cksumtype);
    if (ret != 0)
	return ret;

    ret = krb5_c_checksum_length(context, *cksumtype, &len);
    if (ret != 0)
	return ret;

    ret = k5_pac_locate_buffer(context, pac, type, &cksumdata);
    if (ret == 0) {
	/* If we're resigning PAC, make sure we can fit checksum into existing buffer */
	if (cksumdata.length != PAC_SIGNATURE_DATA_LENGTH + len)
	    return ERANGE;

	memset(cksumdata.data, 0, cksumdata.length);
    } else {
	/* Add a zero filled buffer */
	cksumdata.length = PAC_SIGNATURE_DATA_LENGTH + len;
	cksumdata.data = NULL;

	ret = k5_pac_add_buffer(context, pac, type, &cksumdata, TRUE, &cksumdata);
	if (ret != 0)
	    return ret;
    }

    /* Encode checksum type into buffer */
    store_32_le((krb5_ui_4)*cksumtype, cksumdata.data);

    return 0;
}

/* in-place encoding of PAC header */
static krb5_error_code
k5_pac_encode_header(krb5_context context, krb5_pac pac)
{
    size_t i;
    unsigned char *p;
    size_t header_len;

    header_len = PACTYPE_LENGTH + (pac->pac->cBuffers * PAC_INFO_BUFFER_LENGTH);
    assert(pac->data.length >= header_len);

    p = (unsigned char *)pac->data.data;

    store_32_le(pac->pac->cBuffers, p);
    p += 4;
    store_32_le(pac->pac->Version, p);
    p += 4;

    for (i = 0; i < pac->pac->cBuffers; i++) {
	PAC_INFO_BUFFER *buffer = &pac->pac->Buffers[i];

	store_32_le(buffer->ulType, p);
	p += 4;
	store_32_le(buffer->cbBufferSize, p);
	p += 4;
	store_64_le(buffer->Offset, p);
	p += 8;

	assert((buffer->Offset % PAC_ALIGNMENT) == 0);
	assert(buffer->Offset + buffer->cbBufferSize <= pac->data.length);
	assert(buffer->Offset >= header_len);

	if (buffer->Offset % PAC_ALIGNMENT ||
	    buffer->Offset + buffer->cbBufferSize > pac->data.length ||
	    buffer->Offset < header_len)
	    return ERANGE;
    }

    return 0;
}


#if 0
/*
 * SUNW17PACresync
 * We don't have the new MIT iov interfaces yet and don't need them yet.
 * We'll need this for full 1.7 resync.
 */
krb5_error_code KRB5_CALLCONV
krb5int_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)
{
    krb5_error_code ret;
    krb5_data server_cksum, privsvr_cksum;
    krb5_cksumtype server_cksumtype, privsvr_cksumtype;
    krb5_crypto_iov iov[2];

    data->length = 0;
    data->data = NULL;

    if (principal != NULL) {
	ret = k5_insert_client_info(context, pac, authtime, principal);
	if (ret != 0)
	    return ret;
    }

    /* Create zeroed buffers for both checksums */
    ret = k5_insert_checksum(context, pac, PAC_SERVER_CHECKSUM,
			     server_key, &server_cksumtype);
    if (ret != 0)
	return ret;

    ret = k5_insert_checksum(context, pac, PAC_PRIVSVR_CHECKSUM,
			     privsvr_key, &privsvr_cksumtype);
    if (ret != 0)
	return ret;

    /* Now, encode the PAC header so that the checksums will include it */
    ret = k5_pac_encode_header(context, pac);
    if (ret != 0)
	return ret;

    /* Generate the server checksum over the entire PAC */
    ret = k5_pac_locate_buffer(context, pac, PAC_SERVER_CHECKSUM, &server_cksum);
    if (ret != 0)
	return ret;

    assert(server_cksum.length > PAC_SIGNATURE_DATA_LENGTH);

    iov[0].flags = KRB5_CRYPTO_TYPE_DATA;
    iov[0].data = pac->data;

    iov[1].flags = KRB5_CRYPTO_TYPE_CHECKSUM;
    iov[1].data.data = server_cksum.data + PAC_SIGNATURE_DATA_LENGTH;
    iov[1].data.length = server_cksum.length - PAC_SIGNATURE_DATA_LENGTH;

    ret = krb5_c_make_checksum_iov(context, server_cksumtype,
				   server_key, KRB5_KEYUSAGE_APP_DATA_CKSUM,
				   iov, sizeof(iov)/sizeof(iov[0]));
    if (ret != 0)
	return ret;

    /* Generate the privsvr checksum over the server checksum buffer */
    ret = k5_pac_locate_buffer(context, pac, PAC_PRIVSVR_CHECKSUM, &privsvr_cksum);
    if (ret != 0)
	return ret;

    assert(privsvr_cksum.length > PAC_SIGNATURE_DATA_LENGTH);

    iov[0].flags = KRB5_CRYPTO_TYPE_DATA;
    iov[0].data.data = server_cksum.data + PAC_SIGNATURE_DATA_LENGTH;
    iov[0].data.length = server_cksum.length - PAC_SIGNATURE_DATA_LENGTH;

    iov[1].flags = KRB5_CRYPTO_TYPE_CHECKSUM;
    iov[1].data.data = privsvr_cksum.data + PAC_SIGNATURE_DATA_LENGTH;
    iov[1].data.length = privsvr_cksum.length - PAC_SIGNATURE_DATA_LENGTH;

    ret = krb5_c_make_checksum_iov(context, privsvr_cksumtype,
				   privsvr_key, KRB5_KEYUSAGE_APP_DATA_CKSUM,
				   iov, sizeof(iov)/sizeof(iov[0]));
    if (ret != 0)
	return ret;

    data->data = malloc(pac->data.length);
    if (data->data == NULL)
	return ENOMEM;

    data->length = pac->data.length;

    memcpy(data->data, pac->data.data, pac->data.length);
    memset(pac->data.data, 0, PACTYPE_LENGTH + (pac->pac->cBuffers * PAC_INFO_BUFFER_LENGTH));

    return 0;
}
#endif