I'd like to propose an addition to login_yubikey.c to support an
optional PIN.  

I think that using the Yubikey for authentication is worthwhile. The
current implementation of login_yubikey.c, however, relies entirely on
the one-time password.  I think the system would be stronger combining
the Yubikey with an additional PIN so that a compromise of the physical
security of the token doesn't compromise the associated account.

My work is loosely based off of Remi Locherer's suggested patch (link
below).  Where it differs is that I'd like to add an optional additional
PIN to the authentication rather than use an existing credential, such
as the system password.  My thinking is, if you are using the Yubikey
token already, the PIN probably can be a fairly low strength password. 
The system password shouldn't be set to something simple.  This allows
for relaxed rules the the Yubikey PIN without affecting the system
password policy as a whole.

http://comments.gmane.org/gmane.os.openbsd.tech/34693

I propose adding a new /var/db/yubi/$user.pin file that contains an
encrypted additional PIN (password).  If present, this PIN must precede
the Yubikey one-time password when authenticating.  

The password in $user.pin is encrypted in a manner similar to those in
/etc/master.passwd.  Obviously, in a multi-user system some tool would
need to be devised to maintain these PIN/passwords.  For my purposes,
the following works

---
# encrypt > /var/db/yubi/$user.pin
password_goes_here
---

This has a couple nice side-effects:

1) By verifying hashed passwords I believe it is less susceptible to
timing attacks
2) Physical compromise of the Yubikey token does not immediately yield
access to the associated account
3) Compromise (read) of the contents of /var/db/yubi does not
immediately allow an attacker to access those accounts

This change is non-breaking in that, if the $user.pin file is not
present, login_yubikey works as before.

Thoughts?

Thanks,
Jeff
_______________________________________________

Common subdirectories: login_yubikey.orig/CVS and
login_yubikey.modified/CVS
diff -u login_yubikey.orig/login_yubikey.8
login_yubikey.modified/login_yubikey.8
--- login_yubikey.orig/login_yubikey.8  Mon May  5 13:57:11 2014
+++ login_yubikey.modified/login_yubikey.8      Mon May  5 14:00:45 2014
@@ -85,8 +85,10 @@
 .Em user.uid ,
 the user's key (32 hex digits) from
 .Em user.key ,
-and the user's last-use counter from
-.Em user.ctr
+the user's last-use counter from
+.Em user.ctr ,
+and the user's PIN (optional) from
+.Em user.pin
 in the
 .Em /var/db/yubikey
 directory.
@@ -99,6 +101,14 @@
 does not have a last-use counter, a value of zero is used and
 any counter is accepted during the first login.
 .Pp
+If
+.Ar user
+does have a PIN file, the PIN must be provided before the one-time
password
+and the PIN will be verified (using 
+.Xr crypt 8 ) against the contents of the PIN
+file.  If the PIN file is not present, the user must provide only the
one-time
+password.
+.Pp
 The one-time password provided by the user is decrypted using the
 user's key.
 After the decryption, the checksum embedded in the one-time password
@@ -124,4 +134,5 @@
 .El
 .Sh SEE ALSO
 .Xr login 1 ,
-.Xr login.conf 5
+.Xr login.conf 5 ,
+.Xr crypt 8
diff -u login_yubikey.orig/login_yubikey.c
login_yubikey.modified/login_yubikey.c
--- login_yubikey.orig/login_yubikey.c  Mon May  5 13:57:11 2014
+++ login_yubikey.modified/login_yubikey.c      Mon May  5 13:57:23 2014
@@ -44,6 +44,7 @@
 #include <syslog.h>
 #include <unistd.h>
 #include <errno.h>
+#include <util.h>
 
 #include "yubikey.h"
 
@@ -54,15 +55,18 @@
 #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, mode = MODE_LOGIN;
+       int ch, ret, ret_pin, mode = MODE_LOGIN;
        FILE *f = NULL;
        char *username, *password = NULL;
        char response[1024];
@@ -151,9 +155,33 @@
                }
        }
 
-       ret = yubikey_login(username, password);
+       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));
-       if (ret == AUTH_OK) {
+       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 {
@@ -174,6 +202,38 @@
        }
        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)

Attachment: login_yubikey_with_pin.20150505.jsc.patch
Description: Binary data

Reply via email to