Kerberos is primarily an authentication service employed for network services. As a side effect of the requirements to perform authentication, Kerberos also provides an API for encryption and decryption, although the number of supported ciphers is considerably fewer than those provided by other cryptographic protocols. Authentication yields a cryptographically strong session key that can be used as a key for encryption.
This recipe works on Unix and Windows with the Heimdal and MIT Kerberos implementations. The code presented here will not work on Windows systems that are Kerberos-enabled with the built-in Windows support, because Windows does not expose the Kerberos API in such a way that the code could be made to work. In particular, the encryption and decryption functions used in this recipe are not present on Windows unless you are using either Heimdal or MIT Kerberos. Instead, you should use CryptoAPI on Windows (see Recipe 5.25).
Kerberos provides authentication between clients and servers, communicating over an established data connection. The Kerberos API provides no support for establishing, terminating, or passing arbitrary data over a data connection, whether pipes, sockets, or otherwise. Once its job has been successfully performed, a cryptographically strong session key that can be used as a key for encryption is "left behind."
We present a discussion of how to authenticate using Kerberos in
Recipe 8.13. In this recipe, we pick up at the point where Kerberos
authentication has completed successfully. At this point,
you'll be left with at least a
krb5_context
object and a
krb5_auth_context
object. Using these two objects,
you can obtain a krb5_keyblock
object that
contains the session key by calling
krb5_auth_con_getremotesubkey(
)
. The prototype for this function is as
follows:
krb5_error_code krb5_auth_con_getremotesubkey(krb5_context context, krb5_auth_context auth_context, krb5_keyblock **key_block);
Once you have the session key, you can use it for encryption and decryption.
Kerberos supports only a limited
number of symmetric ciphers, which may vary depending on the version
of Kerberos that you are using. For maximum portability, you are
limited primarily to DES and 3-key Triple-DES in CBC mode. The key
returned from krb_auth_con_getremotesubkey(
)
will have an algorithm already associated
with it, so you don't even have to choose. As part
of the authentication process, the client and server will negotiate
the strongest cipher that both are capable of supporting, which will
(we hope) be Triple-DES (or something stronger) instead of DES, which
is actually rather weak. In fact, if DES is negotiated, you may want
to consider refusing to proceed.
Many different implementations of
Kerberos exist today. The most
prominent among the free implementations is the MIT implementation,
which is distributed with Darwin and many Linux distributions.
Another popular implementation is the Heimdal implementation, which
is distributed with FreeBSD and OpenBSD. Unfortunately, while the two
implementations share much of the same API, there are differences. In
particular, the API for encryption services that we will be using in
this recipe differs between the two. To determine which
implementation is being used, we test for the existence of the
KRB5_GENERAL_ _
preprocessor macro, which
will be defined by the MIT implementation but not the Heimdal
implementation.
Given a krb5_keyblock
object, you can determine
whether DES was negotiated using the following
function:
#include <krb5.h> int spc_krb5_isdes(krb5_keyblock *key) { #ifdef KRB5_GENERAL_ _ if (key->enctype = = ENCTYPE_DES_CBC_CRC || key->enctype = = ENCTYPE_DES_CBC_MD4 || key->enctype = = ENCTYPE_DES_CBC_MD5 || key->enctype = = ENCTYPE_DES_CBC_RAW) return 1; #else if (key->keytype = = ETYPE_DES_CBC_CRC || key->keytype = = ETYPE_DES_CBC_MD4 || key->keytype = = ETYPE_DES_CBC_MD5 || key->keytype = = ETYPE_DES_CBC_NONE || key->keytype = = ETYPE_DES_CFB64_NONE || key->keytype = = ETYPE_DES_PCBC_NONE) return 1; #endif return 0; }
The krb5_context
object and the
krb5_keyblock
object can then be used together as
arguments to spc_krb5_encrypt(
)
, which we implement below. The function also
requires a buffer that holds the data to be encrypted along with the
size of the buffer, as well as a pointer to receive a dynamically
allocated buffer that will hold the encrypted data on return, and a
pointer to receive the size of the encrypted data buffer.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <krb5.h> int spc_krb5_encrypt(krb5_context ctx, krb5_keyblock *key, void *inbuf, size_t inlen, void **outbuf, size_t *outlen) { #ifdef KRB5_GENERAL_ _ size_t blksz, newlen; krb5_data in_data; krb5_enc_data out_data; if (krb5_c_block_size(ctx, key->enctype, &blksz)) return 0; if (!(inlen % blksz)) newlen = inlen + blksz; else newlen = ((inlen + blksz - 1) / blksz) * blksz; in_data.magic = KV5M_DATA; in_data.length = newlen; in_data.data = malloc(newlen); if (!in_data.data) return 0; memcpy(in_data.data, inbuf, inlen); spc_add_padding((unsigned char *)in_data.data + inlen, inlen, blksz); if (krb5_c_encrypt_length(ctx, key->enctype, in_data.length, outlen)) { free(in_data.data); return 0; } out_data.magic = KV5M_ENC_DATA; out_data.enctype = key->enctype; out_data.kvno = 0; out_data.ciphertext.magic = KV5M_ENCRYPT_BLOCK; out_data.ciphertext.length = *outlen; out_data.ciphertext.data = malloc(*outlen); if (!out_data.ciphertext.data) { free(in_data.data); return 0; } if (krb5_c_encrypt(ctx, key, 0, 0, &in_data, &out_data)) { free(in_data.data); return 0; } *outbuf = out_data.ciphertext.data; free(in_data.data); return 1; #else int result; void *tmp; size_t blksz, newlen; krb5_data edata; krb5_crypto crypto; if (krb5_crypto_init(ctx, key, 0, &crypto) != 0) return 0; if (krb5_crypto_getblocksize(ctx, crypto, &blksz)) { krb5_crypto_destroy(ctx, crypto); return 0; } if (!(inlen % blksz)) newlen = inlen + blksz; else newlen = ((inlen + blksz - 1) / blksz) * blksz; if (!(tmp = malloc(newlen))) { krb5_crypto_destroy(ctx, crypto); return 0; } memcpy(tmp, inbuf, inlen); spc_add_padding((unsigned char *)tmp + inlen, inlen, blksz); if (!krb5_encrypt(ctx, crypto, 0, tmp, inlen, &edata)) { if ((*outbuf = malloc(edata.length)) != 0) { result = 1; memcpy(*outbuf, edata.data, edata.length); *outlen = edata.length; } krb5_data_free(&edata); } free(tmp); krb5_crypto_destroy(ctx, crypto); return result; #endif }
The decryption function works identically to the encryption function. Remember that DES and Triple-DES are block mode ciphers, so padding may be necessary if the data you're encrypting is not an exact multiple of the block size. While the Kerberos library will do any necessary padding for you, it does so by padding with zero bytes, which is a poor way to pad out the block. Therefore, we do our own padding using the code from Recipe 5.11 to perform PKCS block padding.
#include <stdlib.h> #include <string.h> #include <krb5.h> int spc_krb5_decrypt(krb5_context ctx, krb5_keyblock *key, void *inbuf, size_t inlen, void **outbuf, size_t *outlen) { #ifdef KRB5_GENERAL_ _ int padding; krb5_data out_data; krb5_enc_data in_data; in_data.magic = KV5M_ENC_DATA; in_data.enctype = key->enctype; in_data.kvno = 0; in_data.ciphertext.magic = KV5M_ENCRYPT_BLOCK; in_data.ciphertext.length = inlen; in_data.ciphertext.data = inbuf; out_data.magic = KV5M_DATA; out_data.length = inlen; out_data.data = malloc(inlen); if (!out_data.data) return 0; if (krb5_c_block_size(ctx, key->enctype, &blksz)) { free(out_data.data); return 0; } if (krb5_c_decrypt(ctx, key, 0, 0, &in_data, &out_data)) { free(out_data.data); return 0; } if ((padding = spc_remove_padding((unsigned char *)out_data.data + out_data.length - blksz, blksz)) = = -1) { free(out_data.data); return 0; } *outlen = out_data.length - (blksz - padding); if (!(*outbuf = realloc(out_data.data, *outlen))) *outbuf = out_data.data; return 1; #else int padding, result; void *tmp; size_t blksz; krb5_data edata; krb5_crypto crypto; if (krb5_crypto_init(ctx, key, 0, &crypto) != 0) return 0; if (krb5_crypto_getblocksize(ctx, crypto, &blksz) != 0) { krb5_crypto_destroy(ctx, crypto); return 0; } if (!(tmp = malloc(inlen))) { krb5_crypto_destroy(ctx, crypto); return 0; } memcpy(tmp, inbuf, inlen); if (!krb5_decrypt(ctx, crypto, 0, tmp, inlen, &edata)) { if ((padding = spc_remove_padding((unsigned char *)edata.data + edata.length - blksz, blksz)) != -1) { *outlen = edata.length - (blksz - padding); if ((*outbuf = malloc(*outlen)) != 0) { result = 1; memcpy(*outbuf, edata.data, *outlen); } } krb5_data_free(&edata); } free(tmp); krb5_crypto_destroy(ctx, crypto); return result; #endif }