root/usr.bin/x99token/x99token.c
/*      $OpenBSD: x99token.c,v 1.13 2017/05/03 09:51:39 mestre Exp $    */

/*
 * X9.9 calculator
 * This software is provided AS IS with no express or implied warranty
 * October 1995, Paul Borman <prb@krystal.com>
 *
 * Donated to the Public Domain by Paul Borman
 */

#include <sys/stat.h>

#include <ctype.h>
#include <err.h>
#include <pwd.h>
#include <readpassphrase.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <openssl/des.h>

#define KEYFILE         ".keyfile.des"
#define HEXDIGITS       "0123456789abcdef"
#define DECDIGITS       "0123456789012345"

void predict(DES_key_schedule, const char *, int);

char *digits = HEXDIGITS;
extern char *__progname;

int
main(int argc, char **argv)
{
        int i;
        char buf[256];
        DES_key_schedule ks;
        DES_cblock key;
        char _keyfile[PATH_MAX];
        char *keyfile = 0;
        FILE *fp;
        int init = 0;
        int hex = 1;
        int cnt = 1;
        unsigned int pin;
        struct passwd *pwd;

        if (pledge("stdio rpath wpath cpath getpw tty", NULL) == -1)
                err(1, "pledge");

        while ((i = getopt(argc, argv, "dk:in:")) != -1) {
                switch (i) {
                case 'k':
                        keyfile = optarg;
                        break;
                case 'i':
                        init = 1;
                        break;
                case 'd':
                        hex = 0;
                        break;
                case 'n':
                        cnt = atoi(optarg);
                        if (cnt <= 0)
                                err(1, "invalid count: %s", optarg);
                        break;
                default:
                        fprintf(stderr,
                            "usage: %s [-d] [-k keyfile] [-n count]\n"
                            "       %s -i [-k keyfile]\n",
                            __progname, __progname);
                        exit(1);
                }
        }

        if (!keyfile) {
                if ((pwd = getpwuid(getuid())) == NULL) {
                        fprintf(stderr, "Say, just who are you, anyhow?\n");
                        exit(1);
                }
                snprintf(_keyfile, sizeof(_keyfile), "%s/%s", pwd->pw_dir,
                    KEYFILE);
                keyfile = _keyfile;
        }

        if (init)
                readpassphrase("Enter Key: ", buf, sizeof(buf), 0);
        else if ((fp = fopen(keyfile, "r")) == NULL)
                err(1, "unable to open %s", keyfile);
        else {
                if (fgets(buf, sizeof(buf), fp) == NULL) {
                        fprintf(stderr, "No key in %s\n", keyfile);
                        exit(1);
                }
                fclose(fp);
        }

        memset(key, 0, sizeof(key));
        if (init && buf[3] == ' ') {
                char *b = buf;
                /* Assume octal input */
                for (i = 0; i < 8; ++i) {
                        if (!*b)
                                fprintf(stderr, "%s: invalid key\n", buf);
                        while (isdigit((unsigned char)*b))
                                key[i] = key[i] << 3 | (*b++ - '0');
                        while (*b && !isdigit((unsigned char)*b))
                                ++b;
                }
        } else {
                for (i = 0; i < 16; ++i) {
                        int d;

                        if (islower((unsigned char)buf[i]))
                                buf[i] = toupper((unsigned char)buf[i]);
                        if (buf[i] >= '0' && buf[i] <= '9')
                                d = buf[i] - '0';
                        else if (buf[i] >= 'A' && buf[i] <= 'F')
                                d = buf[i] - 'A' + 10;
                        else {
                                fprintf(stderr, "invalid key: %s\n", buf);
                                exit(1);
                        }
                        key[i>>1] |= d << ((i & 1) ? 0 : 4);
                }
        }

        /* XXX - should warn on non-space or non-digit */
        readpassphrase("Enter Pin: ", buf, sizeof(buf), 0);
        for (i = 0, pin = 0; buf[i] && buf[i] != '\n'; ++i)
                if (isdigit((unsigned char)buf[i]))
                        pin = pin * 16 + buf[i] - '0' + 1;

        if ((pin & 0xffff0000) == 0)
                pin |= pin << 16;

        for (i = 0; i < 8; ++i)
                key[0] ^= (pin >> ((i * 7) % 26)) & 0x7f;

        if (init) {
                umask(S_IRWXG | S_IRWXO);
                unlink(keyfile);
                if ((fp = fopen(keyfile, "w")) == NULL)
                        err(1, "could not open %s for writing", keyfile);
                for (i = 0; i < 8; ++i) {
                        fprintf(fp, "%c", digits[(key[i]>>4)&0xf]);
                        fprintf(fp, "%c", digits[(key[i]>>0)&0xf]);
                }
                fputc('\n', fp);
                fclose(fp);
                exit(0);
        }

        DES_fixup_key_parity(&key);
        DES_key_sched(&key, &ks);

        buf[0] = '\0';
        readpassphrase("Enter challenge: ", buf, sizeof(buf), RPP_ECHO_ON);
        if (buf[0] == '\0')
                exit(0);

        for (i = 0; i < 8; ++i)
                if (buf[i] == '\n')
                        buf[i] = '\0';

        if (!hex)
                digits = DECDIGITS;

        predict(ks, buf, cnt);

        explicit_bzero(&ks, sizeof(ks));
        explicit_bzero(buf, sizeof(buf));

        exit(0);
}

void
predict(DES_key_schedule ks, const char *chal, int cnt)
{
        int i;
        DES_cblock cb;

        memcpy(&cb, chal, sizeof(cb));
        while (cnt-- > 0) {
                printf("%.8s: ", (char *)cb);
                DES_ecb_encrypt(&cb, &cb, &ks, DES_ENCRYPT);
                for (i = 0; i < 4; ++i) {
                        printf("%c", digits[(cb[i]>>4) & 0xf]);
                        printf("%c", digits[(cb[i]>>0) & 0xf]);
                }
                putchar('\n');
                for (i = 0; i < 8; ++i) {
                        if ((cb[i] &= 0xf) > 9)
                                cb[i] -= 10;
                        cb[i] |= 0x30;
                }
        }
        memset(&cb, 0, sizeof(cb));
}