Beispiel #1
0
krb5_error_code
krb5int_dk_cmac_encrypt(const struct krb5_keytypes *ktp, krb5_key key,
			krb5_keyusage usage, const krb5_data *ivec,
			krb5_crypto_iov *data, size_t num_data)
{
    const struct krb5_enc_provider *enc = ktp->enc;
    krb5_error_code ret;
    krb5_crypto_iov *header, *trailer, *padding;
    krb5_data cksum = empty_data();
    krb5_key ke = NULL, ki = NULL;

    /* E(Confounder | Plaintext | Pad) | Checksum */

    /* Validate header and trailer lengths, and zero out padding length. */
    header = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_HEADER);
    if (header == NULL || header->data.length < enc->block_size)
        return KRB5_BAD_MSIZE;
    trailer = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_TRAILER);
    if (trailer == NULL || trailer->data.length < enc->block_size)
        return KRB5_BAD_MSIZE;
    padding = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_PADDING);
    if (padding != NULL)
	padding->data.length = 0;

    /* Derive the encryption and integrity keys. */
    ret = derive_keys(enc, key, usage, &ke, &ki);
    if (ret != 0)
        goto cleanup;

    /* Generate confounder. */
    header->data.length = enc->block_size;
    ret = krb5_c_random_make_octets(NULL, &header->data);
    if (ret != 0)
        goto cleanup;

    /* Checksum the plaintext. */
    ret = krb5int_cmac_checksum(enc, ki, data, num_data, &trailer->data);
    if (ret != 0)
        goto cleanup;

    /* Encrypt the plaintext (header | data | padding) */
    ret = enc->encrypt(ke, ivec, data, num_data);
    if (ret != 0)
        goto cleanup;

cleanup:
    krb5_k_free_key(NULL, ke);
    krb5_k_free_key(NULL, ki);
    zapfree(cksum.data, cksum.length);
    return ret;
}
Beispiel #2
0
krb5_error_code
krb5int_dk_cmac_decrypt(const struct krb5_keytypes *ktp, krb5_key key,
                        krb5_keyusage usage, const krb5_data *ivec,
                        krb5_crypto_iov *data, size_t num_data)
{
    const struct krb5_enc_provider *enc = ktp->enc;
    krb5_error_code ret;
    krb5_crypto_iov *header, *trailer;
    krb5_data cksum = empty_data();
    krb5_key ke = NULL, ki = NULL;

    /* E(Confounder | Plaintext | Pad) | Checksum */

    /* Validate header and trailer lengths. */
    header = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_HEADER);
    if (header == NULL || header->data.length != enc->block_size)
        return KRB5_BAD_MSIZE;
    trailer = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_TRAILER);
    if (trailer == NULL || trailer->data.length != enc->block_size)
        return KRB5_BAD_MSIZE;

    /* Derive the encryption and integrity keys. */
    ret = derive_keys(enc, key, usage, &ke, &ki);
    if (ret != 0)
        goto cleanup;

    /* Decrypt the plaintext (header | data | padding). */
    ret = enc->decrypt(ke, ivec, data, num_data);
    if (ret != 0)
        goto cleanup;

    /* Verify the hash. */
    ret = alloc_data(&cksum, enc->block_size);
    if (ret != 0)
        goto cleanup;
    ret = krb5int_cmac_checksum(enc, ki, data, num_data, &cksum);
    if (ret != 0)
        goto cleanup;
    if (k5_bcmp(cksum.data, trailer->data.data, enc->block_size) != 0)
        ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;

