Skip to content

Instantly share code, notes, and snippets.

@jclement
Last active August 29, 2015 14:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jclement/6f7d853c992c1f935057 to your computer and use it in GitHub Desktop.
Save jclement/6f7d853c992c1f935057 to your computer and use it in GitHub Desktop.
Updated "login_yubikey.c" for OpenBSD that adds /var/db/yubi/$user.pin with additional user pin/password that must be entered with Yubikey token.
/* $OpenBSD: login_yubikey.c,v 1.8 2013/11/27 21:25:25 deraadt 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/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <ctype.h>
#include <login_cap.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <errno.h>
#include <util.h>
#include "yubikey.h"
#define MODE_LOGIN 0
#define MODE_CHALLENGE 1
#define MODE_RESPONSE 2
#define AUTH_OK 0
#define AUTH_FAILED -1
#define YUBIKEY_LENGTH 44
static const char *path = "/var/db/yubikey";
static int clean_string(const char *);
static int yubikey_login(const char *, const char *);
static int pin_login(const char *, const char *);
int
main(int argc, char *argv[])
{
int ch, ret, ret_pin, mode = MODE_LOGIN;
FILE *f = NULL;
char *username, *password = NULL;
char response[1024];
setpriority(PRIO_PROCESS, 0, 0);
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 = getpass("Password:")) == NULL) {
syslog(LOG_ERR, "user %s: getpass: %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) */
int count;
mode = 0;
count = -1;
while (++count < sizeof(response) &&
read(3, &response[count], (size_t)1) ==
(ssize_t)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;
}
}
int password_length = strlen(password)-YUBIKEY_LENGTH;
/* if the password length < 0 that means this isn't even long enough to contain a valid yubi token */
if (password_length < 0) {
syslog(LOG_INFO, "user %s: reject", username);
fprintf(f, "%s\n ", BI_REJECT);
closelog();
return (EXIT_SUCCESS);
}
char password_pin[password_length +1];
char password_yubi[YUBIKEY_LENGTH + 1];
/* first password_length bytes are PIN */
strlcpy(password_pin, password, password_length + 1);
/* remaining 44 bytes are yubikey token */
strlcpy(password_yubi, (char*)password + password_length, YUBIKEY_LENGTH + 1);
ret = yubikey_login(username, password_yubi);
ret_pin = pin_login(username, password_pin);
memset(password, 0, strlen(password));
memset(password_pin, 0, strlen(password_pin));
memset(password_yubi, 0, strlen(password_yubi));
if (ret == AUTH_OK && ret_pin == AUTH_OK) { /* successfull login calls both yubi/pin code and requires AUTH_OK from both */
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
pin_login(const char *username, const char *pin)
{
char fn[MAXPATHLEN];
FILE *f;
char encrypted_pin[101]; // pin is salted/hashed (crypt)
snprintf(fn, sizeof(fn), "%s/%s.pin", path, username);
if ((f = fopen(fn, "r")) == NULL) {
if (strlen(pin) > 0) {
syslog(LOG_ERR, "user %s: fopen: %s: %m", username, fn);
return (AUTH_FAILED);
} else {
/* if pin is empty and file is missing revert to original behaviour */
return (AUTH_OK);
}
}
if (fscanf(f, "%100s", encrypted_pin) != 1) {
syslog(LOG_ERR, "user %s: fscanf: %s: %m", username, fn);
fclose(f);
return (AUTH_FAILED);
}
fclose(f);
char* salted_pin = crypt(pin, encrypted_pin);
if (strcmp(salted_pin, encrypted_pin) != 0)
return (AUTH_FAILED);
return (AUTH_OK);
};
static int
yubikey_login(const char *username, const char *password)
{
char fn[MAXPATHLEN];
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);
/*
* 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);
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);
return (AUTH_FAILED);
default:
syslog(LOG_DEBUG, "user %s failed: %s",
username, strerror(r));
return (AUTH_FAILED);
}
break; /* only reached through the bottom of case 0 */
}
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);
}
@jclement
Copy link
Author

jclement commented May 5, 2014

Can generate passwords using encrypt(1). Something like the following...

echo "password" | encrypt > /var/db/yubi/$user.pin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment