root/lib/libskey/skeysubr.c
/*      $NetBSD: skeysubr.c,v 1.29 2023/01/17 14:13:48 msaitoh Exp $    */

/* S/KEY v1.1b (skeysubr.c)
 *
 * Authors:
 *          Neil M. Haller <nmh@thumper.bellcore.com>
 *          Philip R. Karn <karn@chicago.qualcomm.com>
 *          John S. Walden <jsw@thumper.bellcore.com>
 *
 * Modifications:
 *          Scott Chasin <chasin@crimelab.com>
 *          Todd C. Miller <Todd.Miller@courtesan.com>
 *
 * S/KEY misc routines.
 */

#include <sys/cdefs.h>
__RCSID("$NetBSD: skeysubr.c,v 1.29 2023/01/17 14:13:48 msaitoh Exp $");

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <termios.h>

#include <md4.h>
#include <md5.h>
#include <sys/rmd160.h>
#include <sha1.h>

#include "skey.h"

/* Default hash function to use (index into skey_hash_types array) */
#ifndef SKEY_HASH_DEFAULT
#define SKEY_HASH_DEFAULT        0              /* MD4 */
#endif

static void f_md4(char *);
static void f_md5(char *);
static void f_sha1(char *);
/* static void f_rmd160(char *x); */
static int keycrunch_md4(char *, const char *, const char *);
static int keycrunch_md5(char *, const char *, const char *);
static int keycrunch_sha1(char *, const char *, const char *);
/* static int keycrunch_rmd160(char *, const char *, const char *); */
static void lowcase(char *);
static void skey_echo(int);
static void trapped(int) __dead;
static char *mkSeedPassword(const char *, const char *, size_t *);

/* Current hash type (index into skey_hash_types array) */
static int skey_hash_type = SKEY_HASH_DEFAULT;

/*
 * Hash types we support.
 * Each has an associated keycrunch() and f() function.
 */

struct skey_algorithm_table {
        const char *name;
        int (*keycrunch)(char *, const char *, const char *);
        void (*f)(char *);
};
static struct skey_algorithm_table skey_algorithm_table[] = {
        { "md4", keycrunch_md4, f_md4 },
        { "md5", keycrunch_md5, f_md5 },
        { "sha1", keycrunch_sha1, f_sha1 },
#if 0
        { "rmd160", keycrunch_rmd160, f_rmd160 },
#endif
        { NULL, NULL, NULL }
};

/*
 * Crunch a key:
 * concatenate the (lower cased) seed and the password, run through
 * the hash algorithm and collapse to 64 bits.
 * This is defined as the user's starting key.
 */
int keycrunch(char *result,         /* SKEY_BINKEY_SIZE result */
              const char *seed,     /* Seed, any length */
              const char *passwd)   /* Password, any length */
{
    return(skey_algorithm_table[skey_hash_type].keycrunch(result, seed, passwd));
}

static char *mkSeedPassword(const char *seed, const char *passwd,
                            size_t *buflen)
{
        char *buf;

        *buflen = strlen(seed) + strlen(passwd);
        if ((buf = (char *) malloc(*buflen + 1)) == NULL)
                return NULL;
        strcpy(buf, seed);
        lowcase(buf);
        strcat(buf, passwd);
        sevenbit(buf);
        
        return buf;
}

static int keycrunch_md4(char *result,       /* SKEY_BINKEY_SIZE result */
                         const char *seed,   /* Seed, any length */
                         const char *passwd) /* Password, any length */
{
        char *buf;
        MD4_CTX md;
        size_t buflen;
        u_int32_t results[4];

        if ((buf = mkSeedPassword(seed, passwd, &buflen)) == NULL)
                return -1;

        /* Crunch the key through MD4 */
        MD4Init(&md);
        MD4Update(&md, (unsigned char *) buf, buflen);
        MD4Final((unsigned char *) (void *) results, &md);
        free(buf);

        /* Fold result from 128 to 64 bits */
        results[0] ^= results[2];
        results[1] ^= results[3];

        (void)memcpy(result, results, SKEY_BINKEY_SIZE);

        return 0;
}

static int keycrunch_md5(char *result,          /* SKEY_BINKEY_SIZE result */
                         const char *seed,      /* Seed, any length */
                         const char *passwd)    /* Password, any length */
{
        char *buf;
        MD5_CTX md;
        u_int32_t results[4];
        size_t buflen;

        if ((buf = mkSeedPassword(seed, passwd, &buflen)) == NULL)
                return -1;

        /* Crunch the key through MD5 */
        MD5Init(&md);
        MD5Update(&md, (unsigned char *)buf, buflen);
        MD5Final((unsigned char *) (void *)results, &md);
        free(buf);

        /* Fold result from 128 to 64 bits */
        results[0] ^= results[2];
        results[1] ^= results[3];

        (void)memcpy((void *)result, (void *)results, SKEY_BINKEY_SIZE);

        return(0);
}