cleanup:
    krb5_k_free_key(NULL, ke);
    krb5_k_free_key(NULL, ki);
    zapfree(cksum.data, cksum.length);
    return ret;
}
Beispiel #3
0
static krb5_error_code
krb5int_raw_decrypt_iov(const struct krb5_aead_provider *aead,
			const struct krb5_enc_provider *enc,
			const struct krb5_hash_provider *hash,
			const krb5_keyblock *key,
			krb5_keyusage usage,
			const krb5_data *ivec,
			krb5_crypto_iov *data,
			size_t num_data)
{
    krb5_error_code ret;
    size_t i;
    unsigned int blocksize = 0; /* careful, this is enc block size not confounder len */
    unsigned int cipherlen = 0;

    if (krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_STREAM) != NULL) {
	return krb5int_c_iov_decrypt_stream(aead, enc, hash, key,
					    usage, ivec, data, num_data);
    }


    /* E(Confounder | Plaintext | Pad) | Checksum */

    ret = aead->crypto_length(aead, enc, hash, KRB5_CRYPTO_TYPE_PADDING, &blocksize);
    if (ret != 0)
	return ret;

    for (i = 0; i < num_data; i++) {
	const krb5_crypto_iov *iov = &data[i];

	if (ENCRYPT_DATA_IOV(iov))
	    cipherlen += iov->data.length;
    }

    if (blocksize == 0) {
	/* Check for correct input length in CTS mode */
	if (enc->block_size != 0 && cipherlen < enc->block_size)
	    return KRB5_BAD_MSIZE;
    } else {
	/* Check that the input data is correctly padded */
	if ((cipherlen % blocksize) != 0)
	    return KRB5_BAD_MSIZE;
    }

    /* Validate header and trailer lengths */

    /* derive the keys */

    /* decrypt the plaintext (header | data | padding) */
    assert(enc->decrypt_iov != NULL);

    ret = enc->decrypt_iov(key, ivec, data, num_data); /* will update ivec */

    return ret;
}
krb5_error_code KRB5_CALLCONV
krb5_c_make_checksum_iov(krb5_context context,
			 krb5_cksumtype cksumtype,
			 const krb5_keyblock *key,
			 krb5_keyusage usage,
			 krb5_crypto_iov *data,
			 size_t num_data)
{
    unsigned int i;
    size_t cksumlen;
    krb5_error_code ret;
    krb5_data cksum_data;
    krb5_crypto_iov *checksum;

    for (i = 0; i < krb5_cksumtypes_length; i++) {
	if (krb5_cksumtypes_list[i].ctype == cksumtype)
	    break;
    }

    if (i == krb5_cksumtypes_length)
	return(KRB5_BAD_ENCTYPE);

    if (krb5_cksumtypes_list[i].keyhash != NULL)
	cksum_data.length = krb5_cksumtypes_list[i].keyhash->hashsize;
    else
	cksum_data.length = krb5_cksumtypes_list[i].hash->hashsize;

    if (krb5_cksumtypes_list[i].trunc_size != 0)
	cksumlen = krb5_cksumtypes_list[i].trunc_size;
    else
	cksumlen = cksum_data.length;

    checksum = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_CHECKSUM);
    if (checksum == NULL || checksum->data.length < cksumlen)
	return(KRB5_BAD_MSIZE);

    cksum_data.data = malloc(cksum_data.length);
    if (cksum_data.data == NULL)
	return(ENOMEM);

    ret = krb5int_c_make_checksum_iov(&krb5_cksumtypes_list[i],
				      key, usage, data, num_data,
				      &cksum_data);
    if (ret == 0) {
	memcpy(checksum->data.data, cksum_data.data, cksumlen);
	checksum->data.length = cksumlen;
    }

    free(cksum_data.data);

    return(ret);
}
Beispiel #5
0
static krb5_error_code
krb5int_raw_encrypt_iov(const struct krb5_aead_provider *aead,
			const struct krb5_enc_provider *enc,
			const struct krb5_hash_provider *hash,
			const krb5_keyblock *key,
			krb5_keyusage usage,
			const krb5_data *ivec,
			krb5_crypto_iov *data,
			size_t num_data)
{
    krb5_error_code ret;
    krb5_crypto_iov *padding;
    size_t i;
    unsigned int blocksize = 0;
    unsigned int plainlen = 0;
    unsigned int padsize = 0;

    ret = aead->crypto_length(aead, enc, hash, KRB5_CRYPTO_TYPE_PADDING, &blocksize);
    if (ret != 0)
	return ret;

    for (i = 0; i < num_data; i++) {
	krb5_crypto_iov *iov = &data[i];

	if (iov->flags == KRB5_CRYPTO_TYPE_DATA)
	    plainlen += iov->data.length;
    }

    if (blocksize != 0) {
	/* Check that the input data is correctly padded */
	if (plainlen % blocksize)
	    padsize = blocksize - (plainlen % blocksize);
    }

    padding = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_PADDING);
    if (padsize && (padding == NULL || padding->data.length < padsize))
	return KRB5_BAD_MSIZE;

    if (padding != NULL) {
	memset(padding->data.data, 0, padsize);
	padding->data.length = padsize;
    }

    assert(enc->encrypt_iov != NULL);

    ret = enc->encrypt_iov(key, ivec, data, num_data); /* will update ivec */

    return ret;
}
Beispiel #6
0
krb5_error_code KRB5_CALLCONV
krb5_k_decrypt_iov(krb5_context context, krb5_key key, krb5_keyusage usage,
                   const krb5_data *cipher_state, krb5_crypto_iov *data,
                   size_t num_data)
{
    const struct krb5_keytypes *ktp;

    ktp = find_enctype(key->keyblock.enctype);
    if (ktp == NULL)
        return KRB5_BAD_ENCTYPE;

    if (krb5int_c_locate_iov(data, num_data,
                             KRB5_CRYPTO_TYPE_STREAM) != NULL) {
        return krb5int_c_iov_decrypt_stream(ktp, key, usage, cipher_state,
                                            data, num_data);
    }

    return ktp->decrypt(ktp, key, usage, cipher_state, data, num_data);
}
static krb5_error_code
krb5int_arcfour_encrypt_iov(const struct krb5_aead_provider *aead,
			    const struct krb5_enc_provider *enc,
			    const struct krb5_hash_provider *hash,
			    const krb5_keyblock *key,
			    krb5_keyusage usage,
			    const krb5_data *ivec,
			    krb5_crypto_iov *data,
			    size_t num_data)
{
    krb5_error_code ret;
    krb5_crypto_iov *header, *trailer;
    krb5_keyblock k1, k2, k3;
    krb5_data d1, d2, d3;
    krb5_data checksum, confounder, header_data;
    krb5_keyusage ms_usage;
    char salt_data[14];
    krb5_data salt;
    size_t i;

    d1.length = d2.length = d3.length = 0;
    d1.data = d2.data = d3.data = NULL;

    /*
     * Caller must have provided space for the header, padding
     * and trailer; per RFC 4757 we will arrange it as:
     *
     *	    Checksum | E(Confounder | Plaintext)
     */

    header = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_HEADER);
    if (header == NULL ||
	header->data.length < hash->hashsize + CONFOUNDERLENGTH)
	return KRB5_BAD_MSIZE;

    header_data = header->data;

    /* Trailer may be absent */
    trailer = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_TRAILER);
    if (trailer != NULL)
	trailer->data.length = 0;

    /* Ensure that there is no padding */
    for (i = 0; i < num_data; i++) {
	if (data[i].flags == KRB5_CRYPTO_TYPE_PADDING)
	    data[i].data.length = 0;
    }

    ret = alloc_derived_key(enc, &k1, &d1, key);
    if (ret != 0)
	goto cleanup;

    ret = alloc_derived_key(enc, &k2, &d2, key);
    if (ret != 0)
	goto cleanup;

    ret = alloc_derived_key(enc, &k3, &d3, key);
    if (ret != 0)
	goto cleanup;

    /* Begin the encryption, compute K1 */
    salt.data = salt_data;
    salt.length = sizeof(salt_data);

    ms_usage = krb5int_arcfour_translate_usage(usage);

    if (key->enctype == ENCTYPE_ARCFOUR_HMAC_EXP) {
	strncpy(salt.data, krb5int_arcfour_l40, salt.length);
	store_32_le(ms_usage, salt.data + 10);
    } else {
	salt.length = 4;
	store_32_le(ms_usage, salt.data);
    }
    ret = krb5_hmac(hash, key, 1, &salt, &d1);
    if (ret != 0)
	goto cleanup;

    memcpy(k2.contents, k1.contents, k2.length);

    if (key->enctype == ENCTYPE_ARCFOUR_HMAC_EXP)
	memset(k1.contents + 7, 0xAB, 9);

    header->data.length = hash->hashsize + CONFOUNDERLENGTH;

    confounder.data = header->data.data + hash->hashsize;
    confounder.length = CONFOUNDERLENGTH;

    ret = krb5_c_random_make_octets(0, &confounder);
    if (ret != 0)
	goto cleanup;

    checksum.data = header->data.data;
    checksum.length = hash->hashsize;

    /* Adjust pointers so confounder is at start of header */
    header->data.length -= hash->hashsize;
    header->data.data   += hash->hashsize;

    ret = krb5int_hmac_iov(hash, &k2, data, num_data, &checksum);
    if (ret != 0)
	goto cleanup;

    ret = krb5_hmac(hash, &k1, 1, &checksum, &d3);
    if (ret != 0)
	goto cleanup;

    ret = enc->encrypt_iov(&k3, ivec, data, num_data);
    if (ret != 0)
	goto cleanup;

