#include "k5-int.h"
#include "int-proto.h"
#include "authdata.h"
static krb5_error_code
insert_client_info(krb5_context context, krb5_pac pac, krb5_timestamp authtime,
krb5_const_principal principal, krb5_boolean with_realm)
{
krb5_error_code ret;
krb5_data client_info;
char *princ_name_utf8 = NULL;
uint8_t *princ_name_utf16 = NULL, *p;
size_t princ_name_utf16_len = 0;
uint64_t nt_authtime;
int flags = 0;
if (k5_pac_locate_buffer(context, pac, KRB5_PAC_CLIENT_INFO,
&client_info) == 0) {
return k5_pac_validate_client(context, pac, authtime, principal,
with_realm);
}
if (!with_realm) {
flags |= KRB5_PRINCIPAL_UNPARSE_NO_REALM;
} else if (principal->type == KRB5_NT_ENTERPRISE_PRINCIPAL) {
flags |= KRB5_PRINCIPAL_UNPARSE_DISPLAY;
}
ret = krb5_unparse_name_flags(context, principal, flags, &princ_name_utf8);
if (ret)
goto cleanup;
ret = k5_utf8_to_utf16le(princ_name_utf8, &princ_name_utf16,
&princ_name_utf16_len);
if (ret)
goto cleanup;
client_info.length = PAC_CLIENT_INFO_LENGTH + princ_name_utf16_len;
client_info.data = NULL;
ret = k5_pac_add_buffer(context, pac, KRB5_PAC_CLIENT_INFO,
&client_info, TRUE, &client_info);
if (ret)
goto cleanup;
p = (uint8_t *)client_info.data;
k5_seconds_since_1970_to_time(authtime, &nt_authtime);
store_64_le(nt_authtime, p);
p += 8;
store_16_le(princ_name_utf16_len, p);
p += 2;
memcpy(p, princ_name_utf16, princ_name_utf16_len);
cleanup:
if (princ_name_utf16 != NULL)
free(princ_name_utf16);
krb5_free_unparsed_name(context, princ_name_utf8);
return ret;
}
static krb5_error_code
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)
return ret;
ret = krb5_c_checksum_length(context, *cksumtype, &len);
if (ret)
return ret;
ret = k5_pac_locate_buffer(context, pac, type, &cksumdata);
if (!ret) {
if (cksumdata.length != PAC_SIGNATURE_DATA_LENGTH + len)
return ERANGE;
memset(cksumdata.data, 0, cksumdata.length);
} else {
cksumdata.length = PAC_SIGNATURE_DATA_LENGTH + len;
cksumdata.data = NULL;
ret = k5_pac_add_buffer(context, pac, type, &cksumdata, TRUE,
&cksumdata);
if (ret)
return ret;
}
store_32_le((krb5_ui_4)*cksumtype, cksumdata.data);
return 0;
}
static krb5_error_code
encode_header(krb5_context context, krb5_pac pac)
{
size_t i;
unsigned char *p;
size_t header_len;
header_len = PACTYPE_LENGTH + (pac->nbuffers * PAC_INFO_BUFFER_LENGTH);
assert(pac->data.length >= header_len);
p = (uint8_t *)pac->data.data;
store_32_le(pac->nbuffers, p);
p += 4;
store_32_le(pac->version, p);
p += 4;
for (i = 0; i < pac->nbuffers; i++) {
struct k5_pac_buffer *buffer = &pac->buffers[i];
store_32_le(buffer->type, p);
p += 4;
store_32_le(buffer->size, p);
p += 4;
store_64_le(buffer->offset, p);
p += 8;
assert((buffer->offset % PAC_ALIGNMENT) == 0);
assert(buffer->size < pac->data.length);
assert(buffer->offset <= pac->data.length - buffer->size);
assert(buffer->offset >= header_len);
if (buffer->offset % PAC_ALIGNMENT ||
buffer->size > pac->data.length ||
buffer->offset > pac->data.length - buffer->size ||
buffer->offset < header_len)
return ERANGE;
}
return 0;
}
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)
{
krb5_error_code ret;
krb5_data buf;
krb5_crypto_iov iov[2];
ret = k5_pac_locate_buffer(context, pac, buftype, &buf);
if (ret)
return ret;
assert(buf.length > PAC_SIGNATURE_DATA_LENGTH);
*cksum_out = make_data(buf.data + PAC_SIGNATURE_DATA_LENGTH,
buf.length - PAC_SIGNATURE_DATA_LENGTH);
iov[0].flags = KRB5_CRYPTO_TYPE_DATA;
iov[0].data = *data;
iov[1].flags = KRB5_CRYPTO_TYPE_CHECKSUM;
iov[1].data = *cksum_out;
return krb5_c_make_checksum_iov(context, cksumtype, key,
KRB5_KEYUSAGE_APP_DATA_CKSUM, iov, 2);
}
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)
{
krb5_error_code ret;
krb5_data full_cksum, server_cksum, privsvr_cksum;
krb5_cksumtype server_cksumtype, privsvr_cksumtype;
data->length = 0;
data->data = NULL;
if (principal != NULL) {
ret = insert_client_info(context, pac, authtime, principal,
with_realm);
if (ret)
return ret;
}
ret = insert_checksum(context, pac, KRB5_PAC_SERVER_CHECKSUM, server_key,
&server_cksumtype);
if (ret)
return ret;
ret = insert_checksum(context, pac, KRB5_PAC_PRIVSVR_CHECKSUM, privsvr_key,
&privsvr_cksumtype);
if (ret)
return ret;
if (is_service_tkt) {
ret = insert_checksum(context, pac, KRB5_PAC_FULL_CHECKSUM,
privsvr_key, &privsvr_cksumtype);
if (ret)
return ret;
}
ret = encode_header(context, pac);
if (ret)
return ret;
if (is_service_tkt) {
ret = compute_pac_checksum(context, pac, KRB5_PAC_FULL_CHECKSUM,
privsvr_key, privsvr_cksumtype,
&pac->data, &full_cksum);
if (ret)
return ret;
}
ret = compute_pac_checksum(context, pac, KRB5_PAC_SERVER_CHECKSUM,
server_key, server_cksumtype, &pac->data,
&server_cksum);
if (ret)
return ret;
ret = compute_pac_checksum(context, pac, KRB5_PAC_PRIVSVR_CHECKSUM,
privsvr_key, privsvr_cksumtype, &server_cksum,
&privsvr_cksum);
if (ret)
return ret;
data->data = k5memdup(pac->data.data, pac->data.length, &ret);
if (data->data == NULL)
return ret;
data->length = pac->data.length;
memset(pac->data.data, 0,
PACTYPE_LENGTH + (pac->nbuffers * PAC_INFO_BUFFER_LENGTH));
return 0;
}
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)
{
return sign_pac(context, pac, authtime, principal, server_key,
privsvr_key, FALSE, FALSE, data);
}
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)
{
return sign_pac(context, pac, authtime, principal, server_key, privsvr_key,
with_realm, FALSE, data);
}
static krb5_error_code
add_ticket_signature(krb5_context context, const krb5_pac pac,
krb5_data *der_enc_tkt, const krb5_keyblock *privsvr)
{
krb5_error_code ret;
krb5_data ticket_cksum;
krb5_cksumtype ticket_cksumtype;
krb5_crypto_iov iov[2];
ret = insert_checksum(context, pac, KRB5_PAC_TICKET_CHECKSUM, privsvr,
&ticket_cksumtype);
if (ret)
return ret;
ret = k5_pac_locate_buffer(context, pac, KRB5_PAC_TICKET_CHECKSUM,
&ticket_cksum);
if (ret)
return ret;
iov[0].flags = KRB5_CRYPTO_TYPE_DATA;
iov[0].data = *der_enc_tkt;
iov[1].flags = KRB5_CRYPTO_TYPE_CHECKSUM;
iov[1].data = make_data(ticket_cksum.data + PAC_SIGNATURE_DATA_LENGTH,
ticket_cksum.length - PAC_SIGNATURE_DATA_LENGTH);
ret = krb5_c_make_checksum_iov(context, ticket_cksumtype, privsvr,
KRB5_KEYUSAGE_APP_DATA_CKSUM, iov, 2);
if (ret)
return ret;
store_32_le(ticket_cksumtype, ticket_cksum.data);
return 0;
}
static krb5_error_code
encode_pac_ad(krb5_context context, krb5_data *pac_data, krb5_authdata **out)
{
krb5_error_code ret;
krb5_authdata *container[2], **encoded_container = NULL;
krb5_authdata pac_ad = { KV5M_AUTHDATA, KRB5_AUTHDATA_WIN2K_PAC };
uint8_t z = 0;
pac_ad.contents = (pac_data != NULL) ? (uint8_t *)pac_data->data : &z;
pac_ad.length = (pac_data != NULL) ? pac_data->length : 1;
container[0] = &pac_ad;
container[1] = NULL;
ret = krb5_encode_authdata_container(context, KRB5_AUTHDATA_IF_RELEVANT,
container, &encoded_container);
if (ret)
return ret;
*out = encoded_container[0];
free(encoded_container);
return 0;
}
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)
{
krb5_error_code ret;
krb5_data *der_enc_tkt = NULL, pac_data = empty_data();
krb5_authdata **list, *pac_ad;
krb5_boolean is_service_tkt;
size_t count;
list = enc_tkt->authorization_data;
for (count = 0; list != NULL && list[count] != NULL; count++);
list = realloc(enc_tkt->authorization_data, (count + 2) * sizeof(*list));
if (list == NULL)
return ENOMEM;
list[count] = NULL;
enc_tkt->authorization_data = list;
ret = encode_pac_ad(context, NULL, &pac_ad);
if (ret)
goto cleanup;
memmove(list + 1, list, (count + 1) * sizeof(*list));
list[0] = pac_ad;
is_service_tkt = k5_pac_should_have_ticket_signature(server_princ);
if (is_service_tkt) {
ret = encode_krb5_enc_tkt_part(enc_tkt, &der_enc_tkt);
if (ret)
goto cleanup;
assert(privsvr != NULL);
ret = add_ticket_signature(context, pac, der_enc_tkt, privsvr);
if (ret)
goto cleanup;
}
ret = sign_pac(context, pac, enc_tkt->times.authtime, client_princ, server,
privsvr, with_realm, is_service_tkt, &pac_data);
if (ret)
goto cleanup;
ret = encode_pac_ad(context, &pac_data, &pac_ad);
if (ret)
goto cleanup;
free(list[0]->contents);
free(list[0]);
list[0] = pac_ad;
cleanup:
krb5_free_data(context, der_enc_tkt);
krb5_free_data_contents(context, &pac_data);
return ret;
}