root/usr/src/uts/common/gssapi/mechs/krb5/crypto/dk/derive.c
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright (C) 1998 by the FundsXpress, INC.
 *
 * 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 FundsXpress. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  FundsXpress makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

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

#define K5CLENGTH 5 /* 32 bit net byte order integer + one byte seed */

/*
 * Search for a derived key based on the input key,
 * the  usage constant and the dkid byte.
 *
 * Return *derived key on success, NULL on failure.
 */
krb5_keyblock *
find_derived_key(krb5_keyusage usage, uchar_t dkid,
                krb5_keyblock *key)
{
        krb5_dk_node *dknode = key->dk_list;

        while (dknode != NULL) {
                if (usage == dknode->usage &&
                    dkid == dknode->dkid) {
                        KRB5_LOG1(KRB5_INFO,
                                "find_derived_key - MATCH FOUND %d 0x%0x",
                                usage, dkid);
                        return(dknode->derived_key);
                }
                dknode = dknode->next;
        }
        KRB5_LOG0(KRB5_INFO, "find_derived_key - no match");
        return(NULL);
}

/*
 * Add a derived key to the dk_list for the indicated key.
 */
krb5_error_code
add_derived_key(krb5_keyblock *key,
                krb5_keyusage usage, uchar_t dkid,
                krb5_keyblock *derived_key)
{
        krb5_dk_node *dknode;
        KRB5_LOG1(KRB5_INFO, "add_derived_key: %d 0x%0x",
                usage, dkid);

        if (key->dk_list == NULL) {
                key->dk_list = MALLOC(sizeof(krb5_dk_node));
                if (key->dk_list == NULL)
                        return (ENOMEM);
                dknode = key->dk_list;
        } else {
                dknode = key->dk_list;
                /*
                 * Find last derived key in list
                 */
                while (dknode->next != NULL)
                        dknode = dknode->next;
                dknode->next = MALLOC(sizeof(krb5_dk_node));
                if (dknode->next == NULL)
                        return (ENOMEM);
                dknode = dknode->next;
        }
        dknode->usage = usage;
        dknode->dkid = dkid;
        dknode->derived_key = derived_key;
        dknode->next = NULL;

        return (0);
}

/*
 * Utility function to create a new keyblock
 * Return NULL on failure.
 */
krb5_keyblock *
krb5_create_derived_keyblock(int keysize)
{
        krb5_keyblock *key = MALLOC(sizeof(krb5_keyblock));

        KRB5_LOG0(KRB5_INFO, "krb5_create_derived_keyblock()");
        if (key == NULL)
                return (NULL);

        bzero(key, sizeof(krb5_keyblock));

        key->length = keysize;
        key->contents = (uchar_t *)MALLOC(key->length);
        if (key->contents == NULL) {
                FREE(key, sizeof(krb5_keyblock));
                return (NULL);
        }

        bzero(key->contents, key->length);
#ifdef _KERNEL
        key->kef_mt = CRYPTO_MECH_INVALID;
        key->key_tmpl = NULL;
#else
        key->hKey = CK_INVALID_HANDLE;
#endif /* _KERNEL */
        return(key);
}

/*
 * initialize the derived key values in the context.
 */
krb5_error_code
init_derived_keydata(krb5_context context,
                    const struct krb5_enc_provider *enc,
                    krb5_keyblock *key,
                    krb5_keyusage usage,
                    krb5_keyblock **d_encr_key,
                    krb5_keyblock **d_hmac_key)
{
        krb5_error_code rv = 0;
        unsigned char constantdata[K5CLENGTH];
        krb5_keyblock *cached_key;
        krb5_data d1;

        KRB5_LOG0(KRB5_INFO,"init_ef_derived_keydata().");

        /*
         * Get a derived encryption key, either from the cache
         * or by calculation.
         */
        cached_key = find_derived_key(usage, DK_ENCR_KEY_BYTE, key);
        if (cached_key != NULL)
                *d_encr_key = cached_key;
        else {
                *d_encr_key = krb5_create_derived_keyblock(key->length);
                if (*d_encr_key == NULL) {
                        return (ENOMEM);
                }

                (*d_encr_key)->enctype = key->enctype;

                constantdata[0] = (usage>>24)&0xff;
                constantdata[1] = (usage>>16)&0xff;
                constantdata[2] = (usage>>8)&0xff;
                constantdata[3] = usage&0xff;
                constantdata[4] = DK_ENCR_KEY_BYTE;

                d1.data = (char *)constantdata;
                d1.length = sizeof(constantdata);
                rv = krb5_derive_key(context, enc, key,
                                    *d_encr_key, &d1);
                if (rv != 0) {
                        krb5_free_keyblock(context, *d_encr_key);
                        *d_encr_key = NULL;
                        return (rv);
                }
                rv = add_derived_key(key, usage, DK_ENCR_KEY_BYTE,
                            *d_encr_key);

                if (rv != 0) {
                        krb5_free_keyblock(context, *d_encr_key);
                        *d_encr_key = NULL;
                        return (rv);
                }
        }

        /*
         * Get a derived HMAC key, either from the cache
         * or by calculation.
         */
        cached_key = find_derived_key(usage, DK_HASH_KEY_BYTE, key);
        if (cached_key != NULL)
                *d_hmac_key = cached_key;
        else {
                *d_hmac_key = krb5_create_derived_keyblock(key->length);
                if (*d_hmac_key == NULL) {
                        return (ENOMEM);
                }
                (*d_hmac_key)->enctype = key->enctype;

                constantdata[0] = (usage>>24)&0xff;
                constantdata[1] = (usage>>16)&0xff;
                constantdata[2] = (usage>>8)&0xff;
                constantdata[3] = usage&0xff;
                constantdata[4] = DK_HASH_KEY_BYTE;

                d1.data = (char *)constantdata;
                d1.length = sizeof(constantdata);
                rv = krb5_derive_key(context, enc, key, *d_hmac_key, &d1);
                if (rv != 0) {
                        krb5_free_keyblock(context, *d_hmac_key);
                        *d_hmac_key = NULL;
                        return (rv);
                }
#ifdef _KERNEL
                /*
                 * By default, derived keys get the "mech_type"
                 * that was associated with their parent.
                 * we need to switch the mech type of the derived HMAC key
                 * to correspond to the mech type for the hmac key.
                 */
                if ((*d_hmac_key)->kef_mt != context->kef_hash_mt) {
                        (*d_hmac_key)->kef_mt = context->kef_hash_mt;

                        if ((*d_hmac_key)->key_tmpl != NULL) {
                                crypto_destroy_ctx_template((*d_hmac_key)->key_tmpl);
                                (*d_hmac_key)->key_tmpl = NULL;
                        }
                        rv = update_key_template(*d_hmac_key);

                        if (rv != 0) {
                                krb5_free_keyblock(context, *d_hmac_key);
                                *d_hmac_key = NULL;
                                return (rv);
                        }
                }
#endif /* _KERNEL */
                if (rv == 0) {
                        rv = add_derived_key(key, usage, DK_HASH_KEY_BYTE,
                                    *d_hmac_key);
                        if (rv != 0) {
                                krb5_free_keyblock(context, *d_hmac_key);
                                *d_hmac_key = NULL;
                                return (rv);
                        }
                }
        }
        KRB5_LOG0(KRB5_INFO,"init_ef_derived_keydata() end.");
        return (rv);
}