cleanup:
    header->data = header_data; /* restore header pointers */

    if (d1.data != NULL) {
	memset(d1.data, 0, d1.length);
	free(d1.data);
    }
    if (d2.data != NULL) {
	memset(d2.data, 0, d2.length);
	free(d2.data);
    }
    if (d3.data != NULL) {
	memset(d3.data, 0, d3.length);
	free(d3.data);
    }

    return ret;
}
static krb5_error_code
krb5int_arcfour_decrypt_iov(const struct krb5_aead_provider *aead,
			    const struct krb5_enc_provider *enc,
			    const struct krb5_hash_provider *hash,
			    const krb5_keyblock *key,
			    krb5_keyusage usage,
			    const krb5_data *ivec,
			    krb5_crypto_iov *data,
			    size_t num_data)
{
    krb5_error_code ret;
    krb5_crypto_iov *header, *trailer;
    krb5_keyblock k1, k2, k3;
    krb5_data d1, d2, d3;
    krb5_data checksum, header_data;
    krb5_keyusage ms_usage;
    char salt_data[14];
    krb5_data salt;

    d1.length = d2.length = d3.length = 0;
    d1.data = d2.data = d3.data = NULL;

    header = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_HEADER);
    if (header == NULL ||
        header->data.length != hash->hashsize + CONFOUNDERLENGTH)
	return KRB5_BAD_MSIZE;

    header_data = header->data;

    trailer = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_TRAILER);
    if (trailer != NULL && trailer->data.length != 0)
	return KRB5_BAD_MSIZE;

    ret = alloc_derived_key(enc, &k1, &d1, key);
    if (ret != 0)
	goto cleanup;

    ret = alloc_derived_key(enc, &k2, &d2, key);
    if (ret != 0)
	goto cleanup;

    ret = alloc_derived_key(enc, &k3, &d3, key);
    if (ret != 0)
	goto cleanup;

    /* Begin the decryption, compute K1 */
    salt.data = salt_data;
    salt.length = sizeof(salt_data);

    ms_usage = krb5int_arcfour_translate_usage(usage);

    if (key->enctype == ENCTYPE_ARCFOUR_HMAC_EXP) {
	strncpy(salt.data, krb5int_arcfour_l40, salt.length);
	store_32_le(ms_usage, (unsigned char *)salt.data + 10);
    } else {
	salt.length = 4;
	store_32_le(ms_usage, (unsigned char *)salt.data);
    }
    ret = krb5_hmac(hash, key, 1, &salt, &d1);
    if (ret != 0)
	goto cleanup;

    memcpy(k2.contents, k1.contents, k2.length);

    if (key->enctype == ENCTYPE_ARCFOUR_HMAC_EXP)
	memset(k1.contents + 7, 0xAB, 9);

    checksum.data = header->data.data;
    checksum.length = hash->hashsize;

    /* Adjust pointers so confounder is at start of header */
    header->data.length -= hash->hashsize;
    header->data.data   += hash->hashsize;

    ret = krb5_hmac(hash, &k1, 1, &checksum, &d3);
    if (ret != 0)
	goto cleanup;

    ret = enc->decrypt_iov(&k3, ivec, data, num_data);
    if (ret != 0)
	goto cleanup;

    ret = krb5int_hmac_iov(hash, &k2, data, num_data, &d1);
    if (ret != 0)
	goto cleanup;

    if (memcmp(checksum.data, d1.data, hash->hashsize) != 0) {
	ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
	goto cleanup;
    }

