root/usr.sbin/keyserv/setkey.c
/*
 * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
 * unrestricted use provided that this legend is included on all tape
 * media and as a part of the software program in whole or part.  Users
 * may copy or modify Sun RPC without charge, but are not authorized
 * to license or distribute it to anyone else except as part of a product or
 * program developed by the user.
 * 
 * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
 * WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
 * 
 * Sun RPC is provided with no support and without any obligation on the
 * part of Sun Microsystems, Inc. to assist in its use, correction,
 * modification or enhancement.
 * 
 * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
 * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
 * OR ANY PART THEREOF.
 * 
 * In no event will Sun Microsystems, Inc. be liable for any lost revenue
 * or profits or other special, indirect and consequential damages, even if
 * Sun has been advised of the possibility of such damages.
 * 
 * Sun Microsystems, Inc.
 * 2550 Garcia Avenue
 * Mountain View, California  94043
 *
 * @(#)setkey.c 1.11    94/04/25 SMI
 * $FreeBSD: src/usr.sbin/keyserv/setkey.c,v 1.3 1999/08/28 01:16:41 peter Exp $
 */

/*
 * Copyright (c) 1986 - 1991 by Sun Microsystems, Inc.
 */

/*
 * Do the real work of the keyserver.
 * Store secret keys. Compute common keys,
 * and use them to decrypt and encrypt DES keys.
 * Cache the common keys, so the expensive computation is avoided.
 */
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <rpc/rpc.h>
#include <rpc/key_prot.h>
#include <rpc/des_crypt.h>
#include <rpc/des.h>
#include <sys/errno.h>
#include "keyserv.h"
#include <openssl/bn.h>
#include <openssl/crypto.h>
#include <openssl/err.h>

static BIGNUM *modulus;
static char *fetchsecretkey( uid_t );
static void writecache( char *, char *, des_block * );
static int readcache( char *, char *, des_block * );
static void extractdeskey ( BIGNUM *, des_block * );
static int storesecretkey( uid_t, keybuf );
static keystatus pk_crypt( uid_t, char *, netobj *, des_block *, int);
static int nodefaultkeys = 0;


/*
 * prohibit the nobody key on this machine k (the -d flag)
 */
void
pk_nodefaultkeys(void)
{
        nodefaultkeys = 1;
}

/*
 * Set the modulus for all our Diffie-Hellman operations
 */
void
setmodulus(char *modx)
{
        modulus = NULL;
        if (BN_hex2bn(&modulus, modx) == 0)
                errx(1, "could not convert modulus to BIGNUM: %s",
                     ERR_error_string(ERR_get_error(), 0));
}

/*
 * Set the secretkey key for this uid
 */
keystatus
pk_setkey(uid_t uid, keybuf skey)
{
        if (!storesecretkey(uid, skey)) {
                return (KEY_SYSTEMERR);
        }
        return (KEY_SUCCESS);
}

/*
 * Encrypt the key using the public key associated with remote_name and the
 * secret key associated with uid.
 */
keystatus
pk_encrypt(uid_t uid, char *remote_name, netobj *remote_key, des_block *key)
{
        return (pk_crypt(uid, remote_name, remote_key, key, DES_ENCRYPT));
}

/*
 * Decrypt the key using the public key associated with remote_name and the
 * secret key associated with uid.
 */
keystatus
pk_decrypt(uid_t uid, char *remote_name, netobj *remote_key, des_block *key)
{
        return (pk_crypt(uid, remote_name, remote_key, key, DES_DECRYPT));
}

static int store_netname( uid_t, key_netstarg * );
static int fetch_netname( uid_t, key_netstarg * );

keystatus
pk_netput(uid_t uid, key_netstarg *netstore)
{
        if (!store_netname(uid, netstore)) {
                return (KEY_SYSTEMERR);
        }
        return (KEY_SUCCESS);
}

keystatus
pk_netget(uid_t uid, key_netstarg *netstore)
{
        if (!fetch_netname(uid, netstore)) {
                return (KEY_SYSTEMERR);
        }
        return (KEY_SUCCESS);
}


/*
 * Do the work of pk_encrypt && pk_decrypt
 */