krb5_error_code
krb5_derive_key(context, enc, inkey, outkey, in_constant)
     krb5_context context;
     krb5_const struct krb5_enc_provider *enc;
     krb5_const krb5_keyblock *inkey;
     krb5_keyblock *outkey;
     krb5_const krb5_data *in_constant;
{
    size_t blocksize, keybytes, keylength, n;
    unsigned char *inblockdata, *outblockdata, *rawkey;
    krb5_data inblock, outblock;
    krb5_error_code ret = 0;

    KRB5_LOG0(KRB5_INFO, "krb5_derive_key() start");

    blocksize = enc->block_size;
    keybytes = enc->keybytes;
    keylength = enc->keylength;


    if ((inkey->length != keylength) ||
        (outkey->length != keylength))
        return(KRB5_CRYPTO_INTERNAL);

    /* allocate and set up buffers */
    if ((inblockdata = (unsigned char *) MALLOC(blocksize)) == NULL)
        return(ENOMEM);

    if ((outblockdata = (unsigned char *) MALLOC(blocksize)) == NULL) {
        FREE(inblockdata, blocksize);
        return(ENOMEM);
    }

    if ((rawkey = (unsigned char *) MALLOC(keybytes)) == NULL) {
        FREE(outblockdata, blocksize);
        FREE(inblockdata, blocksize);
        return(ENOMEM);
    }

    inblock.data = (char *) inblockdata;
    inblock.length = blocksize;

    outblock.data = (char *) outblockdata;
    outblock.length = blocksize;

    /* initialize the input block */
    if (in_constant->length == inblock.length) {
        (void) memcpy(inblock.data, in_constant->data, inblock.length);
    } else {
        krb5_nfold(in_constant->length*8,
                (krb5_const unsigned char *) in_constant->data,
                   inblock.length*8, (unsigned char *) inblock.data);
    }

    /* loop encrypting the blocks until enough key bytes are generated */
    n = 0;
    while (n < keybytes) {
      ret = (*(enc->encrypt))(context, inkey, 0, &inblock, &outblock);

      if (ret) {
        KRB5_LOG(KRB5_INFO, "krb5_derive_key() encrypt error: %d", ret);
        goto cleanup;
      }

        if ((keybytes - n) <= outblock.length) {
            (void) memcpy(rawkey+n, outblock.data, (keybytes - n));
            break;
        }

        (void) memcpy(rawkey+n, outblock.data, outblock.length);
        (void) memcpy(inblock.data, outblock.data, outblock.length);
        n += outblock.length;
    }

    /* postprocess the key */
    inblock.data = (char *) rawkey;
    inblock.length = keybytes;

    outkey->enctype = inkey->enctype;
    ret = (*(enc->make_key))(context, &inblock, outkey);

    /* clean memory, free resources and exit */
cleanup:
    (void) memset(inblockdata, 0, blocksize);
    (void) memset(outblockdata, 0, blocksize);
    (void) memset(rawkey, 0, keybytes);

    FREE(rawkey, keybytes);
    FREE(outblockdata, blocksize);
    FREE(inblockdata, blocksize);

    KRB5_LOG0(KRB5_INFO, "krb5_derive_key() end");
    return(ret);
}