cleanup:
    header->data = header_data; /* restore header pointers */

    if (d1.data != NULL) {
	memset(d1.data, 0, d1.length);
	free(d1.data);
    }
    if (d2.data != NULL) {
	memset(d2.data, 0, d2.length);
	free(d2.data);
    }
    if (d3.data != NULL) {
	memset(d3.data, 0, d3.length);
	free(d3.data);
    }

    return ret;
}
Beispiel #9
0
krb5_error_code
krb5int_arcfour_decrypt(const struct krb5_keytypes *ktp, krb5_key key,
                        krb5_keyusage usage, const krb5_data *ivec,
                        krb5_crypto_iov *data, size_t num_data)
{
    const struct krb5_enc_provider *enc = ktp->enc;
    const struct krb5_hash_provider *hash = ktp->hash;
    krb5_error_code ret;
    krb5_crypto_iov *header, *trailer;
    krb5_keyblock *usage_keyblock = NULL, *enc_keyblock = NULL;
    krb5_data checksum, header_data, comp_checksum = empty_data();

    header = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_HEADER);
    if (header == NULL ||
        header->data.length != hash->hashsize + CONFOUNDERLENGTH)
        return KRB5_BAD_MSIZE;

    header_data = header->data;

    trailer = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_TRAILER);
    if (trailer != NULL && trailer->data.length != 0)
        return KRB5_BAD_MSIZE;

    /* Allocate buffers. */
    ret = alloc_data(&comp_checksum, hash->hashsize);
    if (ret != 0)
        goto cleanup;
    ret = krb5int_c_init_keyblock(NULL, key->keyblock.enctype, enc->keybytes,
                                  &usage_keyblock);
    if (ret != 0)
        goto cleanup;
    ret = krb5int_c_init_keyblock(NULL, key->keyblock.enctype, enc->keybytes,
                                  &enc_keyblock);
    if (ret != 0)
        goto cleanup;

    checksum = make_data(header->data.data, hash->hashsize);

    /* Adjust pointers so confounder is at start of header. */
    header->data.length -= hash->hashsize;
    header->data.data += hash->hashsize;

    /* We may have to try two usage values; see below. */
    do {
        /* Derive a usage key from the session key and usage. */
        ret = usage_key(enc, hash, &key->keyblock, usage, usage_keyblock);
        if (ret != 0)
            goto cleanup;

        /* Derive the encryption key from the usage key and checksum. */
        ret = enc_key(enc, hash, usage_keyblock, &checksum, enc_keyblock);
        if (ret)
            goto cleanup;

        /* Decrypt the ciphertext. */
        ret = keyblock_crypt(enc, enc_keyblock, ivec, data, num_data);
        if (ret != 0)
            goto cleanup;

        /* Compute HMAC(usage key, plaintext) to get the checksum. */
        ret = krb5int_hmac_keyblock(hash, usage_keyblock, data, num_data,
                                    &comp_checksum);
        if (ret != 0)
            goto cleanup;

        if (k5_bcmp(checksum.data, comp_checksum.data, hash->hashsize) != 0) {
            if (usage == 9) {
                /*
                 * RFC 4757 specifies usage 8 for TGS-REP encrypted parts
                 * encrypted in a subkey, but the value used by MS is actually
                 * 9.  We now use 9 to start with, but fall back to 8 on
                 * failure in case we are communicating with a KDC using the
                 * value from the RFC.  ivec is always NULL in this case.
                 * We need to re-encrypt the data in the wrong key first.
                 */
                ret = keyblock_crypt(enc, enc_keyblock, NULL, data, num_data);
                if (ret != 0)
                    goto cleanup;
                usage = 8;
                continue;
            }
            ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
            goto cleanup;
        }

        break;
    } while (1);

