[BACK]Return to login_yubikey.c CVS log [TXT][DIR] Up to [local] / src / libexec / login_yubikey

File: [local] / src / libexec / login_yubikey / login_yubikey.c (download)

Revision 1.16, Sat Sep 3 11:01:44 2016 UTC (7 years, 8 months ago) by gsoares
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, OPENBSD_7_0_BASE, OPENBSD_7_0, OPENBSD_6_9_BASE, OPENBSD_6_9, OPENBSD_6_8_BASE, OPENBSD_6_8, OPENBSD_6_7_BASE, OPENBSD_6_7, OPENBSD_6_6_BASE, OPENBSD_6_6, OPENBSD_6_5_BASE, OPENBSD_6_5, OPENBSD_6_4_BASE, OPENBSD_6_4, OPENBSD_6_3_BASE, OPENBSD_6_3, OPENBSD_6_2_BASE, OPENBSD_6_2, OPENBSD_6_1_BASE, OPENBSD_6_1, HEAD
Changes since 1.15: +6 -3 lines

convert to use readpassphrase() instead of DEPRECATED/getpass()
OK millert@

/* $OpenBSD: login_yubikey.c,v 1.16 2016/09/03 11:01:44 gsoares Exp $ */

/*
 * Copyright (c) 2010 Daniel Hartmeier <daniel@benzedrine.cx>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    - Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    - Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials provided
 *      with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/unistd.h>
#include <ctype.h>
#include <login_cap.h>
#include <pwd.h>
#include <readpassphrase.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>

#include "yubikey.h"

#define	MODE_LOGIN	0
#define	MODE_CHALLENGE	1
#define	MODE_RESPONSE	2

#define	AUTH_OK		0
#define	AUTH_FAILED	-1

static const char *path = "/var/db/yubikey";

static int clean_string(const char *);
static int yubikey_login(const char *, const char *);

int
main(int argc, char *argv[])
{
	int ch, ret, mode = MODE_LOGIN, count;
	FILE *f = NULL;
	char *username, *password = NULL;
	char pbuf[1024];
	char response[1024];

	setpriority(PRIO_PROCESS, 0, 0);

	if (pledge("stdio tty wpath rpath cpath", NULL) == -1) {
		syslog(LOG_AUTH|LOG_ERR, "pledge: %m");
		exit(EXIT_FAILURE);
	}

	openlog(NULL, LOG_ODELAY, LOG_AUTH);

	while ((ch = getopt(argc, argv, "dv:s:")) != -1) {
		switch (ch) {
		case 'd':
			f = stdout;
			break;
		case 'v':
			break;
		case 's':
			if (!strcmp(optarg, "login"))
				mode = MODE_LOGIN;
			else if (!strcmp(optarg, "response"))
				mode = MODE_RESPONSE;
			else if (!strcmp(optarg, "challenge"))
				mode = MODE_CHALLENGE;
			else {
				syslog(LOG_ERR, "%s: invalid service", optarg);
				exit(EXIT_FAILURE);
			}
			break;
		default:
			syslog(LOG_ERR, "usage error1");
			exit(EXIT_FAILURE);
		}
	}
	argc -= optind;
	argv += optind;
	if (argc != 2 && argc != 1) {
		syslog(LOG_ERR, "usage error2");
		exit(EXIT_FAILURE);
	}
	username = argv[0];
	/* passed by sshd(8) for non-existing users */
	if (!strcmp(username, "NOUSER"))
		exit(EXIT_FAILURE);
	if (!clean_string(username)) {
		syslog(LOG_ERR, "clean_string username");
		exit(EXIT_FAILURE);
	}

	if (f == NULL && (f = fdopen(3, "r+")) == NULL) {
		syslog(LOG_ERR, "user %s: fdopen: %m", username);
		exit(EXIT_FAILURE);
	}

	switch (mode) {
	case MODE_LOGIN:
		if ((password = readpassphrase("Password:", pbuf, sizeof(pbuf),
			    RPP_ECHO_OFF)) == NULL) {
			syslog(LOG_ERR, "user %s: readpassphrase: %m",
			    username);
			exit(EXIT_FAILURE);
		}
		break;
	case MODE_CHALLENGE:
		/* see login.conf(5) section CHALLENGES */
		fprintf(f, "%s\n", BI_SILENT);
		exit(EXIT_SUCCESS);
		break;
	case MODE_RESPONSE:
		/* see login.conf(5) section RESPONSES */
		/* this happens e.g. when called from sshd(8) */
		mode = 0;
		count = -1;
		while (++count < sizeof(response) &&
		    read(3, &response[count], 1) == 1) {
			if (response[count] == '\0' && ++mode == 2)
				break;
			if (response[count] == '\0' && mode == 1)
				password = response + count + 1;
		}
		if (mode < 2) {
			syslog(LOG_ERR, "user %s: protocol error "
			    "on back channel", username);
			exit(EXIT_FAILURE);
		}
		break;
	}

	ret = yubikey_login(username, password);
	explicit_bzero(password, strlen(password));
	if (ret == AUTH_OK) {
		syslog(LOG_INFO, "user %s: authorize", username);
		fprintf(f, "%s\n", BI_AUTH);
	} else {
		syslog(LOG_INFO, "user %s: reject", username);
		fprintf(f, "%s\n", BI_REJECT);
	}
	closelog();
	return (EXIT_SUCCESS);
}

