root/usr/src/lib/pkcs11/pkcs11_softtoken/common/softSSL.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2020 Joyent, Inc.
 */

#include <fcntl.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/sha1.h>
#include <sys/md5.h>
#include <sys/sysmacros.h>
#include <security/cryptoki.h>
#include "softGlobal.h"
#include "softKeys.h"
#include "softKeystore.h"
#include "softMAC.h"
#include "softObject.h"
#include "softSession.h"
#include "softSSL.h"

/*
 * This files contains the implementation of the following PKCS#11
 * mechanisms needed by SSL:
 * CKM_SSL3_MASTER_KEY_DERIVE
 * CKM_SSL3_MASTER_KEY_DERIVE_DH
 * CKM_SSL3_KEY_AND_DERIVE
 * CKM_TLS_MASTER_KEY_DERIVE
 * CKM_TLS_MASTER_KEY_DERIVE_DH
 * CKM_TLS_KEY_AND_DERIVE
 *
 * SSL refers to common functions between SSL v3.0 and SSL v3.1 (a.k.a TLS.)
 */

#define MAX_KEYBLOCK    160     /* should be plenty for all known cipherspecs */

#define MAX_DEFAULT_ATTRS       10      /* Enough for major applicarions */

static  char *ssl3_const_vals[] = {
        "A",
        "BB",
        "CCC",
        "DDDD",
        "EEEEE",
        "FFFFFF",
        "GGGGGGG",
        "HHHHHHHH",
        "IIIIIIIII",
        "JJJJJJJJJJ",
};
static  uint_t ssl3_const_lens[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

static uchar_t TLS_MASTER_SECRET_LABEL[] = {"master secret"};
#define TLS_MASTER_SECRET_LABEL_LEN     13

static uchar_t TLS_KEY_EXPANSION_LABEL[] = {"key expansion"};
#define TLS_KEY_EXPANSION_LABEL_LEN     13

static uchar_t TLS_CLIENT_KEY_LABEL[] = {"client write key"};
#define TLS_CLIENT_KEY_LABEL_LEN        16

static uchar_t TLS_SERVER_KEY_LABEL[] = {"server write key"};
#define TLS_SERVER_KEY_LABEL_LEN        16

static uchar_t TLS_IV_BLOCK_LABEL[] = {"IV block"};
#define TLS_IV_BLOCK_LABEL_LEN  8

static void P_MD5(uchar_t *, uint_t, uchar_t *, uint_t, uchar_t *, uint_t,
    uchar_t *, uint_t, uchar_t *, uint_t, boolean_t);
static void P_SHA1(uchar_t *, uint_t, uchar_t *, uint_t, uchar_t *, uint_t,
    uchar_t *, uint_t, uchar_t *, uint_t, boolean_t);

static CK_RV soft_add_derived_key(CK_ATTRIBUTE_PTR, CK_ULONG,
    CK_OBJECT_HANDLE_PTR, soft_session_t *, soft_object_t *);
static void soft_delete_derived_key(soft_session_t *, soft_object_t *);
static void soft_ssl_weaken_key(CK_MECHANISM_PTR, uchar_t *, uint_t,
    uchar_t *, uint_t, uchar_t *, uint_t, uchar_t *, boolean_t);

/*
 * soft_ssl3_churn()
 * Called for derivation of the master secret from the pre-master secret,
 * and for the derivation of the key_block in an SSL3 handshake
 * result is assumed to be larger than rounds * MD5_HASH_SIZE.
 */
static void
soft_ssl3_churn(uchar_t *secret, uint_t secretlen, uchar_t *rand1,
    uint_t rand1len, uchar_t *rand2, uint_t rand2len, int rounds,
    uchar_t *result)
{
        SHA1_CTX sha1_ctx;
        MD5_CTX md5_ctx;
        uchar_t sha1_digest[SHA1_HASH_SIZE];
        int i;
        uchar_t *ms = result;
        for (i = 0; i < rounds; i++) {
                SHA1Init(&sha1_ctx);
                SHA1Update(&sha1_ctx, (const uint8_t *)ssl3_const_vals[i],
                    ssl3_const_lens[i]);
                SHA1Update(&sha1_ctx, secret, secretlen);
                SHA1Update(&sha1_ctx, rand1, rand1len);
                SHA1Update(&sha1_ctx, rand2, rand2len);
                SHA1Final(sha1_digest, &sha1_ctx);

                MD5Init(&md5_ctx);
                MD5Update(&md5_ctx, secret, secretlen);
                MD5Update(&md5_ctx, sha1_digest, SHA1_HASH_SIZE);
                MD5Final(ms, &md5_ctx);
                ms += MD5_HASH_SIZE;
        }
}

/*
 * This TLS generic Pseudo Random Function expands a triplet
 * {secret, label, seed} into any arbitrary length string of pseudo
 * random bytes.
 * Here, it is called for the derivation of the master secret from the
 * pre-master secret, and for the derivation of the key_block in a TLS
 * handshake
 */
static void
soft_tls_prf(uchar_t *secret, uint_t secretlen, uchar_t *label, uint_t labellen,
    uchar_t *rand1, uint_t rand1len, uchar_t *rand2, uint_t rand2len,
    uchar_t *result, uint_t resultlen)
{
        uchar_t *S1, *S2;
        uchar_t md5_digested_key[MD5_HASH_SIZE];
        uchar_t sha1_digested_key[SHA1_HASH_SIZE];
        uint_t L_S, L_S1, L_S2;

        /* secret is NULL for IV's in exportable ciphersuites */
        if (secret == NULL) {
                L_S = 0;
                L_S2 = L_S1 = 0;
                S1 = NULL;
                S2 = NULL;
                goto do_P_HASH;
        }

        L_S = roundup(secretlen, 2) / 2;
        L_S1 = L_S;
        L_S2 = L_S;
        S1 = secret;
        S2 = secret + (secretlen / 2);  /* Possible overlap of S1 and S2. */

        /* Reduce the half secrets if bigger than the HASH's block size */
        if (L_S > MD5_HMAC_BLOCK_SIZE) {
                MD5_CTX md5_ctx;
                SHA1_CTX sha1_ctx;

                MD5Init(&md5_ctx);
                MD5Update(&md5_ctx, S1, L_S);
                MD5Final(md5_digested_key, &md5_ctx);
                S1 = md5_digested_key;
                L_S1 = MD5_HASH_SIZE;

                SHA1Init(&sha1_ctx);
                SHA1Update(&sha1_ctx, S2, L_S);
                SHA1Final(sha1_digested_key, &sha1_ctx);
                S2 = sha1_digested_key;
                L_S2 = SHA1_HASH_SIZE;
        }

        /*
         * PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR
         *                              P_SHA-1(S2, label + seed);
         * the 'seed' here is rand1 + rand2
         */
do_P_HASH:
        /* The first one writes directly to the result */
        P_MD5(S1, L_S1, label, labellen, rand1, rand1len, rand2, rand2len,
            result, resultlen, B_FALSE);

        /* The second one XOR's with the result. */
        P_SHA1(S2, L_S2, label, labellen, rand1, rand1len, rand2, rand2len,
            result, resultlen, B_TRUE);
}

/*
 * These two expansion routines are very similar. (they can merge one day).
 * They implement the P_HASH() function for MD5 and for SHA1, as defined in
 * RFC2246:
 *
 *      P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
 *                              HMAC_hash(secret, A(2) + seed) +
 *                              HMAC_hash(secret, A(3) + seed) + ...
 * Where + indicates concatenation.
 * A() is defined as:
 *      A(0) = seed
 *      A(i) = HMAC_hash(secret, A(i-1))
 *
 * The seed is the concatenation of 'babel', 'rand1', and 'rand2'.
 */
static void
P_MD5(uchar_t *secret, uint_t secretlen, uchar_t *label, uint_t labellen,
    uchar_t *rand1, uint_t rand1len, uchar_t *rand2, uint_t rand2len,
    uchar_t *result, uint_t resultlen, boolean_t xor_it)
{
        uint32_t md5_ipad[MD5_HMAC_INTS_PER_BLOCK];
        uint32_t md5_opad[MD5_HMAC_INTS_PER_BLOCK];
        uchar_t md5_hmac[MD5_HASH_SIZE];
        uchar_t A[MD5_HASH_SIZE];
        md5_hc_ctx_t md5_hmac_ctx;
        uchar_t *res, *cur;
        uint_t left = resultlen;
        int i;

        /* good compilers will leverage the aligment */
        bzero(md5_ipad, MD5_HMAC_BLOCK_SIZE);
        bzero(md5_opad, MD5_HMAC_BLOCK_SIZE);

        if (secretlen > 0) {
                bcopy(secret, md5_ipad, secretlen);
                bcopy(secret, md5_opad, secretlen);
        }

        /* A(1) = HMAC_MD5(secret, rand1 + rand2) */
        md5_hmac_ctx_init(&md5_hmac_ctx, md5_ipad, md5_opad);
        SOFT_MAC_UPDATE(MD5, &md5_hmac_ctx, label, labellen);
        SOFT_MAC_UPDATE(MD5, &md5_hmac_ctx, rand1, rand1len);
        SOFT_MAC_UPDATE(MD5, &md5_hmac_ctx, rand2, rand2len);
        SOFT_MAC_FINAL(MD5, &md5_hmac_ctx, A);

        if (xor_it) {
                res = md5_hmac;
                cur = result;
        } else {
                res = result;
        }

        while (left > 0) {
                /*
                 * Compute HMAC_MD5(secret, A(i) + seed);
                 * The secret is already expanded in the ictx and octx, so
                 * we can call the SOFT_MAC_INIT_CTX() directly.
                 */
                SOFT_MAC_INIT_CTX(MD5, &md5_hmac_ctx, md5_ipad, md5_opad,
                    MD5_HMAC_BLOCK_SIZE);
                SOFT_MAC_UPDATE(MD5, &md5_hmac_ctx, A, MD5_HASH_SIZE);
                SOFT_MAC_UPDATE(MD5, &md5_hmac_ctx, label, labellen);
                SOFT_MAC_UPDATE(MD5, &md5_hmac_ctx, rand1, rand1len);
                SOFT_MAC_UPDATE(MD5, &md5_hmac_ctx, rand2, rand2len);

                if (left > MD5_HASH_SIZE) {
                        SOFT_MAC_FINAL(MD5, &md5_hmac_ctx, res);
                        if (xor_it) {
                                for (i = 0; i < MD5_HASH_SIZE; i++) {
                                        *cur ^= res[i];
                                        cur++;
                                }
                        } else {
                                res += MD5_HASH_SIZE;
                        }
                        left -= MD5_HASH_SIZE;
                } else {
                        SOFT_MAC_FINAL(MD5, &md5_hmac_ctx, md5_hmac);
                        if (xor_it) {
                                for (i = 0; i < left; i++) {
                                        *cur ^= md5_hmac[i];
                                        cur++;
                                }
                        } else {
                                bcopy(md5_hmac, res, left);
                        }
                        break;
                }
                /* A(i) = HMAC_MD5(secret, A(i-1) */
                SOFT_MAC_INIT_CTX(MD5, &md5_hmac_ctx, md5_ipad, md5_opad,
                    MD5_HMAC_BLOCK_SIZE);
                SOFT_MAC_UPDATE(MD5, &md5_hmac_ctx, A, MD5_HASH_SIZE);
                SOFT_MAC_FINAL(MD5, &md5_hmac_ctx, A);
        }
}
static void
P_SHA1(uchar_t *secret, uint_t secretlen, uchar_t *label, uint_t labellen,
    uchar_t *rand1, uint_t rand1len, uchar_t *rand2, uint_t rand2len,
    uchar_t *result, uint_t resultlen, boolean_t xor_it)
{
        uint32_t sha1_ipad[SHA1_HMAC_INTS_PER_BLOCK];
        uint32_t sha1_opad[SHA1_HMAC_INTS_PER_BLOCK];
        uchar_t sha1_hmac[SHA1_HASH_SIZE];
        uchar_t A[SHA1_HASH_SIZE];
        sha1_hc_ctx_t sha1_hmac_ctx;
        uchar_t *res, *cur;
        uint_t left = resultlen;
        int i;

        /* good compilers will leverage the aligment */
        bzero(sha1_ipad, SHA1_HMAC_BLOCK_SIZE);
        bzero(sha1_opad, SHA1_HMAC_BLOCK_SIZE);

        if (secretlen > 0) {
                bcopy(secret, sha1_ipad, secretlen);
                bcopy(secret, sha1_opad, secretlen);
        }

        /* A(1) = HMAC_SHA1(secret, rand1 + rand2) */
        sha1_hmac_ctx_init(&sha1_hmac_ctx, sha1_ipad, sha1_opad);
        SOFT_MAC_UPDATE(SHA1, &sha1_hmac_ctx, label, labellen);
        SOFT_MAC_UPDATE(SHA1, &sha1_hmac_ctx, rand1, rand1len);
        SOFT_MAC_UPDATE(SHA1, &sha1_hmac_ctx, rand2, rand2len);
        SOFT_MAC_FINAL(SHA1, &sha1_hmac_ctx, A);

        if (xor_it) {
                res = sha1_hmac;
                cur = result;
        } else {
                res = result;
        }

        while (left > 0) {
                /*
                 * Compute HMAC_SHA1(secret, A(i) + seed);
                 * The secret is already expanded in the ictx and octx, so
                 * we can call the SOFT_MAC_INIT_CTX() directly.
                 */
                SOFT_MAC_INIT_CTX(SHA1, &sha1_hmac_ctx,
                    (const uchar_t *)sha1_ipad, (const uchar_t *)sha1_opad,
                    SHA1_HMAC_BLOCK_SIZE);
                SOFT_MAC_UPDATE(SHA1, &sha1_hmac_ctx, A, SHA1_HASH_SIZE);
                SOFT_MAC_UPDATE(SHA1, &sha1_hmac_ctx, label, labellen);
                SOFT_MAC_UPDATE(SHA1, &sha1_hmac_ctx, rand1, rand1len);
                SOFT_MAC_UPDATE(SHA1, &sha1_hmac_ctx, rand2, rand2len);

                if (left > SHA1_HASH_SIZE) {
                        SOFT_MAC_FINAL(SHA1, &sha1_hmac_ctx, res);
                        if (xor_it) {
                                for (i = 0; i < SHA1_HASH_SIZE; i++) {
                                        *cur ^= res[i];
                                        cur++;
                                }
                        } else {
                                res += SHA1_HASH_SIZE;
                        }
                        left -= SHA1_HASH_SIZE;
                } else {
                        SOFT_MAC_FINAL(SHA1, &sha1_hmac_ctx, sha1_hmac);
                        if (xor_it) {
                                for (i = 0; i < left; i++) {
                                        *cur ^= sha1_hmac[i];
                                        cur++;
                                }
                        } else {
                                bcopy(sha1_hmac, res, left);
                        }
                        break;
                }
                /* A(i) = HMAC_SHA1(secret, A(i-1) */
                SOFT_MAC_INIT_CTX(SHA1, &sha1_hmac_ctx,
                    (const uchar_t *)sha1_ipad, (const uchar_t *)sha1_opad,
                    SHA1_HMAC_BLOCK_SIZE);
                SOFT_MAC_UPDATE(SHA1, &sha1_hmac_ctx, A, SHA1_HASH_SIZE);
                SOFT_MAC_FINAL(SHA1, &sha1_hmac_ctx, A);
        }
}

/* This function handles the call from C_DeriveKey for CKM_TLS_PRF */
CK_RV
derive_tls_prf(CK_TLS_PRF_PARAMS_PTR param, soft_object_t *basekey_p)
{

        if (param->pOutput == NULL || param->pulOutputLen == 0)
                return (CKR_BUFFER_TOO_SMALL);

        (void) soft_tls_prf(OBJ_SEC_VALUE(basekey_p),
            OBJ_SEC_VALUE_LEN(basekey_p), param->pLabel, param->ulLabelLen,
            param->pSeed, param->ulSeedLen, NULL, 0, param->pOutput,
            *param->pulOutputLen);

        return (CKR_OK);
}


/*
 * soft_ssl_master_key_derive()
 *
 * Arguments:
 * . session_p
 * . mech_p:    key derivation mechanism. the mechanism parameter carries the
 *              client and master random from the Hello handshake messages.
 * . basekey_p: The pre-master secret key.
 * . pTemplate & ulAttributeCount: Any extra attributes for the key to be
 *              created.
 * . phKey:     store for handle to the derived key.
 *
 * Description:
 *      Derive the SSL master secret from the pre-master secret, the client
 *      and server random.
 *      In SSL 3.0, master_secret =
 *          MD5(pre_master_secret + SHA('A' + pre_master_secret +
 *              ClientHello.random + ServerHello.random)) +
 *          MD5(pre_master_secret + SHA('BB' + pre_master_secret +
 *              ClientHello.random + ServerHello.random)) +
 *          MD5(pre_master_secret + SHA('CCC' + pre_master_secret +
 *              ClientHello.random + ServerHello.random));
 *
 *      In TLS 1.0 (a.k.a. SSL 3.1), master_secret =
 *          PRF(pre_master_secret, "master secret",
 *              ClientHello.random + ServerHello.random)
 */
CK_RV
soft_ssl_master_key_derive(soft_session_t *sp, CK_MECHANISM_PTR mech,
    soft_object_t *basekey_p, CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey)
{
        uchar_t *pmsecret = OBJ_SEC_VALUE(basekey_p);
#ifdef  __sparcv9
        /* LINTED */
        uint_t pmlen = (uint_t)OBJ_SEC_VALUE_LEN(basekey_p);
#else   /* __sparcv9 */
        uint_t pmlen = OBJ_SEC_VALUE_LEN(basekey_p);
#endif  /* __sparcv9 */
        CK_SSL3_MASTER_KEY_DERIVE_PARAMS *mkd_params;
        CK_SSL3_RANDOM_DATA *random_data;
        CK_VERSION_PTR  pVersion;
        uchar_t ssl_master_secret[48];
        CK_OBJECT_CLASS class = CKO_SECRET_KEY;
        CK_KEY_TYPE keyType = CKK_GENERIC_SECRET;
        CK_BBOOL true = TRUE;
        CK_ATTRIBUTE obj_tmpl[MAX_DEFAULT_ATTRS];
        CK_ATTRIBUTE_PTR new_tmpl;
        CK_ULONG newattrcount;
        boolean_t new_tmpl_allocated = B_FALSE, is_tls = B_FALSE;
        ulong_t i;
        CK_RV rv = CKR_OK;
        uint_t ClientRandomLen, ServerRandomLen;

        /* Check the validity of the mechanism's parameter */

        mkd_params = (CK_SSL3_MASTER_KEY_DERIVE_PARAMS *)mech->pParameter;

        if (mkd_params == NULL ||
            mech->ulParameterLen != sizeof (CK_SSL3_MASTER_KEY_DERIVE_PARAMS))
                return (CKR_MECHANISM_PARAM_INVALID);

        pVersion = mkd_params->pVersion;

        switch (mech->mechanism) {
        case CKM_TLS_MASTER_KEY_DERIVE:
                is_tls = B_TRUE;
        /* FALLTHRU */
        case CKM_SSL3_MASTER_KEY_DERIVE:
                /* Invalid pre-master key length. What else to return? */
                if (pmlen != 48)
                        return (CKR_ARGUMENTS_BAD);

                /* Get the SSL version number from the premaster secret */
                if (pVersion == NULL_PTR)
                        return (CKR_MECHANISM_PARAM_INVALID);

                bcopy(pmsecret, pVersion, sizeof (CK_VERSION));

                break;
        case CKM_TLS_MASTER_KEY_DERIVE_DH:
                is_tls = B_TRUE;
        /* FALLTHRU */
        case CKM_SSL3_MASTER_KEY_DERIVE_DH:
                if (pVersion != NULL_PTR)
                        return (CKR_MECHANISM_PARAM_INVALID);
        }

        random_data = &mkd_params->RandomInfo;
#ifdef  __sparcv9
        /* LINTED */
        ClientRandomLen = (uint_t)random_data->ulClientRandomLen;
        /* LINTED */
        ServerRandomLen = (uint_t)random_data->ulServerRandomLen;
#else   /* __sparcv9 */
        ClientRandomLen = random_data->ulClientRandomLen;
        ServerRandomLen = random_data->ulServerRandomLen;
#endif  /* __sparcv9 */

        if (random_data->pClientRandom == NULL_PTR || ClientRandomLen == 0 ||
            random_data->pServerRandom == NULL_PTR || ServerRandomLen == 0) {
                return (CKR_MECHANISM_PARAM_INVALID);
        }

        /* Now the actual secret derivation */
        if (!is_tls) {
                soft_ssl3_churn(pmsecret, pmlen, random_data->pClientRandom,
                    ClientRandomLen, random_data->pServerRandom,
                    ServerRandomLen, 3, ssl_master_secret);
        } else {
                soft_tls_prf(pmsecret, pmlen, TLS_MASTER_SECRET_LABEL,
                    TLS_MASTER_SECRET_LABEL_LEN, random_data->pClientRandom,
                    ClientRandomLen, random_data->pServerRandom,
                    ServerRandomLen, ssl_master_secret, 48);
        }

        /*
         * The object creation attributes need to be in one contiguous
         * array. In addition to the attrs from the application supplied
         * pTemplates, We need to add the class, type, value, valuelen and
         * CKA_DERIVE.
         * In the most likely case, the application passes between zero and
         * handful of attributes, We optimize for that case by allocating
         * the new template on the stack. Oherwise we malloc() it.
         */

        newattrcount = ulAttributeCount + 4;
        if (newattrcount > MAX_DEFAULT_ATTRS) {
                new_tmpl = malloc(sizeof (CK_ATTRIBUTE) * newattrcount);

                if (new_tmpl == NULL)
                        return (CKR_HOST_MEMORY);

                new_tmpl_allocated = B_TRUE;
        } else
                new_tmpl = obj_tmpl;

        /*
         * Fill in the new template.
         * We put the attributes contributed by the mechanism first
         * so that they override the application supplied ones.
         */
        new_tmpl[0].type = CKA_CLASS;
        new_tmpl[0].pValue = &class;
        new_tmpl[0].ulValueLen = sizeof (class);
        new_tmpl[1].type = CKA_KEY_TYPE;
        new_tmpl[1].pValue = &keyType;
        new_tmpl[1].ulValueLen = sizeof (keyType);
        new_tmpl[2].type = CKA_DERIVE;
        new_tmpl[2].pValue = &true;
        new_tmpl[2].ulValueLen = sizeof (true);
        new_tmpl[3].type = CKA_VALUE;
        new_tmpl[3].pValue = ssl_master_secret;
        new_tmpl[3].ulValueLen = 48;

        /* Any attributes left? */
        if (ulAttributeCount > 0) {

                /* Validate the default class and type attributes */
                for (i = 0; i < ulAttributeCount; i++) {
                        /* The caller is responsible for proper alignment */
                        if ((pTemplate[i].type == CKA_CLASS) &&
                            (*((CK_OBJECT_CLASS *)pTemplate[i].pValue) !=
                            CKO_SECRET_KEY)) {
                                rv = CKR_TEMPLATE_INCONSISTENT;
                                goto out;
                        }
                        if ((pTemplate[i].type == CKA_KEY_TYPE) &&
                            (*((CK_KEY_TYPE *)pTemplate[i].pValue) !=
                            CKK_GENERIC_SECRET)) {
                                rv = CKR_TEMPLATE_INCONSISTENT;
                                goto out;
                        }
                }
                bcopy(pTemplate, &new_tmpl[4],
                    ulAttributeCount * sizeof (CK_ATTRIBUTE));
        }

        rv = soft_add_derived_key(new_tmpl, newattrcount, phKey, sp, basekey_p);
out:
        if (new_tmpl_allocated)
                free(new_tmpl);

        return (rv);
}

/*
 * soft_ssl3_key_and_mac_derive()
 *
 * Arguments:
 * . session_p
 * . mech_p:    key derivation mechanism. the mechanism parameter carries the
 *              client and mastter random from the Hello handshake messages,
 *              the specification of the key and IV sizes, and the location
 *              for the resulting keys and IVs.
 * . basekey_p: The master secret key.
 * . pTemplate & ulAttributeCount: Any extra attributes for the key to be
 *              created.
 *
 * Description:
 *      Derive the SSL key material (Client and server MAC secrets, symmetric
 *      keys and IVs), from the master secret and the client
 *      and server random.
 *      First a keyblock is generated usining the following formula:
 *      key_block =
 *              MD5(master_secret + SHA(`A' + master_secret +
 *                                      ServerHello.random +
 *                                      ClientHello.random)) +
 *              MD5(master_secret + SHA(`BB' + master_secret +
 *                                      ServerHello.random +
 *                                      ClientHello.random)) +
 *              MD5(master_secret + SHA(`CCC' + master_secret +
 *                                      ServerHello.random +
 *                                      ClientHello.random)) + [...];
 *
 *      In TLS 1.0 (a.k.a. SSL 3.1), key_block =
 *          PRF(master_secret, "key expansion",
 *              ServerHello.random + ClientHello.random)
 *
 *      Then the keys materials are taken from the keyblock.
 */

CK_RV
soft_ssl_key_and_mac_derive(soft_session_t *sp, CK_MECHANISM_PTR mech,
    soft_object_t *basekey_p, CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulAttributeCount)
{
        uchar_t *msecret = OBJ_SEC_VALUE(basekey_p);
#ifdef  __sparcv9
        /* LINTED */
        uint_t mslen = (uint_t)OBJ_SEC_VALUE_LEN(basekey_p);
#else   /* __sparcv9 */
        uint_t mslen = OBJ_SEC_VALUE_LEN(basekey_p);
#endif  /* __sparcv9 */
        CK_SSL3_KEY_MAT_PARAMS *km_params;
        CK_SSL3_RANDOM_DATA *random_data;
        CK_SSL3_KEY_MAT_OUT *kmo;
        uchar_t key_block[MAX_KEYBLOCK], *kb, *export_keys = NULL;
        CK_OBJECT_CLASS class = CKO_SECRET_KEY;
        CK_KEY_TYPE keyType = CKK_GENERIC_SECRET;
        CK_BBOOL true = TRUE;
        CK_ATTRIBUTE obj_tmpl[MAX_DEFAULT_ATTRS];
        CK_ATTRIBUTE_PTR new_tmpl;
        ulong_t newattrcount, mac_key_bytes, secret_key_bytes, iv_bytes;
        ulong_t extra_attr_count;
        uint_t size;
        int rounds, n = 0;
        boolean_t new_tmpl_allocated = B_FALSE, isExport;
        CK_RV rv = CKR_OK;
        uint_t ClientRandomLen, ServerRandomLen;

        /* Check the validity of the mechanism's parameter */

        km_params = (CK_SSL3_KEY_MAT_PARAMS *)mech->pParameter;

        if (km_params == NULL ||
            mech->ulParameterLen != sizeof (CK_SSL3_KEY_MAT_PARAMS) ||
            (kmo = km_params->pReturnedKeyMaterial) == NULL)
                return (CKR_MECHANISM_PARAM_INVALID);

        isExport = (km_params->bIsExport == TRUE);

        random_data = &km_params->RandomInfo;
#ifdef  __sparcv9
        /* LINTED */
        ClientRandomLen = (uint_t)random_data->ulClientRandomLen;
        /* LINTED */
        ServerRandomLen = (uint_t)random_data->ulServerRandomLen;
#else   /* __sparcv9 */
        ClientRandomLen = random_data->ulClientRandomLen;
        ServerRandomLen = random_data->ulServerRandomLen;
#endif  /* __sparcv9 */

        if (random_data->pClientRandom == NULL_PTR || ClientRandomLen == 0 ||
            random_data->pServerRandom == NULL_PTR || ServerRandomLen == 0) {
                return (CKR_MECHANISM_PARAM_INVALID);
        }

        mac_key_bytes = km_params->ulMacSizeInBits / 8;
        secret_key_bytes = km_params->ulKeySizeInBits / 8;
        iv_bytes = km_params->ulIVSizeInBits / 8;

        if ((iv_bytes > 0) &&
            ((kmo->pIVClient == NULL) || (kmo->pIVServer == NULL)))
                return (CKR_MECHANISM_PARAM_INVALID);

        /*
         * For exportable ciphersuites, the IV's aren't taken from the
         * key block. They are directly derived from the client and
         * server random data.
         * For SSL3.0:
         *      client_write_IV = MD5(ClientHello.random + ServerHello.random);
         *      server_write_IV = MD5(ServerHello.random + ClientHello.random);
         * For TLS1.0:
         *      iv_block = PRF("", "IV block", client_random +
         *                      server_random)[0..15]
         *      client_write_IV = iv_block[0..7]
         *      server_write_IV = iv_block[8..15]
         */
        if ((isExport) && (iv_bytes > 0)) {

                if (mech->mechanism == CKM_SSL3_KEY_AND_MAC_DERIVE) {
                        MD5_CTX exp_md5_ctx;

                        if (iv_bytes > MD5_HASH_SIZE)
                                return (CKR_MECHANISM_PARAM_INVALID);

                        MD5Init(&exp_md5_ctx);
                        MD5Update(&exp_md5_ctx, random_data->pClientRandom,
                            ClientRandomLen);
                        MD5Update(&exp_md5_ctx, random_data->pServerRandom,
                            ServerRandomLen);

                        /* there's room in key_block. use it */
                        MD5Final(key_block, &exp_md5_ctx);
                        bcopy(key_block, kmo->pIVClient, iv_bytes);

                        MD5Init(&exp_md5_ctx);
                        MD5Update(&exp_md5_ctx, random_data->pServerRandom,
                            ServerRandomLen);
                        MD5Update(&exp_md5_ctx, random_data->pClientRandom,
                            ClientRandomLen);
                        MD5Final(key_block, &exp_md5_ctx);
                        bcopy(key_block, kmo->pIVServer, iv_bytes);
                } else {
                        uchar_t iv_block[16];

                        if (iv_bytes != 8)
                                return (CKR_MECHANISM_PARAM_INVALID);

                        soft_tls_prf(NULL, 0, TLS_IV_BLOCK_LABEL,
                            TLS_IV_BLOCK_LABEL_LEN,
                            random_data->pClientRandom, ClientRandomLen,
                            random_data->pServerRandom, ServerRandomLen,
                            iv_block, 16);
                        bcopy(iv_block, kmo->pIVClient, 8);
                        bcopy(iv_block + 8, kmo->pIVServer, 8);
                }
                /* so we won't allocate a key_block bigger than needed */
                iv_bytes = 0;
        }

        /* Now the actual secret derivation */

#ifdef  __sparcv9
        /* LINTED */
        size = (uint_t)((mac_key_bytes + secret_key_bytes + iv_bytes) * 2);
#else   /* __sparcv9 */
        size = (mac_key_bytes + secret_key_bytes + iv_bytes) * 2;
#endif  /* __sparcv9 */

        /* Need to handle this better */
        if (size > MAX_KEYBLOCK)
                return (CKR_MECHANISM_PARAM_INVALID);

        rounds = howmany(size, MD5_HASH_SIZE);

        kb = key_block;

        if (mech->mechanism == CKM_SSL3_KEY_AND_MAC_DERIVE) {
                soft_ssl3_churn(msecret, mslen, random_data->pServerRandom,
                    ServerRandomLen, random_data->pClientRandom,
                    ClientRandomLen, rounds, kb);
        } else {
                soft_tls_prf(msecret, mslen, TLS_KEY_EXPANSION_LABEL,
                    TLS_KEY_EXPANSION_LABEL_LEN,
                    random_data->pServerRandom, ServerRandomLen,
                    random_data->pClientRandom, ClientRandomLen,
                    kb, size);
        }

        /* Now create the objects */

        kmo->hClientMacSecret = CK_INVALID_HANDLE;
        kmo->hServerMacSecret = CK_INVALID_HANDLE;
        kmo->hClientKey = CK_INVALID_HANDLE;
        kmo->hServerKey = CK_INVALID_HANDLE;

        /* First the MAC secrets */
        if (mac_key_bytes > 0) {
                obj_tmpl[0].type = CKA_CLASS;
                obj_tmpl[0].pValue = &class;    /* CKO_SECRET_KEY */
                obj_tmpl[0].ulValueLen = sizeof (class);
                obj_tmpl[1].type = CKA_KEY_TYPE;
                obj_tmpl[1].pValue = &keyType;  /* CKK_GENERIC_SECRET */
                obj_tmpl[1].ulValueLen = sizeof (keyType);
                obj_tmpl[2].type = CKA_DERIVE;
                obj_tmpl[2].pValue = &true;
                obj_tmpl[2].ulValueLen = sizeof (true);
                obj_tmpl[3].type = CKA_SIGN;
                obj_tmpl[3].pValue = &true;
                obj_tmpl[3].ulValueLen = sizeof (true);
                obj_tmpl[4].type = CKA_VERIFY;
                obj_tmpl[4].pValue = &true;
                obj_tmpl[4].ulValueLen = sizeof (true);
                obj_tmpl[5].type = CKA_VALUE;
                obj_tmpl[5].pValue = kb;
                obj_tmpl[5].ulValueLen = mac_key_bytes;

                rv = soft_add_derived_key(obj_tmpl, 6,
                    &(kmo->hClientMacSecret), sp, basekey_p);

                if (rv != CKR_OK)
                        goto out_err;

                kb += mac_key_bytes;

                obj_tmpl[5].pValue = kb;
                rv = soft_add_derived_key(obj_tmpl, 6,
                    &(kmo->hServerMacSecret), sp, basekey_p);

                if (rv != CKR_OK)
                        goto out_err;

                kb += mac_key_bytes;
        }

        /* Then the symmetric ciphers keys */

        extra_attr_count = (secret_key_bytes == 0) ? 6 : 5;
        newattrcount = ulAttributeCount + extra_attr_count;
        if (newattrcount > MAX_DEFAULT_ATTRS) {
                new_tmpl = malloc(sizeof (CK_ATTRIBUTE) * newattrcount);

                if (new_tmpl == NULL)
                        return (CKR_HOST_MEMORY);

                new_tmpl_allocated = B_TRUE;
        } else
                new_tmpl = obj_tmpl;

        new_tmpl[n].type = CKA_CLASS;
        new_tmpl[n].pValue = &class;    /* CKO_SECRET_KEY */
        new_tmpl[n].ulValueLen = sizeof (class);
        ++n;
        /*
         * The keyType comes from the application's template, and depends
         * on the ciphersuite. The only exception is authentication only
         * ciphersuites which do not use cipher keys.
         */
        if (secret_key_bytes == 0) {
                new_tmpl[n].type = CKA_KEY_TYPE;
                new_tmpl[n].pValue = &keyType;  /* CKK_GENERIC_SECRET */
                new_tmpl[n].ulValueLen = sizeof (keyType);
                n++;
        }
        new_tmpl[n].type = CKA_DERIVE;
        new_tmpl[n].pValue = &true;
        new_tmpl[n].ulValueLen = sizeof (true);
        n++;
        new_tmpl[n].type = CKA_ENCRYPT;
        new_tmpl[n].pValue = &true;
        new_tmpl[n].ulValueLen = sizeof (true);
        n++;
        new_tmpl[n].type = CKA_DECRYPT;
        new_tmpl[n].pValue = &true;
        new_tmpl[n].ulValueLen = sizeof (true);
        n++;
        new_tmpl[n].type = CKA_VALUE;
        new_tmpl[n].pValue = NULL;
        new_tmpl[n].ulValueLen = 0;

        if (secret_key_bytes > 0) {
                if (isExport) {
                        if (secret_key_bytes > MD5_HASH_SIZE) {
                                rv = CKR_MECHANISM_PARAM_INVALID;
                                goto out_err;
                        }
                        if ((export_keys = malloc(2 * MD5_HASH_SIZE)) == NULL) {
                                rv = CKR_HOST_MEMORY;
                                goto out_err;
                        }
#ifdef  __sparcv9
                        /* LINTED */
                        soft_ssl_weaken_key(mech, kb, (uint_t)secret_key_bytes,
                            random_data->pClientRandom, ClientRandomLen,
                            random_data->pServerRandom, ServerRandomLen,
                            export_keys, B_TRUE);
#else   /* __sparcv9 */
                        soft_ssl_weaken_key(mech, kb, secret_key_bytes,
                            random_data->pClientRandom, ClientRandomLen,
                            random_data->pServerRandom, ServerRandomLen,
                            export_keys, B_TRUE);
#endif  /* __sparcv9 */
                        new_tmpl[n].pValue = export_keys;
                        new_tmpl[n].ulValueLen = MD5_HASH_SIZE;
                } else {
                        new_tmpl[n].pValue = kb;
                        new_tmpl[n].ulValueLen = secret_key_bytes;
                }
        }

        if (ulAttributeCount > 0)
                bcopy(pTemplate, &new_tmpl[extra_attr_count],
                    ulAttributeCount * sizeof (CK_ATTRIBUTE));

        rv = soft_add_derived_key(new_tmpl, newattrcount,
            &(kmo->hClientKey), sp, basekey_p);

        if (rv != CKR_OK)
                goto out_err;

        kb += secret_key_bytes;

        if (secret_key_bytes > 0) {
                if (isExport) {
#ifdef  __sparcv9
                        /* LINTED */
                        soft_ssl_weaken_key(mech, kb, (uint_t)secret_key_bytes,
                            random_data->pServerRandom, ServerRandomLen,
                            random_data->pClientRandom, ClientRandomLen,
                            export_keys + MD5_HASH_SIZE, B_FALSE);
#else   /* __sparcv9 */
                        soft_ssl_weaken_key(mech, kb, secret_key_bytes,
                            random_data->pServerRandom, ServerRandomLen,
                            random_data->pClientRandom, ClientRandomLen,
                            export_keys + MD5_HASH_SIZE, B_FALSE);
#endif  /* __sparcv9 */
                        new_tmpl[n].pValue = export_keys + MD5_HASH_SIZE;
                } else
                        new_tmpl[n].pValue = kb;
        }

        rv = soft_add_derived_key(new_tmpl, newattrcount,
            &(kmo->hServerKey), sp, basekey_p);

        if (rv != CKR_OK)
                goto out_err;

        kb += secret_key_bytes;

        /* Finally, the IVs */
        if (iv_bytes > 0) {
                bcopy(kb, kmo->pIVClient, iv_bytes);
                kb += iv_bytes;
                bcopy(kb, kmo->pIVServer, iv_bytes);
        }

        if (new_tmpl_allocated)
                free(new_tmpl);

        freezero(export_keys, 2 * MD5_HASH_SIZE);

        return (rv);

out_err:
        if (kmo->hClientMacSecret != CK_INVALID_HANDLE) {
                (void) soft_delete_derived_key(sp,
                    (soft_object_t *)(kmo->hClientMacSecret));
                kmo->hClientMacSecret = CK_INVALID_HANDLE;
        }
        if (kmo->hServerMacSecret != CK_INVALID_HANDLE) {
                (void) soft_delete_derived_key(sp,
                    (soft_object_t *)(kmo->hServerMacSecret));
                kmo->hServerMacSecret = CK_INVALID_HANDLE;
        }
        if (kmo->hClientKey != CK_INVALID_HANDLE) {
                (void) soft_delete_derived_key(sp,
                    (soft_object_t *)(kmo->hClientKey));
                kmo->hClientKey = CK_INVALID_HANDLE;
        }
        if (kmo->hServerKey != CK_INVALID_HANDLE) {
                (void) soft_delete_derived_key(sp,
                    (soft_object_t *)(kmo->hServerKey));
                kmo->hServerKey = CK_INVALID_HANDLE;
        }

        if (new_tmpl_allocated)
                free(new_tmpl);

        freezero(export_keys, 2 * MD5_HASH_SIZE);

        return (rv);
}

/*
 * Add the derived key to the session, and, if it's a token object,
 * write it to the token.
 */
static CK_RV
soft_add_derived_key(CK_ATTRIBUTE_PTR tmpl, CK_ULONG attrcount,
    CK_OBJECT_HANDLE_PTR phKey, soft_session_t *sp, soft_object_t *basekey_p)
{
        CK_RV rv;
        soft_object_t *secret_key;

        if ((secret_key = calloc(1, sizeof (soft_object_t))) == NULL) {
                return (CKR_HOST_MEMORY);
        }

        if (((rv = soft_build_secret_key_object(tmpl, attrcount, secret_key,
            SOFT_CREATE_OBJ_INT, 0, (CK_KEY_TYPE)~0UL)) != CKR_OK) ||
            ((rv = soft_pin_expired_check(secret_key)) != CKR_OK) ||
            ((rv = soft_object_write_access_check(sp, secret_key)) != CKR_OK)) {

                free(secret_key);
                return (rv);
        }

        /* Set the sensitivity and extractability attributes as a needed */
        soft_derive_enforce_flags(basekey_p, secret_key);

        /* Initialize the rest of stuffs in soft_object_t. */
        (void) pthread_mutex_init(&secret_key->object_mutex, NULL);
        secret_key->magic_marker = SOFTTOKEN_OBJECT_MAGIC;

        /* ... and, if it needs to persist, write on the token */
        if (IS_TOKEN_OBJECT(secret_key)) {
                secret_key->session_handle = CK_INVALID_HANDLE;
                soft_add_token_object_to_slot(secret_key);
                rv = soft_put_object_to_keystore(secret_key);
                if (rv != CKR_OK) {
                        soft_delete_token_object(secret_key, B_FALSE, B_FALSE);
                        return (rv);
                }
                *phKey = set_objecthandle(secret_key);

                return (CKR_OK);
        }

        /* Add the new object to the session's object list. */
        soft_add_object_to_session(secret_key, sp);
        secret_key->session_handle = sp->handle;

        *phKey = set_objecthandle(secret_key);

        return (rv);
}

/*
 * Delete the derived key from the session, and, if it's a token object,
 * remove it from the token.
 */
static void
soft_delete_derived_key(soft_session_t *sp, soft_object_t *key)
{
        /* session_handle is the creating session. It's NULL for token objs */

        if (IS_TOKEN_OBJECT(key))
                soft_delete_token_object(key, B_FALSE, B_FALSE);
        else
                soft_delete_object(sp, key, B_FALSE, B_FALSE);
}

/*
 * soft_ssl_weaken_key()
 * Reduce the key length to an exportable size.
 * For SSL3.0:
 *      final_client_write_key = MD5(client_write_key +
 *                                ClientHello.random +
 *                                ServerHello.random);
 *      final_server_write_key = MD5(server_write_key +
 *                                ServerHello.random +
 *                                ClientHello.random);
 * For TLS1.0:
 *      final_client_write_key = PRF(SecurityParameters.client_write_key,
 *                                  "client write key",
 *                                  SecurityParameters.client_random +
 *                                  SecurityParameters.server_random)[0..15];
 *      final_server_write_key = PRF(SecurityParameters.server_write_key,
 *                                  "server write key",
 *                                  SecurityParameters.client_random +
 *                                  SecurityParameters.server_random)[0..15];
 */
static void
soft_ssl_weaken_key(CK_MECHANISM_PTR mech, uchar_t *secret, uint_t secretlen,
    uchar_t *rand1, uint_t rand1len, uchar_t *rand2, uint_t rand2len,
    uchar_t *result, boolean_t isclient)
{
        MD5_CTX exp_md5_ctx;
        uchar_t *label;
        uint_t labellen;

        if (mech->mechanism == CKM_SSL3_KEY_AND_MAC_DERIVE) {
                MD5Init(&exp_md5_ctx);
                MD5Update(&exp_md5_ctx, secret, secretlen);
                MD5Update(&exp_md5_ctx, rand1, rand1len);
                MD5Update(&exp_md5_ctx, rand2, rand2len);
                MD5Final(result, &exp_md5_ctx);
        } else {
                if (isclient) {
                        label = TLS_CLIENT_KEY_LABEL;
                        labellen = TLS_CLIENT_KEY_LABEL_LEN;
                        soft_tls_prf(secret, secretlen, label, labellen,
                            rand1, rand1len, rand2, rand2len, result, 16);
                } else {
                        label = TLS_SERVER_KEY_LABEL;
                        labellen = TLS_SERVER_KEY_LABEL_LEN;
                        soft_tls_prf(secret, secretlen, label, labellen,
                            rand2, rand2len, rand1, rand1len, result, 16);
                }
        }
}