cleanup:
    header->data = header_data; /* Restore header pointers. */
    krb5int_c_free_keyblock(NULL, usage_keyblock);
    krb5int_c_free_keyblock(NULL, enc_keyblock);
    zapfree(comp_checksum.data, comp_checksum.length);
    return ret;
}
Beispiel #10
0
krb5_error_code
krb5int_arcfour_encrypt(const struct krb5_keytypes *ktp, krb5_key key,
                        krb5_keyusage usage, const krb5_data *ivec,
                        krb5_crypto_iov *data, size_t num_data)
{
    const struct krb5_enc_provider *enc = ktp->enc;
    const struct krb5_hash_provider *hash = ktp->hash;
    krb5_error_code ret;
    krb5_crypto_iov *header, *trailer;
    krb5_keyblock *usage_keyblock = NULL, *enc_keyblock = NULL;
    krb5_data checksum, confounder, header_data;
    size_t i;

    /*
     * Caller must have provided space for the header, padding
     * and trailer; per RFC 4757 we will arrange it as:
     *
     *      Checksum | E(Confounder | Plaintext)
     */

    header = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_HEADER);
    if (header == NULL ||
        header->data.length < hash->hashsize + CONFOUNDERLENGTH)
        return KRB5_BAD_MSIZE;

    header_data = header->data;

    /* Trailer may be absent. */
    trailer = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_TRAILER);
    if (trailer != NULL)
        trailer->data.length = 0;

    /* Ensure that there is no padding. */
    for (i = 0; i < num_data; i++) {
        if (data[i].flags == KRB5_CRYPTO_TYPE_PADDING)
            data[i].data.length = 0;
    }

    ret = krb5int_c_init_keyblock(NULL, key->keyblock.enctype, enc->keybytes,
                                  &usage_keyblock);
    if (ret != 0)
        goto cleanup;
    ret = krb5int_c_init_keyblock(NULL, key->keyblock.enctype, enc->keybytes,
                                  &enc_keyblock);
    if (ret != 0)
        goto cleanup;

    /* Derive a usage key from the session key and usage. */
    ret = usage_key(enc, hash, &key->keyblock, usage, usage_keyblock);
    if (ret != 0)
        goto cleanup;

    /* Generate a confounder in the header block, after the checksum. */
    header->data.length = hash->hashsize + CONFOUNDERLENGTH;
    confounder = make_data(header->data.data + hash->hashsize,
                           CONFOUNDERLENGTH);
    ret = krb5_c_random_make_octets(0, &confounder);
    if (ret != 0)
        goto cleanup;
    checksum = make_data(header->data.data, hash->hashsize);

    /* Adjust pointers so confounder is at start of header. */
    header->data.length -= hash->hashsize;
    header->data.data += hash->hashsize;

    /* Compute the checksum using the usage key. */
    ret = krb5int_hmac_keyblock(hash, usage_keyblock, data, num_data,
                                &checksum);
    if (ret != 0)
        goto cleanup;

    /* Derive the encryption key from the usage key and checksum. */
    ret = enc_key(enc, hash, usage_keyblock, &checksum, enc_keyblock);
    if (ret)
        goto cleanup;

    ret = keyblock_crypt(enc, enc_keyblock, ivec, data, num_data);