static keystatus
pk_crypt(uid_t uid, char *remote_name, netobj *remote_key, des_block *key,
         int mode)
{
        char *xsecret;
        char xpublic[1024];
        char xsecret_hold[1024];
        des_block deskey;
        int error;
        BIGNUM *public, *secret, *common;
        BN_CTX *ctx;
        char zero[8];

        xsecret = fetchsecretkey(uid);
        if (xsecret == NULL || xsecret[0] == 0) {
                memset(zero, 0, sizeof (zero));
                xsecret = xsecret_hold;
                if (nodefaultkeys)
                        return (KEY_NOSECRET);

                if (!getsecretkey("nobody", xsecret, zero) || xsecret[0] == 0) {
                        return (KEY_NOSECRET);
                }
        }
        if (remote_key) {
                memcpy(xpublic, remote_key->n_bytes, remote_key->n_len);
        } else {
                bzero((char *)&xpublic, sizeof(xpublic));
                if (!getpublickey(remote_name, xpublic)) {
                        if (nodefaultkeys || !getpublickey("nobody", xpublic))
                                return (KEY_UNKNOWN);
                }
        }

        if (!readcache(xpublic, xsecret, &deskey)) {
                if ((ctx = BN_CTX_new()) == NULL)
                        return (KEY_SYSTEMERR);
                public = NULL;
                if (BN_hex2bn(&public, xpublic) == 0) {
                        BN_CTX_free(ctx);
                        return (KEY_SYSTEMERR);
                }
                secret = NULL;
                if (BN_hex2bn(&secret, xsecret) == 0) {
                        BN_free(public);
                        BN_CTX_free(ctx);
                        return (KEY_SYSTEMERR);
                }

                if ((common = BN_new()) == NULL) {
                        BN_free(secret);
                        BN_free(public);
                        BN_CTX_free(ctx);
                        return (KEY_SYSTEMERR);
                }
                BN_zero(common);
                BN_mod_exp(common, public, secret, modulus, ctx);
                extractdeskey(common, &deskey);
                writecache(xpublic, xsecret, &deskey);
                BN_free(secret);
                BN_free(public);
                BN_free(common);
                BN_CTX_free(ctx);
        }
        error = ecb_crypt((char *)&deskey, (char *)key, sizeof (des_block),
                DES_HW | mode);
        if (DES_FAILED(error)) {
                return (KEY_SYSTEMERR);
        }
        return (KEY_SUCCESS);
}

keystatus
pk_get_conv_key(uid_t uid, keybuf xpublic, cryptkeyres *result)
{
        char *xsecret;
        char xsecret_hold[1024];
        BIGNUM *public, *secret, *common;
        BN_CTX *ctx;
        char zero[8];


        xsecret = fetchsecretkey(uid);

        if (xsecret == NULL || xsecret[0] == 0) {
                memset(zero, 0, sizeof (zero));
                xsecret = xsecret_hold;
                if (nodefaultkeys)
                        return (KEY_NOSECRET);

                if (!getsecretkey("nobody", xsecret, zero) ||
                        xsecret[0] == 0)
                        return (KEY_NOSECRET);
        }

        if (!readcache(xpublic, xsecret, &result->cryptkeyres_u.deskey)) {
                if ((ctx = BN_CTX_new()) == NULL)
                        return (KEY_SYSTEMERR);
                public = NULL;
                if (BN_hex2bn(&public, xpublic) == 0) {
                        BN_CTX_free(ctx);
                        return (KEY_SYSTEMERR);
                }
                secret = NULL;
                if (BN_hex2bn(&secret, xsecret) == 0) {
                        BN_free(public);
                        BN_CTX_free(ctx);
                        return (KEY_SYSTEMERR);
                }

                if ((common = BN_new()) == NULL) {
                        BN_free(secret);
                        BN_free(public);
                        BN_CTX_free(ctx);
                        return (KEY_SYSTEMERR);
                }
                BN_zero(common);
                BN_mod_exp(common, public, secret, modulus, ctx);

                extractdeskey(common, &result->cryptkeyres_u.deskey);
                writecache(xpublic, xsecret, &result->cryptkeyres_u.deskey);
                BN_free(secret);
                BN_free(public);
                BN_free(common);
                BN_CTX_free(ctx);
        }

        return (KEY_SUCCESS);
}

/*
 * Choose middle 64 bits of the common key to use as our des key, possibly
 * overwriting the lower order bits by setting parity.
 */
static void
extractdeskey(BIGNUM *ck, des_block *deskey)
{
        BIGNUM *a;
        int i;
        BN_ULONG r, base = (1 << 8);
        char *k;

        if ((a = BN_dup(ck)) == NULL)
                errx(1, "could not copy BIGNUM");

        for (i = 0; i < ((KEYSIZE - 64) / 2) / 8; i++) {
                r = BN_div_word(a, base);
        }
        k = deskey->c;
        for (i = 0; i < 8; i++) {
                r = BN_div_word(a, base);
                *k++ = r;
        }
        BN_free(a);
        des_setparity((char *)deskey);
}

/*
 * Key storage management
 */

#define KEY_ONLY 0
#define KEY_NAME 1
struct secretkey_netname_list {
        uid_t uid;
        key_netstarg keynetdata;
        u_char sc_flag;
        struct secretkey_netname_list *next;
};



static struct secretkey_netname_list *g_secretkey_netname;

/*
 * Store the keys and netname for this uid
 */