static int keycrunch_sha1(char *result,         /* SKEY_BINKEY_SIZE result */
                          const char *seed,     /* Seed, any length */
                          const char *passwd)   /* Password, any length */
{
        char *buf;
        SHA1_CTX sha;
        size_t buflen;
        int i, j;

        if ((buf = mkSeedPassword(seed, passwd, &buflen)) == NULL)
                return -1;

        /* Crunch the key through SHA1 */
        SHA1Init(&sha);
        SHA1Update(&sha, (unsigned char *)buf, buflen);
        SHA1Final(NULL, &sha);
        free(buf);

        /* Fold 160 to 64 bits */
        sha.state[0] ^= sha.state[2];
        sha.state[1] ^= sha.state[3];
        sha.state[0] ^= sha.state[4];

        /*
         * SHA1 is a big endian algorithm but RFC2289 mandates that
         * the result be in little endian form, so we copy to the
         * result buffer manually.
         */

        for(i=j=0; j<8; i++, j+=4) {
                result[j]   = (unsigned char)(sha.state[i] & 0xff);
                result[j+1] = (unsigned char)((sha.state[i] >> 8) & 0xff);
                result[j+2] = (unsigned char)((sha.state[i] >> 16) & 0xff);
                result[j+3] = (unsigned char)((sha.state[i] >> 24) & 0xff);
        }

        return(0);
}

#if 0
static int keycrunch_rmd160(char *result,       /* SKEY_BINKEY_SIZE result */
                            const char *seed,   /* Seed, any length */
                            const char *passwd) /* Password, any length */
{
        char *buf;
        RMD160_CTX rmd;
        u_int32_t results[5];
        size_t buflen;

        if ((buf = mkSeedPassword(seed, passwd, &buflen)) == NULL)
                return -1;

        /* Crunch the key through RMD-160 */
        RMD160Init(&rmd);
        RMD160Update(&rmd, (unsigned char *)buf, buflen);
        RMD160Final((unsigned char *)(void *)results, &rmd);
        free(buf);

        /* Fold 160 to 64 bits */
        results[0] ^= results[2];
        results[1] ^= results[3];
        results[0] ^= results[4];

        (void)memcpy((void *)result, (void *)results, SKEY_BINKEY_SIZE);

        return(0);
}
#endif

/* The one-way function f(). Takes 8 bytes and returns 8 bytes in place */
void f(char *x)
{
        skey_algorithm_table[skey_hash_type].f(x);
}

static void f_md4(char *x)
{
        MD4_CTX md;
        u_int32_t results[4];

        MD4Init(&md);
        MD4Update(&md, (unsigned char *) x, SKEY_BINKEY_SIZE);
        MD4Final((unsigned char *) (void *) results, &md);

        /* Fold 128 to 64 bits */
        results[0] ^= results[2];
        results[1] ^= results[3];

        (void)memcpy(x, results, SKEY_BINKEY_SIZE);
}

static void f_md5(char *x)
{
        MD5_CTX md;
        u_int32_t results[4];

        MD5Init(&md);
        MD5Update(&md, (unsigned char *)x, SKEY_BINKEY_SIZE);
        MD5Final((unsigned char *) (void *)results, &md);

        /* Fold 128 to 64 bits */
        results[0] ^= results[2];
        results[1] ^= results[3];

        (void)memcpy((void *)x, (void *)results, SKEY_BINKEY_SIZE);
}

static void f_sha1(char *x)
{
        SHA1_CTX sha;
        int i, j;
        
        SHA1Init(&sha);
        SHA1Update(&sha, (unsigned char *)x, SKEY_BINKEY_SIZE);
        SHA1Final(NULL, &sha);

        /* Fold 160 to 64 bits */
        sha.state[0] ^= sha.state[2];
        sha.state[1] ^= sha.state[3];
        sha.state[0] ^= sha.state[4];

        for(i=j=0; j<8; i++, j+=4) {
                x[j]   = (unsigned char)(sha.state[i] & 0xff);
                x[j+1] = (unsigned char)((sha.state[i] >> 8) & 0xff);
                x[j+2] = (unsigned char)((sha.state[i] >> 16) & 0xff);
                x[j+3] = (unsigned char)((sha.state[i] >> 24) & 0xff);
        }
}

#if 0
static void f_rmd160(char *x)
{
        RMD160_CTX rmd;
        u_int32_t results[5];

        RMD160Init(&rmd);
        RMD160Update(&rmd, (unsigned char *)x, SKEY_BINKEY_SIZE);
        RMD160Final((unsigned char *)(void *)results, &rmd);

        /* Fold 160 to 64 bits */
        results[0] ^= results[2];
        results[1] ^= results[3];
        results[0] ^= results[4];

        (void)memcpy((void *)x, (void *)results, SKEY_BINKEY_SIZE);
}
#endif