cleanup:
    header->data = header_data; /* Restore header pointers. */
    krb5int_c_free_keyblock(NULL, usage_keyblock);
    krb5int_c_free_keyblock(NULL, enc_keyblock);
    return ret;
}
Beispiel #11
0
krb5_error_code
krb5int_c_iov_decrypt_stream(const struct krb5_keytypes *ktp, krb5_key key,
                             krb5_keyusage keyusage, const krb5_data *ivec,
                             krb5_crypto_iov *data, size_t num_data)
{
    krb5_error_code ret;
    unsigned int header_len, trailer_len;
    krb5_crypto_iov *iov;
    krb5_crypto_iov *stream;
    size_t i, j;
    int got_data = 0;

    stream = krb5int_c_locate_iov(data, num_data, KRB5_CRYPTO_TYPE_STREAM);
    assert(stream != NULL);

    header_len = ktp->crypto_length(ktp, KRB5_CRYPTO_TYPE_HEADER);
    trailer_len = ktp->crypto_length(ktp, KRB5_CRYPTO_TYPE_TRAILER);

    if (stream->data.length < header_len + trailer_len)
        return KRB5_BAD_MSIZE;

    iov = calloc(num_data + 2, sizeof(krb5_crypto_iov));
    if (iov == NULL)
        return ENOMEM;

    i = 0;

    iov[i].flags = KRB5_CRYPTO_TYPE_HEADER; /* takes place of STREAM */
    iov[i].data = make_data(stream->data.data, header_len);
    i++;

    for (j = 0; j < num_data; j++) {
        if (data[j].flags == KRB5_CRYPTO_TYPE_DATA) {
            if (got_data) {
                free(iov);
                return KRB5_BAD_MSIZE;
            }

            got_data++;

            data[j].data.data = stream->data.data + header_len;
            data[j].data.length = stream->data.length - header_len
                - trailer_len;
        }
        if (data[j].flags == KRB5_CRYPTO_TYPE_SIGN_ONLY ||
            data[j].flags == KRB5_CRYPTO_TYPE_DATA)
            iov[i++] = data[j];
    }

    /* Use empty padding since tokens don't indicate the padding length. */
    iov[i].flags = KRB5_CRYPTO_TYPE_PADDING;
    iov[i].data = empty_data();
    i++;

    iov[i].flags = KRB5_CRYPTO_TYPE_TRAILER;
    iov[i].data = make_data(stream->data.data + stream->data.length -
                            trailer_len, trailer_len);
    i++;

    assert(i <= num_data + 2);

    ret = ktp->decrypt(ktp, key, keyusage, ivec, iov, i);
    free(iov);
    return ret;
}