root/usr/src/common/crypto/dh/dh_impl.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 (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * This file contains DH helper routines common to
 * the PKCS11 soft token code and the kernel DH code.
 */

#include <sys/types.h>
#include <sys/sysmacros.h>
#include <bignum.h>

#ifdef _KERNEL
#include <sys/param.h>
#else
#include <strings.h>
#include <cryptoutil.h>
#endif

#include <sys/crypto/common.h>
#include <des/des_impl.h>
#include "dh_impl.h"


static CK_RV
convert_rv(BIG_ERR_CODE err)
{
        switch (err) {

        case BIG_OK:
                return (CKR_OK);

        case BIG_NO_MEM:
                return (CKR_HOST_MEMORY);

        case BIG_NO_RANDOM:
                return (CKR_DEVICE_ERROR);

        case BIG_INVALID_ARGS:
                return (CKR_ARGUMENTS_BAD);

        case BIG_DIV_BY_0:
        default:
                return (CKR_GENERAL_ERROR);
        }
}

/* size is in bits */
static BIG_ERR_CODE
DH_key_init(DHkey *key, int size)
{
        BIG_ERR_CODE err = BIG_OK;
        int len;

        len = BITLEN2BIGNUMLEN(size);
        key->size = size;

        if ((err = big_init(&(key->p), len)) != BIG_OK)
                return (err);
        if ((err = big_init(&(key->g), len)) != BIG_OK)
                goto ret1;
        if ((err = big_init(&(key->x), len)) != BIG_OK)
                goto ret2;
        if ((err = big_init(&(key->y), len)) != BIG_OK)
                goto ret3;

        return (BIG_OK);

ret3:
        big_finish(&(key->x));
ret2:
        big_finish(&(key->g));
ret1:
        big_finish(&(key->p));
        return (err);
}

static void
DH_key_finish(DHkey *key)
{

        big_finish(&(key->y));
        big_finish(&(key->x));
        big_finish(&(key->g));
        big_finish(&(key->p));

}

/*
 * Generate DH key pair x and y, given prime p and base g.
 * Can optionally provided bit length of x, not to exceed bit length of p.
 *
 * For those not familiar with DH keys, there are 4 components:
 * p - a known prime
 * g - the base 0 < g < p
 * x - a random number 0 < x < p-1, or if a smaller value is desired,
 *     2^(len-1) <= x < 2^(len)
 * y = g^x mod p, this implies 0 < y < p.  That is important!
 */
CK_RV
dh_genkey_pair(DHbytekey *bkey)
{
        CK_RV           rv = CKR_OK;
        BIG_ERR_CODE    brv;
        uint32_t        primebit_len;
        DHkey           dhkey;
        int             (*rf)(void *, size_t);
        uint32_t        prime_bytes;

        if (bkey == NULL)
                return (CKR_ARGUMENTS_BAD);

        /* Must have prime and base set, value bits can be 0 or non-0 */
        if (bkey->prime_bits == 0 || bkey->prime == NULL ||
            bkey->base_bytes == 0 || bkey->base == NULL)
                return (CKR_ARGUMENTS_BAD);

        prime_bytes = CRYPTO_BITS2BYTES(bkey->prime_bits);

        if ((prime_bytes < MIN_DH_KEYLENGTH_IN_BYTES) ||
            (prime_bytes > MAX_DH_KEYLENGTH_IN_BYTES)) {
                return (CKR_KEY_SIZE_RANGE);
        }

        /*
         * Initialize the DH key.
         * Note: big_extend takes length in words.
         */
        if ((brv = DH_key_init(&dhkey, bkey->prime_bits)) != BIG_OK) {
                rv = convert_rv(brv);
                goto ret;
        }

        /* Convert prime p to bignum. */
        if ((brv = big_extend(&(dhkey.p), CHARLEN2BIGNUMLEN(prime_bytes))) !=
            BIG_OK) {
                rv = convert_rv(brv);
                goto ret;
        }
        bytestring2bignum(&(dhkey.p), bkey->prime, prime_bytes);

        /* Convert base g to bignum. */
        if ((brv = big_extend(&(dhkey.g),
            CHARLEN2BIGNUMLEN(bkey->base_bytes))) != BIG_OK) {
                rv = convert_rv(brv);
                goto ret;
        }
        bytestring2bignum(&(dhkey.g), bkey->base, bkey->base_bytes);

        /* Base g cannot be greater than prime p. */
        if (big_cmp_abs(&(dhkey.g), &(dhkey.p)) >= 0) {
                rv = CKR_ATTRIBUTE_VALUE_INVALID;
                goto ret;
        }

        /*
         * The intention of selecting a private-value length is to reduce
         * the computation time for key agreement, while maintaining a
         * given level of security.
         */

        /* Maximum bit length for private-value x is bit length of prime p */
        primebit_len = big_bitlength(&(dhkey.p));

        if (bkey->value_bits == 0)
                bkey->value_bits = primebit_len;

        if (bkey->value_bits > primebit_len) {
                rv = CKR_ATTRIBUTE_VALUE_INVALID;
                goto ret;
        }

        /* Generate DH key pair private and public values. */
        if ((brv = big_extend(&(dhkey.x), BITLEN2BIGNUMLEN(bkey->value_bits)))
            != BIG_OK) {
                rv = convert_rv(brv);
                goto ret;
        }

        if ((brv = big_extend(&(dhkey.y), CHARLEN2BIGNUMLEN(prime_bytes)))
            != BIG_OK) {
                rv = convert_rv(brv);
                goto ret;
        }

        /*
         * The big integer of the private value shall be generated privately
         * and randomly.
         */
        rf = bkey->rfunc;
        if (rf == NULL) {
#ifdef _KERNEL
                rf = random_get_pseudo_bytes;
#else
                rf = pkcs11_get_urandom;
#endif
        }

        if ((brv = big_random(&(dhkey.x), bkey->value_bits, rf)) != BIG_OK) {
                rv = convert_rv(brv);
                goto ret;
        }

        /*
         * The base g shall be raised to the private value x modulo p to
         * give an integer y, the integer public value, i.e. y = (g^x) mod p.
         */
        if ((brv = big_modexp(&(dhkey.y), &(dhkey.g), &(dhkey.x),
            &(dhkey.p), NULL)) != BIG_OK) {
                rv = convert_rv(brv);
                goto ret;
        }

        bignum2bytestring(bkey->private_x, &(dhkey.x),
            CRYPTO_BITS2BYTES(bkey->value_bits));
        bignum2bytestring(bkey->public_y, &(dhkey.y), prime_bytes);

ret:
        DH_key_finish(&dhkey);

        return (rv);
}

/*
 * DH key derive operation, flag is ignored in userland
 */
CK_RV
dh_key_derive(DHbytekey *bkey, uint32_t key_type,       /* = CKK_KEY_TYPE */
    uchar_t *secretkey, uint32_t *secretkey_len,        /* derived secret */
    int flag)
{
        CK_RV           rv = CKR_OK;
        BIG_ERR_CODE    brv;
        DHkey           dhkey;
        uchar_t         *s = NULL;
        uint32_t        s_bytes = 0;
        uint32_t        prime_bytes;
        uint32_t        value_bytes;
        size_t          s_alloc;

        if (bkey == NULL)
                return (CKR_ARGUMENTS_BAD);

        /* Must have prime, private value and public value */
        if (bkey->prime_bits == 0 || bkey->prime == NULL ||
            bkey->value_bits == 0 || bkey->private_x == NULL ||
            bkey->public_y == NULL)
                return (CKR_ARGUMENTS_BAD);

        if (secretkey == NULL) {
                return (CKR_ARGUMENTS_BAD);
        }

        prime_bytes = CRYPTO_BITS2BYTES(bkey->prime_bits);
        value_bytes = CRYPTO_BITS2BYTES(bkey->value_bits);

        /*
         * Initialize the DH key.
         * Note: big_extend takes length in words.
         */
        if ((brv = DH_key_init(&dhkey, bkey->prime_bits)) != BIG_OK) {
                rv = convert_rv(brv);
                goto ret;
        }

        /* Convert prime p to bignum. */
        if ((brv = big_extend(&(dhkey.p), CHARLEN2BIGNUMLEN(prime_bytes))) !=
            BIG_OK) {
                rv = convert_rv(brv);
                goto ret;
        }
        bytestring2bignum(&(dhkey.p), bkey->prime, prime_bytes);

        /* Convert private-value x to bignum. */
        if ((brv = big_extend(&(dhkey.x), CHARLEN2BIGNUMLEN(value_bytes))) !=
            BIG_OK) {
                rv = convert_rv(brv);
                goto ret;
        }
        bytestring2bignum(&(dhkey.x), bkey->private_x, value_bytes);

        /* Convert public-value y to bignum. */
        if ((brv = big_extend(&(dhkey.y), CHARLEN2BIGNUMLEN(prime_bytes))) !=
            BIG_OK) {
                rv = convert_rv(brv);
                goto ret;
        }
        bytestring2bignum(&(dhkey.y), bkey->public_y, prime_bytes);

        /*
         * Recycle base g as a temporary variable to compute the derived
         * secret value which is "g" = (y^x) mod p.  (Not recomputing g.)
         */
        if ((brv = big_extend(&(dhkey.g), CHARLEN2BIGNUMLEN(prime_bytes))) !=
            BIG_OK) {
                rv = convert_rv(brv);
                goto ret;
        }

        if ((brv = big_modexp(&(dhkey.g), &(dhkey.y), &(dhkey.x),
            &(dhkey.p), NULL)) != BIG_OK) {
                rv = convert_rv(brv);
                goto ret;
        }

        s_alloc = P2ROUNDUP_TYPED(prime_bytes, sizeof (BIG_CHUNK_TYPE), size_t);

#ifdef _KERNEL
        if ((s = kmem_alloc(s_alloc, flag)) == NULL) {
                rv = CKR_HOST_MEMORY;
                goto ret;
        }
#else
        if ((s = malloc(s_alloc)) == NULL) {
                rv = CKR_HOST_MEMORY;
                goto ret;
        }
#endif
        s_bytes = dhkey.g.len * (int)sizeof (BIG_CHUNK_TYPE);
        bignum2bytestring(s, &(dhkey.g), s_bytes);

        switch (key_type) {

        case CKK_DES:
                *secretkey_len = DES_KEYSIZE;
                break;
        case CKK_DES2:
                *secretkey_len = DES2_KEYSIZE;
                break;
        case CKK_DES3:
                *secretkey_len = DES3_KEYSIZE;
                break;
        case CKK_RC4:
        case CKK_AES:
        case CKK_GENERIC_SECRET:
                /* use provided secret key length, if any */
                break;
        default:
                /* invalid key type */
                rv = CKR_ATTRIBUTE_TYPE_INVALID;
                goto ret;
        }

        if (*secretkey_len == 0) {
                *secretkey_len = s_bytes;
        }

        if (*secretkey_len > s_bytes) {
                rv = CKR_ATTRIBUTE_VALUE_INVALID;
                goto ret;
        }

        /*
         * The truncation removes bytes from the leading end of the
         * secret value.
         */
        (void) memcpy(secretkey, (s + s_bytes - *secretkey_len),
            *secretkey_len);

ret:
        if (s != NULL)
#ifdef _KERNEL
                kmem_free(s, s_alloc);
#else
                free(s);
#endif

        DH_key_finish(&dhkey);

        return (rv);
}