/* Strip trailing cr/lf from a line of text */
void rip(char *buf)
{
        buf += strcspn(buf, "\r\n");

        if (*buf)
                *buf = '\0';
}

/* Read in secret password (turns off echo) */
char *readpass(char *buf, int n)
{
        void *old_handler;

        /* Turn off echoing */
        skey_echo(0);

        /* Catch SIGINT and save old signal handler */
        old_handler = signal(SIGINT, trapped);

        fgets(buf, n, stdin);
        rip(buf);

        putc('\n', stderr);
        fflush(stderr);

        /* Restore signal handler and turn echo back on */
        if (old_handler != SIG_ERR)
                (void)signal(SIGINT, old_handler);
        skey_echo(1);

        sevenbit(buf);

        return buf;
}

/* Read in an s/key OTP (does not turn off echo) */
char *readskey(char *buf, int n)
{
        fgets(buf, n, stdin);

        rip(buf);

        sevenbit (buf);

        return buf;
}

/* Signal handler for trapping ^C */
/*ARGSUSED*/
static void trapped(int sig)
{
        fputs("^C\n", stderr);
        fflush(stderr);

        /* Turn on echo if necessary */
        skey_echo(1);

        exit(1);
}

/*
 * Convert 8-byte hex-ascii string to binary array
 * Returns 0 on success, -1 on error
 */
int atob8(char *out, const char *in)
{
        int i;
        int val;

        if (in == NULL || out == NULL)
                return -1;

        for (i=0; i<8; i++) {
                if ((in = skipspace(in)) == NULL)
                        return -1;
                if ((val = htoi(*in++)) == -1)
                        return -1;
                *out = val << 4;

                if ((in = skipspace(in)) == NULL)
                        return -1;
                if ((val = htoi(*in++)) == -1)
                        return -1;
                *out++ |= val;
        }
        return 0;
}

/* Convert 8-byte binary array to hex-ascii string */
int btoa8(char *out, const char *in)
{
        int i;

        if (in == NULL || out == NULL)
                return -1;

        for (i=0;i<8;i++) {
                sprintf(out, "%02x", *in++ & 0xff);
                out += 2;
        }
        return 0;
}


/* Convert hex digit to binary integer */
int htoi(int c)
{
        if ('0' <= c && c <= '9')
                return c - '0';
        if ('a' <= c && c <= 'f')
                return 10 + c - 'a';
        if ('A' <= c && c <= 'F')
                return 10 + c - 'A';
        return -1;
}

/* Skip leading spaces from the string */
const char *skipspace(const char *cp)
{
        while (*cp == ' ' || *cp == '\t')
                cp++;

        if (*cp == '\0')
                return NULL;
        else
                return cp;
}

/* Remove backspaced over characters from the string */
void backspace(char *buf)
{
        char bs = 0x8;
        char *cp = buf;
        char *out = buf;

        while (*cp) {
                if (*cp == bs) {
                        if (out == buf) {
                                cp++;
                                continue;
                        } else {
                          cp++;
                          out--;
                        }
                } else {
                        *out++ = *cp++;
                }

        }
        *out = '\0';
}

/* Make sure line is all seven bits */
void sevenbit(char *s)
{
        while (*s)
                *s++ &= 0x7f;
}

/* Set hash algorithm type */
const char *skey_set_algorithm(const char *new)
{
        int i;

        for (i = 0; skey_algorithm_table[i].name; i++) {
                if (strcmp(new, skey_algorithm_table[i].name) == 0) {
                        skey_hash_type = i;
                        return(new);
                }
        }

        return(NULL);
}

/* Get current hash type */
const char *skey_get_algorithm(void)
{
        return(skey_algorithm_table[skey_hash_type].name);
}

/* Turn echo on/off */
static void skey_echo(int action)
{
        static struct termios term;
        static int echo = 0;

        if (action == 0) {
                /* Turn echo off */
                (void) tcgetattr(fileno(stdin), &term);
                if ((echo = (term.c_lflag & ECHO)) != 0) {
                        term.c_lflag &= ~ECHO;
                        (void) tcsetattr(fileno(stdin), TCSAFLUSH|TCSASOFT, &term);
                }
        } else if (action && echo) {
                /* Turn echo on */
                term.c_lflag |= ECHO;
                (void) tcsetattr(fileno(stdin), TCSAFLUSH|TCSASOFT, &term);
                echo = 0;
        }
}

/* Convert string to lower case */
static void lowcase(char *s)
{
        u_char *p;

        for (p = (u_char *) s; *p; p++)
                if (isupper(*p))
                        *p = tolower(*p);
}