static int
clean_string(const char *s)
{
	while (*s) {
		if (!isalnum((unsigned char)*s) && *s != '-' && *s != '_')
			return (0);
		++s;
	}
	return (1);
}

static int
yubikey_login(const char *username, const char *password)
{
	char fn[PATH_MAX];
	char hexkey[33], key[YUBIKEY_KEY_SIZE];
	char hexuid[13], uid[YUBIKEY_UID_SIZE];
	FILE *f;
	yubikey_token_st tok;
	u_int32_t last_ctr = 0, ctr;
	int r, i = 0, mapok = 0, crcok = 0;

	snprintf(fn, sizeof(fn), "%s/%s.uid", path, username);
	if ((f = fopen(fn, "r")) == NULL) {
		syslog(LOG_ERR, "user %s: fopen: %s: %m", username, fn);
		return (AUTH_FAILED);
	}
	if (fscanf(f, "%12s", hexuid) != 1) {
		syslog(LOG_ERR, "user %s: fscanf: %s: %m", username, fn);
		fclose(f);
		return (AUTH_FAILED);
	}
	fclose(f);

	snprintf(fn, sizeof(fn), "%s/%s.key", path, username);
	if ((f = fopen(fn, "r")) == NULL) {
		syslog(LOG_ERR, "user %s: fopen: %s: %m", username, fn);
		return (AUTH_FAILED);
	}
	if (fscanf(f, "%32s", hexkey) != 1) {
		syslog(LOG_ERR, "user %s: fscanf: %s: %m", username, fn);
		fclose(f);
		return (AUTH_FAILED);
	}
	fclose(f);
	if (strlen(hexkey) != 32) {
		syslog(LOG_ERR, "user %s: key len != 32", username);
		return (AUTH_FAILED);
	}

	snprintf(fn, sizeof(fn), "%s/%s.ctr", path, username);
	if ((f = fopen(fn, "r")) != NULL) {
		if (fscanf(f, "%u", &last_ctr) != 1)
			last_ctr = 0;
		fclose(f);
	}

	yubikey_hex_decode(uid, hexuid, YUBIKEY_UID_SIZE);
	yubikey_hex_decode(key, hexkey, YUBIKEY_KEY_SIZE);

	explicit_bzero(hexkey, sizeof(hexkey));

	/*
	 * Cycle through the key mapping table.
         * XXX brute force, unoptimized; a lookup table for valid mappings may
	 * be appropriate.
	 */
	while (1) {
		r = yubikey_parse((uint8_t *)password, (uint8_t *)key, &tok, i++);
		switch (r) {
		case EMSGSIZE:
			syslog(LOG_INFO, "user %s failed: password too short.",
			    username);
			explicit_bzero(key, sizeof(key));
			return (AUTH_FAILED);
		case EINVAL:	/* keyboard mapping invalid */
			continue;
		case 0:		/* found a valid keyboard mapping */
			mapok++;
			if (!yubikey_crc_ok_p((uint8_t *)&tok))
				continue;	/* try another one */
			crcok++;
			syslog(LOG_DEBUG, "user %s: crc %04x ok",
			    username, tok.crc);

			if (memcmp(tok.uid, uid, YUBIKEY_UID_SIZE)) {
				char h[13];

				yubikey_hex_encode(h, (const char *)tok.uid,
				    YUBIKEY_UID_SIZE);
				syslog(LOG_DEBUG, "user %s: uid %s != %s",
				    username, h, hexuid);
				continue;	/* try another one */
			}
			break; /* uid matches */
		case -1:
			syslog(LOG_INFO, "user %s: could not decode password "
			    "with any keymap (%d crc ok)",
			    username, crcok);
			explicit_bzero(key, sizeof(key));
			return (AUTH_FAILED);
		default:
			syslog(LOG_DEBUG, "user %s failed: %s",
			    username, strerror(r));
			explicit_bzero(key, sizeof(key));
			return (AUTH_FAILED);
		}
		break; /* only reached through the bottom of case 0 */
	}

	explicit_bzero(key, sizeof(key));

	syslog(LOG_INFO, "user %s uid %s: %d matching keymaps (%d checked), "
	    "%d crc ok", username, hexuid, mapok, i, crcok);

	ctr = ((u_int32_t)yubikey_counter(tok.ctr) << 8) | tok.use;
	if (ctr <= last_ctr) {
		syslog(LOG_INFO, "user %s: counter %u.%u <= %u.%u "
		    "(REPLAY ATTACK!)", username, ctr / 256, ctr % 256,
		    last_ctr / 256, last_ctr % 256);
		return (AUTH_FAILED);
	}
	syslog(LOG_INFO, "user %s: counter %u.%u > %u.%u",
	    username, ctr / 256, ctr % 256, last_ctr / 256, last_ctr % 256);
	umask(S_IRWXO);
	if ((f = fopen(fn, "w")) == NULL) {
		syslog(LOG_ERR, "user %s: fopen: %s: %m", username, fn);
		return (AUTH_FAILED);
	}
	fprintf(f, "%u", ctr);
	fclose(f);

	return (AUTH_OK);
}