static int
store_netname(uid_t uid, key_netstarg *netstore)
{
        struct secretkey_netname_list *new;
        struct secretkey_netname_list **l;

        for (l = &g_secretkey_netname; *l != NULL && (*l)->uid != uid;
                        l = &(*l)->next) {
        }
        if (*l == NULL) {
                new = (struct secretkey_netname_list *)malloc(sizeof (*new));
                if (new == NULL) {
                        return (0);
                }
                new->uid = uid;
                new->next = NULL;
                *l = new;
        } else {
                new = *l;
                if (new->keynetdata.st_netname)
                        free(new->keynetdata.st_netname);
        }
        memcpy(new->keynetdata.st_priv_key, netstore->st_priv_key,
                HEXKEYBYTES);
        memcpy(new->keynetdata.st_pub_key, netstore->st_pub_key, HEXKEYBYTES);

        if (netstore->st_netname)
                new->keynetdata.st_netname = strdup(netstore->st_netname);
        else
                new->keynetdata.st_netname = NULL;
        new->sc_flag = KEY_NAME;
        return (1);

}

/*
 * Fetch the keys and netname for this uid
 */

static int
fetch_netname(uid_t uid, struct key_netstarg *key_netst)
{
        struct secretkey_netname_list *l;

        for (l = g_secretkey_netname; l != NULL; l = l->next) {
                if ((l->uid == uid) && (l->sc_flag == KEY_NAME)){

                        memcpy(key_netst->st_priv_key,
                                l->keynetdata.st_priv_key, HEXKEYBYTES);

                        memcpy(key_netst->st_pub_key,
                                l->keynetdata.st_pub_key, HEXKEYBYTES);

                        if (l->keynetdata.st_netname)
                                key_netst->st_netname =
                                        strdup(l->keynetdata.st_netname);
                        else
                                key_netst->st_netname = NULL;
                return (1);
                }
        }

        return (0);
}

static char *
fetchsecretkey(uid_t uid)
{
        struct secretkey_netname_list *l;

        for (l = g_secretkey_netname; l != NULL; l = l->next) {
                if (l->uid == uid) {
                        return (l->keynetdata.st_priv_key);
                }
        }
        return (NULL);
}

/*
 * Store the secretkey for this uid
 */
static int
storesecretkey(uid_t uid, keybuf key)
{
        struct secretkey_netname_list *new;
        struct secretkey_netname_list **l;

        for (l = &g_secretkey_netname; *l != NULL && (*l)->uid != uid;
                        l = &(*l)->next) {
        }
        if (*l == NULL) {
                new = (struct secretkey_netname_list *) malloc(sizeof (*new));
                if (new == NULL) {
                        return (0);
                }
                new->uid = uid;
                new->sc_flag = KEY_ONLY;
                memset(new->keynetdata.st_pub_key, 0, HEXKEYBYTES);
                new->keynetdata.st_netname = NULL;
                new->next = NULL;
                *l = new;
        } else {
                new = *l;
        }

        memcpy(new->keynetdata.st_priv_key, key,
                HEXKEYBYTES);
        return (1);
}

static int
hexdigit(int val)
{
        return ("0123456789abcdef"[val]);
}

void
bin2hex(unsigned char *bin, unsigned char *hex, int size)
{
        int i;

        for (i = 0; i < size; i++) {
                *hex++ = hexdigit(*bin >> 4);
                *hex++ = hexdigit(*bin++ & 0xf);
        }
}

static int
hexval(char dig)
{
        if ('0' <= dig && dig <= '9') {
                return (dig - '0');
        } else if ('a' <= dig && dig <= 'f') {
                return (dig - 'a' + 10);
        } else if ('A' <= dig && dig <= 'F') {
                return (dig - 'A' + 10);
        } else {
                return (-1);
        }
}

void
hex2bin(unsigned char *hex, unsigned char *bin, int size)
{
        int i;

        for (i = 0; i < size; i++) {
                *bin = hexval(*hex++) << 4;
                *bin++ |= hexval(*hex++);
        }
}

/*
 * Exponential caching management
 */
struct cachekey_list {
        keybuf secret;
        keybuf public;
        des_block deskey;
        struct cachekey_list *next;
};
static struct cachekey_list *g_cachedkeys;

/*
 * cache result of expensive multiple precision exponential operation
 */
static void
writecache(char *pub, char *sec, des_block *deskey)
{
        struct cachekey_list *new;

        new = (struct cachekey_list *) malloc(sizeof (struct cachekey_list));
        if (new == NULL) {
                return;
        }
        memcpy(new->public, pub, sizeof (keybuf));
        memcpy(new->secret, sec, sizeof (keybuf));
        new->deskey = *deskey;
        new->next = g_cachedkeys;
        g_cachedkeys = new;
}

/*
 * Try to find the common key in the cache
 */
static int
readcache(char *pub, char *sec, des_block *deskey)
{
        struct cachekey_list *found;
        struct cachekey_list **l;

#define cachehit(pub, sec, list)        \
                (memcmp(pub, (list)->public, sizeof (keybuf)) == 0 && \
                memcmp(sec, (list)->secret, sizeof (keybuf)) == 0)

        for (l = &g_cachedkeys; (*l) != NULL && !cachehit(pub, sec, *l);
                l = &(*l)->next)
                ;
        if ((*l) == NULL) {
                return (0);
        }
        found = *l;
        (*l) = (*l)->next;
        found->next = g_cachedkeys;
        g_cachedkeys = found;
        *deskey = found->deskey;
        return (1);
}